From f66bdd4753aacb60d5fa3067a545283a381f94da Mon Sep 17 00:00:00 2001 From: Hemant Kumar Date: Wed, 21 Aug 2019 21:51:53 -0400 Subject: [PATCH 1/2] bump (github.com/gophercloud/gophercloud) Fix bug with mutex lock on reauth --- glide.lock | 15 +- glide.yaml | 2 +- vendor/github.com/docker/go-metrics/go.mod | 5 + vendor/github.com/docker/go-metrics/go.sum | 67 ++ vendor/github.com/google/btree/btree_test.go | 8 +- .../gophercloud/.github/CONTRIBUTING.md | 10 +- .../gophercloud/.github/ISSUE_TEMPLATE | 2 +- .../gophercloud/.github/PULL_REQUEST_TEMPLATE | 3 + .../gophercloud/gophercloud/.gitignore | 1 + .../gophercloud/gophercloud/.travis.yml | 18 +- .../gophercloud/gophercloud/.zuul.yaml | 102 ++ .../run.yaml | 26 + .../gophercloud-acceptance-test/run.yaml | 27 + .../playbooks/gophercloud-unittest/run.yaml | 16 + .../gophercloud/gophercloud/README.md | 8 +- .../gophercloud/acceptance/README.md | 3 +- .../gophercloud/acceptance/clients/clients.go | 250 +++- .../acceptance/clients/conditions.go | 31 + .../gophercloud/acceptance/clients/http.go | 23 +- .../baremetal/noauth/allocations_test.go | 45 + .../openstack/baremetal/noauth/doc.go | 8 + .../openstack/baremetal/noauth/nodes_test.go | 95 ++ .../openstack/baremetal/noauth/ports_test.go | 73 ++ .../baremetal/v1/allocations_test.go | 44 + .../openstack/baremetal/v1/baremetal.go | 115 ++ .../openstack/baremetal/v1/nodes_test.go | 96 ++ .../openstack/baremetal/v1/ports_test.go | 72 ++ .../blockstorage/extensions/backups_test.go | 41 + .../blockstorage/extensions/extensions.go | 63 + .../extensions/schedulerhints_test.go | 59 + .../extensions/schedulerstats_test.go | 15 +- .../blockstorage/extensions/services_test.go | 15 +- .../extensions/volumeactions_test.go | 83 +- .../extensions/volumetenants_test.go | 46 + .../blockstorage/noauth/blockstorage.go | 2 +- .../openstack/blockstorage/v1/blockstorage.go | 6 +- .../openstack/blockstorage/v1/volumes_test.go | 17 + .../openstack/blockstorage/v2/blockstorage.go | 87 +- .../blockstorage/v2/snapshots_test.go | 54 +- .../openstack/blockstorage/v2/volumes_test.go | 123 +- .../openstack/blockstorage/v3/blockstorage.go | 132 ++- .../blockstorage/v3/quotaset_test.go | 145 +++ .../blockstorage/v3/snapshots_test.go | 83 +- .../openstack/blockstorage/v3/volumes_test.go | 138 ++- .../blockstorage/v3/volumetypes_test.go | 83 +- .../openstack/clustering/v1/actions_test.go | 31 + .../openstack/clustering/v1/clustering.go | 472 ++++++++ .../openstack/clustering/v1/clusters_test.go | 510 ++++++++ .../openstack/clustering/v1/events_test.go | 31 + .../openstack/clustering/v1/nodes_test.go | 215 ++++ .../openstack/clustering/v1/policies_test.go | 69 ++ .../clustering/v1/policytypes_test.go | 21 + .../openstack/clustering/v1/profiles_test.go | 76 ++ .../clustering/v1/profiletypes_test.go | 47 + .../openstack/clustering/v1/receivers_test.go | 82 ++ .../clustering/v1/webhooktrigger_test.go | 64 + .../compute/v2/bootfromvolume_test.go | 36 + .../openstack/compute/v2/compute.go | 114 +- .../openstack/compute/v2/flavors_test.go | 3 +- .../openstack/compute/v2/hypervisors_test.go | 4 +- .../openstack/compute/v2/keypairs_test.go | 21 + .../openstack/compute/v2/migrate_test.go | 2 + .../openstack/compute/v2/quotaset_test.go | 10 + .../compute/v2/remoteconsoles_test.go | 29 + .../compute/v2/rescueunrescue_test.go | 25 + .../openstack/compute/v2/secgroup_test.go | 8 +- .../openstack/compute/v2/servers_test.go | 110 +- .../openstack/compute/v2/usage_test.go | 54 +- .../openstack/container/v1/capsules.go | 47 + .../openstack/container/v1/capsules_test.go | 98 ++ .../openstack/container/v1/fixtures.go | 46 + .../containerinfra/v1/certificates_test.go | 45 + .../containerinfra/v1/clusters_test.go | 78 ++ .../v1/clustertemplates_test.go | 70 ++ .../containerinfra/v1/containerinfra.go | 224 ++++ .../v1/pkg.go | 0 .../containerinfra/v1/quotas_test.go | 20 + .../openstack/db/v1/configurations_test.go | 33 +- .../acceptance/openstack/dns/v2/dns.go | 15 +- .../openstack/dns/v2/recordsets_test.go | 111 +- .../acceptance/openstack/dns/v2/zones_test.go | 57 +- .../openstack/identity/v2/extension_test.go | 33 +- .../openstack/identity/v2/identity.go | 11 +- .../openstack/identity/v2/role_test.go | 61 +- .../openstack/identity/v2/tenant_test.go | 47 +- .../openstack/identity/v2/token_test.go | 39 +- .../openstack/identity/v2/user_test.go | 42 +- .../v3/applicationcredentials_test.go | 168 +++ .../openstack/identity/v3/credentials_test.go | 94 ++ .../openstack/identity/v3/domains_test.go | 71 +- .../openstack/identity/v3/endpoint_test.go | 54 +- .../openstack/identity/v3/groups_test.go | 99 +- .../openstack/identity/v3/identity.go | 18 + .../openstack/identity/v3/policies_test.go | 143 +++ .../openstack/identity/v3/projects_test.go | 136 ++- .../openstack/identity/v3/reauth_test.go | 34 + .../openstack/identity/v3/regions_test.go | 54 +- .../openstack/identity/v3/roles_test.go | 641 +++++++--- .../openstack/identity/v3/service_test.go | 37 +- .../openstack/identity/v3/token_test.go | 33 +- .../openstack/identity/v3/users_test.go | 315 +++-- .../imageservice/v2/imagedata_test.go | 29 + .../imageservice/v2/imageimport_test.go | 33 + .../openstack/imageservice/v2/images_test.go | 136 ++- .../openstack/imageservice/v2/imageservice.go | 118 +- .../openstack/imageservice/v2/tasks_test.go | 63 + .../openstack/keymanager/v1/acls_test.go | 103 ++ .../keymanager/v1/containers_test.go | 188 +++ .../openstack/keymanager/v1/keymanager.go | 716 ++++++++++++ .../openstack/keymanager/v1/orders_test.go | 84 ++ .../openstack/keymanager/v1/secrets_test.go | 242 ++++ .../loadbalancer/v2/amphorae_test.go | 32 + .../loadbalancer/v2/l7policies_test.go | 32 + .../loadbalancer/v2/listeners_test.go | 32 + .../openstack/loadbalancer/v2/loadbalancer.go | 463 ++++++++ .../loadbalancer/v2/loadbalancers_test.go | 496 ++++++++ .../loadbalancer/v2/monitors_test.go | 32 + .../{networking => loadbalancer}/v2/pkg.go | 0 .../openstack/loadbalancer/v2/pools_test.go | 32 + .../openstack/messaging/v2/claims_test.go | 63 + .../openstack/messaging/v2/message_test.go | 301 +++++ .../openstack/messaging/v2/messaging.go | 173 +++ .../openstack/messaging/v2/queue_test.go | 154 +++ .../networking/v2/apiversion_test.go | 2 +- .../v2/extensions/agents/agents_test.go | 28 + .../networking/v2/extensions/agents/doc.go | 2 + .../v2/extensions/attributestags_test.go | 131 +++ .../networking/v2/extensions/dns/dns.go | 135 +++ .../networking/v2/extensions/dns/dns_test.go | 266 +++++ .../networking/v2/extensions/extensions.go | 24 +- .../v2/extensions/fwaas/firewall_test.go | 143 +-- .../networking/v2/extensions/fwaas/fwaas.go | 30 +- .../networking/v2/extensions/fwaas/pkg.go | 1 - .../v2/extensions/fwaas/policy_test.go | 64 +- .../v2/extensions/fwaas/rule_test.go | 53 +- .../extensions/layer3/addressscopes_test.go | 53 + .../v2/extensions/layer3/floatingips_test.go | 199 ++-- .../networking/v2/extensions/layer3/layer3.go | 107 +- .../v2/extensions/layer3/routers_test.go | 144 ++- .../v2/extensions/lbaas/members_test.go | 4 +- .../v2/extensions/lbaas/monitors_test.go | 4 +- .../networking/v2/extensions/lbaas/pkg.go | 1 - .../v2/extensions/lbaas/pools_test.go | 4 +- .../v2/extensions/lbaas/vips_test.go | 4 +- .../v2/extensions/lbaas_v2/l7policies_test.go | 32 + .../v2/extensions/lbaas_v2/lbaas_v2.go | 187 ++- .../v2/extensions/lbaas_v2/listeners_test.go | 4 +- .../extensions/lbaas_v2/loadbalancers_test.go | 341 +++--- .../v2/extensions/lbaas_v2/monitors_test.go | 4 +- .../networking/v2/extensions/lbaas_v2/pkg.go | 1 - .../v2/extensions/lbaas_v2/pools_test.go | 4 +- .../networking/v2/extensions/mtu/mtu.go | 61 + .../networking/v2/extensions/mtu/mtu_test.go | 133 +++ .../networkipavailabilities_test.go | 31 + .../openstack/networking/v2/extensions/pkg.go | 1 - .../v2/extensions/portsbinding/pkg.go | 1 - .../extensions/portsbinding/portsbinding.go | 9 +- .../portsbinding/portsbinding_test.go | 59 +- .../networking/v2/extensions/provider_test.go | 13 +- .../qos/ruletypes/ruletypes_test.go | 31 + .../v2/extensions/rbacpolicies/pkg.go | 1 - .../extensions/rbacpolicies/rbacpolicies.go | 4 + .../rbacpolicies/rbacpolicies_test.go | 53 +- .../networking/v2/extensions/security_test.go | 86 +- .../v2/extensions/subnetpools/subnetpools.go | 12 +- .../subnetpools/subnetpools_test.go | 45 +- .../networking/v2/extensions/trunks/trunks.go | 46 + .../v2/extensions/trunks/trunks_test.go | 286 +++++ .../vlantransparent/vlantransparent.go | 106 ++ .../vlantransparent/vlantransparent_test.go | 45 + .../v2/extensions/vpnaas/group_test.go | 30 +- .../v2/extensions/vpnaas/ikepolicy_test.go | 30 +- .../v2/extensions/vpnaas/ipsecpolicy_test.go | 29 +- .../v2/extensions/vpnaas/service_test.go | 29 +- .../extensions/vpnaas/siteconnection_test.go | 65 +- .../networking/v2/extensions/vpnaas/vpnaas.go | 16 +- .../openstack/networking/v2/networking.go | 253 +++- .../openstack/networking/v2/networks_test.go | 108 +- .../openstack/networking/v2/ports_test.go | 238 ++-- .../openstack/networking/v2/subnets_test.go | 217 ++-- .../objectstorage/v1/containers_test.go | 27 +- .../objectstorage/v1/objects_test.go | 10 +- .../orchestration/v1/buildinfo_test.go | 6 +- .../openstack/orchestration/v1/common.go | 44 - .../orchestration/v1/hello-compute.json | 13 - .../orchestration/v1/orchestration.go | 115 ++ .../orchestration/v1/stackevents_test.go | 67 +- .../orchestration/v1/stackresources_test.go | 74 +- .../openstack/orchestration/v1/stacks_test.go | 148 +-- .../orchestration/v1/stacktemplates_test.go | 73 +- .../sharedfilesystems/v2/messages/messages.go | 19 + .../v2/messages/messages_test.go | 105 ++ .../sharedfilesystems/v2/messages/pkg.go | 3 + .../sharedfilesystems/v2/securityservices.go | 23 +- .../v2/securityservices_test.go | 27 +- .../sharedfilesystems/v2/sharenetworks.go | 37 +- .../v2/sharenetworks_test.go | 39 +- .../openstack/sharedfilesystems/v2/shares.go | 133 ++- .../sharedfilesystems/v2/shares_test.go | 182 ++- .../sharedfilesystems/v2/sharetypes.go | 8 - .../sharedfilesystems/v2/sharetypes_test.go | 11 +- .../sharedfilesystems/v2/snapshots.go | 101 ++ .../sharedfilesystems/v2/snapshots_test.go | 118 ++ .../openstack/workflow/v2/crontrigger.go | 70 ++ .../workflow/v2/crontriggers_test.go | 58 + .../openstack/workflow/v2/execution.go | 83 ++ .../openstack/workflow/v2/executions_test.go | 54 + .../openstack/workflow/v2/workflow.go | 95 ++ .../openstack/workflow/v2/workflows_test.go | 46 + .../gophercloud/gophercloud/auth_options.go | 159 ++- .../gophercloud/gophercloud/auth_result.go | 52 + .../github.com/gophercloud/gophercloud/doc.go | 63 +- .../gophercloud/gophercloud/{ => docs}/FAQ.md | 4 + .../gophercloud/docs/MICROVERSIONS.md | 79 ++ .../gophercloud/{ => docs}/MIGRATING.md | 2 +- .../gophercloud/{ => docs}/STYLEGUIDE.md | 6 + .../gophercloud/{ => docs}/assets/openlab.png | Bin .../{ => docs}/assets/vexxhost.png | Bin .../contributor-tutorial/.template/doc.go | 13 + .../.template/requests.go | 105 ++ .../contributor-tutorial/.template/results.go | 83 ++ .../.template/testing/fixtures.go | 118 ++ .../.template/testing/requests_test.go | 89 ++ .../contributor-tutorial/.template/urls.go | 23 + .../docs/contributor-tutorial/README.md | 12 + .../step-01-introduction.md | 16 + .../contributor-tutorial/step-02-issues.md | 124 ++ .../step-03-code-hunting.md | 104 ++ .../step-04-acceptance-testing.md | 27 + .../step-05-pull-requests.md | 189 +++ .../step-06-code-review.md | 93 ++ .../step-07-congratulations.md | 9 + .../gophercloud/gophercloud/errors.go | 56 +- .../github.com/gophercloud/gophercloud/go.mod | 7 + .../github.com/gophercloud/gophercloud/go.sum | 8 + .../gophercloud/openstack/auth_env.go | 84 +- .../openstack/baremetal/noauth/doc.go | 17 + .../openstack/baremetal/noauth/requests.go | 36 + .../baremetal/noauth/testing/requests_test.go | 16 + .../baremetal/v1/allocations/requests.go | 131 +++ .../baremetal/v1/allocations/results.go | 114 ++ .../v1/allocations/testing/fixtures.go | 168 +++ .../v1/allocations/testing/requests_test.go | 79 ++ .../baremetal/v1/allocations/urls.go | 23 + .../openstack/baremetal/v1/drivers/doc.go | 43 + .../baremetal/v1/drivers/requests.go | 70 ++ .../openstack/baremetal/v1/drivers/results.go | 198 ++++ .../baremetal/v1/drivers/testing/doc.go | 2 + .../baremetal/v1/drivers/testing/fixtures.go | 407 +++++++ .../v1/drivers/testing/requests_test.go | 84 ++ .../openstack/baremetal/v1/drivers/urls.go | 19 + .../openstack/baremetal/v1/nodes/doc.go | 130 +++ .../openstack/baremetal/v1/nodes/requests.go | 593 ++++++++++ .../openstack/baremetal/v1/nodes/results.go | 313 +++++ .../baremetal/v1/nodes/testing/doc.go | 2 + .../baremetal/v1/nodes/testing/fixtures.go | 1033 +++++++++++++++++ .../v1/nodes/testing/requests_test.go | 432 +++++++ .../openstack/baremetal/v1/nodes/urls.go | 59 + .../openstack/baremetal/v1/ports/doc.go | 85 ++ .../openstack/baremetal/v1/ports/requests.go | 216 ++++ .../openstack/baremetal/v1/ports/results.go | 131 +++ .../baremetal/v1/ports/testing/doc.go | 2 + .../baremetal/v1/ports/testing/fixtures.go | 252 ++++ .../v1/ports/testing/requests_test.go | 144 +++ .../openstack/baremetal/v1/ports/urls.go | 31 + .../baremetalintrospection/noauth/doc.go | 15 + .../baremetalintrospection/noauth/requests.go | 36 + .../noauth/testing/requests_test.go | 16 + .../v1/introspection/doc.go | 59 + .../v1/introspection/requests.go | 116 ++ .../v1/introspection/results.go | 293 +++++ .../v1/introspection/testing/doc.go | 1 + .../v1/introspection/testing/fixtures.go | 396 +++++++ .../v1/introspection/testing/requests_test.go | 98 ++ .../v1/introspection/testing/results_test.go | 29 + .../v1/introspection/urls.go | 23 + .../blockstorage/extensions/backups/doc.go | 61 + .../extensions/backups/microversions.go | 21 + .../extensions/backups/requests.go | 184 +++ .../extensions/backups/results.go | 155 +++ .../extensions/backups/testing/fixtures.go | 133 +++ .../backups/testing/requests_test.go | 96 ++ .../blockstorage/extensions/backups/urls.go | 23 + .../blockstorage/extensions/quotasets/doc.go | 42 + .../extensions/quotasets/requests.go | 94 ++ .../extensions/quotasets/results.go | 165 +++ .../extensions/quotasets/testing/doc.go | 2 + .../extensions/quotasets/testing/fixtures.go | 162 +++ .../quotasets/testing/requests_test.go | 80 ++ .../blockstorage/extensions/quotasets/urls.go | 21 + .../extensions/schedulerhints/doc.go | 52 + .../extensions/schedulerhints/requests.go | 124 ++ .../extensions/schedulerhints/testing/doc.go | 2 + .../schedulerhints/testing/requests_test.go | 58 + .../extensions/schedulerstats/results.go | 4 +- .../blockstorage/v1/apiversions/urls.go | 12 +- .../blockstorage/v1/snapshots/requests.go | 7 +- .../blockstorage/v1/volumes/requests.go | 11 +- .../v1/volumes/testing/requests_test.go | 3 +- .../blockstorage/v2/snapshots/requests.go | 7 +- .../blockstorage/v2/volumes/requests.go | 58 +- .../blockstorage/v2/volumes/results.go | 15 +- .../v2/volumes/testing/requests_test.go | 5 +- .../blockstorage/v3/snapshots/requests.go | 7 +- .../blockstorage/v3/volumes/requests.go | 45 +- .../blockstorage/v3/volumes/results.go | 2 + .../v3/volumes/testing/fixtures.go | 4 + .../v3/volumes/testing/requests_test.go | 6 +- .../blockstorage/v3/volumetypes/requests.go | 6 +- .../v3/volumetypes/testing/requests_test.go | 3 +- .../gophercloud/openstack/client.go | 88 +- .../openstack/clustering/v1/actions/doc.go | 33 + .../clustering/v1/actions/requests.go | 51 + .../clustering/v1/actions/results.go | 106 ++ .../clustering/v1/actions/testing/doc.go | 2 + .../clustering/v1/actions/testing/fixtures.go | 166 +++ .../v1/actions/testing/requests_test.go | 44 + .../openstack/clustering/v1/actions/urls.go | 22 + .../openstack/clustering/v1/clusters/doc.go | 287 +++++ .../clustering/v1/clusters/requests.go | 694 +++++++++++ .../clustering/v1/clusters/results.go | 219 ++++ .../clustering/v1/clusters/testing/doc.go | 2 + .../v1/clusters/testing/fixtures.go | 705 +++++++++++ .../v1/clusters/testing/requests_test.go | 493 ++++++++ .../openstack/clustering/v1/clusters/urls.go | 58 + .../openstack/clustering/v1/events/doc.go | 33 + .../clustering/v1/events/requests.go | 53 + .../openstack/clustering/v1/events/results.go | 66 ++ .../clustering/v1/events/testing/doc.go | 2 + .../clustering/v1/events/testing/fixtures.go | 131 +++ .../v1/events/testing/requests_test.go | 45 + .../openstack/clustering/v1/events/urls.go | 22 + .../openstack/clustering/v1/nodes/doc.go | 111 ++ .../openstack/clustering/v1/nodes/requests.go | 255 ++++ .../openstack/clustering/v1/nodes/results.go | 144 +++ .../clustering/v1/nodes/testing/doc.go | 2 + .../clustering/v1/nodes/testing/fixtures.go | 364 ++++++ .../v1/nodes/testing/requests_test.go | 144 +++ .../openstack/clustering/v1/nodes/urls.go | 42 + .../openstack/clustering/v1/policies/doc.go | 96 ++ .../clustering/v1/policies/requests.go | 193 +++ .../clustering/v1/policies/results.go | 185 +++ .../clustering/v1/policies/testing/doc.go | 2 + .../v1/policies/testing/fixtures.go | 513 ++++++++ .../v1/policies/testing/requests_test.go | 148 +++ .../openstack/clustering/v1/policies/urls.go | 32 + .../clustering/v1/policytypes/doc.go | 16 +- .../clustering/v1/policytypes/requests.go | 12 + .../clustering/v1/policytypes/results.go | 44 +- .../v1/policytypes/testing/fixtures.go | 206 +++- .../v1/policytypes/testing/requests_test.go | 12 + .../clustering/v1/policytypes/urls.go | 4 + .../openstack/clustering/v1/profiles/doc.go | 110 ++ .../clustering/v1/profiles/requests.go | 173 +++ .../clustering/v1/profiles/results.go | 166 +++ .../clustering/v1/profiles/testing/doc.go | 2 + .../v1/profiles/testing/fixtures.go | 463 ++++++++ .../v1/profiles/testing/requests_test.go | 140 +++ .../openstack/clustering/v1/profiles/urls.go | 38 + .../clustering/v1/profiletypes/doc.go | 47 + .../clustering/v1/profiletypes/requests.go | 28 + .../clustering/v1/profiletypes/results.go | 67 ++ .../clustering/v1/profiletypes/testing/doc.go | 2 + .../v1/profiletypes/testing/fixtures.go | 296 +++++ .../v1/profiletypes/testing/requests_test.go | 74 ++ .../clustering/v1/profiletypes/urls.go | 28 + .../openstack/clustering/v1/receivers/doc.go | 81 ++ .../clustering/v1/receivers/requests.go | 157 +++ .../clustering/v1/receivers/results.go | 126 ++ .../clustering/v1/receivers/testing/doc.go | 2 + .../v1/receivers/testing/fixtures.go | 241 ++++ .../v1/receivers/testing/requests_test.go | 105 ++ .../openstack/clustering/v1/receivers/urls.go | 38 + .../openstack/clustering/v1/webhooks/doc.go | 15 + .../clustering/v1/webhooks/requests.go | 53 + .../clustering/v1/webhooks/results.go | 22 + .../clustering/v1/webhooks/testing/doc.go | 2 + .../v1/webhooks/testing/requests_test.go | 135 +++ .../openstack/clustering/v1/webhooks/urls.go | 7 + .../extensions/attachinterfaces/requests.go | 2 +- .../v2/extensions/attachinterfaces/results.go | 4 +- .../v2/extensions/bootfromvolume/requests.go | 8 + .../bootfromvolume/testing/fixtures.go | 275 +++++ .../bootfromvolume/testing/requests_test.go | 307 +---- .../extendedserverattributes/doc.go | 67 ++ .../extendedserverattributes/microversions.go | 82 ++ .../extendedserverattributes/results.go | 10 + .../extendedserverattributes/testing/doc.go | 1 + .../testing/fixtures.go | 28 + .../testing/requests_test.go | 58 + .../compute/v2/extensions/hypervisors/doc.go | 27 +- .../v2/extensions/hypervisors/requests.go | 12 +- .../v2/extensions/hypervisors/results.go | 95 +- .../hypervisors/testing/fixtures.go | 219 +++- .../hypervisors/testing/requests_test.go | 55 + .../v2/extensions/keypairs/requests.go | 2 +- .../v2/extensions/quotasets/requests.go | 26 +- .../extensions/quotasets/testing/fixtures.go | 14 +- .../v2/extensions/remoteconsoles/doc.go | 25 + .../v2/extensions/remoteconsoles/requests.go | 83 ++ .../v2/extensions/remoteconsoles/results.go | 38 + .../extensions/remoteconsoles/testing/doc.go | 1 + .../remoteconsoles/testing/fixtures.go | 22 + .../remoteconsoles/testing/requests_test.go | 40 + .../v2/extensions/remoteconsoles/urls.go | 17 + .../v2/extensions/rescueunrescue/doc.go | 28 + .../v2/extensions/rescueunrescue/requests.go | 48 + .../v2/extensions/rescueunrescue/results.go | 28 + .../extensions/rescueunrescue/testing/doc.go | 1 + .../rescueunrescue/testing/fixtures.go | 25 + .../rescueunrescue/testing/requests_test.go | 49 + .../v2/extensions/rescueunrescue/urls.go | 7 + .../v2/extensions/secgroups/requests.go | 18 +- .../v2/extensions/secgroups/results.go | 2 +- .../secgroups/testing/requests_test.go | 3 +- .../compute/v2/extensions/serverusage/doc.go | 20 + .../v2/extensions/serverusage/results.go | 34 + .../v2/extensions/serverusage/testing/doc.go | 1 + .../serverusage/testing/fixtures.go | 20 + .../serverusage/testing/requests_test.go | 44 + .../compute/v2/extensions/services/results.go | 18 +- .../extensions/services/testing/fixtures.go | 212 +++- .../services/testing/requests_test.go | 32 + .../compute/v2/extensions/usage/doc.go | 58 +- .../compute/v2/extensions/usage/requests.go | 70 +- .../compute/v2/extensions/usage/results.go | 56 +- .../v2/extensions/usage/testing/fixtures.go | 177 +++ .../extensions/usage/testing/requests_test.go | 39 +- .../compute/v2/extensions/usage/urls.go | 2 +- .../openstack/compute/v2/flavors/requests.go | 10 +- .../openstack/compute/v2/flavors/results.go | 2 +- .../v2/flavors/testing/requests_test.go | 4 +- .../compute/v2/servers/microversions.go | 11 + .../openstack/compute/v2/servers/requests.go | 93 +- .../openstack/compute/v2/servers/results.go | 30 +- .../compute/v2/servers/testing/fixtures.go | 155 ++- .../v2/servers/testing/requests_test.go | 73 +- .../openstack/container/v1/capsules/doc.go | 4 + .../openstack/container/v1/capsules/errors.go | 15 + .../container/v1/capsules/microversions.go | 101 ++ .../container/v1/capsules/requests.go | 102 ++ .../container/v1/capsules/results.go | 371 ++++++ .../container/v1/capsules/template.go | 27 + .../container/v1/capsules/testing/doc.go | 1 + .../container/v1/capsules/testing/fixtures.go | 670 +++++++++++ .../v1/capsules/testing/requests_test.go | 159 +++ .../v1/capsules/testing/template_test.go | 29 + .../openstack/container/v1/capsules/urls.go | 21 + .../containerinfra/apiversions/doc.go | 3 + .../containerinfra/apiversions/errors.go | 23 + .../containerinfra/apiversions/requests.go | 19 + .../containerinfra/apiversions/results.go | 68 ++ .../containerinfra/apiversions/testing/doc.go | 2 + .../apiversions/testing/fixtures.go | 88 ++ .../apiversions/testing/requests_test.go | 37 + .../containerinfra/apiversions/urls.go | 20 + .../containerinfra/v1/certificates/doc.go | 33 + .../v1/certificates/requests.go | 65 ++ .../containerinfra/v1/certificates/results.go | 40 + .../v1/certificates/testing/doc.go | 1 + .../v1/certificates/testing/fixtures.go | 111 ++ .../v1/certificates/testing/requests_test.go | 55 + .../containerinfra/v1/certificates/urls.go | 23 + .../containerinfra/v1/clusters/doc.go | 101 ++ .../containerinfra/v1/clusters/requests.go | 214 ++++ .../containerinfra/v1/clusters/results.go | 119 ++ .../containerinfra/v1/clusters/testing/doc.go | 1 + .../v1/clusters/testing/fixtures.go | 292 +++++ .../v1/clusters/testing/requests_test.go | 201 ++++ .../containerinfra/v1/clusters/urls.go | 43 + .../containerinfra/v1/clustertemplates/doc.go | 90 ++ .../v1/clustertemplates/requests.go | 178 +++ .../v1/clustertemplates/results.go | 114 ++ .../v1/clustertemplates/testing/doc.go | 1 + .../v1/clustertemplates/testing/fixtures.go | 532 +++++++++ .../clustertemplates/testing/requests_test.go | 212 ++++ .../v1/clustertemplates/urls.go | 35 + .../openstack/containerinfra/v1/quotas/doc.go | 18 + .../containerinfra/v1/quotas/requests.go | 43 + .../containerinfra/v1/quotas/results.go | 57 + .../containerinfra/v1/quotas/testing/doc.go | 1 + .../v1/quotas/testing/fixtures.go | 36 + .../v1/quotas/testing/requests_test.go | 36 + .../containerinfra/v1/quotas/urls.go | 15 + .../db/v1/configurations/requests.go | 2 +- .../openstack/db/v1/configurations/results.go | 4 +- .../openstack/db/v1/instances/results.go | 33 + .../db/v1/instances/testing/fixtures.go | 86 +- .../db/v1/instances/testing/requests_test.go | 30 + .../openstack/dns/v2/recordsets/requests.go | 19 +- .../v2/recordsets/testing/requests_test.go | 6 +- .../openstack/dns/v2/zones/requests.go | 2 +- .../dns/v2/zones/testing/requests_test.go | 3 +- .../openstack/identity/v2/tenants/requests.go | 2 +- .../v2/tenants/testing/requests_test.go | 3 +- .../openstack/identity/v2/tokens/results.go | 15 + .../v3/applicationcredentials/requests.go | 95 ++ .../v3/applicationcredentials/results.go | 127 ++ .../testing/fixtures.go | 404 +++++++ .../testing/requests_test.go | 124 ++ .../v3/applicationcredentials/urls.go | 19 + .../identity/v3/credentials/requests.go | 125 ++ .../identity/v3/credentials/results.go | 94 ++ .../v3/credentials/testing/fixtures.go | 217 ++++ .../v3/credentials/testing/requests_test.go | 99 ++ .../openstack/identity/v3/credentials/urls.go | 23 + .../openstack/identity/v3/domains/requests.go | 2 +- .../v3/domains/testing/requests_test.go | 3 +- .../identity/v3/endpoints/requests.go | 7 +- .../openstack/identity/v3/groups/errors.go | 17 + .../openstack/identity/v3/groups/requests.go | 24 +- .../v3/groups/testing/requests_test.go | 35 +- .../openstack/identity/v3/policies/doc.go | 77 ++ .../openstack/identity/v3/policies/errors.go | 31 + .../identity/v3/policies/requests.go | 195 ++++ .../openstack/identity/v3/policies/results.go | 131 +++ .../identity/v3/policies/testing/doc.go | 2 + .../identity/v3/policies/testing/fixtures.go | 229 ++++ .../v3/policies/testing/requests_test.go | 229 ++++ .../openstack/identity/v3/policies/urls.go | 25 + .../openstack/identity/v3/projects/errors.go | 17 + .../identity/v3/projects/requests.go | 24 +- .../v3/projects/testing/requests_test.go | 35 +- .../openstack/identity/v3/regions/requests.go | 2 +- .../v3/regions/testing/requests_test.go | 3 +- .../openstack/identity/v3/roles/doc.go | 23 + .../openstack/identity/v3/roles/errors.go | 17 + .../openstack/identity/v3/roles/requests.go | 78 ++ .../identity/v3/roles/testing/fixtures.go | 95 ++ .../v3/roles/testing/requests_test.go | 120 ++ .../openstack/identity/v3/roles/urls.go | 4 + .../openstack/identity/v3/tokens/requests.go | 98 +- .../openstack/identity/v3/tokens/results.go | 7 + .../v3/tokens/testing/requests_test.go | 42 + .../openstack/identity/v3/users/doc.go | 49 + .../openstack/identity/v3/users/errors.go | 17 + .../openstack/identity/v3/users/requests.go | 98 +- .../openstack/identity/v3/users/results.go | 30 + .../identity/v3/users/testing/fixtures.go | 57 +- .../v3/users/testing/requests_test.go | 71 ++ .../openstack/identity/v3/users/urls.go | 16 + .../imageservice/v2/imagedata/doc.go | 15 + .../imageservice/v2/imagedata/requests.go | 11 + .../imageservice/v2/imagedata/results.go | 6 + .../v2/imagedata/testing/fixtures.go | 17 + .../v2/imagedata/testing/requests_test.go | 14 + .../imageservice/v2/imagedata/urls.go | 12 +- .../imageservice/v2/imageimport/doc.go | 27 + .../imageservice/v2/imageimport/requests.go | 53 + .../imageservice/v2/imageimport/results.go | 38 + .../v2/imageimport/testing/doc.go | 1 + .../v2/imageimport/testing/fixtures.go | 25 + .../v2/imageimport/testing/requests_test.go | 60 + .../imageservice/v2/imageimport/urls.go | 17 + .../imageservice/v2/images/requests.go | 59 +- .../imageservice/v2/images/results.go | 10 +- .../v2/images/testing/fixtures.go | 66 +- .../v2/images/testing/requests_test.go | 82 +- .../openstack/imageservice/v2/images/urls.go | 22 +- .../openstack/imageservice/v2/tasks/doc.go | 55 + .../imageservice/v2/tasks/requests.go | 128 ++ .../imageservice/v2/tasks/results.go | 114 ++ .../imageservice/v2/tasks/testing/doc.go | 2 + .../imageservice/v2/tasks/testing/fixtures.go | 124 ++ .../v2/tasks/testing/requests_test.go | 138 +++ .../openstack/imageservice/v2/tasks/urls.go | 55 + .../openstack/keymanager/v1/acls/doc.go | 54 + .../openstack/keymanager/v1/acls/requests.go | 112 ++ .../openstack/keymanager/v1/acls/results.go | 85 ++ .../keymanager/v1/acls/testing/fixtures.go | 168 +++ .../v1/acls/testing/requests_test.go | 115 ++ .../openstack/keymanager/v1/acls/urls.go | 11 + .../openstack/keymanager/v1/containers/doc.go | 86 ++ .../keymanager/v1/containers/requests.go | 212 ++++ .../keymanager/v1/containers/results.go | 232 ++++ .../v1/containers/testing/fixtures.go | 321 +++++ .../v1/containers/testing/requests_test.go | 144 +++ .../keymanager/v1/containers/urls.go | 31 + .../openstack/keymanager/v1/orders/doc.go | 45 + .../keymanager/v1/orders/requests.go | 129 ++ .../openstack/keymanager/v1/orders/results.go | 170 +++ .../keymanager/v1/orders/testing/fixtures.go | 186 +++ .../v1/orders/testing/requests_test.go | 81 ++ .../openstack/keymanager/v1/orders/urls.go | 19 + .../openstack/keymanager/v1/secrets/doc.go | 142 +++ .../keymanager/v1/secrets/requests.go | 424 +++++++ .../keymanager/v1/secrets/results.go | 227 ++++ .../keymanager/v1/secrets/testing/fixtures.go | 356 ++++++ .../v1/secrets/testing/requests_test.go | 182 +++ .../openstack/keymanager/v1/secrets/urls.go | 35 + .../openstack/loadbalancer/v2/amphorae/doc.go | 25 + .../loadbalancer/v2/amphorae/requests.go | 59 + .../loadbalancer/v2/amphorae/results.go | 148 +++ .../loadbalancer/v2/amphorae/testing/doc.go | 1 + .../v2/amphorae/testing/fixtures.go | 169 +++ .../v2/amphorae/testing/requests_test.go | 65 ++ .../loadbalancer/v2/amphorae/urls.go | 16 + .../loadbalancer/v2/apiversions/doc.go | 22 + .../loadbalancer/v2/apiversions/requests.go | 13 + .../loadbalancer/v2/apiversions/results.go | 32 + .../v2/apiversions/testing/doc.go | 2 + .../v2/apiversions/testing/fixture.go | 93 ++ .../v2/apiversions/testing/requests_test.go | 24 + .../loadbalancer/v2/apiversions/urls.go | 14 + .../openstack/loadbalancer/v2/doc.go | 3 + .../loadbalancer/v2/l7policies/doc.go | 123 ++ .../loadbalancer/v2/l7policies/requests.go | 376 ++++++ .../loadbalancer/v2/l7policies/results.go | 237 ++++ .../loadbalancer/v2/l7policies/testing/doc.go | 2 + .../v2/l7policies/testing/fixtures.go | 426 +++++++ .../v2/l7policies/testing/requests_test.go | 317 +++++ .../loadbalancer/v2/l7policies/urls.go | 25 + .../loadbalancer/v2/listeners/doc.go | 74 ++ .../loadbalancer/v2/listeners/requests.go | 245 ++++ .../loadbalancer/v2/listeners/results.go | 187 +++ .../loadbalancer/v2/listeners/testing/doc.go | 2 + .../v2/listeners/testing/fixtures.go | 280 +++++ .../v2/listeners/testing/requests_test.go | 160 +++ .../loadbalancer/v2/listeners/urls.go | 21 + .../loadbalancer/v2/loadbalancers/doc.go | 92 ++ .../loadbalancer/v2/loadbalancers/requests.go | 241 ++++ .../loadbalancer/v2/loadbalancers/results.go | 200 ++++ .../v2/loadbalancers/testing/doc.go | 2 + .../v2/loadbalancers/testing/fixtures.go | 346 ++++++ .../v2/loadbalancers/testing/requests_test.go | 171 +++ .../loadbalancer/v2/loadbalancers/urls.go | 31 + .../openstack/loadbalancer/v2/monitors/doc.go | 69 ++ .../loadbalancer/v2/monitors/requests.go | 257 ++++ .../loadbalancer/v2/monitors/results.go | 156 +++ .../loadbalancer/v2/monitors/testing/doc.go | 2 + .../v2/monitors/testing/fixtures.go | 215 ++++ .../v2/monitors/testing/requests_test.go | 155 +++ .../loadbalancer/v2/monitors/urls.go | 16 + .../openstack/loadbalancer/v2/pools/doc.go | 154 +++ .../loadbalancer/v2/pools/requests.go | 378 ++++++ .../loadbalancer/v2/pools/results.go | 328 ++++++ .../loadbalancer/v2/pools/testing/doc.go | 2 + .../loadbalancer/v2/pools/testing/fixtures.go | 440 +++++++ .../v2/pools/testing/requests_test.go | 328 ++++++ .../openstack/loadbalancer/v2/pools/urls.go | 25 + .../loadbalancer/v2/testhelper/client.go | 14 + .../openstack/messaging/v2/claims/doc.go | 53 + .../openstack/messaging/v2/claims/requests.go | 123 ++ .../openstack/messaging/v2/claims/results.go | 52 + .../messaging/v2/claims/testing/doc.go | 2 + .../messaging/v2/claims/testing/fixtures.go | 133 +++ .../v2/claims/testing/requests_test.go | 58 + .../openstack/messaging/v2/claims/urls.go | 24 + .../openstack/messaging/v2/messages/doc.go | 121 ++ .../messaging/v2/messages/requests.go | 253 ++++ .../messaging/v2/messages/results.go | 132 +++ .../messaging/v2/messages/testing/doc.go | 2 + .../messaging/v2/messages/testing/fixtures.go | 316 +++++ .../v2/messages/testing/requests_test.go | 126 ++ .../openstack/messaging/v2/messages/urls.go | 49 + .../openstack/messaging/v2/queues/doc.go | 111 ++ .../openstack/messaging/v2/queues/requests.go | 292 +++++ .../openstack/messaging/v2/queues/results.go | 201 ++++ .../messaging/v2/queues/testing/doc.go | 2 + .../messaging/v2/queues/testing/fixtures.go | 315 +++++ .../v2/queues/testing/requests_test.go | 133 +++ .../openstack/messaging/v2/queues/urls.go | 61 + .../networking/v2/apiversions/requests.go | 4 +- .../networking/v2/apiversions/urls.go | 13 +- .../networking/v2/extensions/agents/doc.go | 32 + .../v2/extensions/agents/requests.go | 66 ++ .../v2/extensions/agents/results.go | 128 ++ .../v2/extensions/agents/testing/doc.go | 2 + .../v2/extensions/agents/testing/fixtures.go | 125 ++ .../agents/testing/requests_test.go | 90 ++ .../networking/v2/extensions/agents/urls.go | 21 + .../v2/extensions/attributestags/doc.go | 37 + .../v2/extensions/attributestags/requests.go | 81 ++ .../v2/extensions/attributestags/results.go | 57 + .../attributestags/testing/fixtures.go | 19 + .../attributestags/testing/requests_test.go | 138 +++ .../v2/extensions/attributestags/urls.go | 31 + .../networking/v2/extensions/dns/requests.go | 171 +++ .../networking/v2/extensions/dns/results.go | 30 + .../v2/extensions/dns/testing/fixtures.go | 320 +++++ .../extensions/dns/testing/requests_test.go | 388 +++++++ .../networking/v2/extensions/external/doc.go | 9 +- .../v2/extensions/external/requests.go | 28 + .../extensions/external/testing/fixtures.go | 2 + .../external/testing/requests_test.go | 26 + .../external/testing/results_test.go | 3 +- .../v2/extensions/fwaas/firewalls/requests.go | 10 +- .../fwaas/firewalls/testing/requests_test.go | 6 +- .../v2/extensions/fwaas/policies/requests.go | 4 +- .../fwaas/policies/testing/requests_test.go | 6 +- .../routerinsertion/testing/requests_test.go | 12 +- .../v2/extensions/layer3/addressscopes/doc.go | 64 + .../layer3/addressscopes/requests.go | 147 +++ .../layer3/addressscopes/results.go | 99 ++ .../layer3/addressscopes/testing/doc.go | 2 + .../layer3/addressscopes/testing/fixtures.go | 112 ++ .../addressscopes/testing/requests_test.go | 153 +++ .../extensions/layer3/addressscopes/urls.go | 33 + .../v2/extensions/layer3/floatingips/doc.go | 2 +- .../extensions/layer3/floatingips/requests.go | 48 +- .../extensions/layer3/floatingips/results.go | 20 +- .../layer3/floatingips/testing/fixtures.go | 50 + .../floatingips/testing/requests_test.go | 31 +- .../v2/extensions/layer3/routers/requests.go | 7 + .../v2/extensions/layer3/routers/results.go | 10 +- .../layer3/routers/testing/requests_test.go | 15 +- .../v2/extensions/lbaas/pools/requests.go | 2 +- .../lbaas/pools/testing/requests_test.go | 3 +- .../v2/extensions/lbaas_v2/l7policies/doc.go | 123 ++ .../lbaas_v2/l7policies/requests.go | 376 ++++++ .../extensions/lbaas_v2/l7policies/results.go | 245 ++++ .../lbaas_v2/l7policies/testing/doc.go | 2 + .../lbaas_v2/l7policies/testing/fixtures.go | 428 +++++++ .../l7policies/testing/requests_test.go | 317 +++++ .../v2/extensions/lbaas_v2/l7policies/urls.go | 25 + .../extensions/lbaas_v2/listeners/requests.go | 25 +- .../extensions/lbaas_v2/listeners/results.go | 10 + .../lbaas_v2/listeners/testing/fixtures.go | 1 + .../listeners/testing/requests_test.go | 7 +- .../extensions/lbaas_v2/loadbalancers/doc.go | 8 + .../lbaas_v2/loadbalancers/requests.go | 10 +- .../lbaas_v2/loadbalancers/results.go | 37 + .../loadbalancers/testing/fixtures.go | 98 +- .../loadbalancers/testing/requests_test.go | 19 +- .../extensions/lbaas_v2/loadbalancers/urls.go | 11 +- .../extensions/lbaas_v2/monitors/requests.go | 2 +- .../extensions/lbaas_v2/monitors/results.go | 4 + .../monitors/testing/requests_test.go | 3 +- .../v2/extensions/lbaas_v2/pools/doc.go | 8 +- .../v2/extensions/lbaas_v2/pools/requests.go | 10 +- .../v2/extensions/lbaas_v2/pools/results.go | 18 + .../lbaas_v2/pools/testing/requests_test.go | 12 +- .../networking/v2/extensions/mtu/requests.go | 87 ++ .../networking/v2/extensions/mtu/results.go | 8 + .../v2/extensions/mtu/testing/fixtures.go | 55 + .../extensions/mtu/testing/requests_test.go | 24 + .../v2/extensions/mtu/testing/results_test.go | 152 +++ .../extensions/networkipavailabilities/doc.go | 30 + .../networkipavailabilities/requests.go | 61 + .../networkipavailabilities/results.go | 139 +++ .../networkipavailabilities/testing/doc.go | 2 + .../testing/fixtures.go | 130 +++ .../testing/requests_test.go | 89 ++ .../networkipavailabilities/urls.go | 21 + .../v2/extensions/portsbinding/requests.go | 17 +- .../v2/extensions/portsbinding/results.go | 8 +- .../portsbinding/testing/requests_test.go | 6 +- .../provider/testing/results_test.go | 6 +- .../v2/extensions/qos/policies/doc.go | 181 +++ .../v2/extensions/qos/policies/requests.go | 114 ++ .../v2/extensions/qos/policies/results.go | 7 + .../qos/policies/testing/fixtures.go | 134 +++ .../qos/policies/testing/requests_test.go | 294 +++++ .../v2/extensions/qos/ruletypes/doc.go | 19 + .../v2/extensions/qos/ruletypes/requests.go | 13 + .../v2/extensions/qos/ruletypes/results.go | 26 + .../extensions/qos/ruletypes/testing/doc.go | 2 + .../qos/ruletypes/testing/fixtures.go | 19 + .../qos/ruletypes/testing/requests_test.go | 41 + .../v2/extensions/qos/ruletypes/urls.go | 7 + .../v2/extensions/rbacpolicies/requests.go | 4 + .../v2/extensions/rbacpolicies/results.go | 3 + .../v2/extensions/security/groups/requests.go | 30 +- .../v2/extensions/security/groups/results.go | 3 + .../v2/extensions/security/rules/requests.go | 4 + .../v2/extensions/security/rules/results.go | 3 + .../security/rules/testing/requests_test.go | 4 + .../v2/extensions/subnetpools/requests.go | 4 + .../v2/extensions/subnetpools/results.go | 3 + .../networking/v2/extensions/trunks/doc.go | 143 +++ .../v2/extensions/trunks/requests.go | 195 ++++ .../v2/extensions/trunks/results.go | 137 +++ .../v2/extensions/trunks/testing/doc.go | 2 + .../v2/extensions/trunks/testing/fixtures.go | 384 ++++++ .../trunks/testing/requests_test.go | 305 +++++ .../networking/v2/extensions/trunks/urls.go | 45 + .../v2/extensions/vlantransparent/doc.go | 97 ++ .../v2/extensions/vlantransparent/requests.go | 84 ++ .../v2/extensions/vlantransparent/results.go | 8 + .../extensions/vlantransparent/testing/doc.go | 2 + .../vlantransparent/testing/fixtures.go | 133 +++ .../vlantransparent/testing/requests_test.go | 172 +++ .../ikepolicies/testing/requests_test.go | 38 +- .../openstack/networking/v2/networks/doc.go | 3 +- .../networking/v2/networks/requests.go | 20 +- .../networking/v2/networks/results.go | 6 + .../v2/networks/testing/fixtures.go | 15 +- .../v2/networks/testing/requests_test.go | 5 +- .../openstack/networking/v2/ports/requests.go | 20 +- .../openstack/networking/v2/ports/results.go | 6 + .../networking/v2/ports/testing/fixtures.go | 16 + .../v2/ports/testing/requests_test.go | 18 +- .../openstack/networking/v2/subnets/doc.go | 6 +- .../networking/v2/subnets/requests.go | 30 +- .../networking/v2/subnets/results.go | 6 + .../networking/v2/subnets/testing/fixtures.go | 98 ++ .../v2/subnets/testing/requests_test.go | 180 ++- .../objectstorage/v1/accounts/requests.go | 2 +- .../objectstorage/v1/accounts/results.go | 13 + .../v1/accounts/testing/fixtures.go | 18 + .../v1/accounts/testing/requests_test.go | 27 + .../objectstorage/v1/containers/doc.go | 7 + .../objectstorage/v1/containers/requests.go | 46 +- .../objectstorage/v1/containers/results.go | 1 + .../v1/containers/testing/requests_test.go | 5 +- .../openstack/objectstorage/v1/objects/doc.go | 4 + .../objectstorage/v1/objects/requests.go | 66 +- .../objectstorage/v1/objects/results.go | 59 +- .../v1/objects/testing/fixtures.go | 2 + .../v1/objects/testing/requests_test.go | 83 +- .../v1/swauth/testing/requests_test.go | 8 + .../orchestration/v1/apiversions/requests.go | 2 +- .../orchestration/v1/apiversions/urls.go | 13 +- .../orchestration/v1/stackevents/doc.go | 21 +- .../orchestration/v1/stackevents/results.go | 14 +- .../v1/stackevents/testing/fixtures.go | 31 +- .../v1/stackevents/testing/requests_test.go | 2 +- .../orchestration/v1/stackresources/doc.go | 74 +- .../v1/stackresources/requests.go | 36 + .../v1/stackresources/results.go | 59 +- .../v1/stackresources/testing/fixtures.go | 36 +- .../stackresources/testing/requests_test.go | 13 + .../orchestration/v1/stackresources/urls.go | 4 + .../openstack/orchestration/v1/stacks/doc.go | 245 +++- .../orchestration/v1/stacks/errors.go | 8 + .../orchestration/v1/stacks/requests.go | 136 ++- .../orchestration/v1/stacks/results.go | 87 +- .../v1/stacks/testing/fixtures.go | 46 +- .../v1/stacks/testing/requests_test.go | 69 +- .../openstack/orchestration/v1/stacks/urls.go | 4 + .../orchestration/v1/stacks/utils.go | 2 +- .../orchestration/v1/stacktemplates/doc.go | 44 +- .../sharedfilesystems/apiversions/urls.go | 14 +- .../sharedfilesystems/v2/errors/errors.go | 44 + .../v2/errors/testing/fixtures.go | 42 + .../v2/errors/testing/request_test.go | 34 + .../sharedfilesystems/v2/messages/requests.go | 72 ++ .../sharedfilesystems/v2/messages/results.go | 99 ++ .../v2/messages/testing/fixtures.go | 115 ++ .../v2/messages/testing/requests_test.go | 125 ++ .../sharedfilesystems/v2/messages/urls.go | 15 + .../v2/securityservices/requests.go | 20 +- .../v2/securityservices/results.go | 2 + .../securityservices/testing/requests_test.go | 3 +- .../v2/sharenetworks/requests.go | 4 +- .../v2/sharenetworks/testing/requests_test.go | 12 +- .../sharedfilesystems/v2/shares/requests.go | 260 ++++- .../sharedfilesystems/v2/shares/results.go | 147 +++ .../v2/shares/testing/fixtures.go | 221 ++++ .../v2/shares/testing/request_test.go | 148 +++ .../sharedfilesystems/v2/shares/urls.go | 24 + .../v2/snapshots/requests.go | 161 +++ .../sharedfilesystems/v2/snapshots/results.go | 193 +++ .../v2/snapshots/testing/fixtures.go | 206 ++++ .../v2/snapshots/testing/request_test.go | 127 ++ .../sharedfilesystems/v2/snapshots/urls.go | 23 + .../openstack/utils/base_endpoint.go | 28 + .../utils/testing/base_endpoint_test.go | 88 ++ .../openstack/workflow/v2/crontriggers/doc.go | 73 ++ .../workflow/v2/crontriggers/requests.go | 254 ++++ .../workflow/v2/crontriggers/results.go | 156 +++ .../v2/crontriggers/testing/requests_test.go | 281 +++++ .../workflow/v2/crontriggers/urls.go | 19 + .../openstack/workflow/v2/executions/doc.go | 70 ++ .../workflow/v2/executions/requests.go | 239 ++++ .../workflow/v2/executions/results.go | 158 +++ .../v2/executions/testing/requests_test.go | 267 +++++ .../openstack/workflow/v2/executions/urls.go | 19 + .../openstack/workflow/v2/workflows/doc.go | 73 ++ .../workflow/v2/workflows/requests.go | 212 ++++ .../workflow/v2/workflows/results.go | 132 +++ .../v2/workflows/testing/requests_test.go | 256 ++++ .../openstack/workflow/v2/workflows/urls.go | 21 + .../gophercloud/pagination/pager.go | 32 +- .../gophercloud/gophercloud/params.go | 28 +- .../gophercloud/provider_client.go | 156 ++- .../gophercloud/gophercloud/results.go | 88 +- .../gophercloud/script/acceptancetest | 91 +- .../gophercloud/script/acceptancetest-ironic | 29 + .../gophercloud/gophercloud/script/format | 26 +- .../gophercloud/gophercloud/script/stackenv | 26 + .../gophercloud/gophercloud/script/unittest | 4 +- .../gophercloud/gophercloud/service_client.go | 30 + .../gophercloud/testing/params_test.go | 6 +- .../testing/provider_client_test.go | 223 +++- .../testing/service_client_test.go | 19 + 886 files changed, 69936 insertions(+), 4021 deletions(-) create mode 100644 vendor/github.com/docker/go-metrics/go.mod create mode 100644 vendor/github.com/docker/go-metrics/go.sum create mode 100644 vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml create mode 100644 vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test/run.yaml create mode 100644 vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-unittest/run.yaml create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/allocations_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/nodes_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/ports_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/allocations_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/baremetal.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/nodes_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/ports_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/backups_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerhints_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumetenants_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/quotaset_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/actions_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clustering.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clusters_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/events_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/nodes_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policies_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiles_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiletypes_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/receivers_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/webhooktrigger_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/remoteconsoles_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/rescueunrescue_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/certificates_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clusters_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clustertemplates_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/containerinfra.go rename vendor/github.com/gophercloud/gophercloud/acceptance/openstack/{orchestration => containerinfra}/v1/pkg.go (100%) create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/quotas_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/applicationcredentials_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/credentials_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/policies_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/reauth_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imagedata_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageimport_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/tasks_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/acls_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/containers_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/keymanager.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/orders_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/secrets_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/amphorae_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/l7policies_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/listeners_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancer.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/monitors_test.go rename vendor/github.com/gophercloud/gophercloud/acceptance/openstack/{networking => loadbalancer}/v2/pkg.go (100%) create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/pools_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/claims_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/message_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/messaging.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/queue_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/agents_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/attributestags_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns_test.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/l7policies_test.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/networkipavailabilities/networkipavailabilities_test.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/pkg.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/qos/ruletypes/ruletypes_test.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/common.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/orchestration.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontrigger.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontriggers_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/execution.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/executions_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflow.go create mode 100644 vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflows_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/auth_result.go rename vendor/github.com/gophercloud/gophercloud/{ => docs}/FAQ.md (97%) create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/MICROVERSIONS.md rename vendor/github.com/gophercloud/gophercloud/{ => docs}/MIGRATING.md (95%) rename vendor/github.com/gophercloud/gophercloud/{ => docs}/STYLEGUIDE.md (95%) rename vendor/github.com/gophercloud/gophercloud/{ => docs}/assets/openlab.png (100%) rename vendor/github.com/gophercloud/gophercloud/{ => docs}/assets/vexxhost.png (100%) create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/README.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-01-introduction.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-02-issues.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-03-code-hunting.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-04-acceptance-testing.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-05-pull-requests.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-06-code-review.md create mode 100644 vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-07-congratulations.md create mode 100644 vendor/github.com/gophercloud/gophercloud/go.mod create mode 100644 vendor/github.com/gophercloud/gophercloud/go.sum create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/results_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/microversions.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/microversions.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/microversions.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/template.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/template_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/fixture.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper/client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/results_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/request_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/utils/testing/base_endpoint_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/testing/requests_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/urls.go create mode 100755 vendor/github.com/gophercloud/gophercloud/script/acceptancetest-ironic create mode 100644 vendor/github.com/gophercloud/gophercloud/script/stackenv diff --git a/glide.lock b/glide.lock index 57c8c88753f6..84062d21114c 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 9409c48ec721d1a3a1a6336845d10efe49bbf0dfe5e363e5d95b8b657beaefc3 -updated: 2019-08-16T14:35:45.701339235-05:00 +hash: cf7ec48f1bb6db439c6d0bf9046f6d82e59ce06f073571a6454cc722ad67f0b7 +updated: 2019-10-14T10:41:19.59248441-04:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -373,7 +373,7 @@ imports: - sockets - tlsconfig - name: github.com/docker/go-metrics - version: b84716841b82eab644a0c64fc8b42d480e49add5 + version: b619b3592b65de4f087d9f16863a7e6ff905973c - name: github.com/docker/go-units version: 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 - name: github.com/docker/libnetwork @@ -513,7 +513,7 @@ imports: subpackages: - mat64 - name: github.com/google/btree - version: 20236160a414454a9c64b6c8829381c6f4bddcaa + version: be84af90a1f71c9eeac820a4cdacb863122396d6 - name: github.com/google/cadvisor version: 087e94f7f4d628ac57e427d292607984dde59041 repo: https://github.com/openshift/google-cadvisor.git @@ -581,7 +581,7 @@ imports: - compiler - extensions - name: github.com/gophercloud/gophercloud - version: 781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d + version: c2d73b246b48e239d3f03c455905e06fe26e33c3 subpackages: - openstack - openstack/blockstorage/extensions/volumeactions @@ -602,6 +602,7 @@ imports: - openstack/networking/v2/extensions/external - openstack/networking/v2/extensions/layer3/floatingips - openstack/networking/v2/extensions/layer3/routers + - openstack/networking/v2/extensions/lbaas_v2/l7policies - openstack/networking/v2/extensions/lbaas_v2/listeners - openstack/networking/v2/extensions/lbaas_v2/loadbalancers - openstack/networking/v2/extensions/lbaas_v2/monitors @@ -1402,7 +1403,7 @@ imports: - storage/v1alpha1 - storage/v1beta1 - name: k8s.io/apiextensions-apiserver - version: 45cb0b8537cf79d23914721e0bf41a3479769b22 + version: 7e311602c75d6a2483b2928d99c44183e42a0136 repo: https://github.com/openshift/kubernetes-apiextensions-apiserver.git subpackages: - pkg/apis/apiextensions @@ -1862,7 +1863,7 @@ imports: - pkg/util/proto/testing - pkg/util/proto/validation - name: k8s.io/kubernetes - version: 58be19a7e8bc2e29eef4936c8f6dedbcaa0d0388 + version: ad88da9c44a16b896cd5eecdbed1d63515543b2d repo: https://github.com/openshift/kubernetes.git subpackages: - cmd/controller-manager/app diff --git a/glide.yaml b/glide.yaml index a70f9896a705..49bffc810332 100644 --- a/glide.yaml +++ b/glide.yaml @@ -346,7 +346,7 @@ import: - package: github.com/googleapis/gnostic version: 0c5108395e2debce0d731cf0287ddf7242066aba - package: github.com/gophercloud/gophercloud - version: 781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d + version: 0.1.0 - package: github.com/gorilla/context version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - package: github.com/gorilla/mux diff --git a/vendor/github.com/docker/go-metrics/go.mod b/vendor/github.com/docker/go-metrics/go.mod new file mode 100644 index 000000000000..7e328f0cffe3 --- /dev/null +++ b/vendor/github.com/docker/go-metrics/go.mod @@ -0,0 +1,5 @@ +module github.com/docker/go-metrics + +go 1.11 + +require github.com/prometheus/client_golang v1.1.0 diff --git a/vendor/github.com/docker/go-metrics/go.sum b/vendor/github.com/docker/go-metrics/go.sum new file mode 100644 index 000000000000..b8fb9d079d3f --- /dev/null +++ b/vendor/github.com/docker/go-metrics/go.sum @@ -0,0 +1,67 @@ +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/google/btree/btree_test.go b/vendor/github.com/google/btree/btree_test.go index 78a90cd8a6c8..feed0879cb46 100644 --- a/vendor/github.com/google/btree/btree_test.go +++ b/vendor/github.com/google/btree/btree_test.go @@ -648,14 +648,16 @@ func BenchmarkDescendLessOrEqual(b *testing.B) { const cloneTestSize = 10000 -func cloneTest(t *testing.T, b *BTree, start int, p []Item, wg *sync.WaitGroup, trees *[]*BTree) { +func cloneTest(t *testing.T, b *BTree, start int, p []Item, wg *sync.WaitGroup, trees *[]*BTree, lock *sync.Mutex) { t.Logf("Starting new clone at %v", start) + lock.Lock() *trees = append(*trees, b) + lock.Unlock() for i := start; i < cloneTestSize; i++ { b.ReplaceOrInsert(p[i]) if i%(cloneTestSize/5) == 0 { wg.Add(1) - go cloneTest(t, b.Clone(), i+1, p, wg, trees) + go cloneTest(t, b.Clone(), i+1, p, wg, trees, lock) } } wg.Done() @@ -667,7 +669,7 @@ func TestCloneConcurrentOperations(t *testing.T) { p := perm(cloneTestSize) var wg sync.WaitGroup wg.Add(1) - go cloneTest(t, b, 0, p, &wg, &trees) + go cloneTest(t, b, 0, p, &wg, &trees, &sync.Mutex{}) wg.Wait() want := rang(cloneTestSize) t.Logf("Starting equality checks on %d trees", len(trees)) diff --git a/vendor/github.com/gophercloud/gophercloud/.github/CONTRIBUTING.md b/vendor/github.com/gophercloud/gophercloud/.github/CONTRIBUTING.md index d6c894637e1f..af0bdb979a8a 100644 --- a/vendor/github.com/gophercloud/gophercloud/.github/CONTRIBUTING.md +++ b/vendor/github.com/gophercloud/gophercloud/.github/CONTRIBUTING.md @@ -1,10 +1,16 @@ # Contributing to Gophercloud +- [New Contributor Tutorial](#new-contributor-tutorial) - [3 ways to get involved](#3-ways-to-get-involved) - [Getting started](#getting-started) - [Tests](#tests) - [Style guide](#basic-style-guide) +## New Contributor Tutorial + +For new contributors, we've put together a detailed tutorial +[here](https://github.com/gophercloud/gophercloud/tree/master/docs/contributor-tutorial)! + ## 3 ways to get involved There are three main ways you can get involved in our open-source project, and @@ -101,7 +107,7 @@ need to checkout a new feature branch: git commit ``` -7. Submit your branch as a [Pull Request](https://help.github.com/articles/creating-a-pull-request/). When submitting a Pull Request, please follow our [Style Guide](https://github.com/gophercloud/gophercloud/blob/master/STYLEGUIDE.md). +7. Submit your branch as a [Pull Request](https://help.github.com/articles/creating-a-pull-request/). When submitting a Pull Request, please follow our [Style Guide](https://github.com/gophercloud/gophercloud/blob/master/docs/STYLEGUIDE.md). > Further information about using Git can be found [here](https://git-scm.com/book/en/v2). @@ -247,4 +253,4 @@ To run tests for a particular sub-package: ## Style guide -See [here](/STYLEGUIDE.md) +See [here](/docs/STYLEGUIDE.md) diff --git a/vendor/github.com/gophercloud/gophercloud/.github/ISSUE_TEMPLATE b/vendor/github.com/gophercloud/gophercloud/.github/ISSUE_TEMPLATE index 1451b81b4b1c..2c3be773ad01 100644 --- a/vendor/github.com/gophercloud/gophercloud/.github/ISSUE_TEMPLATE +++ b/vendor/github.com/gophercloud/gophercloud/.github/ISSUE_TEMPLATE @@ -1 +1 @@ -Before starting a PR, please read the [style guide](https://github.com/gophercloud/gophercloud/blob/master/STYLEGUIDE.md). +Before starting a PR, please read our [contributor tutorial](https://github.com/gophercloud/gophercloud/tree/master/docs/contributor-tutorial). diff --git a/vendor/github.com/gophercloud/gophercloud/.github/PULL_REQUEST_TEMPLATE b/vendor/github.com/gophercloud/gophercloud/.github/PULL_REQUEST_TEMPLATE index 43aafa02f816..ea3abc8f88fe 100644 --- a/vendor/github.com/gophercloud/gophercloud/.github/PULL_REQUEST_TEMPLATE +++ b/vendor/github.com/gophercloud/gophercloud/.github/PULL_REQUEST_TEMPLATE @@ -1,3 +1,6 @@ +Prior to starting a PR, please make sure you have read our +[contributor tutorial](https://github.com/gophercloud/gophercloud/tree/master/docs/contributor-tutorial). + Prior to a PR being reviewed, there needs to be a Github issue that the PR addresses. Replace the brackets and text below with that issue number. diff --git a/vendor/github.com/gophercloud/gophercloud/.gitignore b/vendor/github.com/gophercloud/gophercloud/.gitignore index df9048a010a9..dd91ed205597 100644 --- a/vendor/github.com/gophercloud/gophercloud/.gitignore +++ b/vendor/github.com/gophercloud/gophercloud/.gitignore @@ -1,2 +1,3 @@ **/*.swp .idea +.vscode diff --git a/vendor/github.com/gophercloud/gophercloud/.travis.yml b/vendor/github.com/gophercloud/gophercloud/.travis.yml index 59c419495272..9153a00fc55f 100644 --- a/vendor/github.com/gophercloud/gophercloud/.travis.yml +++ b/vendor/github.com/gophercloud/gophercloud/.travis.yml @@ -1,21 +1,25 @@ language: go sudo: false install: -- go get golang.org/x/crypto/ssh -- go get -v -tags 'fixtures acceptance' ./... -- go get github.com/wadey/gocovmerge -- go get github.com/mattn/goveralls -- go get golang.org/x/tools/cmd/goimports +- GO111MODULE=off go get golang.org/x/crypto/ssh +- GO111MODULE=off go get -v -tags 'fixtures acceptance' ./... +- GO111MODULE=off go get github.com/wadey/gocovmerge +- GO111MODULE=off go get github.com/mattn/goveralls +- GO111MODULE=off go get golang.org/x/tools/cmd/goimports go: -- 1.8 -- tip +- "1.10" +- "1.11" +- "1.12" +- "tip" env: global: - secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ=" + - GO111MODULE=on before_script: - go vet ./... script: - ./script/coverage +- ./script/unittest - ./script/format after_success: - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml index c259d03e184f..135e3b203a8d 100644 --- a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml +++ b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml @@ -1,12 +1,114 @@ +- job: + name: gophercloud-unittest + parent: golang-test + description: | + Run gophercloud unit test + run: .zuul/playbooks/gophercloud-unittest/run.yaml + nodeset: ubuntu-xenial-ut + +- job: + name: gophercloud-acceptance-test + parent: golang-test + description: | + Run gophercloud acceptance test on master branch + run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml + +- job: + name: gophercloud-acceptance-test-ironic + parent: golang-test + description: | + Run gophercloud ironic acceptance test on master branch + run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml + +- job: + name: gophercloud-acceptance-test-stein + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on stein branch + vars: + global_env: + OS_BRANCH: stable/stein + +- job: + name: gophercloud-acceptance-test-rocky + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on rocky branch + vars: + global_env: + OS_BRANCH: stable/rocky + +- job: + name: gophercloud-acceptance-test-queens + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on queens branch + vars: + global_env: + OS_BRANCH: stable/queens + +- job: + name: gophercloud-acceptance-test-pike + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on pike branch + vars: + global_env: + OS_BRANCH: stable/pike + +- job: + name: gophercloud-acceptance-test-ocata + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on ocata branch + vars: + global_env: + OS_BRANCH: stable/ocata + +- job: + name: gophercloud-acceptance-test-newton + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on newton branch + vars: + global_env: + OS_BRANCH: stable/newton + +- job: + name: gophercloud-acceptance-test-mitaka + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on mitaka branch + vars: + global_env: + OS_BRANCH: stable/mitaka + nodeset: ubuntu-trusty + - project: name: gophercloud/gophercloud check: jobs: - gophercloud-unittest - gophercloud-acceptance-test + - gophercloud-acceptance-test-ironic recheck-mitaka: jobs: - gophercloud-acceptance-test-mitaka + recheck-newton: + jobs: + - gophercloud-acceptance-test-newton + recheck-ocata: + jobs: + - gophercloud-acceptance-test-ocata recheck-pike: jobs: - gophercloud-acceptance-test-pike + recheck-queens: + jobs: + - gophercloud-acceptance-test-queens + recheck-rocky: + jobs: + - gophercloud-acceptance-test-rocky + recheck-stein: + jobs: + - gophercloud-acceptance-test-stein diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml new file mode 100644 index 000000000000..64b098c282d0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml @@ -0,0 +1,26 @@ +- hosts: all + become: yes + roles: + - config-golang + - clone-devstack-gate-to-workspace + - role: create-devstack-local-conf + enable_services: + - 'ironic' + - role: install-devstack + environment: + OVERRIDE_ENABLED_SERVICES: 'g-api,g-reg,q-agt,q-dhcp,q-l3,q-svc,key,mysql,rabbit,ir-api,ir-cond,s-account,s-container,s-object,s-proxy,tempest' + PROJECTS: 'openstack/ironic' + tasks: + - name: Run ironic acceptance tests with gophercloud + shell: + cmd: | + set -e + set -o pipefail + set -x + echo $(export |grep OS_BRANCH) + + go get ./... || true + ./script/acceptancetest-ironic -v 2>&1 | tee $TEST_RESULTS_TXT + executable: /bin/bash + chdir: '{{ zuul.project.src_dir }}' + environment: '{{ global_env }}' diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test/run.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test/run.yaml new file mode 100644 index 000000000000..26ed03921391 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-acceptance-test/run.yaml @@ -0,0 +1,27 @@ +- hosts: all + become: yes + roles: + - config-golang + - clone-devstack-gate-to-workspace + - role: create-devstack-local-conf + enable_services: + - 'manila' + - 'designate' + - 'zun' + - 'octavia' + - 'neutron-ext' + - install-devstack + tasks: + - name: Run acceptance tests with gophercloud + shell: + cmd: | + set -e + set -o pipefail + set -x + echo $(export |grep OS_BRANCH) + + go get ./... || true + ./script/acceptancetest -v 2>&1 | tee $TEST_RESULTS_TXT + executable: /bin/bash + chdir: '{{ zuul.project.src_dir }}' + environment: '{{ global_env }}' diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-unittest/run.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-unittest/run.yaml new file mode 100644 index 000000000000..feefa54f2eed --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.zuul/playbooks/gophercloud-unittest/run.yaml @@ -0,0 +1,16 @@ +- hosts: all + become: yes + roles: + - config-golang + tasks: + - name: Run unit tests with gophercloud + shell: + cmd: | + set -e + set -o pipefail + set -x + go get ./... || true + ./script/unittest -v 2>&1 | tee $TEST_RESULTS_TXT + executable: /bin/bash + chdir: '{{ zuul.project.src_dir }}' + environment: '{{ global_env }}' diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md index bb218c3fe9e1..ad29041d9bfe 100644 --- a/vendor/github.com/gophercloud/gophercloud/README.md +++ b/vendor/github.com/gophercloud/gophercloud/README.md @@ -127,7 +127,7 @@ new resource in the `server` variable (a ## Advanced Usage -Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works. +Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works. ## Backwards-Compatibility Guarantees @@ -140,7 +140,7 @@ See the [contributing guide](./.github/CONTRIBUTING.md). ## Help and feedback If you're struggling with something or have spotted a potential bug, feel free -to submit an issue to our [bug tracker](/issues). +to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues). ## Thank You @@ -148,12 +148,12 @@ We'd like to extend special thanks and appreciation to the following: ### OpenLab - + OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases. ### VEXXHOST - + VEXXHOST is providing their services to assist with the development and testing of Gophercloud. diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/README.md b/vendor/github.com/gophercloud/gophercloud/acceptance/README.md index ab3569574836..b2a5b35101ea 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/README.md +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/README.md @@ -75,7 +75,8 @@ to set them manually. #### Shared file systems |Name|Description| |---|---| -|`OS_SHARE_NETWORK_ID`| The share network ID to use when creating shares| +|`OS_NETWORK_ID`| The network ID to use when creating shared network| +|`OS_SUBNET_ID`| The subnet ID to use when creating shared network| ### 3. Run the test suite diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go index 4c916309a049..e8490edd1c39 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go @@ -11,7 +11,8 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" - "github.com/gophercloud/gophercloud/openstack/blockstorage/noauth" + baremetalNoAuth "github.com/gophercloud/gophercloud/openstack/baremetal/noauth" + blockstorageNoAuth "github.com/gophercloud/gophercloud/openstack/blockstorage/noauth" ) // AcceptanceTestChoices contains image and flavor selections for use by the acceptance tests. @@ -29,15 +30,24 @@ type AcceptanceTestChoices struct { // FloatingIPPool contains the name of the pool from where to obtain floating IPs. FloatingIPPoolName string + // MagnumKeypair contains the ID of a valid key pair. + MagnumKeypair string + + // MagnumImageID contains the ID of a valid magnum image. + MagnumImageID string + // NetworkName is the name of a network to launch the instance on. NetworkName string + // NetworkID is the ID of a network to launch the instance on. + NetworkID string + + // SubnetID is the ID of a subnet to launch the instance on. + SubnetID string + // ExternalNetworkID is the network ID of the external network. ExternalNetworkID string - // ShareNetworkID is the Manila Share network ID - ShareNetworkID string - // DBDatastoreType is the datastore type for DB tests. DBDatastoreType string @@ -51,10 +61,13 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { imageID := os.Getenv("OS_IMAGE_ID") flavorID := os.Getenv("OS_FLAVOR_ID") flavorIDResize := os.Getenv("OS_FLAVOR_ID_RESIZE") + magnumImageID := os.Getenv("OS_MAGNUM_IMAGE_ID") + magnumKeypair := os.Getenv("OS_MAGNUM_KEYPAIR") networkName := os.Getenv("OS_NETWORK_NAME") + networkID := os.Getenv("OS_NETWORK_ID") + subnetID := os.Getenv("OS_SUBNET_ID") floatingIPPoolName := os.Getenv("OS_POOL_NAME") externalNetworkID := os.Getenv("OS_EXTGW_ID") - shareNetworkID := os.Getenv("OS_SHARE_NETWORK_ID") dbDatastoreType := os.Getenv("OS_DB_DATASTORE_TYPE") dbDatastoreVersion := os.Getenv("OS_DB_DATASTORE_VERSION") @@ -74,12 +87,19 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { if externalNetworkID == "" { missing = append(missing, "OS_EXTGW_ID") } + + /* // Temporarily disabled, see https://github.com/gophercloud/gophercloud/issues/1345 + if networkID == "" { + missing = append(missing, "OS_NETWORK_ID") + } + if subnetID == "" { + missing = append(missing, "OS_SUBNET_ID") + } + */ + if networkName == "" { networkName = "private" } - if shareNetworkID == "" { - missing = append(missing, "OS_SHARE_NETWORK_ID") - } notDistinct := "" if flavorID == flavorIDResize { notDistinct = "OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE must be distinct." @@ -102,9 +122,12 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { FlavorID: flavorID, FlavorIDResize: flavorIDResize, FloatingIPPoolName: floatingIPPoolName, + MagnumImageID: magnumImageID, + MagnumKeypair: magnumKeypair, NetworkName: networkName, + NetworkID: networkID, + SubnetID: subnetID, ExternalNetworkID: externalNetworkID, - ShareNetworkID: shareNetworkID, DBDatastoreType: dbDatastoreType, DBDatastoreVersion: dbDatastoreVersion, }, nil @@ -177,7 +200,7 @@ func NewBlockStorageV3Client() (*gophercloud.ServiceClient, error) { // making calls to the OpenStack Block Storage v2 API. An error will be // returned if client creation was not possible. func NewBlockStorageV2NoAuthClient() (*gophercloud.ServiceClient, error) { - client, err := noauth.NewClient(gophercloud.AuthOptions{ + client, err := blockstorageNoAuth.NewClient(gophercloud.AuthOptions{ Username: os.Getenv("OS_USERNAME"), TenantName: os.Getenv("OS_TENANT_NAME"), }) @@ -187,7 +210,7 @@ func NewBlockStorageV2NoAuthClient() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return noauth.NewBlockStorageNoAuth(client, noauth.EndpointOpts{ + return blockstorageNoAuth.NewBlockStorageNoAuth(client, blockstorageNoAuth.EndpointOpts{ CinderEndpoint: os.Getenv("CINDER_ENDPOINT"), }) } @@ -196,7 +219,7 @@ func NewBlockStorageV2NoAuthClient() (*gophercloud.ServiceClient, error) { // making calls to the OpenStack Block Storage v2 API. An error will be // returned if client creation was not possible. func NewBlockStorageV3NoAuthClient() (*gophercloud.ServiceClient, error) { - client, err := noauth.NewClient(gophercloud.AuthOptions{ + client, err := blockstorageNoAuth.NewClient(gophercloud.AuthOptions{ Username: os.Getenv("OS_USERNAME"), TenantName: os.Getenv("OS_TENANT_NAME"), }) @@ -206,7 +229,7 @@ func NewBlockStorageV3NoAuthClient() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return noauth.NewBlockStorageNoAuth(client, noauth.EndpointOpts{ + return blockstorageNoAuth.NewBlockStorageNoAuth(client, blockstorageNoAuth.EndpointOpts{ CinderEndpoint: os.Getenv("CINDER_ENDPOINT"), }) } @@ -232,6 +255,57 @@ func NewComputeV2Client() (*gophercloud.ServiceClient, error) { }) } +// NewBareMetalV1Client returns a *ServiceClient for making calls +// to the OpenStack Bare Metal v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewBareMetalV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewBareMetalV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewBareMetalV1NoAuthClient returns a *ServiceClient for making calls +// to the OpenStack Bare Metal v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewBareMetalV1NoAuthClient() (*gophercloud.ServiceClient, error) { + return baremetalNoAuth.NewBareMetalNoAuth(baremetalNoAuth.EndpointOpts{ + IronicEndpoint: os.Getenv("IRONIC_ENDPOINT"), + }) +} + +// NewBareMetalIntrospectionV1Client returns a *ServiceClient for making calls +// to the OpenStack Bare Metal Introspection v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewBareMetalIntrospectionV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewBareMetalIntrospectionV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + // NewDBV1Client returns a *ServiceClient for making calls // to the OpenStack Database v1 API. An error will be returned // if authentication or client creation was not possible. @@ -460,6 +534,111 @@ func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) { }) } +// NewLoadBalancerV2Client returns a *ServiceClient for making calls to the +// OpenStack Octavia v2 API. An error will be returned if authentication +// or client creation was not possible. +func NewLoadBalancerV2Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewLoadBalancerV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewClusteringV1Client returns a *ServiceClient for making calls +// to the OpenStack Clustering v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewClusteringV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewClusteringV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewMessagingV2Client returns a *ServiceClient for making calls +// to the OpenStack Messaging (Zaqar) v2 API. An error will be returned +// if authentication or client creation was not possible. +func NewMessagingV2Client(clientID string) (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewMessagingV2(client, clientID, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewContainerV1Client returns a *ServiceClient for making calls +// to the OpenStack Container V1 API. An error will be returned +// if authentication or client creation was not possible. +func NewContainerV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewContainerV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewKeyManagerV1Client returns a *ServiceClient for making calls +// to the OpenStack Key Manager (Barbican) v1 API. An error will be +// returned if authentication or client creation was not possible. +func NewKeyManagerV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewKeyManagerV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + // configureDebug will configure the provider client to print the API // requests and responses if OS_DEBUG is enabled. func configureDebug(client *gophercloud.ProviderClient) *gophercloud.ProviderClient { @@ -474,10 +653,10 @@ func configureDebug(client *gophercloud.ProviderClient) *gophercloud.ProviderCli return client } -// NewLoadBalancerV2Client returns a *ServiceClient for making calls to the -// OpenStack Octavia v2 API. An error will be returned if authentication -// or client creation was not possible. -func NewLoadBalancerV2Client() (*gophercloud.ServiceClient, error) { +// NewContainerInfraV1Client returns a *ServiceClient for making calls +// to the OpenStack Container Infra Management v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewContainerInfraV1Client() (*gophercloud.ServiceClient, error) { ao, err := openstack.AuthOptionsFromEnv() if err != nil { return nil, err @@ -488,15 +667,17 @@ func NewLoadBalancerV2Client() (*gophercloud.ServiceClient, error) { return nil, err } - return openstack.NewLoadBalancerV2(client, gophercloud.EndpointOpts{ + client = configureDebug(client) + + return openstack.NewContainerInfraV1(client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } -// NewClusteringV1Client returns a *ServiceClient for making calls to the -// OpenStack Clustering v1 API. An error will be returned if authentication -// or client creation was not possible. -func NewClusteringV1Client() (*gophercloud.ServiceClient, error) { +// NewWorkflowV2Client returns a *ServiceClient for making calls +// to the OpenStack Workflow v2 API (Mistral). An error will be returned if +// authentication or client creation failed. +func NewWorkflowV2Client() (*gophercloud.ServiceClient, error) { ao, err := openstack.AuthOptionsFromEnv() if err != nil { return nil, err @@ -507,7 +688,30 @@ func NewClusteringV1Client() (*gophercloud.ServiceClient, error) { return nil, err } - return openstack.NewClusteringV1(client, gophercloud.EndpointOpts{ + client = configureDebug(client) + + return openstack.NewWorkflowV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewOrchestrationV1Client returns a *ServiceClient for making calls +// to the OpenStack Orchestration v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewOrchestrationV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/conditions.go b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/conditions.go index 9c62c29c1177..bc8b306d084f 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/conditions.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/conditions.go @@ -12,6 +12,21 @@ func RequireAdmin(t *testing.T) { } } +// RequireNonAdmin will restrict a test to only be run by non-admin users. +func RequireNonAdmin(t *testing.T) { + if os.Getenv("OS_USERNAME") == "admin" { + t.Skip("must be a non-admin to run this test") + } +} + +// RequireDNS will restrict a test to only be run in environments +// that support DNSaaS. +func RequireDNS(t *testing.T) { + if os.Getenv("OS_DNS_ENVIRONMENT") == "" { + t.Skip("this test requires DNSaaS") + } +} + // RequireGuestAgent will restrict a test to only be run in // environments that support the QEMU guest agent. func RequireGuestAgent(t *testing.T) { @@ -20,6 +35,14 @@ func RequireGuestAgent(t *testing.T) { } } +// 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) { @@ -42,3 +65,11 @@ func RequireNovaNetwork(t *testing.T) { t.Skip("this test requires nova-network and to set OS_NOVANET to 1") } } + +// SkipRelease will have the test be skipped on a certain +// release. Releases are named such as 'stable/mitaka', master, etc. +func SkipRelease(t *testing.T, release string) { + if os.Getenv("OS_BRANCH") == release { + t.Skipf("this is not supported in %s", release) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/http.go b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/http.go index 3f42231e32ed..c8d266f89dc7 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/http.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/http.go @@ -73,8 +73,6 @@ func (lrt *LogRoundTripper) logRequest(original io.ReadCloser, contentType strin if strings.HasPrefix(contentType, "application/json") { debugInfo := lrt.formatJSON(bs.Bytes()) log.Printf("[DEBUG] OpenStack Request Body: %s", debugInfo) - } else { - log.Printf("[DEBUG] OpenStack Request Body: %s", bs.String()) } return ioutil.NopCloser(strings.NewReader(bs.String())), nil @@ -104,14 +102,25 @@ func (lrt *LogRoundTripper) logResponse(original io.ReadCloser, contentType stri // formatJSON will try to pretty-format a JSON body. // It will also mask known fields which contain sensitive information. func (lrt *LogRoundTripper) formatJSON(raw []byte) string { - var data map[string]interface{} + var rawData interface{} - err := json.Unmarshal(raw, &data) + err := json.Unmarshal(raw, &rawData) if err != nil { log.Printf("[DEBUG] Unable to parse OpenStack JSON: %s", err) return string(raw) } + data, ok := rawData.(map[string]interface{}) + if !ok { + pretty, err := json.MarshalIndent(rawData, "", " ") + if err != nil { + log.Printf("[DEBUG] Unable to re-marshal OpenStack JSON: %s", err) + return string(raw) + } + + return string(pretty) + } + // Mask known password fields if v, ok := data["auth"].(map[string]interface{}); ok { if v, ok := v["identity"].(map[string]interface{}); ok { @@ -120,6 +129,12 @@ func (lrt *LogRoundTripper) formatJSON(raw []byte) string { v["password"] = "***" } } + if v, ok := v["application_credential"].(map[string]interface{}); ok { + v["secret"] = "***" + } + if v, ok := v["token"].(map[string]interface{}); ok { + v["id"] = "***" + } } } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/allocations_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/allocations_test.go new file mode 100644 index 000000000000..dc66a107bddd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/allocations_test.go @@ -0,0 +1,45 @@ +// +build acceptance baremetal allocations + +package noauth + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + v1 "github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestAllocationsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + + client.Microversion = "1.52" + + allocation, err := v1.CreateAllocation(t, client) + th.AssertNoErr(t, err) + defer v1.DeleteAllocation(t, client, allocation) + + found := false + err = allocations.List(client, allocations.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + allocationList, err := allocations.ExtractAllocations(page) + if err != nil { + return false, err + } + + for _, a := range allocationList { + if a.UUID == allocation.UUID { + found = true + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, found, true) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/doc.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/doc.go new file mode 100644 index 000000000000..e5869bf3c540 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/doc.go @@ -0,0 +1,8 @@ +package noauth + +/* +Acceptance tests for Ironic endpoints with auth_strategy=noauth. Specify +IRONIC_ENDPOINT environment variable. For example: + + IRONIC_ENDPOINT="http://127.0.0.1:6385/v1" go test ./acceptance/openstack/baremetal/noauth/... +*/ diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/nodes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/nodes_test.go new file mode 100644 index 000000000000..8095cf59a07e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/nodes_test.go @@ -0,0 +1,95 @@ +package noauth + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + v1 "github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + "github.com/gophercloud/gophercloud/pagination" + + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNodesCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + client.Microversion = "1.50" + + node, err := v1.CreateNode(t, client) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + + found := false + err = nodes.List(client, nodes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + nodeList, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + for _, n := range nodeList { + if n.UUID == node.UUID { + found = true + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) + + th.AssertEquals(t, found, true) +} + +func TestNodesUpdate(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + client.Microversion = "1.50" + + node, err := v1.CreateNode(t, client) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + + updated, err := nodes.Update(client, node.UUID, nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.ReplaceOp, + Path: "/maintenance", + Value: "true", + }, + }).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, updated.Maintenance, true) +} + +func TestNodesRAIDConfig(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + client.Microversion = "1.50" + + node, err := v1.CreateNode(t, client) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + + sizeGB := 100 + isTrue := true + + err = nodes.SetRAIDConfig(client, node.UUID, nodes.RAIDConfigOpts{ + LogicalDisks: []nodes.LogicalDisk{ + { + SizeGB: &sizeGB, + IsRootVolume: &isTrue, + RAIDLevel: nodes.RAID5, + DiskType: nodes.HDD, + NumberOfPhysicalDisks: 5, + }, + }, + }).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/ports_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/ports_test.go new file mode 100644 index 000000000000..2498cdb68f26 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/noauth/ports_test.go @@ -0,0 +1,73 @@ +// +build acceptance baremetal ports + +package noauth + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + v1 "github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" + "github.com/gophercloud/gophercloud/pagination" + + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestPortsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + client.Microversion = "1.53" + + node, err := v1.CreateFakeNode(t, client) + port, err := v1.CreatePort(t, client, node) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + defer v1.DeletePort(t, client, port) + + found := false + err = ports.List(client, ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + for _, p := range portList { + if p.UUID == port.UUID { + found = true + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) + + th.AssertEquals(t, found, true) +} + +func TestPortsUpdate(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + client.Microversion = "1.53" + + node, err := v1.CreateFakeNode(t, client) + port, err := v1.CreatePort(t, client, node) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + defer v1.DeletePort(t, client, port) + + updated, err := ports.Update(client, port.UUID, ports.UpdateOpts{ + ports.UpdateOperation{ + Op: ports.ReplaceOp, + Path: "/address", + Value: "aa:bb:cc:dd:ee:ff", + }, + }).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, updated.Address, "aa:bb:cc:dd:ee:ff") +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/allocations_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/allocations_test.go new file mode 100644 index 000000000000..e342dc1fe6f0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/allocations_test.go @@ -0,0 +1,44 @@ +// +build acceptance baremetal allocations + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestAllocationsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.52" + + allocation, err := CreateAllocation(t, client) + th.AssertNoErr(t, err) + defer DeleteAllocation(t, client, allocation) + + found := false + err = allocations.List(client, allocations.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + allocationList, err := allocations.ExtractAllocations(page) + if err != nil { + return false, err + } + + for _, a := range allocationList { + if a.UUID == allocation.UUID { + found = true + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, found, true) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/baremetal.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/baremetal.go new file mode 100644 index 000000000000..332fb4c15964 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/baremetal.go @@ -0,0 +1,115 @@ +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" +) + +// CreateNode creates a basic node with a randomly generated name. +func CreateNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Node, error) { + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create bare metal node: %s", name) + + node, err := nodes.Create(client, nodes.CreateOpts{ + Name: name, + Driver: "ipmi", + BootInterface: "pxe", + RAIDInterface: "agent", + DriverInfo: map[string]interface{}{ + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin", + }, + }).Extract() + + return node, err +} + +// DeleteNode deletes a bare metal node via its UUID. +func DeleteNode(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Node) { + err := nodes.Delete(client, node.UUID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete node %s: %s", node.UUID, err) + } + + t.Logf("Deleted server: %s", node.UUID) +} + +// CreateAllocation creates an allocation +func CreateAllocation(t *testing.T, client *gophercloud.ServiceClient) (*allocations.Allocation, error) { + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create bare metal allocation: %s", name) + + allocation, err := allocations.Create(client, allocations.CreateOpts{ + Name: name, + ResourceClass: "baremetal", + }).Extract() + + return allocation, err +} + +// DeleteAllocation deletes a bare metal allocation via its UUID. +func DeleteAllocation(t *testing.T, client *gophercloud.ServiceClient, allocation *allocations.Allocation) { + err := allocations.Delete(client, allocation.UUID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete allocation %s: %s", allocation.UUID, err) + } + + t.Logf("Deleted allocation: %s", allocation.UUID) +} + +// CreateFakeNode creates a node with fake-hardware to use for port tests. +func CreateFakeNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Node, error) { + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create bare metal node: %s", name) + + node, err := nodes.Create(client, nodes.CreateOpts{ + Name: name, + Driver: "fake-hardware", + BootInterface: "pxe", + DriverInfo: map[string]interface{}{ + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin", + }, + }).Extract() + + return node, err +} + +// CreatePort - creates a port for a node with a fixed Address +func CreatePort(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Node) (*ports.Port, error) { + mac := "e6:72:1f:52:00:f4" + t.Logf("Attempting to create Port for Node: %s with Address: %s", node.UUID, mac) + + iTrue := true + port, err := ports.Create(client, ports.CreateOpts{ + NodeUUID: node.UUID, + Address: mac, + PXEEnabled: &iTrue, + }).Extract() + + return port, err +} + +// DeletePort - deletes a port via its UUID +func DeletePort(t *testing.T, client *gophercloud.ServiceClient, port *ports.Port) { + err := ports.Delete(client, port.UUID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete port %s: %s", port.UUID, err) + } + + t.Logf("Deleted port: %s", port.UUID) + +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/nodes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/nodes_test.go new file mode 100644 index 000000000000..27afaef8069a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/nodes_test.go @@ -0,0 +1,96 @@ +// +build acceptance baremetal nodes + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + "github.com/gophercloud/gophercloud/pagination" + + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNodesCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.38" + + node, err := CreateNode(t, client) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + + found := false + err = nodes.List(client, nodes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + nodeList, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + for _, n := range nodeList { + if n.UUID == node.UUID { + found = true + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) + + th.AssertEquals(t, found, true) +} + +func TestNodesUpdate(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.38" + + node, err := CreateNode(t, client) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + + updated, err := nodes.Update(client, node.UUID, nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.ReplaceOp, + Path: "/maintenance", + Value: "true", + }, + }).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, updated.Maintenance, true) +} + +func TestNodesRAIDConfig(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.50" + + node, err := CreateNode(t, client) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + + sizeGB := 100 + isTrue := true + + err = nodes.SetRAIDConfig(client, node.UUID, nodes.RAIDConfigOpts{ + LogicalDisks: []nodes.LogicalDisk{ + { + SizeGB: &sizeGB, + IsRootVolume: &isTrue, + RAIDLevel: nodes.RAID5, + DiskType: nodes.HDD, + NumberOfPhysicalDisks: 5, + }, + }, + }).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/ports_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/ports_test.go new file mode 100644 index 000000000000..3547e95cc7d6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/baremetal/v1/ports_test.go @@ -0,0 +1,72 @@ +// +build acceptance baremetal ports + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" + "github.com/gophercloud/gophercloud/pagination" + + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestPortsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.53" + + node, err := CreateFakeNode(t, client) + port, err := CreatePort(t, client, node) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + defer DeletePort(t, client, port) + + found := false + err = ports.List(client, ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + for _, p := range portList { + if p.UUID == port.UUID { + found = true + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) + + th.AssertEquals(t, found, true) +} + +func TestPortsUpdate(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.53" + + node, err := CreateFakeNode(t, client) + port, err := CreatePort(t, client, node) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + defer DeletePort(t, client, port) + + updated, err := ports.Update(client, port.UUID, ports.UpdateOpts{ + ports.UpdateOperation{ + Op: ports.ReplaceOp, + Path: "/address", + Value: "aa:bb:cc:dd:ee:ff", + }, + }).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, updated.Address, "aa:bb:cc:dd:ee:ff") +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/backups_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/backups_test.go new file mode 100644 index 000000000000..41e45c5e6d49 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/backups_test.go @@ -0,0 +1,41 @@ +// +build acceptance blockstorage + +package extensions + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups" + + blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestBackupsCRUD(t *testing.T) { + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volume, err := blockstorage.CreateVolume(t, blockClient) + th.AssertNoErr(t, err) + defer blockstorage.DeleteVolume(t, blockClient, volume) + + backup, err := CreateBackup(t, blockClient, volume.ID) + th.AssertNoErr(t, err) + defer DeleteBackup(t, blockClient, backup.ID) + + allPages, err := backups.List(blockClient, nil).AllPages() + th.AssertNoErr(t, err) + + allBackups, err := backups.ExtractBackups(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allBackups { + if backup.Name == v.Name { + found = true + } + } + + th.AssertEquals(t, found, true) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/extensions.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/extensions.go index 2856b8b655a0..0be9e7ee3140 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/extensions.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/extensions.go @@ -8,10 +8,12 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/images" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateUploadImage will upload volume it as volume-baked image. An name of new image or err will be @@ -171,3 +173,64 @@ func ExtendVolumeSize(t *testing.T, client *gophercloud.ServiceClient, volume *v return nil } + +// CreateBackup will create a backup based on a volume. An error will be +// will be returned if the backup could not be created. +func CreateBackup(t *testing.T, client *gophercloud.ServiceClient, volumeID string) (*backups.Backup, error) { + t.Logf("Attempting to create a backup of volume %s", volumeID) + + backupName := tools.RandomString("ACPTTEST", 16) + createOpts := backups.CreateOpts{ + VolumeID: volumeID, + Name: backupName, + } + + backup, err := backups.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + err = WaitForBackupStatus(client, backup.ID, "available", 120) + if err != nil { + return nil, err + } + + backup, err = backups.Get(client, backup.ID).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created backup %s", backup.ID) + tools.PrintResource(t, backup) + + th.AssertEquals(t, backup.Name, backupName) + + return backup, nil +} + +// DeleteBackup will delete a backup. A fatal error will occur if the backup +// could not be deleted. This works best when used as a deferred function. +func DeleteBackup(t *testing.T, client *gophercloud.ServiceClient, backupID string) { + if err := backups.Delete(client, backupID).ExtractErr(); err != nil { + t.Fatalf("Unable to delete backup %s: %s", backupID, err) + } + + t.Logf("Deleted backup %s", backupID) +} + +// WaitForBackupStatus will continually poll a backup, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForBackupStatus(client *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := backups.Get(client, id).Extract() + if err != nil { + return false, err + } + + if current.Status == status { + return true, nil + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerhints_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerhints_test.go new file mode 100644 index 000000000000..bfb99bb070c5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerhints_test.go @@ -0,0 +1,59 @@ +// +build acceptance blockstorage + +package extensions + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestSchedulerHints(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volumeName := tools.RandomString("ACPTTEST", 16) + createOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + } + + volume1, err := volumes.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, volume1.ID, "available", 60) + th.AssertNoErr(t, err) + defer volumes.Delete(client, volume1.ID, volumes.DeleteOpts{}) + + volumeName = tools.RandomString("ACPTTEST", 16) + base := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + } + + schedulerHints := schedulerhints.SchedulerHints{ + SameHost: []string{ + volume1.ID, + }, + } + + createOptsWithHints := schedulerhints.CreateOptsExt{ + VolumeCreateOptsBuilder: base, + SchedulerHints: schedulerHints, + } + + volume2, err := volumes.Create(client, createOptsWithHints).Extract() + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, volume2.ID, "available", 60) + th.AssertNoErr(t, err) + + err = volumes.Delete(client, volume2.ID, volumes.DeleteOpts{}).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go index 76093f4c126f..d0cd973c07bb 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/schedulerstats_test.go @@ -8,27 +8,24 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestSchedulerStatsList(t *testing.T) { + clients.RequireAdmin(t) + blockClient, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) listOpts := schedulerstats.ListOpts{ Detail: true, } allPages, err := schedulerstats.List(blockClient, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to query schedulerstats: %v", err) - } + th.AssertNoErr(t, err) allStats, err := schedulerstats.ExtractStoragePools(allPages) - if err != nil { - t.Fatalf("Unable to extract pools: %v", err) - } + th.AssertNoErr(t, err) for _, stat := range allStats { tools.PrintResource(t, stat) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/services_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/services_test.go index b65f586ec8d3..df38e98405e6 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/services_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/services_test.go @@ -8,23 +8,20 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/services" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestServicesList(t *testing.T) { + clients.RequireAdmin(t) + blockClient, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := services.List(blockClient, services.ListOpts{}).AllPages() - if err != nil { - t.Fatalf("Unable to list services: %v", err) - } + th.AssertNoErr(t, err) allServices, err := services.ExtractServices(allPages) - if err != nil { - t.Fatalf("Unable to extract services") - } + th.AssertNoErr(t, err) for _, service := range allServices { tools.PrintResource(t, service) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumeactions_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumeactions_test.go index 0c885167655f..9bd9e3cfd5b2 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumeactions_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumeactions_test.go @@ -6,120 +6,87 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" - "github.com/gophercloud/gophercloud/acceptance/tools" - "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" - blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2" compute "github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestVolumeActionsUploadImageDestroy(t *testing.T) { + t.Skip("This test is diabled because it sometimes fails in OpenLab") + blockClient, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) + computeClient, err := clients.NewComputeV2Client() - if err != nil { - t.Fatalf("Unable to create a compute client: %v", err) - } + th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, blockClient) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer blockstorage.DeleteVolume(t, blockClient, volume) volumeImage, err := CreateUploadImage(t, blockClient, volume) - if err != nil { - t.Fatalf("Unable to upload volume-backed image: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, volumeImage) err = DeleteUploadedImage(t, computeClient, volumeImage.ImageName) - if err != nil { - t.Fatalf("Unable to delete volume-backed image: %v", err) - } + th.AssertNoErr(t, err) } func TestVolumeActionsAttachCreateDestroy(t *testing.T) { blockClient, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) computeClient, err := clients.NewComputeV2Client() - if err != nil { - t.Fatalf("Unable to create a compute client: %v", err) - } + th.AssertNoErr(t, err) server, err := compute.CreateServer(t, computeClient) - if err != nil { - t.Fatalf("Unable to create server: %v", err) - } + th.AssertNoErr(t, err) defer compute.DeleteServer(t, computeClient, server) volume, err := blockstorage.CreateVolume(t, blockClient) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer blockstorage.DeleteVolume(t, blockClient, volume) err = CreateVolumeAttach(t, blockClient, volume, server) - if err != nil { - t.Fatalf("Unable to attach volume: %v", err) - } + th.AssertNoErr(t, err) newVolume, err := volumes.Get(blockClient, volume.ID).Extract() - if err != nil { - t.Fatal("Unable to get updated volume information: %v", err) - } + th.AssertNoErr(t, err) DeleteVolumeAttach(t, blockClient, newVolume) } func TestVolumeActionsReserveUnreserve(t *testing.T) { client, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create blockstorage client: %v", err) - } + th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer blockstorage.DeleteVolume(t, client, volume) err = CreateVolumeReserve(t, client, volume) - if err != nil { - t.Fatalf("Unable to create volume reserve: %v", err) - } + th.AssertNoErr(t, err) defer DeleteVolumeReserve(t, client, volume) } func TestVolumeActionsExtendSize(t *testing.T) { blockClient, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) volume, err := blockstorage.CreateVolume(t, blockClient) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer blockstorage.DeleteVolume(t, blockClient, volume) tools.PrintResource(t, volume) err = ExtendVolumeSize(t, blockClient, volume) - if err != nil { - t.Fatalf("Unable to resize volume: %v", err) - } + th.AssertNoErr(t, err) newVolume, err := volumes.Get(blockClient, volume.ID).Extract() - if err != nil { - t.Fatal("Unable to get updated volume information: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newVolume) } @@ -143,7 +110,7 @@ func TestVolumeConns(t *testing.T) { th.AssertNoErr(t, err) t.Logf("Deleting volume") - err = volumes.Delete(client, cv.ID).ExtractErr() + err = volumes.Delete(client, cv.ID, volumes.DeleteOpts{}).ExtractErr() th.AssertNoErr(t, err) }() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumetenants_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumetenants_test.go new file mode 100644 index 000000000000..5396880c55b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/extensions/volumetenants_test.go @@ -0,0 +1,46 @@ +// +build acceptance blockstorage + +package extensions + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestVolumeTenants(t *testing.T) { + type volumeWithTenant struct { + volumes.Volume + volumetenants.VolumeTenantExt + } + + var allVolumes []volumeWithTenant + + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + listOpts := volumes.ListOpts{ + Name: "I SHOULD NOT EXIST", + } + allPages, err := volumes.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + err = volumes.ExtractVolumesInto(allPages, &allVolumes) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(allVolumes)) + + volume1, err := blockstorage.CreateVolume(t, client) + th.AssertNoErr(t, err) + defer blockstorage.DeleteVolume(t, client, volume1) + + allPages, err = volumes.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + err = volumes.ExtractVolumesInto(allPages, &allVolumes) + th.AssertNoErr(t, err) + th.AssertEquals(t, true, len(allVolumes) > 0) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go index 9e8aeab9dc3b..d453dac94a86 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go @@ -78,7 +78,7 @@ func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*vo // DeleteVolume will delete a volume. A fatal error will occur if the volume // failed to be deleted. This works best when used as a deferred function. func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { - err := volumes.Delete(client, volume.ID).ExtractErr() + err := volumes.Delete(client, volume.ID, volumes.DeleteOpts{}).ExtractErr() if err != nil { t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/blockstorage.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/blockstorage.go index 41f24e1ab294..c2b35333f54a 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/blockstorage.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/blockstorage.go @@ -50,11 +50,13 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol } volumeName := tools.RandomString("ACPTTEST", 16) + volumeDescription := tools.RandomString("ACPTTEST-DESC", 16) t.Logf("Attempting to create volume: %s", volumeName) createOpts := volumes.CreateOpts{ - Size: 1, - Name: volumeName, + Size: 1, + Name: volumeName, + Description: volumeDescription, } volume, err := volumes.Create(client, createOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/volumes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/volumes_test.go index 9a555009fb1a..bdbadf1d56fb 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/volumes_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v1/volumes_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestVolumesList(t *testing.T) { @@ -49,4 +50,20 @@ func TestVolumesCreateDestroy(t *testing.T) { } tools.PrintResource(t, newVolume) + th.AssertEquals(t, volume.Name, newVolume.Name) + th.AssertEquals(t, volume.Description, newVolume.Description) + + // Update volume + updatedVolumeName := "" + updatedVolumeDescription := "" + updateOpts := volumes.UpdateOpts{ + Name: &updatedVolumeName, + Description: &updatedVolumeDescription, + } + updatedVolume, err := volumes.Update(client, volume.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, updatedVolume) + th.AssertEquals(t, updatedVolume.Name, updatedVolumeName) + th.AssertEquals(t, updatedVolume.Description, updatedVolumeDescription) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/blockstorage.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/blockstorage.go index 7b4682bbf1a6..b6b93b4c1a20 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/blockstorage.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/blockstorage.go @@ -14,19 +14,45 @@ import ( th "github.com/gophercloud/gophercloud/testhelper" ) +// CreateSnapshot will create a snapshot of the specified volume. +// Snapshot will be assigned a random name and description. +func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { + snapshotName := tools.RandomString("ACPTTEST", 16) + snapshotDescription := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create snapshot: %s", snapshotName) + + createOpts := snapshots.CreateOpts{ + VolumeID: volume.ID, + Name: snapshotName, + Description: snapshotDescription, + } + + snapshot, err := snapshots.Create(client, createOpts).Extract() + if err != nil { + return snapshot, err + } + + err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60) + if err != nil { + return snapshot, err + } + + t.Logf("Successfully created snapshot: %s", snapshot.ID) + + return snapshot, nil +} + // CreateVolume will create a volume with a random name and size of 1GB. An // error will be returned if the volume was unable to be created. func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) { - if testing.Short() { - t.Skip("Skipping test that requires volume creation in short mode.") - } - volumeName := tools.RandomString("ACPTTEST", 16) + volumeDescription := tools.RandomString("ACPTTEST-DESC", 16) t.Logf("Attempting to create volume: %s", volumeName) createOpts := volumes.CreateOpts{ - Size: 1, - Name: volumeName, + Size: 1, + Name: volumeName, + Description: volumeDescription, } volume, err := volumes.Create(client, createOpts).Extract() @@ -39,6 +65,13 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol return volume, err } + tools.PrintResource(t, volume) + th.AssertEquals(t, volume.Name, volumeName) + th.AssertEquals(t, volume.Description, volumeDescription) + th.AssertEquals(t, volume.Size, 1) + + t.Logf("Successfully created volume: %s", volume.ID) + return volume, nil } @@ -77,53 +110,29 @@ func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*vo th.AssertEquals(t, newVolume.Name, volumeName) th.AssertEquals(t, newVolume.Size, 1) + t.Logf("Successfully created volume from image: %s", newVolume.ID) + return newVolume, nil } // DeleteVolume will delete a volume. A fatal error will occur if the volume // failed to be deleted. This works best when used as a deferred function. func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { - err := volumes.Delete(client, volume.ID).ExtractErr() - if err != nil { - t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) - } - - t.Logf("Deleted volume: %s", volume.ID) -} - -// CreateSnapshot will create a snapshot of the specified volume. -// Snapshot will be assigned a random name and description. -func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { - if testing.Short() { - t.Skip("Skipping test that requires snapshot creation in short mode.") - } - - snapshotName := tools.RandomString("ACPTTEST", 16) - snapshotDescription := tools.RandomString("ACPTTEST", 16) - t.Logf("Attempting to create snapshot: %s", snapshotName) + t.Logf("Attempting to delete volume: %s", volume.ID) - createOpts := snapshots.CreateOpts{ - VolumeID: volume.ID, - Name: snapshotName, - Description: snapshotDescription, - } - - snapshot, err := snapshots.Create(client, createOpts).Extract() + err := volumes.Delete(client, volume.ID, volumes.DeleteOpts{}).ExtractErr() if err != nil { - return snapshot, err - } - - err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60) - if err != nil { - return snapshot, err + t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) } - return snapshot, nil + t.Logf("Successfully deleted volume: %s", volume.ID) } // DeleteSnapshot will delete a snapshot. A fatal error will occur if the // snapshot failed to be deleted. func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) { + t.Logf("Attempting to delete snapshot: %s", snapshot.ID) + err := snapshots.Delete(client, snapshot.ID).ExtractErr() if err != nil { t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err) @@ -143,5 +152,5 @@ func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *s t.Fatalf("Error waiting for snapshot to delete: %v", err) } - t.Logf("Deleted snapshot: %s", snapshot.ID) + t.Logf("Successfully deleted snapshot: %s", snapshot.ID) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/snapshots_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/snapshots_test.go index 7c1a4e5a560c..a24f4dcefff8 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/snapshots_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/snapshots_test.go @@ -8,51 +8,39 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestSnapshotsList(t *testing.T) { - client, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } - - allPages, err := snapshots.List(client, snapshots.ListOpts{}).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve snapshots: %v", err) - } +func TestSnapshots(t *testing.T) { + clients.RequireLong(t) - allSnapshots, err := snapshots.ExtractSnapshots(allPages) - if err != nil { - t.Fatalf("Unable to extract snapshots: %v", err) - } - - for _, snapshot := range allSnapshots { - tools.PrintResource(t, snapshot) - } -} - -func TestSnapshotsCreateDelete(t *testing.T) { client, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) volume, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer DeleteVolume(t, client, volume) snapshot, err := CreateSnapshot(t, client, volume) - if err != nil { - t.Fatalf("Unable to create snapshot: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSnapshot(t, client, snapshot) newSnapshot, err := snapshots.Get(client, snapshot.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve snapshot: %v", err) + th.AssertNoErr(t, err) + + allPages, err := snapshots.List(client, snapshots.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + + allSnapshots, err := snapshots.ExtractSnapshots(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allSnapshots { + tools.PrintResource(t, snapshot) + if v.ID == newSnapshot.ID { + found = true + } } - tools.PrintResource(t, newSnapshot) + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/volumes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/volumes_test.go index 630b21e49235..f3b164ca0cd0 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/volumes_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2/volumes_test.go @@ -5,73 +5,124 @@ package v2 import ( "testing" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestVolumesList(t *testing.T) { +func TestVolumesCreateDestroy(t *testing.T) { + clients.RequireLong(t) + client, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) + th.AssertNoErr(t, err) + + volume, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + defer DeleteVolume(t, client, volume) + + newVolume, err := volumes.Get(client, volume.ID).Extract() + th.AssertNoErr(t, err) + + // Update volume + updatedVolumeName := "" + updatedVolumeDescription := "" + updateOpts := volumes.UpdateOpts{ + Name: &updatedVolumeName, + Description: &updatedVolumeDescription, } + updatedVolume, err := volumes.Update(client, volume.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, updatedVolume) + th.AssertEquals(t, updatedVolume.Name, updatedVolumeName) + th.AssertEquals(t, updatedVolume.Description, updatedVolumeDescription) allPages, err := volumes.List(client, volumes.ListOpts{}).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve volumes: %v", err) - } + th.AssertNoErr(t, err) allVolumes, err := volumes.ExtractVolumes(allPages) - if err != nil { - t.Fatalf("Unable to extract volumes: %v", err) - } + th.AssertNoErr(t, err) - for _, volume := range allVolumes { + var found bool + for _, v := range allVolumes { tools.PrintResource(t, volume) + if v.ID == newVolume.ID { + found = true + } } + + th.AssertEquals(t, found, true) } -func TestVolumesCreateDestroy(t *testing.T) { +func TestVolumesCreateForceDestroy(t *testing.T) { + clients.RequireLong(t) + client, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create blockstorage client: %v", err) - } + th.AssertNoErr(t, err) volume, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } - defer DeleteVolume(t, client, volume) + th.AssertNoErr(t, err) newVolume, err := volumes.Get(client, volume.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve volume: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newVolume) + + err = volumeactions.ForceDelete(client, newVolume.ID).ExtractErr() + th.AssertNoErr(t, err) } -func TestVolumesCreateForceDestroy(t *testing.T) { - client, err := clients.NewBlockStorageV2Client() - if err != nil { - t.Fatalf("Unable to create blockstorage client: %v", err) - } +func TestVolumesCascadeDelete(t *testing.T) { + clients.RequireLong(t) - volume, err := CreateVolume(t, client) + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + vol, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, vol.ID, "available", 60) + th.AssertNoErr(t, err) + + snapshot1, err := CreateSnapshot(t, client, vol) + th.AssertNoErr(t, err) + + snapshot2, err := CreateSnapshot(t, client, vol) + th.AssertNoErr(t, err) + + t.Logf("Attempting to delete volume: %s", vol.ID) + + deleteOpts := volumes.DeleteOpts{Cascade: true} + err = volumes.Delete(client, vol.ID, deleteOpts).ExtractErr() if err != nil { - t.Fatalf("Unable to create volume: %v", err) + t.Fatalf("Unable to delete volume %s: %v", vol.ID, err) } - newVolume, err := volumes.Get(client, volume.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve volume: %v", err) + for _, sid := range []string{snapshot1.ID, snapshot2.ID} { + err := gophercloud.WaitFor(120, func() (bool, error) { + _, err := snapshots.Get(client, sid).Extract() + if err != nil { + return true, nil + } + return false, nil + }) + th.AssertNoErr(t, err) + t.Logf("Successfully deleted snapshot: %s", sid) } - tools.PrintResource(t, newVolume) + err = gophercloud.WaitFor(120, func() (bool, error) { + _, err := volumes.Get(client, vol.ID).Extract() + if err != nil { + return true, nil + } + return false, nil + }) + th.AssertNoErr(t, err) + + t.Logf("Successfully deleted volume: %s", vol.ID) - err = volumeactions.ForceDelete(client, newVolume.ID).ExtractErr() - if err != nil { - t.Errorf("Unable to force delete volume: %v", err) - } } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/blockstorage.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/blockstorage.go index 4fedc8b81602..3eccab04afdd 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/blockstorage.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/blockstorage.go @@ -10,21 +10,53 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes" + th "github.com/gophercloud/gophercloud/testhelper" ) +// CreateSnapshot will create a snapshot of the specified volume. +// Snapshot will be assigned a random name and description. +func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { + snapshotName := tools.RandomString("ACPTTEST", 16) + snapshotDescription := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create snapshot: %s", snapshotName) + + createOpts := snapshots.CreateOpts{ + VolumeID: volume.ID, + Name: snapshotName, + Description: snapshotDescription, + } + + snapshot, err := snapshots.Create(client, createOpts).Extract() + if err != nil { + return snapshot, err + } + + err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60) + if err != nil { + return snapshot, err + } + + tools.PrintResource(t, snapshot) + th.AssertEquals(t, snapshot.Name, snapshotName) + th.AssertEquals(t, snapshot.VolumeID, volume.ID) + + t.Logf("Successfully created snapshot: %s", snapshot.ID) + + return snapshot, nil +} + // CreateVolume will create a volume with a random name and size of 1GB. An // error will be returned if the volume was unable to be created. func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) { - if testing.Short() { - t.Skip("Skipping test that requires volume creation in short mode.") - } - volumeName := tools.RandomString("ACPTTEST", 16) + volumeDescription := tools.RandomString("ACPTTEST-DESC", 16) t.Logf("Attempting to create volume: %s", volumeName) createOpts := volumes.CreateOpts{ - Size: 1, - Name: volumeName, + Size: 1, + Name: volumeName, + Description: volumeDescription, } volume, err := volumes.Create(client, createOpts).Extract() @@ -37,48 +69,45 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol return volume, err } - return volume, nil -} + tools.PrintResource(t, volume) + th.AssertEquals(t, volume.Name, volumeName) + th.AssertEquals(t, volume.Description, volumeDescription) + th.AssertEquals(t, volume.Size, 1) -// DeleteVolume will delete a volume. A fatal error will occur if the volume -// failed to be deleted. This works best when used as a deferred function. -func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { - err := volumes.Delete(client, volume.ID).ExtractErr() - if err != nil { - t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) - } + t.Logf("Successfully created volume: %s", volume.ID) - t.Logf("Deleted volume: %s", volume.ID) + return volume, nil } -// CreateSnapshot will create a snapshot of the specified volume. -// Snapshot will be assigned a random name and description. -func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { - if testing.Short() { - t.Skip("Skipping test that requires snapshot creation in short mode.") - } - - snapshotName := tools.RandomString("ACPTTEST", 16) - snapshotDescription := tools.RandomString("ACPTTEST", 16) - t.Logf("Attempting to create snapshot: %s", snapshotName) - - createOpts := snapshots.CreateOpts{ - VolumeID: volume.ID, - Name: snapshotName, - Description: snapshotDescription, +// CreateVolumeType will create a volume type with a random name. An +// error will be returned if the volume was unable to be created. +func CreateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*volumetypes.VolumeType, error) { + name := tools.RandomString("ACPTTEST", 16) + description := "create_from_gophercloud" + t.Logf("Attempting to create volume type: %s", name) + + createOpts := volumetypes.CreateOpts{ + Name: name, + ExtraSpecs: map[string]string{"volume_backend_name": "fake_backend_name"}, + Description: description, } - snapshot, err := snapshots.Create(client, createOpts).Extract() + vt, err := volumetypes.Create(client, createOpts).Extract() if err != nil { - return snapshot, err + return nil, err } - err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60) - if err != nil { - return snapshot, err - } + tools.PrintResource(t, vt) + th.AssertEquals(t, vt.IsPublic, true) + th.AssertEquals(t, vt.Name, name) + th.AssertEquals(t, vt.Description, description) + // TODO: For some reason returned extra_specs are empty even in API reference: https://developer.openstack.org/api-ref/block-storage/v3/?expanded=create-a-volume-type-detail#volume-types-types + // "extra_specs": {} + // th.AssertEquals(t, vt.ExtraSpecs, createOpts.ExtraSpecs) - return snapshot, nil + t.Logf("Successfully created volume type: %s", vt.ID) + + return vt, nil } // DeleteSnapshot will delete a snapshot. A fatal error will occur if the @@ -105,3 +134,30 @@ func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *s t.Logf("Deleted snapshot: %s", snapshot.ID) } + +// DeleteVolume will delete a volume. A fatal error will occur if the volume +// failed to be deleted. This works best when used as a deferred function. +func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { + t.Logf("Attempting to delete volume: %s", volume.ID) + + err := volumes.Delete(client, volume.ID, volumes.DeleteOpts{}).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) + } + + t.Logf("Successfully deleted volume: %s", volume.ID) +} + +// DeleteVolumeType will delete a volume type. A fatal error will occur if the +// volume type failed to be deleted. This works best when used as a deferred +// function. +func DeleteVolumeType(t *testing.T, client *gophercloud.ServiceClient, vt *volumetypes.VolumeType) { + t.Logf("Attempting to delete volume type: %s", vt.ID) + + err := volumetypes.Delete(client, vt.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete volume %s: %v", vt.ID, err) + } + + t.Logf("Successfully deleted volume type: %s", vt.ID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/quotaset_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/quotaset_test.go new file mode 100644 index 000000000000..f69e6866b85d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/quotaset_test.go @@ -0,0 +1,145 @@ +// +build acceptance quotasets + +package v3 + +import ( + "os" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestQuotasetGet(t *testing.T) { + clients.RequireAdmin(t) + + client, projectID := getClientAndProject(t) + + quotaSet, err := quotasets.Get(client, projectID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, quotaSet) +} + +func TestQuotasetGetDefaults(t *testing.T) { + clients.RequireAdmin(t) + + client, projectID := getClientAndProject(t) + + quotaSet, err := quotasets.GetDefaults(client, projectID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, quotaSet) +} + +func TestQuotasetGetUsage(t *testing.T) { + clients.RequireAdmin(t) + + client, projectID := getClientAndProject(t) + + quotaSetUsage, err := quotasets.GetUsage(client, projectID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, quotaSetUsage) +} + +var UpdateQuotaOpts = quotasets.UpdateOpts{ + Volumes: gophercloud.IntToPointer(100), + Snapshots: gophercloud.IntToPointer(200), + Gigabytes: gophercloud.IntToPointer(300), + PerVolumeGigabytes: gophercloud.IntToPointer(50), + Backups: gophercloud.IntToPointer(2), + BackupGigabytes: gophercloud.IntToPointer(300), +} + +var UpdatedQuotas = quotasets.QuotaSet{ + Volumes: 100, + Snapshots: 200, + Gigabytes: 300, + PerVolumeGigabytes: 50, + Backups: 2, + BackupGigabytes: 300, +} + +func TestQuotasetUpdate(t *testing.T) { + clients.RequireAdmin(t) + + client, projectID := getClientAndProject(t) + + // save original quotas + orig, err := quotasets.Get(client, projectID).Extract() + th.AssertNoErr(t, err) + + defer func() { + restore := quotasets.UpdateOpts{} + FillUpdateOptsFromQuotaSet(*orig, &restore) + + _, err = quotasets.Update(client, projectID, restore).Extract() + th.AssertNoErr(t, err) + }() + + // test Update + resultQuotas, err := quotasets.Update(client, projectID, UpdateQuotaOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, UpdatedQuotas, *resultQuotas) + + // We dont know the default quotas, so just check if the quotas are not the + // same as before + newQuotas, err := quotasets.Get(client, projectID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, resultQuotas.Volumes, newQuotas.Volumes) +} + +func TestQuotasetDelete(t *testing.T) { + clients.RequireAdmin(t) + + client, projectID := getClientAndProject(t) + + // save original quotas + orig, err := quotasets.Get(client, projectID).Extract() + th.AssertNoErr(t, err) + + defer func() { + restore := quotasets.UpdateOpts{} + FillUpdateOptsFromQuotaSet(*orig, &restore) + + _, err = quotasets.Update(client, projectID, restore).Extract() + th.AssertNoErr(t, err) + }() + + // Obtain environment default quotaset values to validate deletion. + defaultQuotaSet, err := quotasets.GetDefaults(client, projectID).Extract() + th.AssertNoErr(t, err) + + // Test Delete + err = quotasets.Delete(client, projectID).ExtractErr() + th.AssertNoErr(t, err) + + newQuotas, err := quotasets.Get(client, projectID).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, newQuotas.Volumes, defaultQuotaSet.Volumes) +} + +// getClientAndProject reduces boilerplate by returning a new blockstorage v3 +// ServiceClient and a project ID obtained from the OS_PROJECT_NAME envvar. +func getClientAndProject(t *testing.T) (*gophercloud.ServiceClient, string) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + projectID := os.Getenv("OS_PROJECT_NAME") + th.AssertNoErr(t, err) + return client, projectID +} + +func FillUpdateOptsFromQuotaSet(src quotasets.QuotaSet, dest *quotasets.UpdateOpts) { + dest.Volumes = &src.Volumes + dest.Snapshots = &src.Snapshots + dest.Gigabytes = &src.Gigabytes + dest.PerVolumeGigabytes = &src.PerVolumeGigabytes + dest.Backups = &src.Backups + dest.BackupGigabytes = &src.BackupGigabytes +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/snapshots_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/snapshots_test.go index f8fdc1f0f632..60bfb70597ef 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/snapshots_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/snapshots_test.go @@ -6,90 +6,53 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" - "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestSnapshotsList(t *testing.T) { +func TestSnapshots(t *testing.T) { + clients.RequireLong(t) + client, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) volume1, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } - + th.AssertNoErr(t, err) defer DeleteVolume(t, client, volume1) snapshot1, err := CreateSnapshot(t, client, volume1) - if err != nil { - t.Fatalf("Unable to create snapshot: %v", err) - } - + th.AssertNoErr(t, err) defer DeleteSnapshot(t, client, snapshot1) volume2, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } - + th.AssertNoErr(t, err) defer DeleteVolume(t, client, volume2) snapshot2, err := CreateSnapshot(t, client, volume2) - if err != nil { - t.Fatalf("Unable to create snapshot: %v", err) - } - + th.AssertNoErr(t, err) defer DeleteSnapshot(t, client, snapshot2) - pages := 0 - err = snapshots.List(client, snapshots.ListOpts{Limit: 1}).EachPage(func(page pagination.Page) (bool, error) { - pages++ + listOpts := snapshots.ListOpts{ + Limit: 1, + } + err = snapshots.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { actual, err := snapshots.ExtractSnapshots(page) - if err != nil { - t.Fatalf("Unable to extract snapshots: %v", err) - } - - if len(actual) != 1 { - t.Fatalf("Expected 1 snapshot, got %d", len(actual)) + 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 + } } - tools.PrintResource(t, actual[0]) + th.AssertEquals(t, found, true) return true, nil }) - if pages != 2 { - t.Fatalf("Expected 2 pages, saw %d", pages) - } -} - -func TestSnapshotsCreateDelete(t *testing.T) { - client, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } - - volume, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } - defer DeleteVolume(t, client, volume) - - snapshot, err := CreateSnapshot(t, client, volume) - if err != nil { - t.Fatalf("Unable to create snapshot: %v", err) - } - defer DeleteSnapshot(t, client, snapshot) - - newSnapshot, err := snapshots.Get(client, snapshot.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve snapshot: %v", err) - } - - tools.PrintResource(t, newSnapshot) + th.AssertNoErr(t, err) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumes_test.go index 2e349111d476..999ec1a61eea 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumes_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumes_test.go @@ -5,69 +5,141 @@ package v3 import ( "testing" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestVolumesList(t *testing.T) { +func TestVolumes(t *testing.T) { + clients.RequireLong(t) + client, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) volume1, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer DeleteVolume(t, client, volume1) volume2, err := CreateVolume(t, client) - if err != nil { - t.Fatalf("Unable to create volume: %v", err) - } + th.AssertNoErr(t, err) defer DeleteVolume(t, client, volume2) - pages := 0 - err = volumes.List(client, volumes.ListOpts{Limit: 1}).EachPage(func(page pagination.Page) (bool, error) { - pages++ + // Update volume + updatedVolumeName := "" + updatedVolumeDescription := "" + updateOpts := volumes.UpdateOpts{ + Name: &updatedVolumeName, + Description: &updatedVolumeDescription, + } + updatedVolume, err := volumes.Update(client, volume1.ID, updateOpts).Extract() + th.AssertNoErr(t, err) - actual, err := volumes.ExtractVolumes(page) - if err != nil { - t.Fatalf("Unable to extract volumes: %v", err) - } + tools.PrintResource(t, updatedVolume) + th.AssertEquals(t, updatedVolume.Name, updatedVolumeName) + th.AssertEquals(t, updatedVolume.Description, updatedVolumeDescription) + + listOpts := volumes.ListOpts{ + Limit: 1, + } - if len(actual) != 1 { - t.Fatalf("Expected 1 volume, got %d", len(actual)) + err = volumes.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + actual, err := volumes.ExtractVolumes(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(actual)) + + var found bool + for _, v := range actual { + if v.ID == volume1.ID || v.ID == volume2.ID { + found = true + } } - tools.PrintResource(t, actual[0]) + th.AssertEquals(t, found, true) return true, nil }) - if pages != 2 { - t.Fatalf("Expected 2 pages, saw %d", pages) - } + th.AssertNoErr(t, err) } -func TestVolumesCreateDelete(t *testing.T) { +func TestVolumesMultiAttach(t *testing.T) { + clients.RequireLong(t) + client, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create blockstorage client: %v", err) + th.AssertNoErr(t, err) + + volumeName := tools.RandomString("ACPTTEST", 16) + + volOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + Description: "Testing creation of multiattach enabled volume", + Multiattach: true, } - volume, err := CreateVolume(t, client) + vol, err := volumes.Create(client, volOpts).Extract() + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, vol.ID, "available", 60) + th.AssertNoErr(t, err) + + th.AssertEquals(t, vol.Multiattach, true) + + err = volumes.Delete(client, vol.ID, volumes.DeleteOpts{}).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestVolumesCascadeDelete(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + vol, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, vol.ID, "available", 60) + th.AssertNoErr(t, err) + + snapshot1, err := CreateSnapshot(t, client, vol) + th.AssertNoErr(t, err) + + snapshot2, err := CreateSnapshot(t, client, vol) + th.AssertNoErr(t, err) + + t.Logf("Attempting to delete volume: %s", vol.ID) + + deleteOpts := volumes.DeleteOpts{Cascade: true} + err = volumes.Delete(client, vol.ID, deleteOpts).ExtractErr() if err != nil { - t.Fatalf("Unable to create volume: %v", err) + t.Fatalf("Unable to delete volume %s: %v", vol.ID, err) } - defer DeleteVolume(t, client, volume) - newVolume, err := volumes.Get(client, volume.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve volume: %v", err) + for _, sid := range []string{snapshot1.ID, snapshot2.ID} { + err := gophercloud.WaitFor(120, func() (bool, error) { + _, err := snapshots.Get(client, sid).Extract() + if err != nil { + return true, nil + } + return false, nil + }) + th.AssertNoErr(t, err) + t.Logf("Successfully deleted snapshot: %s", sid) } - tools.PrintResource(t, newVolume) + err = gophercloud.WaitFor(120, func() (bool, error) { + _, err := volumes.Get(client, vol.ID).Extract() + if err != nil { + return true, nil + } + return false, nil + }) + th.AssertNoErr(t, err) + + t.Logf("Successfully deleted volume: %s", vol.ID) + } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumetypes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumetypes_test.go index 8e14c3afb810..99657d794b1d 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumetypes_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3/volumetypes_test.go @@ -11,73 +11,46 @@ import ( th "github.com/gophercloud/gophercloud/testhelper" ) -func TestVolumeTypesList(t *testing.T) { +func TestVolumeTypes(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) - } + th.AssertNoErr(t, err) - listOpts := volumetypes.ListOpts{ - Sort: "name:asc", - Limit: 1, - } + vt, err := CreateVolumeType(t, client) + th.AssertNoErr(t, err) + defer DeleteVolumeType(t, client, vt) - allPages, err := volumetypes.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve volumetypes: %v", err) - } + allPages, err := volumetypes.List(client, nil).AllPages() + th.AssertNoErr(t, err) allVolumeTypes, err := volumetypes.ExtractVolumeTypes(allPages) - if err != nil { - t.Fatalf("Unable to extract volumetypes: %v", err) - } + th.AssertNoErr(t, err) - for _, vt := range allVolumeTypes { - tools.PrintResource(t, vt) - } - - if len(allVolumeTypes) > 0 { - vt, err := volumetypes.Get(client, allVolumeTypes[0].ID).Extract() - if err != nil { - t.Fatalf("Error retrieving volume type: %v", err) + var found bool + for _, v := range allVolumeTypes { + tools.PrintResource(t, v) + if v.ID == vt.ID { + found = true } - - tools.PrintResource(t, vt) - } -} - -func TestVolumeTypesCRUD(t *testing.T) { - client, err := clients.NewBlockStorageV3Client() - if err != nil { - t.Fatalf("Unable to create a blockstorage client: %v", err) } - createOpts := volumetypes.CreateOpts{ - Name: "create_from_gophercloud", - ExtraSpecs: map[string]string{"volume_backend_name": "fake_backend_name"}, - Description: "create_from_gophercloud", - } + th.AssertEquals(t, found, true) - vt, err := volumetypes.Create(client, createOpts).Extract() - if err != nil { - t.Fatalf("Unable to create volumetype: %v", err) + isPublic := false + name := vt.Name + "-UPDATED" + description := "" + updateOpts := volumetypes.UpdateOpts{ + Name: &name, + Description: &description, + IsPublic: &isPublic, } - th.AssertEquals(t, true, vt.IsPublic) - - tools.PrintResource(t, vt) - - defer volumetypes.Delete(client, vt.ID) - - var isPublic = false - - newVT, err := volumetypes.Update(client, vt.ID, volumetypes.UpdateOpts{ - Name: "updated_volume_type", - IsPublic: &isPublic, - }).Extract() - - th.AssertEquals(t, "updated_volume_type", newVT.Name) - th.AssertEquals(t, false, newVT.IsPublic) + newVT, err := volumetypes.Update(client, vt.ID, updateOpts).Extract() + th.AssertNoErr(t, err) tools.PrintResource(t, newVT) + th.AssertEquals(t, name, newVT.Name) + th.AssertEquals(t, description, newVT.Description) + th.AssertEquals(t, isPublic, newVT.IsPublic) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/actions_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/actions_test.go new file mode 100644 index 000000000000..eca8fc62c62d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/actions_test.go @@ -0,0 +1,31 @@ +// +build acceptance clustering actions + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/actions" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestActionsList(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + opts := actions.ListOpts{ + Limit: 200, + } + + allPages, err := actions.List(client, opts).AllPages() + th.AssertNoErr(t, err) + + allActions, err := actions.ExtractActions(allPages) + th.AssertNoErr(t, err) + + for _, action := range allActions { + tools.PrintResource(t, action) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clustering.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clustering.go new file mode 100644 index 000000000000..6c7a406796ab --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clustering.go @@ -0,0 +1,472 @@ +package v1 + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/actions" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/policies" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers" + th "github.com/gophercloud/gophercloud/testhelper" +) + +var TestPolicySpec = policies.Spec{ + Description: "new policy description", + Properties: map[string]interface{}{ + "destroy_after_deletion": true, + "grace_period": 60, + "reduce_desired_capacity": false, + "criteria": "OLDEST_FIRST", + }, + Type: "senlin.policy.deletion", + Version: "1.1", +} + +// CreateCluster creates a random cluster. An error will be returned if +// the cluster could not be created. +func CreateCluster(t *testing.T, client *gophercloud.ServiceClient, profileID string) (*clusters.Cluster, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create cluster: %s", name) + + createOpts := clusters.CreateOpts{ + Name: name, + DesiredCapacity: 1, + ProfileID: profileID, + MinSize: new(int), + MaxSize: 20, + Timeout: 3600, + Metadata: map[string]interface{}{ + "foo": "bar", + "test": map[string]interface{}{ + "nil_interface": interface{}(nil), + "float_value": float64(123.3), + "string_value": "test_string", + "bool_value": false, + }, + }, + Config: map[string]interface{}{}, + } + + res := clusters.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, true, requestID != "") + t.Logf("Cluster %s request ID: %s", name, requestID) + + actionID, err := GetActionID(res.Header) + th.AssertNoErr(t, err) + th.AssertEquals(t, true, actionID != "") + t.Logf("Cluster %s action ID: %s", name, actionID) + + err = WaitForAction(client, actionID) + if err != nil { + return nil, err + } + + cluster, err := res.Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created cluster: %s", cluster.ID) + + tools.PrintResource(t, cluster) + tools.PrintResource(t, cluster.CreatedAt) + + th.AssertEquals(t, name, cluster.Name) + th.AssertEquals(t, profileID, cluster.ProfileID) + + return cluster, nil +} + +// CreateNode creates a random node. An error will be returned if +// the node could not be created. +func CreateNode(t *testing.T, client *gophercloud.ServiceClient, clusterID, profileID string) (*nodes.Node, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create node: %s", name) + + createOpts := nodes.CreateOpts{ + ClusterID: clusterID, + Metadata: map[string]interface{}{ + "foo": "bar", + "test": map[string]interface{}{ + "nil_interface": interface{}(nil), + "float_value": float64(123.3), + "string_value": "test_string", + "bool_value": false, + }, + }, + Name: name, + ProfileID: profileID, + Role: "", + } + + res := nodes.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, true, requestID != "") + t.Logf("Node %s request ID: %s", name, requestID) + + actionID, err := GetActionID(res.Header) + th.AssertNoErr(t, err) + th.AssertEquals(t, true, actionID != "") + t.Logf("Node %s action ID: %s", name, actionID) + + err = WaitForAction(client, actionID) + if err != nil { + return nil, err + } + + node, err := res.Extract() + if err != nil { + return nil, err + } + + err = WaitForNodeStatus(client, node.ID, "ACTIVE") + if err != nil { + return nil, err + } + + t.Logf("Successfully created node: %s", node.ID) + + node, err = nodes.Get(client, node.ID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, node) + tools.PrintResource(t, node.CreatedAt) + + th.AssertEquals(t, profileID, node.ProfileID) + th.AssertEquals(t, clusterID, node.ClusterID) + th.AssertDeepEquals(t, createOpts.Metadata, node.Metadata) + + return node, nil +} + +// CreatePolicy creates a random policy. An error will be returned if the +// policy could not be created. +func CreatePolicy(t *testing.T, client *gophercloud.ServiceClient) (*policies.Policy, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create policy: %s", name) + + createOpts := policies.CreateOpts{ + Name: name, + Spec: TestPolicySpec, + } + + res := policies.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, true, requestID != "") + + t.Logf("Policy %s request ID: %s", name, requestID) + + policy, err := res.Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created policy: %s", policy.ID) + + tools.PrintResource(t, policy) + tools.PrintResource(t, policy.CreatedAt) + + th.AssertEquals(t, name, policy.Name) + + return policy, nil +} + +// CreateProfile will create a random profile. An error will be returned if the +// profile could not be created. +func CreateProfile(t *testing.T, client *gophercloud.ServiceClient) (*profiles.Profile, error) { + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + return nil, err + } + + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create profile: %s", name) + + networks := []map[string]interface{}{ + {"network": choices.NetworkName}, + } + + props := map[string]interface{}{ + "name": name, + "flavor": choices.FlavorID, + "image": choices.ImageID, + "networks": networks, + "security_groups": "", + } + + createOpts := profiles.CreateOpts{ + Name: name, + Spec: profiles.Spec{ + Type: "os.nova.server", + Version: "1.0", + Properties: props, + }, + } + + res := profiles.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, true, requestID != "") + + t.Logf("Profile %s request ID: %s", name, requestID) + + profile, err := res.Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created profile: %s", profile.ID) + + tools.PrintResource(t, profile) + tools.PrintResource(t, profile.CreatedAt) + + th.AssertEquals(t, name, profile.Name) + th.AssertEquals(t, profile.Spec.Type, "os.nova.server") + th.AssertEquals(t, profile.Spec.Version, "1.0") + + return profile, nil +} + +// CreateWebhookReceiver will create a random webhook receiver. An error will be returned if the +// receiver could not be created. +func CreateWebhookReceiver(t *testing.T, client *gophercloud.ServiceClient, clusterID string) (*receivers.Receiver, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create receiver: %s", name) + + createOpts := receivers.CreateOpts{ + Name: name, + ClusterID: clusterID, + Type: receivers.WebhookReceiver, + Action: "CLUSTER_SCALE_OUT", + } + + res := receivers.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + receiver, err := res.Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created webhook receiver: %s", receiver.ID) + + tools.PrintResource(t, receiver) + tools.PrintResource(t, receiver.CreatedAt) + + th.AssertEquals(t, name, receiver.Name) + th.AssertEquals(t, createOpts.Action, receiver.Action) + + return receiver, nil +} + +// CreateMessageReceiver will create a message receiver with a random name. An error will be returned if the +// receiver could not be created. +func CreateMessageReceiver(t *testing.T, client *gophercloud.ServiceClient, clusterID string) (*receivers.Receiver, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create receiver: %s", name) + + createOpts := receivers.CreateOpts{ + Name: name, + ClusterID: clusterID, + Type: receivers.MessageReceiver, + } + + res := receivers.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + receiver, err := res.Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created message receiver: %s", receiver.ID) + + tools.PrintResource(t, receiver) + tools.PrintResource(t, receiver.CreatedAt) + + th.AssertEquals(t, name, receiver.Name) + th.AssertEquals(t, createOpts.Action, receiver.Action) + + return receiver, nil +} + +// DeleteCluster will delete a given policy. A fatal error will occur if the +// cluster could not be deleted. This works best as a deferred function. +func DeleteCluster(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete cluster: %s", id) + + res := clusters.Delete(client, id) + if res.Err != nil { + t.Fatalf("Error deleting cluster %s: %s:", id, res.Err) + } + + actionID, err := GetActionID(res.Header) + if err != nil { + t.Fatalf("Error deleting cluster %s: %s:", id, res.Err) + } + + err = WaitForAction(client, actionID) + if err != nil { + t.Fatalf("Error deleting cluster %s: %s:", id, res.Err) + } + + t.Logf("Successfully deleted cluster: %s", id) + + return +} + +// DeleteNode will delete a given node. A fatal error will occur if the +// node could not be deleted. This works best as a deferred function. +func DeleteNode(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete node: %s", id) + + res := nodes.Delete(client, id) + if res.Err != nil { + t.Fatalf("Error deleting node %s: %s:", id, res.Err) + } + + actionID, err := GetActionID(res.Header) + if err != nil { + t.Fatalf("Error getting actionID %s: %s:", id, err) + } + + err = WaitForAction(client, actionID) + + if err != nil { + t.Fatalf("Error deleting node %s: %s", id, err) + } + + t.Logf("Successfully deleted node: %s", id) + + return +} + +// DeletePolicy will delete a given policy. A fatal error will occur if the +// policy could not be deleted. This works best as a deferred function. +func DeletePolicy(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete policy: %s", id) + + err := policies.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Error deleting policy %s: %s:", id, err) + } + + t.Logf("Successfully deleted policy: %s", id) + + return +} + +// DeleteProfile will delete a given profile. A fatal error will occur if the +// profile could not be deleted. This works best as a deferred function. +func DeleteProfile(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete profile: %s", id) + + err := profiles.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Error deleting profile %s: %s:", id, err) + } + + t.Logf("Successfully deleted profile: %s", id) + + return +} + +// DeleteReceiver will delete a given receiver. A fatal error will occur if the +// receiver could not be deleted. This works best as a deferred function. +func DeleteReceiver(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete Receiver: %s", id) + + res := receivers.Delete(client, id) + if res.Err != nil { + t.Fatalf("Error deleting receiver %s: %s:", id, res.Err) + } + + t.Logf("Successfully deleted receiver: %s", id) + + return +} + +// GetActionID parses an HTTP header and returns the action ID. +func GetActionID(headers http.Header) (string, error) { + location := headers.Get("Location") + v := strings.Split(location, "actions/") + if len(v) < 2 { + return "", fmt.Errorf("unable to determine action ID") + } + + actionID := v[1] + + return actionID, nil +} + +func WaitForAction(client *gophercloud.ServiceClient, actionID string) error { + return tools.WaitFor(func() (bool, error) { + action, err := actions.Get(client, actionID).Extract() + if err != nil { + return false, err + } + + if action.Status == "SUCCEEDED" { + return true, nil + } + + if action.Status == "FAILED" { + return false, fmt.Errorf("Action %s in FAILED state", actionID) + } + + return false, nil + }) +} + +func WaitForNodeStatus(client *gophercloud.ServiceClient, id string, status string) error { + return tools.WaitFor(func() (bool, error) { + latest, err := nodes.Get(client, id).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok && status == "DELETED" { + return true, nil + } + + return false, err + } + + if latest.Status == status { + return true, nil + } + + if latest.Status == "ERROR" { + return false, fmt.Errorf("Node %s in ERROR state", id) + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clusters_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clusters_test.go new file mode 100644 index 000000000000..8bc02f041eed --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/clusters_test.go @@ -0,0 +1,510 @@ +// +build acceptance clustering policies + +package v1 + +import ( + "sort" + "strings" + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/actions" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestClustersCRUD(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + // Test clusters list + allPages, err := clusters.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allClusters, err := clusters.ExtractClusters(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allClusters { + if v.ID == cluster.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + // Test cluster update + updateOpts := clusters.UpdateOpts{ + Name: cluster.Name + "-UPDATED", + } + + res := clusters.Update(client, cluster.ID, updateOpts) + th.AssertNoErr(t, res.Err) + + actionID, err := GetActionID(res.Header) + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + newCluster, err := clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newCluster.Name, cluster.Name+"-UPDATED") + + tools.PrintResource(t, newCluster) + + // Test cluster health + actionID, err = clusters.Check(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) +} + +func TestClustersResize(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + iTrue := true + resizeOpts := clusters.ResizeOpts{ + AdjustmentType: clusters.ChangeInCapacityAdjustment, + Number: 1, + Strict: &iTrue, + } + + actionID, err := clusters.Resize(client, cluster.ID, resizeOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + newCluster, err := clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newCluster.DesiredCapacity, 2) + + tools.PrintResource(t, newCluster) +} + +func TestClustersScale(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + // increase cluster size to 2 + scaleOutOpts := clusters.ScaleOutOpts{ + Count: 1, + } + actionID, err := clusters.ScaleOut(client, cluster.ID, scaleOutOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + newCluster, err := clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newCluster.DesiredCapacity, 2) + + // reduce cluster size to 0 + count := 2 + scaleInOpts := clusters.ScaleInOpts{ + Count: &count, + } + + actionID, err = clusters.ScaleIn(client, cluster.ID, scaleInOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + newCluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newCluster.DesiredCapacity, 0) + + tools.PrintResource(t, newCluster) +} + +func TestClustersPolicies(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.5" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + policy, err := CreatePolicy(t, client) + th.AssertNoErr(t, err) + defer DeletePolicy(t, client, policy.ID) + + iTrue := true + attachPolicyOpts := clusters.AttachPolicyOpts{ + PolicyID: policy.ID, + Enabled: &iTrue, + } + + actionID, err := clusters.AttachPolicy(client, cluster.ID, attachPolicyOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + // List all policies in the cluster to see if the policy was + // successfully attached. + allPages, err := clusters.ListPolicies(client, cluster.ID, nil).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err := clusters.ExtractClusterPolicies(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allPolicies { + tools.PrintResource(t, v) + if v.PolicyID == policy.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + // Set the policy to disabled + iFalse := false + updatePolicyOpts := clusters.UpdatePolicyOpts{ + PolicyID: policy.ID, + Enabled: &iFalse, + } + + actionID, err = clusters.UpdatePolicy(client, cluster.ID, updatePolicyOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + clusterPolicy, err := clusters.GetPolicy(client, cluster.ID, policy.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, clusterPolicy.Enabled, false) + + // Detach the policy + detachPolicyOpts := clusters.DetachPolicyOpts{ + PolicyID: policy.ID, + } + + actionID, err = clusters.DetachPolicy(client, cluster.ID, detachPolicyOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + // List all policies in the cluster to see if the policy was + // successfully detached. + allPages, err = clusters.ListPolicies(client, cluster.ID, nil).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err = clusters.ExtractClusterPolicies(allPages) + th.AssertNoErr(t, err) + + found = false + for _, v := range allPolicies { + tools.PrintResource(t, v) + if v.PolicyID == policy.ID { + found = true + } + } + + th.AssertEquals(t, found, false) +} + +func TestClustersRecovery(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + recoverOpts := clusters.RecoverOpts{ + Operation: clusters.RebuildRecovery, + } + + actionID, err := clusters.Recover(client, cluster.ID, recoverOpts).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + newCluster, err := clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newCluster) +} + +func TestClustersAddNode(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node1, err := CreateNode(t, client, "", profile.ID) + th.AssertNoErr(t, err) + // Even tho deleting the cluster will delete the nodes but only if added into cluster successfully. + defer DeleteNode(t, client, node1.ID) + + node2, err := CreateNode(t, client, "", profile.ID) + th.AssertNoErr(t, err) + // Even tho deleting the cluster will delete the nodes but only if added into cluster successfully. + defer DeleteNode(t, client, node2.ID) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + + nodeIDs := []string{node1.ID, node2.ID} + nodeIDs = append(nodeIDs, cluster.Nodes...) + + nodeNames := []string{node1.Name, node2.Name} + addNodesOpts := clusters.AddNodesOpts{ + Nodes: nodeNames, + } + actionID, err := clusters.AddNodes(client, cluster.ID, addNodesOpts).Extract() + if err != nil { + t.Fatalf("Unable to add nodes to cluster: %v", err) + } + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + + sort.Strings(nodeIDs) + sort.Strings(cluster.Nodes) + + tools.PrintResource(t, nodeIDs) + tools.PrintResource(t, cluster.Nodes) + + th.AssertDeepEquals(t, nodeIDs, cluster.Nodes) + + tools.PrintResource(t, cluster) +} + +func TestClustersRemoveNodeFromCluster(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, cluster) + + opt := clusters.RemoveNodesOpts{Nodes: cluster.Nodes} + res := clusters.RemoveNodes(client, cluster.ID, opt) + err = res.ExtractErr() + th.AssertNoErr(t, err) + + for _, n := range cluster.Nodes { + defer DeleteNode(t, client, n) + } + + actionID, err := GetActionID(res.Header) + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, 0, len(cluster.Nodes)) + + tools.PrintResource(t, cluster) +} + +func TestClustersReplaceNode(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.3" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node1, err := CreateNode(t, client, "", profile.ID) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node1.ID) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, true, len(cluster.Nodes) > 0) + for _, n := range cluster.Nodes { + defer DeleteNode(t, client, n) + } + + nodeIDToBeReplaced := cluster.Nodes[0] + opts := clusters.ReplaceNodesOpts{Nodes: map[string]string{nodeIDToBeReplaced: node1.ID}} + actionID, err := clusters.ReplaceNodes(client, cluster.ID, opts).Extract() + th.AssertNoErr(t, err) + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + + clusterNodes := strings.Join(cluster.Nodes, ",") + th.AssertEquals(t, true, strings.Contains(clusterNodes, node1.ID)) + th.AssertEquals(t, false, strings.Contains(clusterNodes, nodeIDToBeReplaced)) + tools.PrintResource(t, cluster) +} + +func TestClustersCollectAttributes(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.2" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, true, len(cluster.Nodes) > 0) + + _, err = CreateNode(t, client, cluster.ID, profile.ID) + th.AssertNoErr(t, err) + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, true, len(cluster.Nodes) > 0) + + for _, n := range cluster.Nodes { + defer DeleteNode(t, client, n) + } + + opts := clusters.CollectOpts{ + Path: "status", + } + attrs, err := clusters.Collect(client, cluster.ID, opts).Extract() + th.AssertNoErr(t, err) + for _, attr := range attrs { + th.AssertEquals(t, attr.Value, "ACTIVE") + } + + opts = clusters.CollectOpts{ + Path: "data.placement.zone", + } + attrs, err = clusters.Collect(client, cluster.ID, opts).Extract() + th.AssertNoErr(t, err) + for _, attr := range attrs { + th.AssertEquals(t, attr.Value, "nova") + } + +} + +// Performs an operation on a cluster +func TestClustersOps(t *testing.T) { + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.4" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node, err := CreateNode(t, client, cluster.ID, profile.ID) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node.ID) + + ops := []clusters.OperationOpts{ + // TODO: Commented out due to backend returns error, as of 2019-01-09 + //{Operation: clusters.RebuildOperation}, // Error in set_admin_password in nova log + //{Operation: clusters.EvacuateOperation, Params: clusters.OperationParams{"host": cluster.ID, "force": "True"}}, + {Operation: clusters.RebootOperation, Params: clusters.OperationParams{"type": "SOFT"}}, + {Operation: clusters.ChangePasswordOperation, Params: clusters.OperationParams{"admin_pass": "test"}}, + {Operation: clusters.LockOperation}, + {Operation: clusters.UnlockOperation}, + {Operation: clusters.SuspendOperation}, + {Operation: clusters.ResumeOperation}, + {Operation: clusters.RescueOperation, Params: clusters.OperationParams{"image_ref": choices.ImageID}}, + {Operation: clusters.PauseOperation}, + {Operation: clusters.UnpauseOperation}, + {Operation: clusters.StopOperation}, + {Operation: clusters.StartOperation}, + } + + for _, op := range ops { + opName := string(op.Operation) + t.Logf("Attempting to perform '%s' on cluster: %s", opName, cluster.ID) + actionID, res := clusters.Ops(client, cluster.ID, op).Extract() + th.AssertNoErr(t, res) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + action, err := actions.Get(client, actionID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "SUCCEEDED", action.Status) + + t.Logf("Successfully performed '%s' on cluster: %s", opName, cluster.ID) + } + + cluster, err = clusters.Get(client, cluster.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, cluster) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/events_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/events_test.go new file mode 100644 index 000000000000..447fecd54e4e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/events_test.go @@ -0,0 +1,31 @@ +// +build acceptance clustering events + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/events" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestEventsList(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + opts := events.ListOpts{ + Limit: 200, + } + + allPages, err := events.List(client, opts).AllPages() + th.AssertNoErr(t, err) + + allEvents, err := events.ExtractEvents(allPages) + th.AssertNoErr(t, err) + + for _, event := range allEvents { + tools.PrintResource(t, event) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/nodes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/nodes_test.go new file mode 100644 index 000000000000..5f65ce38932b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/nodes_test.go @@ -0,0 +1,215 @@ +// +build acceptance clustering policies + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNodesCRUD(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node, err := CreateNode(t, client, cluster.ID, profile.ID) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node.ID) + + // Test nodes list + allPages, err := nodes.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allNodes, err := nodes.ExtractNodes(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allNodes { + if v.ID == node.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + // Test nodes update + t.Logf("Attempting to update node %s", node.ID) + + updateOpts := nodes.UpdateOpts{ + Metadata: map[string]interface{}{ + "bar": "baz", + }, + } + + res := nodes.Update(client, node.ID, updateOpts) + th.AssertNoErr(t, res.Err) + + actionID, err := GetActionID(res.Header) + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + node, err = nodes.Get(client, node.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, node) + tools.PrintResource(t, node.Metadata) +} + +// Performs an operation on a node +func TestNodesOps(t *testing.T) { + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.4" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node, err := CreateNode(t, client, cluster.ID, profile.ID) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node.ID) + + ops := []nodes.OperationOpts{ + // TODO: Commented out due to backend returns error, as of 2018-12-14 + //{Operation: nodes.RebuildOperation}, + //{Operation: nodes.EvacuateOperation, Params: nodes.OperationParams{"EvacuateHost": node.ID, "EvacuateForce", "True"}}, + {Operation: nodes.RebootOperation, Params: nodes.OperationParams{"type": "SOFT"}}, + {Operation: nodes.ChangePasswordOperation, Params: nodes.OperationParams{"admin_pass": "test"}}, + {Operation: nodes.LockOperation}, + {Operation: nodes.UnlockOperation}, + {Operation: nodes.SuspendOperation}, + {Operation: nodes.ResumeOperation}, + {Operation: nodes.RescueOperation, Params: nodes.OperationParams{"image_ref": choices.ImageID}}, + {Operation: nodes.PauseOperation}, + {Operation: nodes.UnpauseOperation}, + {Operation: nodes.StopOperation}, + {Operation: nodes.StartOperation}, + } + + for _, op := range ops { + opName := string(op.Operation) + t.Logf("Attempting to perform '%s' on node: %s", opName, node.ID) + actionID, res := nodes.Ops(client, node.ID, op).Extract() + th.AssertNoErr(t, res) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + node, err = nodes.Get(client, node.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "Operation '"+opName+"' succeeded", node.StatusReason) + t.Logf("Successfully performed '%s' on node: %s", opName, node.ID) + } +} + +func TestNodesRecover(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.6" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node, err := CreateNode(t, client, cluster.ID, profile.ID) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node.ID) + + checkTrue := true + checkFalse := false + + // TODO: nodes.RebuildRecovery is commented out as of 12/14/2018 the API backend can't perform the action without returning error + ops := []nodes.RecoverOpts{ + // Microversion < 1.6 legacy support where argument DOES NOT support Check + nodes.RecoverOpts{}, + nodes.RecoverOpts{Operation: nodes.RebootRecovery}, + // nodes.RecoverOpts{Operation: nodes.RebuildRecovery}, + + // MicroVersion >= 1.6 that supports Check where Check is true + nodes.RecoverOpts{Check: &checkTrue}, + nodes.RecoverOpts{Operation: nodes.RebootRecovery, Check: &checkTrue}, + //nodes.RecoverOpts{Operation: nodes.RebuildRecovery, Check: &checkTrue}, + + // MicroVersion >= 1.6 that supports Check where Check is false + nodes.RecoverOpts{Check: &checkFalse}, + nodes.RecoverOpts{Operation: nodes.RebootRecovery, Check: &checkFalse}, + //nodes.RecoverOpts{Operation: nodes.RebuildRecovery, Check: &checkFalse}, + } + + for _, recoverOpt := range ops { + if recoverOpt.Check != nil { + t.Logf("Attempting to recover by using '%s' check=%t on node: %s", recoverOpt.Operation, *recoverOpt.Check, node.ID) + } else { + t.Logf("Attempting to recover by using '%s' on node: %s", recoverOpt.Operation, node.ID) + } + + actionID, err := nodes.Recover(client, node.ID, recoverOpt).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + if recoverOpt.Check != nil { + t.Logf("Successfully recovered by using '%s' check=%t on node: %s", recoverOpt.Operation, *recoverOpt.Check, node.ID) + } else { + t.Logf("Successfully recovered by using '%s' on node: %s", recoverOpt.Operation, node.ID) + } + + node, err := nodes.Get(client, node.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, node) + } +} + +func TestNodeCheck(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + node, err := CreateNode(t, client, cluster.ID, profile.ID) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node.ID) + + t.Logf("Attempting to check on node: %s", node.ID) + + actionID, err := nodes.Check(client, node.ID).Extract() + th.AssertNoErr(t, err) + + err = WaitForAction(client, actionID) + th.AssertNoErr(t, err) + + node, err = nodes.Get(client, node.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "Check: Node is ACTIVE.", node.StatusReason) + tools.PrintResource(t, node) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policies_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policies_test.go new file mode 100644 index 000000000000..3d5835156764 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policies_test.go @@ -0,0 +1,69 @@ +// +build acceptance clustering policies + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/policies" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestPoliciesCRUD(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.5" + + policy, err := CreatePolicy(t, client) + th.AssertNoErr(t, err) + defer DeletePolicy(t, client, policy.ID) + + // Test listing policies + allPages, err := policies.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err := policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allPolicies { + if v.ID == policy.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + // Test Get policy + getPolicy, err := policies.Get(client, policy.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, getPolicy) + + // Test updating policy + updateOpts := policies.UpdateOpts{ + Name: policy.Name + "-UPDATE", + } + + t.Logf("Attempting to update policy: %s", policy.ID) + updatePolicy, err := policies.Update(client, policy.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, updatePolicy) + tools.PrintResource(t, updatePolicy.UpdatedAt) + + // Test validating policy + t.Logf("Attempting to validate policy: %s", policy.ID) + validateOpts := policies.ValidateOpts{ + Spec: TestPolicySpec, + } + + validatePolicy, err := policies.Validate(client, validateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, validatePolicy) + + th.AssertEquals(t, validatePolicy.Name, "validated_policy") + th.AssertEquals(t, validatePolicy.Spec.Version, TestPolicySpec.Version) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policytypes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policytypes_test.go index a3bb9c7c77ee..fdb42a315360 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policytypes_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/policytypes_test.go @@ -41,3 +41,24 @@ func TestPolicyTypeList_v_1_5(t *testing.T) { tools.PrintResource(t, v) } } + +func TestPolicyTypeGet(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + policyType, err := policytypes.Get(client, "senlin.policy.batch-1.0").Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, policyType) +} + +func TestPolicyTypeGet_v_1_5(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.5" + policyType, err := policytypes.Get(client, "senlin.policy.batch-1.0").Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, policyType) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiles_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiles_test.go new file mode 100644 index 000000000000..9a7986b8bcfd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiles_test.go @@ -0,0 +1,76 @@ +// +build acceptance clustering policies + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestProfilesCRUD(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + // Test listing profiles + allPages, err := profiles.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allProfiles, err := profiles.ExtractProfiles(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allProfiles { + if v.ID == profile.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + // Test updating profile + updateOpts := profiles.UpdateOpts{ + Name: profile.Name + "-UPDATED", + } + + newProfile, err := profiles.Update(client, profile.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newProfile.Name, profile.Name+"-UPDATED") + + tools.PrintResource(t, newProfile) + tools.PrintResource(t, newProfile.UpdatedAt) +} + +func TestProfileValidate(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + client.Microversion = "1.2" + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + opts := profiles.ValidateOpts{ + Spec: profile.Spec, + } + validatedProfile, err := profiles.Validate(client, opts).Extract() + th.AssertNoErr(t, err) + + // Do not validate the following fields for AssertDeepEquals() because the actual fields are either missing or hardcoded. + profile.CreatedAt = validatedProfile.CreatedAt + profile.Domain = validatedProfile.Domain + profile.ID = validatedProfile.ID + profile.Metadata = validatedProfile.Metadata + profile.Name = "validated_profile" + profile.UpdatedAt = validatedProfile.UpdatedAt + + th.AssertDeepEquals(t, validatedProfile, profile) + tools.PrintResource(t, validatedProfile) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiletypes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiletypes_test.go new file mode 100644 index 000000000000..039f926f5a8a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/profiletypes_test.go @@ -0,0 +1,47 @@ +// +build acceptance clustering profiletypes + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestProfileTypesList(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.5" + + allPages, err := profiletypes.List(client).AllPages() + th.AssertNoErr(t, err) + + allProfileTypes, err := profiletypes.ExtractProfileTypes(allPages) + th.AssertNoErr(t, err) + + for _, profileType := range allProfileTypes { + tools.PrintResource(t, profileType) + } +} +func TestProfileTypesOpsList(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.5" + + profileTypeName := "os.nova.server-1.0" + allPages, err := profiletypes.ListOps(client, profileTypeName).AllPages() + th.AssertNoErr(t, err) + + ops, err := profiletypes.ExtractOps(allPages) + th.AssertNoErr(t, err) + + for k, v := range ops { + tools.PrintResource(t, k) + tools.PrintResource(t, v) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/receivers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/receivers_test.go new file mode 100644 index 000000000000..fbf5565c939c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/receivers_test.go @@ -0,0 +1,82 @@ +// +build acceptance clustering policies + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestReceiversCRUD(t *testing.T) { + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + receiver, err := CreateWebhookReceiver(t, client, cluster.ID) + th.AssertNoErr(t, err) + defer DeleteReceiver(t, client, receiver.ID) + + // Test listing receivers + allPages, err := receivers.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allReceivers, err := receivers.ExtractReceivers(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allReceivers { + if v.ID == receiver.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + // Test updating receivers + newName := receiver.Name + "-UPDATED" + updateOpts := receivers.UpdateOpts{ + Name: newName, + } + + receiver, err = receivers.Update(client, receiver.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, receiver) + tools.PrintResource(t, receiver.UpdatedAt) + + th.AssertEquals(t, receiver.Name, newName) +} + +func TestReceiversNotify(t *testing.T) { + t.Parallel() + client, err := clients.NewClusteringV1Client() + th.AssertNoErr(t, err) + + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + receiver, err := CreateMessageReceiver(t, client, cluster.ID) + th.AssertNoErr(t, err) + defer DeleteReceiver(t, client, receiver.ID) + t.Logf("Created Mesage Receiver Name:[%s] Message Receiver ID:[%s]", receiver.Name, receiver.ID) + + requestID, err := receivers.Notify(client, receiver.ID).Extract() + th.AssertNoErr(t, err) + t.Logf("Receiver Notify Service Request ID: %s", requestID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/webhooktrigger_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/webhooktrigger_test.go new file mode 100644 index 000000000000..627e8c02cce7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/clustering/v1/webhooktrigger_test.go @@ -0,0 +1,64 @@ +// +build acceptance clustering webhooks + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestClusteringWebhookTrigger(t *testing.T) { + + client, err := clients.NewClusteringV1Client() + if err != nil { + t.Fatalf("Unable to create clustering client: %v", err) + } + + opts := webhooks.TriggerOpts{ + V: "1", + } + + // create profile, cluster and receiver first + profile, err := CreateProfile(t, client) + th.AssertNoErr(t, err) + defer DeleteProfile(t, client, profile.ID) + + cluster, err := CreateCluster(t, client, profile.ID) + th.AssertNoErr(t, err) + defer DeleteCluster(t, client, cluster.ID) + + receiver, err := CreateWebhookReceiver(t, client, cluster.ID) + th.AssertNoErr(t, err) + defer DeleteReceiver(t, client, receiver.ID) + + // trigger webhook + actionID, err := webhooks.Trigger(client, receiver.ID, opts).Extract() + if err != nil { + t.Fatalf("Unable to extract webhooks trigger: %v", err) + } else { + t.Logf("Webhook trigger action id %s", actionID) + } + + err = WaitForAction(client, actionID) + if err != nil { + t.Fatalf("Error scaling out cluster %s as a result from webhook trigger: %s:", cluster.ID, err) + } + + // check that new node was created + nodelistopts := nodes.ListOpts{ + ClusterID: cluster.ID, + } + + allPages, err := nodes.List(client, nodelistopts).AllPages() + th.AssertNoErr(t, err) + + allNodes, err := nodes.ExtractNodes(allPages) + th.AssertNoErr(t, err) + + // there should be 2 nodes in the cluster after triggering webhook + th.AssertEquals(t, len(allNodes), 2) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/bootfromvolume_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/bootfromvolume_test.go index 50a2396f94af..ec5d72f75758 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/bootfromvolume_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/bootfromvolume_test.go @@ -266,3 +266,39 @@ func TestAttachExistingVolume(t *testing.T) { // TODO: volumes_attached extension } + +func TestBootFromNewCustomizedVolume(t *testing.T) { + if testing.Short() { + t.Skip("Skipping test that requires server creation in short mode.") + } + + client, err := clients.NewComputeV2Client() + if err != nil { + t.Fatalf("Unable to create a compute client: %v", err) + } + + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + blockDevices := []bootfromvolume.BlockDevice{ + bootfromvolume.BlockDevice{ + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationVolume, + SourceType: bootfromvolume.SourceImage, + UUID: choices.ImageID, + VolumeSize: 2, + DeviceType: "disk", + DiskBus: "virtio", + }, + } + + server, err := CreateBootableVolumeServer(t, client, blockDevices) + if err != nil { + t.Fatalf("Unable to create server: %v", err) + } + defer DeleteServer(t, client, server) + + tools.PrintResource(t, server) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/compute.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/compute.go index cdcbf0bb9e93..72e641060edd 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/compute.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/compute.go @@ -12,6 +12,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/aggregates" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules" @@ -19,6 +20,8 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups" @@ -28,7 +31,6 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" th "github.com/gophercloud/gophercloud/testhelper" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/aggregates" "golang.org/x/crypto/ssh" ) @@ -529,6 +531,66 @@ func CreateServerWithoutImageRef(t *testing.T, client *gophercloud.ServiceClient return server, nil } +// CreateServerWithTags creates a basic instance with a randomly generated name. +// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable. +// The image will be the value of the OS_IMAGE_ID environment variable. +// The instance will be launched on the network specified in OS_NETWORK_NAME. +// Two tags will be assigned to the server. +// An error will be returned if the instance was unable to be created. +func CreateServerWithTags(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*servers.Server, error) { + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create server: %s", name) + + pwd := tools.MakeNewPassword("") + + server, err := servers.Create(client, servers.CreateOpts{ + Name: name, + FlavorRef: choices.FlavorID, + ImageRef: choices.ImageID, + AdminPass: pwd, + Networks: []servers.Network{ + servers.Network{UUID: networkID}, + }, + Metadata: map[string]string{ + "abc": "def", + }, + Personality: servers.Personality{ + &servers.File{ + Path: "/etc/test", + Contents: []byte("hello world"), + }, + }, + Tags: []string{"tag1", "tag2"}, + }).Extract() + if err != nil { + return server, err + } + + if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { + return nil, err + } + + res := servers.Get(client, server.ID) + if res.Err != nil { + return nil, res.Err + } + + newServer, err := res.Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newServer.Name, name) + + tags, err := res.ExtractTags() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, tags, []string{"tag1", "tag2"}) + + return newServer, nil +} + // CreateServerGroup will create a server with a random name. An error will be // returned if the server group failed to be created. func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) { @@ -775,7 +837,9 @@ func DeleteServer(t *testing.T, client *gophercloud.ServiceClient, server *serve t.Fatalf("Error deleting server %s: %s", server.ID, err) } - t.Fatalf("Could not delete server: %s", server.ID) + // If we reach this point, the API returned an actual DELETED status + // which is a very short window of time, but happens occasionally. + t.Logf("Deleted server: %s", server.ID) } // DeleteServerGroup will delete a server group. A fatal error will occur if @@ -971,3 +1035,49 @@ func FillUpdateOptsFromQuotaSet(src quotasets.QuotaSet, dest *quotasets.UpdateOp dest.ServerGroupMembers = &src.ServerGroupMembers dest.MetadataItems = &src.MetadataItems } + +// RescueServer will place the specified server into rescue mode. +func RescueServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error { + t.Logf("Attempting to put server %s into rescue mode", server.ID) + _, err := rescueunrescue.Rescue(client, server.ID, rescueunrescue.RescueOpts{}).Extract() + if err != nil { + return err + } + + if err := WaitForComputeStatus(client, server, "RESCUE"); err != nil { + return err + } + + return nil +} + +// UnrescueServer will return server from rescue mode. +func UnrescueServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error { + t.Logf("Attempting to return server %s from rescue mode", server.ID) + if err := rescueunrescue.Unrescue(client, server.ID).ExtractErr(); err != nil { + return err + } + + if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { + return err + } + + return nil +} + +// CreateRemoteConsole will create a remote noVNC console for the specified server. +func CreateRemoteConsole(t *testing.T, client *gophercloud.ServiceClient, serverID string) (*remoteconsoles.RemoteConsole, error) { + createOpts := remoteconsoles.CreateOpts{ + Protocol: remoteconsoles.ConsoleProtocolVNC, + Type: remoteconsoles.ConsoleTypeNoVNC, + } + + t.Logf("Attempting to create a %s console for the server %s", createOpts.Type, serverID) + remoteConsole, err := remoteconsoles.Create(client, serverID, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created console: %s", remoteConsole.URL) + return remoteConsole, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/flavors_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/flavors_test.go index d4aa341a7439..3972b17bfafe 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/flavors_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/flavors_test.go @@ -6,11 +6,10 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" + identity "github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" th "github.com/gophercloud/gophercloud/testhelper" - - identity "github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3" ) func TestFlavorsList(t *testing.T) { diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/hypervisors_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/hypervisors_test.go index 29d49a277e06..0b57cc57588b 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/hypervisors_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/hypervisors_test.go @@ -80,7 +80,7 @@ func TestHypervisorsGetUptime(t *testing.T) { th.AssertEquals(t, hypervisorID, hypervisor.ID) } -func getHypervisorID(t *testing.T, client *gophercloud.ServiceClient) (int, error) { +func getHypervisorID(t *testing.T, client *gophercloud.ServiceClient) (string, error) { allPages, err := hypervisors.List(client).AllPages() th.AssertNoErr(t, err) @@ -91,5 +91,5 @@ func getHypervisorID(t *testing.T, client *gophercloud.ServiceClient) (int, erro return allHypervisors[0].ID, nil } - return 0, fmt.Errorf("Unable to get hypervisor ID") + return "", fmt.Errorf("Unable to get hypervisor ID") } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/keypairs_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/keypairs_test.go index a3a17d19e66c..d2df89aad212 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/keypairs_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/keypairs_test.go @@ -10,10 +10,31 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" th "github.com/gophercloud/gophercloud/testhelper" + "golang.org/x/crypto/ssh" ) const keyName = "gophercloud_test_key_pair" +func TestKeypairsParse(t *testing.T) { + clients.SkipRelease(t, "stable/mitaka") + clients.SkipRelease(t, "stable/newton") + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + keyPair, err := CreateKeyPair(t, client) + th.AssertNoErr(t, err) + defer DeleteKeyPair(t, client, keyPair) + + // There was a series of OpenStack releases, between Liberty and Ocata, + // where the returned SSH key was not parsable by Go. + // This checks if the issue is happening again. + _, err = ssh.ParsePrivateKey([]byte(keyPair.PrivateKey)) + th.AssertNoErr(t, err) + + tools.PrintResource(t, keyPair) +} + func TestKeypairsCreateDelete(t *testing.T) { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/migrate_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/migrate_test.go index 3f61188ae43d..0661d12dcec5 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/migrate_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/migrate_test.go @@ -11,6 +11,8 @@ import ( ) func TestMigrate(t *testing.T) { + t.Skip("This is not passing in OpenLab. Works locally") + clients.RequireLong(t) clients.RequireAdmin(t) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/quotaset_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/quotaset_test.go index 62b2042b8c3b..7666456a74fb 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/quotaset_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/quotaset_test.go @@ -16,6 +16,11 @@ import ( ) func TestQuotasetGet(t *testing.T) { + clients.SkipRelease(t, "master") + clients.SkipRelease(t, "stable/queens") + clients.SkipRelease(t, "stable/rocky") + clients.SkipRelease(t, "stable/stein") + client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) @@ -100,6 +105,11 @@ var UpdatedQuotas = quotasets.QuotaSet{ } func TestQuotasetUpdateDelete(t *testing.T) { + clients.SkipRelease(t, "master") + clients.SkipRelease(t, "stable/queens") + clients.SkipRelease(t, "stable/rocky") + clients.SkipRelease(t, "stable/stein") + clients.RequireAdmin(t) client, err := clients.NewComputeV2Client() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/remoteconsoles_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/remoteconsoles_test.go new file mode 100644 index 000000000000..9dd5863c9cc1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/remoteconsoles_test.go @@ -0,0 +1,29 @@ +// +build acceptance compute remoteconsoles + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestRemoteConsoleCreate(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + client.Microversion = "2.6" + + server, err := CreateServer(t, client) + th.AssertNoErr(t, err) + defer DeleteServer(t, client, server) + + remoteConsole, err := CreateRemoteConsole(t, client, server.ID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, remoteConsole) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/rescueunrescue_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/rescueunrescue_test.go new file mode 100644 index 000000000000..bbc38fafa8dd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/rescueunrescue_test.go @@ -0,0 +1,25 @@ +// +build acceptance compute rescueunrescue + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestServerRescueUnrescue(t *testing.T) { + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + server, err := CreateServer(t, client) + th.AssertNoErr(t, err) + defer DeleteServer(t, client, server) + + err = RescueServer(t, client, server) + th.AssertNoErr(t, err) + + err = UnrescueServer(t, client, server) + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/secgroup_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/secgroup_test.go index 0d69a18fd0cf..4404665711c9 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/secgroup_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/secgroup_test.go @@ -45,9 +45,10 @@ func TestSecGroupsCRUD(t *testing.T) { tools.PrintResource(t, securityGroup) newName := tools.RandomString("secgroup_", 4) + description := "" updateOpts := secgroups.UpdateOpts{ Name: newName, - Description: tools.RandomString("dec_", 10), + Description: &description, } updatedSecurityGroup, err := secgroups.Update(client, securityGroup.ID, updateOpts).Extract() th.AssertNoErr(t, err) @@ -57,6 +58,7 @@ 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) } func TestSecGroupsRuleCreate(t *testing.T) { @@ -101,10 +103,6 @@ func TestSecGroupsAddGroupToServer(t *testing.T) { th.AssertNoErr(t, err) defer DeleteSecurityGroupRule(t, client, rule.ID) - server, err = CreateServer(t, client) - th.AssertNoErr(t, err) - defer DeleteServer(t, client, server) - t.Logf("Adding group %s to server %s", securityGroup.ID, server.ID) err = secgroups.AddServer(client, server.ID, securityGroup.Name).ExtractErr() th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go index 917795d32698..caacf4613b81 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go @@ -11,11 +11,14 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedstatus" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" th "github.com/gophercloud/gophercloud/testhelper" ) @@ -88,6 +91,7 @@ func TestServersWithExtensionsCreateDestroy(t *testing.T) { servers.Server availabilityzones.ServerAvailabilityZoneExt extendedstatus.ServerExtendedStatusExt + serverusage.UsageExt } client, err := clients.NewComputeV2Client() @@ -105,6 +109,8 @@ func TestServersWithExtensionsCreateDestroy(t *testing.T) { th.AssertEquals(t, int(extendedServer.PowerState), extendedstatus.RUNNING) th.AssertEquals(t, extendedServer.TaskState, "") th.AssertEquals(t, extendedServer.VmState, "active") + th.AssertEquals(t, extendedServer.LaunchedAt.IsZero(), false) + th.AssertEquals(t, extendedServer.TerminatedAt.IsZero(), true) } func TestServersWithoutImageRef(t *testing.T) { @@ -114,7 +120,7 @@ func TestServersWithoutImageRef(t *testing.T) { server, err := CreateServerWithoutImageRef(t, client) if err != nil { if err400, ok := err.(*gophercloud.ErrUnexpectedResponseCode); ok { - if !strings.Contains("Missing imageRef attribute", string(err400.Body)) { + if !strings.Contains(string(err400.Body), "Missing imageRef attribute") { defer DeleteServer(t, client, server) } } @@ -271,7 +277,7 @@ func TestServersActionReboot(t *testing.T) { th.AssertNoErr(t, err) defer DeleteServer(t, client, server) - rebootOpts := &servers.RebootOpts{ + rebootOpts := servers.RebootOpts{ Type: servers.SoftReboot, } @@ -435,6 +441,7 @@ func TestServersActionSuspend(t *testing.T) { func TestServersActionLock(t *testing.T) { clients.RequireLong(t) + clients.RequireNonAdmin(t) client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) @@ -447,12 +454,109 @@ func TestServersActionLock(t *testing.T) { err = lockunlock.Lock(client, server.ID).ExtractErr() th.AssertNoErr(t, err) + t.Logf("Attempting to delete locked server %s", server.ID) err = servers.Delete(client, server.ID).ExtractErr() - th.AssertNoErr(t, err) + th.AssertEquals(t, err != nil, true) + t.Logf("Attempting to unlock server %s", server.ID) err = lockunlock.Unlock(client, server.ID).ExtractErr() th.AssertNoErr(t, err) err = WaitForComputeStatus(client, server, "ACTIVE") th.AssertNoErr(t, err) } + +func TestServersConsoleOutput(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + server, err := CreateServer(t, client) + th.AssertNoErr(t, err) + defer DeleteServer(t, client, server) + + outputOpts := &servers.ShowConsoleOutputOpts{ + Length: 4, + } + output, err := servers.ShowConsoleOutput(client, server.ID, outputOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, output) +} + +func TestServersTags(t *testing.T) { + clients.RequireLong(t) + clients.SkipRelease(t, "mitaka") + clients.SkipRelease(t, "newton") + + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + client.Microversion = "2.52" + + networkClient, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + networkID, err := networks.IDFromName(networkClient, choices.NetworkName) + th.AssertNoErr(t, err) + + server, err := CreateServerWithTags(t, client, networkID) + th.AssertNoErr(t, err) + defer DeleteServer(t, client, server) +} + +func TestServersWithExtendedAttributesCreateDestroy(t *testing.T) { + clients.RequireLong(t) + clients.RequireAdmin(t) + clients.SkipRelease(t, "stable/mitaka") + clients.SkipRelease(t, "stable/newton") + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + client.Microversion = "2.3" + + server, err := CreateServer(t, client) + th.AssertNoErr(t, err) + defer DeleteServer(t, client, server) + + result := servers.Get(client, server.ID) + th.AssertNoErr(t, result.Err) + + reservationID, err := extendedserverattributes.ExtractReservationID(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, reservationID != "", true) + t.Logf("reservationID: %s", reservationID) + + launchIndex, err := extendedserverattributes.ExtractLaunchIndex(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, launchIndex, 0) + t.Logf("launchIndex: %d", launchIndex) + + ramdiskID, err := extendedserverattributes.ExtractRamdiskID(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, ramdiskID == "", true) + t.Logf("ramdiskID: %s", ramdiskID) + + kernelID, err := extendedserverattributes.ExtractKernelID(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, kernelID == "", true) + t.Logf("kernelID: %s", kernelID) + + hostname, err := extendedserverattributes.ExtractHostname(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, hostname != "", true) + t.Logf("hostname: %s", hostname) + + rootDeviceName, err := extendedserverattributes.ExtractRootDeviceName(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, rootDeviceName != "", true) + t.Logf("rootDeviceName: %s", rootDeviceName) + + userData, err := extendedserverattributes.ExtractUserData(result.Result) + th.AssertNoErr(t, err) + th.AssertEquals(t, userData == "", true) + t.Logf("userData: %s", userData) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/usage_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/usage_test.go index 0511f8937c42..c998b59e2f46 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/usage_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/usage_test.go @@ -10,10 +10,13 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage" + "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" ) func TestUsageSingleTenant(t *testing.T) { + t.Skip("This is not passing in OpenLab. Works locally") + clients.RequireLong(t) client, err := clients.NewComputeV2Client() @@ -33,15 +36,56 @@ func TestUsageSingleTenant(t *testing.T) { End: &end, } - page, err := usage.SingleTenant(client, tenantID, opts).AllPages() + err = usage.SingleTenant(client, tenantID, opts).EachPage(func(page pagination.Page) (bool, error) { + tenantUsage, err := usage.ExtractSingleTenant(page) + th.AssertNoErr(t, err) + + tools.PrintResource(t, tenantUsage) + if tenantUsage.TotalHours == 0 { + t.Fatalf("TotalHours should not be 0") + } + + return true, nil + }) + th.AssertNoErr(t, err) +} - tenantUsage, err := usage.ExtractSingleTenant(page) +func TestUsageAllTenants(t *testing.T) { + t.Skip("This is not passing in OpenLab. Works locally") + + clients.RequireLong(t) + + client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - tools.PrintResource(t, tenantUsage) + server, err := CreateServer(t, client) + th.AssertNoErr(t, err) + DeleteServer(t, client, server) - if tenantUsage.TotalHours == 0 { - t.Fatalf("TotalHours should not be 0") + end := time.Now() + start := end.AddDate(0, -1, 0) + opts := usage.AllTenantsOpts{ + Detailed: true, + Start: &start, + End: &end, } + + err = usage.AllTenants(client, opts).EachPage(func(page pagination.Page) (bool, error) { + allUsage, err := usage.ExtractAllTenants(page) + th.AssertNoErr(t, err) + + tools.PrintResource(t, allUsage) + + if len(allUsage) == 0 { + t.Fatalf("No usage returned") + } + + if allUsage[0].TotalHours == 0 { + t.Fatalf("TotalHours should not be 0") + } + return true, nil + }) + + th.AssertNoErr(t, err) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules.go new file mode 100644 index 000000000000..08467ce2b9ea --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules.go @@ -0,0 +1,47 @@ +package v1 + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" +) + +// WaitForCapsuleStatus will poll a capsule's status until it either matches +// the specified status or the status becomes Failed. +func WaitForCapsuleStatus(client *gophercloud.ServiceClient, uuid, status string) error { + return tools.WaitFor(func() (bool, error) { + v, err := capsules.Get(client, uuid).Extract() + if err != nil { + return false, err + } + + var newStatus string + if capsule, ok := v.(*capsules.Capsule); ok { + newStatus = capsule.Status + } + + if capsule, ok := v.(*capsules.CapsuleV132); ok { + newStatus = capsule.Status + } + + fmt.Println(status) + fmt.Println(newStatus) + + if newStatus == status { + // Success! + return true, nil + } + + if newStatus == "Failed" { + return false, fmt.Errorf("Capsule in FAILED state") + } + + if newStatus == "Error" { + return false, fmt.Errorf("Capsule in ERROR state") + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules_test.go new file mode 100644 index 000000000000..98fc8e42ec9a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/capsules_test.go @@ -0,0 +1,98 @@ +// +build acceptance containers capsules + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCapsuleBase(t *testing.T) { + client, err := clients.NewContainerV1Client() + th.AssertNoErr(t, err) + + template := new(capsules.Template) + template.Bin = []byte(capsuleTemplate) + + createOpts := capsules.CreateOpts{ + TemplateOpts: template, + } + + v, err := capsules.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + capsule := v.(*capsules.Capsule) + + err = WaitForCapsuleStatus(client, capsule.UUID, "Running") + th.AssertNoErr(t, err) + + pager := capsules.List(client, nil) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + v, err := capsules.ExtractCapsules(page) + th.AssertNoErr(t, err) + allCapsules := v.([]capsules.Capsule) + + for _, m := range allCapsules { + capsuleUUID := m.UUID + if capsuleUUID != capsule.UUID { + continue + } + capsule, err := capsules.Get(client, capsuleUUID).ExtractBase() + + th.AssertNoErr(t, err) + th.AssertEquals(t, capsule.MetaName, "template") + + err = capsules.Delete(client, capsuleUUID).ExtractErr() + th.AssertNoErr(t, err) + + } + return true, nil + }) + th.AssertNoErr(t, err) +} + +func TestCapsuleV132(t *testing.T) { + client, err := clients.NewContainerV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.32" + + template := new(capsules.Template) + template.Bin = []byte(capsuleTemplate) + + createOpts := capsules.CreateOpts{ + TemplateOpts: template, + } + + capsule, err := capsules.Create(client, createOpts).ExtractV132() + th.AssertNoErr(t, err) + + err = WaitForCapsuleStatus(client, capsule.UUID, "Running") + th.AssertNoErr(t, err) + + pager := capsules.List(client, nil) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allCapsules, err := capsules.ExtractCapsulesV132(page) + th.AssertNoErr(t, err) + + for _, m := range allCapsules { + capsuleUUID := m.UUID + if capsuleUUID != capsule.UUID { + continue + } + capsule, err := capsules.Get(client, capsuleUUID).ExtractV132() + + th.AssertNoErr(t, err) + th.AssertEquals(t, capsule.MetaName, "template") + + err = capsules.Delete(client, capsuleUUID).ExtractErr() + th.AssertNoErr(t, err) + + } + return true, nil + }) + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/fixtures.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/fixtures.go new file mode 100644 index 000000000000..a9480d4b4443 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/container/v1/fixtures.go @@ -0,0 +1,46 @@ +package v1 + +const capsuleTemplate = ` + { + "capsuleVersion": "beta", + "kind": "capsule", + "metadata": { + "labels": { + "app": "web", + "app1": "web1" + }, + "name": "template" + }, + "spec": { + "restartPolicy": "Always", + "containers": [ + { + "command": [ + "sleep", + "1000000" + ], + "env": { + "ENV1": "/usr/local/bin", + "ENV2": "/usr/bin" + }, + "image": "ubuntu", + "ports": [ + { + "containerPort": 80, + "hostPort": 80, + "name": "nginx-port", + "protocol": "TCP" + } + ], + "resources": { + "requests": { + "cpu": 1, + "memory": 1024 + } + }, + "workDir": "/root" + } + ] + } + } +` diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/certificates_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/certificates_test.go new file mode 100644 index 000000000000..c3860f9a7107 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/certificates_test.go @@ -0,0 +1,45 @@ +// +build acceptance containerinfra + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCertificatesCRUD(t *testing.T) { + client, err := clients.NewContainerInfraV1Client() + th.AssertNoErr(t, err) + + clusterUUID := "8934d2d1-6bce-4ffa-a017-fb437777269d" + + opts := certificates.CreateOpts{ + BayUUID: clusterUUID, + CSR: "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIIByjCCATMCAQAwgYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh" + + "MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMR8w" + + "HQYDVQQLExZJbmZvcm1hdGlvbiBUZWNobm9sb2d5MRcwFQYDVQQDEw53d3cuZ29v" + + "Z2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApZtYJCHJ4VpVXHfV" + + "IlstQTlO4qC03hjX+ZkPyvdYd1Q4+qbAeTwXmCUKYHThVRd5aXSqlPzyIBwieMZr" + + "WFlRQddZ1IzXAlVRDWwAo60KecqeAXnnUK+5fXoTI/UgWshre8tJ+x/TMHaQKR/J" + + "cIWPhqaQhsJuzZbvAdGA80BLxdMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIhl" + + "4PvFq+e7ipARgI5ZM+GZx6mpCz44DTo0JkwfRDf+BtrsaC0q68eTf2XhYOsq4fkH" + + "Q0uA0aVog3f5iJxCa3Hp5gxbJQ6zV6kJ0TEsuaaOhEko9sdpCoPOnRBm2i/XRD2D" + + "6iNh8f8z0ShGsFqjDgFHyF3o+lUyj+UC6H1QW7bn\n" + + "-----END CERTIFICATE REQUEST-----", + } + + createResponse, err := certificates.Create(client, opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, opts.CSR, createResponse.CSR) + + certificate, err := certificates.Get(client, clusterUUID).Extract() + th.AssertNoErr(t, err) + t.Log(certificate.PEM) + + err = certificates.Update(client, clusterUUID).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clusters_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clusters_test.go new file mode 100644 index 000000000000..1e2bf8535186 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clusters_test.go @@ -0,0 +1,78 @@ +// +build acceptance containerinfra + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestClustersCRUD(t *testing.T) { + client, err := clients.NewContainerInfraV1Client() + th.AssertNoErr(t, err) + + clusterTemplate, err := CreateClusterTemplate(t, client) + th.AssertNoErr(t, err) + defer DeleteClusterTemplate(t, client, clusterTemplate.UUID) + + clusterID, err := CreateCluster(t, client, clusterTemplate.UUID) + tools.PrintResource(t, clusterID) + defer DeleteCluster(t, client, clusterID) + + allPages, err := clusters.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allClusters, err := clusters.ExtractClusters(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allClusters { + if v.UUID == clusterID { + found = true + } + } + th.AssertEquals(t, found, true) + updateOpts := []clusters.UpdateOptsBuilder{ + clusters.UpdateOpts{ + Op: clusters.ReplaceOp, + Path: "/node_count", + Value: "2", + }, + } + updateResult := clusters.Update(client, clusterID, updateOpts) + th.AssertNoErr(t, updateResult.Err) + + if len(updateResult.Header["X-Openstack-Request-Id"]) > 0 { + t.Logf("Cluster Update Request ID: %s", updateResult.Header["X-Openstack-Request-Id"][0]) + } + + clusterID, err = updateResult.Extract() + th.AssertNoErr(t, err) + + err = WaitForCluster(client, clusterID, "UPDATE_COMPLETE") + th.AssertNoErr(t, err) + + newCluster, err := clusters.Get(client, clusterID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newCluster.UUID, clusterID) + + allPagesDetail, err := clusters.ListDetail(client, nil).AllPages() + th.AssertNoErr(t, err) + + allClustersDetail, err := clusters.ExtractClusters(allPagesDetail) + th.AssertNoErr(t, err) + + var foundDetail bool + for _, v := range allClustersDetail { + if v.UUID == clusterID { + foundDetail = true + } + } + th.AssertEquals(t, foundDetail, true) + + tools.PrintResource(t, newCluster) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clustertemplates_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clustertemplates_test.go new file mode 100644 index 000000000000..fb27d0971f03 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/clustertemplates_test.go @@ -0,0 +1,70 @@ +// +build acceptance containerinfra + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestClusterTemplatesCRUD(t *testing.T) { + client, err := clients.NewContainerInfraV1Client() + th.AssertNoErr(t, err) + + clusterTemplate, err := CreateClusterTemplate(t, client) + th.AssertNoErr(t, err) + t.Log(clusterTemplate.Name) + + defer DeleteClusterTemplate(t, client, clusterTemplate.UUID) + + // Test clusters list + allPages, err := clustertemplates.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allClusterTemplates, err := clustertemplates.ExtractClusterTemplates(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allClusterTemplates { + if v.UUID == clusterTemplate.UUID { + found = true + } + } + + th.AssertEquals(t, found, true) + + template, err := clustertemplates.Get(client, clusterTemplate.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, clusterTemplate.UUID, template.UUID) + + // Test cluster update + updateOpts := []clustertemplates.UpdateOptsBuilder{ + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/master_lb_enabled", + Value: "false", + }, + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/registry_enabled", + Value: "false", + }, + clustertemplates.UpdateOpts{ + Op: clustertemplates.AddOp, + Path: "/labels/test", + Value: "test", + }, + } + + updateClusterTemplate, err := clustertemplates.Update(client, clusterTemplate.UUID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, false, updateClusterTemplate.MasterLBEnabled) + th.AssertEquals(t, false, updateClusterTemplate.RegistryEnabled) + th.AssertEquals(t, "test", updateClusterTemplate.Labels["test"]) + tools.PrintResource(t, updateClusterTemplate) + +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/containerinfra.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/containerinfra.go new file mode 100644 index 000000000000..748952df723b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/containerinfra.go @@ -0,0 +1,224 @@ +package v1 + +import ( + "fmt" + "strings" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + idv3 "github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// CreateClusterTemplate will create a random cluster tempalte. An error will be returned if the +// cluster-template could not be created. +func CreateClusterTemplate(t *testing.T, client *gophercloud.ServiceClient) (*clustertemplates.ClusterTemplate, error) { + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + return nil, err + } + + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create cluster template: %s", name) + + boolFalse := false + createOpts := clustertemplates.CreateOpts{ + COE: "swarm", + DNSNameServer: "8.8.8.8", + DockerStorageDriver: "devicemapper", + ExternalNetworkID: choices.ExternalNetworkID, + FlavorID: choices.FlavorID, + FloatingIPEnabled: &boolFalse, + ImageID: choices.MagnumImageID, + MasterFlavorID: choices.FlavorID, + MasterLBEnabled: &boolFalse, + Name: name, + Public: &boolFalse, + RegistryEnabled: &boolFalse, + ServerType: "vm", + } + + res := clustertemplates.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, true, requestID != "") + + t.Logf("Cluster Template %s request ID: %s", name, requestID) + + clusterTemplate, err := res.Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created cluster template: %s", clusterTemplate.Name) + + tools.PrintResource(t, clusterTemplate) + tools.PrintResource(t, clusterTemplate.CreatedAt) + + th.AssertEquals(t, name, clusterTemplate.Name) + th.AssertEquals(t, choices.ExternalNetworkID, clusterTemplate.ExternalNetworkID) + th.AssertEquals(t, choices.MagnumImageID, clusterTemplate.ImageID) + + return clusterTemplate, nil +} + +// DeleteClusterTemplate will delete a given cluster-template. A fatal error will occur if the +// cluster-template could not be deleted. This works best as a deferred function. +func DeleteClusterTemplate(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete cluster-template: %s", id) + + err := clustertemplates.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Error deleting cluster-template %s: %s:", id, err) + } + + t.Logf("Successfully deleted cluster-template: %s", id) + + return +} + +// CreateCluster will create a random cluster. An error will be returned if the +// cluster could not be created. +func CreateCluster(t *testing.T, client *gophercloud.ServiceClient, clusterTemplateID string) (string, error) { + clusterName := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create cluster: %s using template %s", clusterName, clusterTemplateID) + + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + return "", err + } + + masterCount := 1 + nodeCount := 1 + createTimeout := 100 + createOpts := clusters.CreateOpts{ + ClusterTemplateID: clusterTemplateID, + CreateTimeout: &createTimeout, + FlavorID: choices.FlavorID, + Keypair: choices.MagnumKeypair, + Labels: map[string]string{}, + MasterCount: &masterCount, + MasterFlavorID: choices.FlavorID, + Name: clusterName, + NodeCount: &nodeCount, + } + + createResult := clusters.Create(client, createOpts) + th.AssertNoErr(t, createResult.Err) + if len(createResult.Header["X-Openstack-Request-Id"]) > 0 { + t.Logf("Cluster Create Request ID: %s", createResult.Header["X-Openstack-Request-Id"][0]) + } + + clusterID, err := createResult.Extract() + if err != nil { + return "", err + } + + t.Logf("Cluster created: %+v", clusterID) + + err = WaitForCluster(client, clusterID, "CREATE_COMPLETE") + if err != nil { + return clusterID, err + } + + t.Logf("Successfully created cluster: %s id: %s", clusterName, clusterID) + return clusterID, nil +} + +func DeleteCluster(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete cluster: %s", id) + + r := clusters.Delete(client, id) + err := clusters.Delete(client, id).ExtractErr() + deleteRequestID := "" + idKey := "X-Openstack-Request-Id" + if len(r.Header[idKey]) > 0 { + deleteRequestID = r.Header[idKey][0] + } + if err != nil { + t.Fatalf("Error deleting cluster. requestID=%s clusterID=%s: err%s:", deleteRequestID, id, err) + } + + err = WaitForCluster(client, id, "DELETE_COMPLETE") + if err != nil { + t.Fatalf("Error deleting cluster %s: %s:", id, err) + } + + t.Logf("Successfully deleted cluster: %s", id) + + return +} + +func WaitForCluster(client *gophercloud.ServiceClient, clusterID string, status string) error { + return tools.WaitFor(func() (bool, error) { + cluster, err := clusters.Get(client, clusterID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok && status == "DELETE_COMPLETE" { + return true, nil + } + + return false, err + } + + if cluster.Status == status { + return true, nil + } + + if strings.Contains(cluster.Status, "FAILED") { + return false, fmt.Errorf("Cluster %s FAILED. Status=%s StatusReason=%s", clusterID, cluster.Status, cluster.StatusReason) + } + + return false, nil + }) +} + +// CreateQuota will create a random quota. An error will be returned if the +// quota could not be created. +func CreateQuota(t *testing.T, client *gophercloud.ServiceClient) (*quotas.Quotas, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create quota: %s", name) + + idClient, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + project, err := idv3.CreateProject(t, idClient, nil) + th.AssertNoErr(t, err) + defer idv3.DeleteProject(t, idClient, project.ID) + + createOpts := quotas.CreateOpts{ + Resource: "Cluster", + ProjectID: project.ID, + HardLimit: 10, + } + + res := quotas.Create(client, createOpts) + if res.Err != nil { + return nil, res.Err + } + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, true, requestID != "") + + t.Logf("Quota %s request ID: %s", name, requestID) + + quota, err := res.Extract() + if err == nil { + t.Logf("Successfully created quota: %s", quota.ProjectID) + + tools.PrintResource(t, quota) + + th.AssertEquals(t, project.ID, quota.ProjectID) + th.AssertEquals(t, "Cluster", quota.Resource) + th.AssertEquals(t, 10, quota.HardLimit) + } + + return quota, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/pkg.go similarity index 100% rename from vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/pkg.go rename to vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/pkg.go diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/quotas_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/quotas_test.go new file mode 100644 index 000000000000..b6e83bcaa16e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/containerinfra/v1/quotas_test.go @@ -0,0 +1,20 @@ +// +build acceptance containerinfra + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestQuotasCRUD(t *testing.T) { + client, err := clients.NewContainerInfraV1Client() + th.AssertNoErr(t, err) + + quota, err := CreateQuota(t, client) + th.AssertNoErr(t, err) + tools.PrintResource(t, quota) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/configurations_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/configurations_test.go index 87e883f45831..ed5041702ce0 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/configurations_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/configurations_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/db/v1/configurations" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestConfigurationsCRUD(t *testing.T) { @@ -41,10 +42,38 @@ func TestConfigurationsCRUD(t *testing.T) { t.Fatalf("Unable to create configuration: %v", err) } + readCgroup, err := configurations.Get(client, cgroup.ID).Extract() + if err != nil { + t.Fatalf("Unable to read configuration: %v", err) + } + + tools.PrintResource(t, readCgroup) + th.AssertEquals(t, readCgroup.Name, createOpts.Name) + th.AssertEquals(t, readCgroup.Description, createOpts.Description) + // TODO: verify datastore + //th.AssertDeepEquals(t, readCgroup.Datastore, datastore) + + // Update cgroup + newCgroupName := "New configuration name" + newCgroupDescription := "" + updateOpts := configurations.UpdateOpts{ + Name: newCgroupName, + Description: &newCgroupDescription, + } + err = configurations.Update(client, cgroup.ID, updateOpts).ExtractErr() + th.AssertNoErr(t, err) + + newCgroup, err := configurations.Get(client, cgroup.ID).Extract() + if err != nil { + t.Fatalf("Unable to read updated configuration: %v", err) + } + + tools.PrintResource(t, newCgroup) + th.AssertEquals(t, newCgroup.Name, newCgroupName) + th.AssertEquals(t, newCgroup.Description, newCgroupDescription) + err = configurations.Delete(client, cgroup.ID).ExtractErr() if err != nil { t.Fatalf("Unable to delete configuration: %v", err) } - - tools.PrintResource(t, cgroup) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/dns.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/dns.go index 7a0893ff5cb0..18cd157fc1c0 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/dns.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/dns.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateRecordSet will create a RecordSet with a random name. An error will @@ -38,6 +39,8 @@ func CreateRecordSet(t *testing.T, client *gophercloud.ServiceClient, zone *zone t.Logf("Created record set: %s", newRS.Name) + th.AssertEquals(t, newRS.Name, zone.Name) + return rs, nil } @@ -70,6 +73,10 @@ func CreateZone(t *testing.T, client *gophercloud.ServiceClient) (*zones.Zone, e } t.Logf("Created Zone: %s", zoneName) + + th.AssertEquals(t, newZone.Name, zoneName) + th.AssertEquals(t, newZone.TTL, 7200) + return newZone, nil } @@ -102,6 +109,10 @@ func CreateSecondaryZone(t *testing.T, client *gophercloud.ServiceClient) (*zone } t.Logf("Created Zone: %s", zoneName) + + th.AssertEquals(t, newZone.Name, zoneName) + th.AssertEquals(t, newZone.Masters[0], "10.0.0.1") + return newZone, nil } @@ -132,7 +143,7 @@ func DeleteZone(t *testing.T, client *gophercloud.ServiceClient, zone *zones.Zon // WaitForRecordSetStatus will poll a record set's status until it either matches // the specified status or the status becomes ERROR. func WaitForRecordSetStatus(client *gophercloud.ServiceClient, rs *recordsets.RecordSet, status string) error { - return gophercloud.WaitFor(60, func() (bool, error) { + return gophercloud.WaitFor(600, func() (bool, error) { current, err := recordsets.Get(client, rs.ZoneID, rs.ID).Extract() if err != nil { return false, err @@ -149,7 +160,7 @@ func WaitForRecordSetStatus(client *gophercloud.ServiceClient, rs *recordsets.Re // WaitForZoneStatus will poll a zone's status until it either matches // the specified status or the status becomes ERROR. func WaitForZoneStatus(client *gophercloud.ServiceClient, zone *zones.Zone, status string) error { - return gophercloud.WaitFor(60, func() (bool, error) { + return gophercloud.WaitFor(600, func() (bool, error) { current, err := zones.Get(client, zone.ID).Extract() if err != nil { return false, err diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/recordsets_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/recordsets_test.go index 17c40bb0ce0d..683384e1e76e 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/recordsets_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/recordsets_test.go @@ -8,98 +8,105 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestRecordSetsListByZone(t *testing.T) { + clients.RequireDNS(t) + client, err := clients.NewDNSV2Client() - if err != nil { - t.Fatalf("Unable to create a DNS client: %v", err) - } + th.AssertNoErr(t, err) zone, err := CreateZone(t, client) - if err != nil { - t.Fatal(err) - } + th.AssertNoErr(t, err) defer DeleteZone(t, client, zone) - var allRecordSets []recordsets.RecordSet allPages, err := recordsets.ListByZone(client, zone.ID, nil).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve recordsets: %v", err) - } + th.AssertNoErr(t, err) - allRecordSets, err = recordsets.ExtractRecordSets(allPages) - if err != nil { - t.Fatalf("Unable to extract recordsets: %v", err) - } + allRecordSets, err := recordsets.ExtractRecordSets(allPages) + th.AssertNoErr(t, err) + var found bool for _, recordset := range allRecordSets { tools.PrintResource(t, &recordset) - } -} -func TestRecordSetsListByZoneLimited(t *testing.T) { - client, err := clients.NewDNSV2Client() - if err != nil { - t.Fatalf("Unable to create a DNS client: %v", err) + if recordset.ZoneID == zone.ID { + found = true + } } - zone, err := CreateZone(t, client) - if err != nil { - t.Fatal(err) - } - defer DeleteZone(t, client, zone) + th.AssertEquals(t, found, true) - var allRecordSets []recordsets.RecordSet listOpts := recordsets.ListOpts{ Limit: 1, } - allPages, err := recordsets.ListByZone(client, zone.ID, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve recordsets: %v", err) - } - - allRecordSets, err = recordsets.ExtractRecordSets(allPages) - if err != nil { - t.Fatalf("Unable to extract recordsets: %v", err) - } - for _, recordset := range allRecordSets { - tools.PrintResource(t, &recordset) - } + err = recordsets.ListByZone(client, zone.ID, listOpts).EachPage( + func(page pagination.Page) (bool, error) { + rr, err := recordsets.ExtractRecordSets(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, len(rr), 1) + return true, nil + }, + ) + th.AssertNoErr(t, err) } -func TestRecordSetCRUD(t *testing.T) { +func TestRecordSetsCRUD(t *testing.T) { + clients.RequireDNS(t) + client, err := clients.NewDNSV2Client() - if err != nil { - t.Fatalf("Unable to create a DNS client: %v", err) - } + th.AssertNoErr(t, err) zone, err := CreateZone(t, client) - if err != nil { - t.Fatal(err) - } + th.AssertNoErr(t, err) defer DeleteZone(t, client, zone) tools.PrintResource(t, &zone) rs, err := CreateRecordSet(t, client, zone) - if err != nil { - t.Fatal(err) - } + th.AssertNoErr(t, err) defer DeleteRecordSet(t, client, rs) tools.PrintResource(t, &rs) + description := "" updateOpts := recordsets.UpdateOpts{ - Description: "New description", - TTL: 0, + Description: &description, } newRS, err := recordsets.Update(client, rs.ZoneID, rs.ID, updateOpts).Extract() - if err != nil { - t.Fatal(err) + th.AssertNoErr(t, err) + + tools.PrintResource(t, &newRS) + + th.AssertEquals(t, newRS.Description, description) + + records := []string{"10.1.0.3"} + updateOpts = recordsets.UpdateOpts{ + Records: records, } + newRS, err = recordsets.Update(client, rs.ZoneID, rs.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, &newRS) + + th.AssertDeepEquals(t, newRS.Records, records) + th.AssertEquals(t, newRS.TTL, 3600) + + ttl := 0 + updateOpts = recordsets.UpdateOpts{ + TTL: &ttl, + } + + newRS, err = recordsets.Update(client, rs.ZoneID, rs.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, &newRS) + + th.AssertDeepEquals(t, newRS.Records, records) + th.AssertEquals(t, newRS.TTL, ttl) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/zones_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/zones_test.go index 8e71687898e5..e07867e9befc 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/zones_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/dns/v2/zones_test.go @@ -8,53 +8,48 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestZonesList(t *testing.T) { +func TestZonesCRUD(t *testing.T) { + clients.RequireDNS(t) + client, err := clients.NewDNSV2Client() - if err != nil { - t.Fatalf("Unable to create a DNS client: %v", err) - } + th.AssertNoErr(t, err) - var allZones []zones.Zone - allPages, err := zones.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve zones: %v", err) - } + zone, err := CreateZone(t, client) + th.AssertNoErr(t, err) + defer DeleteZone(t, client, zone) - allZones, err = zones.ExtractZones(allPages) - if err != nil { - t.Fatalf("Unable to extract zones: %v", err) - } + tools.PrintResource(t, &zone) - for _, zone := range allZones { - tools.PrintResource(t, &zone) - } -} + allPages, err := zones.List(client, nil).AllPages() + th.AssertNoErr(t, err) -func TestZonesCRUD(t *testing.T) { - client, err := clients.NewDNSV2Client() - if err != nil { - t.Fatalf("Unable to create a DNS client: %v", err) - } + allZones, err := zones.ExtractZones(allPages) + th.AssertNoErr(t, err) - zone, err := CreateZone(t, client) - if err != nil { - t.Fatal(err) + var found bool + for _, z := range allZones { + tools.PrintResource(t, &z) + + if zone.Name == z.Name { + found = true + } } - defer DeleteZone(t, client, zone) - tools.PrintResource(t, &zone) + th.AssertEquals(t, found, true) + description := "" updateOpts := zones.UpdateOpts{ - Description: "New description", + Description: &description, TTL: 0, } newZone, err := zones.Update(client, zone.ID, updateOpts).Extract() - if err != nil { - t.Fatal(err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, &newZone) + + th.AssertEquals(t, newZone.Description, description) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/extension_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/extension_test.go index c6a2bdef415c..593d75ca4596 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/extension_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/extension_test.go @@ -8,39 +8,42 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v2/extensions" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestExtensionsList(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2Client() - if err != nil { - t.Fatalf("Unable to create an identity client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := extensions.List(client).AllPages() - if err != nil { - t.Fatalf("Unable to list extensions: %v", err) - } + th.AssertNoErr(t, err) allExtensions, err := extensions.ExtractExtensions(allPages) - if err != nil { - t.Fatalf("Unable to extract extensions: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, extension := range allExtensions { tools.PrintResource(t, extension) + if extension.Name == "OS-KSCRUD" { + found = true + } } + + th.AssertEquals(t, found, true) } func TestExtensionsGet(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2Client() - if err != nil { - t.Fatalf("Unable to create an identity client: %v", err) - } + th.AssertNoErr(t, err) extension, err := extensions.Get(client, "OS-KSCRUD").Extract() - if err != nil { - t.Fatalf("Unable to get extension OS-KSCRUD: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, extension) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/identity.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/identity.go index 6d0d0f209054..f74812193b28 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/identity.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/identity.go @@ -10,6 +10,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/identity/v2/extensions/admin/roles" "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants" "github.com/gophercloud/gophercloud/openstack/identity/v2/users" + th "github.com/gophercloud/gophercloud/testhelper" ) // AddUserRole will grant a role to a user in a tenant. An error will be @@ -33,6 +34,7 @@ func AddUserRole(t *testing.T, client *gophercloud.ServiceClient, tenant *tenant // unable to be created. func CreateTenant(t *testing.T, client *gophercloud.ServiceClient, c *tenants.CreateOpts) (*tenants.Tenant, error) { name := tools.RandomString("ACPTTEST", 8) + description := tools.RandomString("ACPTTEST-DESC", 8) t.Logf("Attempting to create tenant: %s", name) var createOpts tenants.CreateOpts @@ -43,15 +45,18 @@ func CreateTenant(t *testing.T, client *gophercloud.ServiceClient, c *tenants.Cr } createOpts.Name = name + createOpts.Description = description tenant, err := tenants.Create(client, createOpts).Extract() if err != nil { - t.Logf("Foo") return tenant, err } t.Logf("Successfully created project %s with ID %s", name, tenant.ID) + th.AssertEquals(t, name, tenant.Name) + th.AssertEquals(t, description, tenant.Description) + return tenant, nil } @@ -74,6 +79,8 @@ func CreateUser(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants return user, err } + th.AssertEquals(t, userName, user.Name) + return user, nil } @@ -182,5 +189,7 @@ func UpdateUser(t *testing.T, client *gophercloud.ServiceClient, user *users.Use return newUser, err } + th.AssertEquals(t, userName, newUser.Name) + return newUser, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/role_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/role_test.go index 83fbd318fa03..bc9d26ec3483 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/role_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/role_test.go @@ -9,69 +9,68 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v2/extensions/admin/roles" "github.com/gophercloud/gophercloud/openstack/identity/v2/users" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestRolesAddToUser(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2AdminClient() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) tenant, err := FindTenant(t, client) - if err != nil { - t.Fatalf("Unable to get a tenant: %v", err) - } + th.AssertNoErr(t, err) role, err := FindRole(t, client) - if err != nil { - t.Fatalf("Unable to get a role: %v", err) - } + th.AssertNoErr(t, err) user, err := CreateUser(t, client, tenant) - if err != nil { - t.Fatalf("Unable to create a user: %v", err) - } + th.AssertNoErr(t, err) defer DeleteUser(t, client, user) err = AddUserRole(t, client, tenant, user, role) - if err != nil { - t.Fatalf("Unable to add role to user: %v", err) - } + th.AssertNoErr(t, err) defer DeleteUserRole(t, client, tenant, user, role) allPages, err := users.ListRoles(client, tenant.ID, user.ID).AllPages() - if err != nil { - t.Fatalf("Unable to obtain roles for user: %v", err) - } + th.AssertNoErr(t, err) allRoles, err := users.ExtractRoles(allPages) - if err != nil { - t.Fatalf("Unable to extract roles: %v", err) - } + th.AssertNoErr(t, err) t.Logf("Roles of user %s:", user.Name) - for _, role := range allRoles { + var found bool + for _, r := range allRoles { tools.PrintResource(t, role) + if r.Name == role.Name { + found = true + } } + + th.AssertEquals(t, found, true) } func TestRolesList(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2AdminClient() - if err != nil { - t.Fatalf("Unable to create an identity client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := roles.List(client).AllPages() - if err != nil { - t.Fatalf("Unable to list all roles: %v", err) - } + th.AssertNoErr(t, err) allRoles, err := roles.ExtractRoles(allPages) - if err != nil { - t.Fatalf("Unable to extract roles: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, r := range allRoles { tools.PrintResource(t, r) + if r.Name == "admin" { + found = true + } } + + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/tenant_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/tenant_test.go index 049ec910a1e8..f53270760a2d 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/tenant_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/tenant_test.go @@ -8,56 +8,59 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestTenantsList(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) allPages, err := tenants.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list tenants: %v", err) - } + th.AssertNoErr(t, err) allTenants, err := tenants.ExtractTenants(allPages) - if err != nil { - t.Fatalf("Unable to extract tenants: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, tenant := range allTenants { tools.PrintResource(t, tenant) + + if tenant.Name == "admin" { + found = true + } } + + th.AssertEquals(t, found, true) } func TestTenantsCRUD(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2AdminClient() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) tenant, err := CreateTenant(t, client, nil) - if err != nil { - t.Fatalf("Unable to create tenant: %v", err) - } + th.AssertNoErr(t, err) defer DeleteTenant(t, client, tenant.ID) tenant, err = tenants.Get(client, tenant.ID).Extract() - if err != nil { - t.Fatalf("Unable to get tenant: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, tenant) + description := "" updateOpts := tenants.UpdateOpts{ - Description: "some tenant", + Description: &description, } newTenant, err := tenants.Update(client, tenant.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update tenant: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newTenant) + + th.AssertEquals(t, newTenant.Description, description) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/token_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/token_test.go index 82a317a157e9..30ebcc2bf005 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/token_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/token_test.go @@ -9,31 +9,27 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestTokenAuthenticate(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2UnauthenticatedClient() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) authOptions, err := openstack.AuthOptionsFromEnv() - if err != nil { - t.Fatalf("Unable to obtain authentication options: %v", err) - } + th.AssertNoErr(t, err) result := tokens.Create(client, authOptions) token, err := result.ExtractToken() - if err != nil { - t.Fatalf("Unable to extract token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, token) catalog, err := result.ExtractServiceCatalog() - if err != nil { - t.Fatalf("Unable to extract service catalog: %v", err) - } + th.AssertNoErr(t, err) for _, entry := range catalog.Entries { tools.PrintResource(t, entry) @@ -41,29 +37,24 @@ func TestTokenAuthenticate(t *testing.T) { } func TestTokenValidate(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) authOptions, err := openstack.AuthOptionsFromEnv() - if err != nil { - t.Fatalf("Unable to obtain authentication options: %v", err) - } + th.AssertNoErr(t, err) result := tokens.Create(client, authOptions) token, err := result.ExtractToken() - if err != nil { - t.Fatalf("Unable to extract token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, token) getResult := tokens.Get(client, token.ID) user, err := getResult.ExtractUser() - if err != nil { - t.Fatalf("Unable to extract user: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, user) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/user_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/user_test.go index faa5bba2f899..caaaaf936a42 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/user_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v2/user_test.go @@ -8,52 +8,52 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v2/users" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestUsersList(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2AdminClient() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := users.List(client).AllPages() - if err != nil { - t.Fatalf("Unable to list users: %v", err) - } + th.AssertNoErr(t, err) allUsers, err := users.ExtractUsers(allPages) - if err != nil { - t.Fatalf("Unable to extract users: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, user := range allUsers { tools.PrintResource(t, user) + + if user.Name == "admin" { + found = true + } } + + th.AssertEquals(t, found, true) } func TestUsersCreateUpdateDelete(t *testing.T) { + clients.RequireIdentityV2(t) + clients.RequireAdmin(t) + client, err := clients.NewIdentityV2AdminClient() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) tenant, err := FindTenant(t, client) - if err != nil { - t.Fatalf("Unable to get a tenant: %v", err) - } + th.AssertNoErr(t, err) user, err := CreateUser(t, client, tenant) - if err != nil { - t.Fatalf("Unable to create a user: %v", err) - } + th.AssertNoErr(t, err) defer DeleteUser(t, client, user) tools.PrintResource(t, user) newUser, err := UpdateUser(t, client, user) - if err != nil { - t.Fatalf("Unable to update user: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newUser) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/applicationcredentials_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/applicationcredentials_test.go new file mode 100644 index 000000000000..c442daa09769 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/applicationcredentials_test.go @@ -0,0 +1,168 @@ +// +build acceptance + +package v3 + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials" + "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestApplicationCredentialsCRD(t *testing.T) { + clients.RequireAdmin(t) + + // maps are required, because Application Credential roles are returned in a random order + rolesToMap := func(roles []applicationcredentials.Role) map[string]string { + rolesMap := map[string]string{} + for _, role := range roles { + rolesMap[role.Name] = role.Name + rolesMap[role.ID] = role.ID + } + return rolesMap + } + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + ao, err := openstack.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + authOptions := tokens.AuthOptions{ + Username: ao.Username, + 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, + DomainID: ao.DomainID, + DomainName: ao.DomainName, + }, + } + + token, err := tokens.Create(client, &authOptions).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, token) + + user, err := tokens.Get(client, token.ID).ExtractUser() + th.AssertNoErr(t, err) + tools.PrintResource(t, user) + + roles, err := tokens.Get(client, token.ID).ExtractRoles() + th.AssertNoErr(t, err) + tools.PrintResource(t, roles) + + project, err := tokens.Get(client, token.ID).ExtractProject() + th.AssertNoErr(t, err) + tools.PrintResource(t, project) + + // prepare create parameters + var apRoles []applicationcredentials.Role + for i, role := range roles { + if i%2 == 0 { + apRoles = append(apRoles, applicationcredentials.Role{Name: role.Name}) + } else { + apRoles = append(apRoles, applicationcredentials.Role{ID: role.ID}) + } + if i > 4 { + break + } + } + tools.PrintResource(t, apRoles) + + // restricted, limited TTL, with limited roles, autogenerated secret + createOpts := applicationcredentials.CreateOpts{ + Name: "test-ac", + Description: "test application credential", + Roles: apRoles, + ExpiresAt: "2119-01-01T12:12:12Z", + } + + applicationCredential, err := applicationcredentials.Create(client, user.ID, createOpts).Extract() + th.AssertNoErr(t, err) + defer applicationcredentials.Delete(client, user.ID, applicationCredential.ID) + tools.PrintResource(t, applicationCredential) + + if applicationCredential.Secret == "" { + t.Fatalf("Application credential secret was not generated") + } + + th.AssertEquals(t, applicationCredential.ExpiresAt.UTC().Format(time.RFC3339), createOpts.ExpiresAt) + th.AssertEquals(t, applicationCredential.Name, createOpts.Name) + th.AssertEquals(t, applicationCredential.Description, createOpts.Description) + th.AssertEquals(t, applicationCredential.Unrestricted, false) + th.AssertEquals(t, applicationCredential.ProjectID, project.ID) + + checkACroles := rolesToMap(applicationCredential.Roles) + for i, role := range roles { + if i%2 == 0 { + th.AssertEquals(t, checkACroles[role.Name], role.Name) + } else { + th.AssertEquals(t, checkACroles[role.ID], role.ID) + } + if i > 4 { + break + } + } + + // Get an application credential + getApplicationCredential, err := applicationcredentials.Get(client, user.ID, applicationCredential.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, getApplicationCredential) + + if getApplicationCredential.Secret != "" { + t.Fatalf("Application credential secret should not be returned by a GET request") + } + + th.AssertEquals(t, getApplicationCredential.ExpiresAt.UTC().Format(time.RFC3339), createOpts.ExpiresAt) + th.AssertEquals(t, getApplicationCredential.Name, createOpts.Name) + th.AssertEquals(t, getApplicationCredential.Description, createOpts.Description) + th.AssertEquals(t, getApplicationCredential.Unrestricted, false) + th.AssertEquals(t, getApplicationCredential.ProjectID, project.ID) + + checkACroles = rolesToMap(getApplicationCredential.Roles) + for i, role := range roles { + if i%2 == 0 { + th.AssertEquals(t, checkACroles[role.Name], role.Name) + } else { + th.AssertEquals(t, checkACroles[role.ID], role.ID) + } + if i > 4 { + break + } + } + + // unrestricted, unlimited TTL, with all possible roles, with a custom secret + createOpts = applicationcredentials.CreateOpts{ + Name: "super-test-ac", + Description: "test unrestricted application credential", + Unrestricted: true, + Secret: "myprecious", + } + + newApplicationCredential, err := applicationcredentials.Create(client, user.ID, createOpts).Extract() + th.AssertNoErr(t, err) + defer applicationcredentials.Delete(client, user.ID, newApplicationCredential.ID) + tools.PrintResource(t, newApplicationCredential) + + th.AssertEquals(t, newApplicationCredential.ExpiresAt.UTC(), time.Time{}) + th.AssertEquals(t, newApplicationCredential.Name, createOpts.Name) + th.AssertEquals(t, newApplicationCredential.Description, createOpts.Description) + th.AssertEquals(t, newApplicationCredential.Secret, createOpts.Secret) + th.AssertEquals(t, newApplicationCredential.Unrestricted, true) + th.AssertEquals(t, newApplicationCredential.ExpiresAt.UTC(), time.Time{}) + th.AssertEquals(t, newApplicationCredential.ProjectID, project.ID) + + checkACroles = rolesToMap(newApplicationCredential.Roles) + for _, role := range roles { + th.AssertEquals(t, checkACroles[role.Name], role.Name) + th.AssertEquals(t, checkACroles[role.ID], role.ID) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/credentials_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/credentials_test.go new file mode 100644 index 000000000000..1910c60b3480 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/credentials_test.go @@ -0,0 +1,94 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/identity/v3/credentials" + "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCredentialsCRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + ao, err := openstack.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + authOptions := tokens.AuthOptions{ + Username: ao.Username, + 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, + DomainID: ao.DomainID, + DomainName: ao.DomainName, + }, + } + token, err := tokens.Create(client, &authOptions).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, token) + + user, err := tokens.Get(client, token.ID).ExtractUser() + th.AssertNoErr(t, err) + tools.PrintResource(t, user) + + project, err := tokens.Get(client, token.ID).ExtractProject() + th.AssertNoErr(t, err) + tools.PrintResource(t, project) + + createOpts := credentials.CreateOpts{ + ProjectID: project.ID, + Type: "ec2", + UserID: user.ID, + Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + } + + // Create a credential + credential, err := credentials.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + + // Delete a credential + defer credentials.Delete(client, credential.ID) + tools.PrintResource(t, credential) + + th.AssertEquals(t, credential.Blob, createOpts.Blob) + th.AssertEquals(t, credential.Type, createOpts.Type) + th.AssertEquals(t, credential.UserID, createOpts.UserID) + th.AssertEquals(t, credential.ProjectID, createOpts.ProjectID) + + // Get a credential + getCredential, err := credentials.Get(client, credential.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, getCredential) + + th.AssertEquals(t, getCredential.Blob, createOpts.Blob) + th.AssertEquals(t, getCredential.Type, createOpts.Type) + th.AssertEquals(t, getCredential.UserID, createOpts.UserID) + th.AssertEquals(t, getCredential.ProjectID, createOpts.ProjectID) + + updateOpts := credentials.UpdateOpts{ + ProjectID: project.ID, + Type: "ec2", + UserID: user.ID, + Blob: "{\"access\":\"181920\",\"secret\":\"mySecret\"}", + } + + // Update a credential + updateCredential, err := credentials.Update(client, credential.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, updateCredential) + + th.AssertEquals(t, updateCredential.Blob, updateOpts.Blob) + +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go index b340bed4bd4d..71a335ea8872 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go @@ -8,13 +8,14 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestDomainsList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) var iTrue bool = true listOpts := domains.ListOpts{ @@ -22,75 +23,69 @@ func TestDomainsList(t *testing.T) { } allPages, err := domains.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list domains: %v", err) - } + th.AssertNoErr(t, err) allDomains, err := domains.ExtractDomains(allPages) - if err != nil { - t.Fatalf("Unable to extract domains: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, domain := range allDomains { tools.PrintResource(t, domain) + + if domain.Name == "Default" { + found = true + } } + + th.AssertEquals(t, found, true) } func TestDomainsGet(t *testing.T) { - client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + clients.RequireAdmin(t) - allPages, err := domains.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list domains: %v", err) - } - - allDomains, err := domains.ExtractDomains(allPages) - if err != nil { - t.Fatalf("Unable to extract domains: %v", err) - } + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) - domain := allDomains[0] - p, err := domains.Get(client, domain.ID).Extract() - if err != nil { - t.Fatalf("Unable to get domain: %v", err) - } + p, err := domains.Get(client, "default").Extract() + th.AssertNoErr(t, err) tools.PrintResource(t, p) + + th.AssertEquals(t, p.Name, "Default") } func TestDomainsCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) var iTrue bool = true + var description = "Testing Domain" createOpts := domains.CreateOpts{ - Description: "Testing Domain", + Description: description, Enabled: &iTrue, } domain, err := CreateDomain(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create domain: %v", err) - } + th.AssertNoErr(t, err) defer DeleteDomain(t, client, domain.ID) tools.PrintResource(t, domain) + th.AssertEquals(t, domain.Description, description) + var iFalse bool = false + description = "" updateOpts := domains.UpdateOpts{ - Description: "Staging Test Domain", + Description: &description, Enabled: &iFalse, } newDomain, err := domains.Update(client, domain.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update domain: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newDomain) + + th.AssertEquals(t, newDomain.Description, description) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/endpoint_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/endpoint_test.go index a589970606a6..4bc606b34c6f 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/endpoint_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/endpoint_test.go @@ -3,6 +3,7 @@ package v3 import ( + "strings" "testing" "github.com/gophercloud/gophercloud" @@ -10,34 +11,38 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints" "github.com/gophercloud/gophercloud/openstack/identity/v3/services" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestEndpointsList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) allPages, err := endpoints.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list endpoints: %v", err) - } + th.AssertNoErr(t, err) allEndpoints, err := endpoints.ExtractEndpoints(allPages) - if err != nil { - t.Fatalf("Unable to extract endpoints: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, endpoint := range allEndpoints { tools.PrintResource(t, endpoint) + + if strings.Contains(endpoint.URL, "/v3") { + found = true + } } + + th.AssertEquals(t, found, true) } func TestEndpointsNavigateCatalog(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) // Discover the service we're interested in. serviceListOpts := services.ListOpts{ @@ -45,18 +50,12 @@ func TestEndpointsNavigateCatalog(t *testing.T) { } allPages, err := services.List(client, serviceListOpts).AllPages() - if err != nil { - t.Fatalf("Unable to lookup compute service: %v", err) - } + th.AssertNoErr(t, err) allServices, err := services.ExtractServices(allPages) - if err != nil { - t.Fatalf("Unable to extract service: %v") - } + th.AssertNoErr(t, err) - if len(allServices) != 1 { - t.Fatalf("Expected one service, got %d", len(allServices)) - } + th.AssertEquals(t, len(allServices), 1) computeService := allServices[0] tools.PrintResource(t, computeService) @@ -68,19 +67,12 @@ func TestEndpointsNavigateCatalog(t *testing.T) { } allPages, err = endpoints.List(client, endpointListOpts).AllPages() - if err != nil { - t.Fatalf("Unable to lookup compute endpoint: %v", err) - } + th.AssertNoErr(t, err) allEndpoints, err := endpoints.ExtractEndpoints(allPages) - if err != nil { - t.Fatalf("Unable to extract endpoint: %v") - } + th.AssertNoErr(t, err) - if len(allEndpoints) != 1 { - t.Fatalf("Expected one endpoint, got %d", len(allEndpoints)) - } + th.AssertEquals(t, len(allServices), 1) tools.PrintResource(t, allEndpoints[0]) - } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go index 3e832c2022f6..c16881788218 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go @@ -8,17 +8,20 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestGroupCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) + description := "Test Groups" + domainID := "default" createOpts := groups.CreateOpts{ - Name: "testgroup", - DomainID: "default", + Description: description, + DomainID: domainID, Extra: map[string]interface{}{ "email": "testgroup@example.com", }, @@ -26,54 +29,108 @@ func TestGroupCRUD(t *testing.T) { // Create Group in the default domain group, err := CreateGroup(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create group: %v", err) - } + th.AssertNoErr(t, err) defer DeleteGroup(t, client, group.ID) tools.PrintResource(t, group) tools.PrintResource(t, group.Extra) + th.AssertEquals(t, group.Description, description) + th.AssertEquals(t, group.DomainID, domainID) + th.AssertDeepEquals(t, group.Extra, createOpts.Extra) + + description = "" updateOpts := groups.UpdateOpts{ - Description: "Test Users", + Description: &description, Extra: map[string]interface{}{ "email": "thetestgroup@example.com", }, } newGroup, err := groups.Update(client, group.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update group: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newGroup) tools.PrintResource(t, newGroup.Extra) + th.AssertEquals(t, newGroup.Description, description) + th.AssertDeepEquals(t, newGroup.Extra, updateOpts.Extra) + listOpts := groups.ListOpts{ DomainID: "default", } // List all Groups in default domain allPages, err := groups.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list groups: %v", err) - } + th.AssertNoErr(t, err) allGroups, err := groups.ExtractGroups(allPages) - if err != nil { - t.Fatalf("Unable to extract groups: %v", err) - } + th.AssertNoErr(t, err) for _, g := range allGroups { tools.PrintResource(t, g) tools.PrintResource(t, g.Extra) } + var found bool + for _, group := range allGroups { + tools.PrintResource(t, group) + tools.PrintResource(t, group.Extra) + + if group.Name == newGroup.Name { + found = true + } + } + + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "TEST", + } + + allPages, err = groups.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allGroups, err = groups.ExtractGroups(allPages) + th.AssertNoErr(t, err) + + found = false + for _, group := range allGroups { + tools.PrintResource(t, group) + tools.PrintResource(t, group.Extra) + + if group.Name == newGroup.Name { + found = true + } + } + + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "foo", + } + + allPages, err = groups.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allGroups, err = groups.ExtractGroups(allPages) + th.AssertNoErr(t, err) + + found = false + for _, group := range allGroups { + tools.PrintResource(t, group) + tools.PrintResource(t, group.Extra) + + if group.Name == newGroup.Name { + found = true + } + } + + th.AssertEquals(t, found, false) + // Get the recently created group by ID p, err := groups.Get(client, group.ID).Extract() - if err != nil { - t.Fatalf("Unable to get group: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, p) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go index ae7560dd584a..718e5bc7ee93 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go @@ -12,6 +12,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" "github.com/gophercloud/gophercloud/openstack/identity/v3/services" "github.com/gophercloud/gophercloud/openstack/identity/v3/users" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateProject will create a project with a random name. @@ -20,6 +21,7 @@ import ( // unable to be created. func CreateProject(t *testing.T, client *gophercloud.ServiceClient, c *projects.CreateOpts) (*projects.Project, error) { name := tools.RandomString("ACPTTEST", 8) + description := tools.RandomString("ACPTTEST-DESC", 8) t.Logf("Attempting to create project: %s", name) var createOpts projects.CreateOpts @@ -30,6 +32,7 @@ func CreateProject(t *testing.T, client *gophercloud.ServiceClient, c *projects. } createOpts.Name = name + createOpts.Description = description project, err := projects.Create(client, createOpts).Extract() if err != nil { @@ -38,6 +41,9 @@ func CreateProject(t *testing.T, client *gophercloud.ServiceClient, c *projects. t.Logf("Successfully created project %s with ID %s", name, project.ID) + th.AssertEquals(t, project.Name, name) + th.AssertEquals(t, project.Description, description) + return project, nil } @@ -65,6 +71,8 @@ func CreateUser(t *testing.T, client *gophercloud.ServiceClient, c *users.Create t.Logf("Successfully created user %s with ID %s", name, user.ID) + th.AssertEquals(t, user.Name, name) + return user, nil } @@ -92,6 +100,8 @@ func CreateGroup(t *testing.T, client *gophercloud.ServiceClient, c *groups.Crea t.Logf("Successfully created group %s with ID %s", name, group.ID) + th.AssertEquals(t, group.Name, name) + return group, nil } @@ -119,6 +129,8 @@ func CreateDomain(t *testing.T, client *gophercloud.ServiceClient, c *domains.Cr t.Logf("Successfully created domain %s with ID %s", name, domain.ID) + th.AssertEquals(t, domain.Name, name) + return domain, nil } @@ -146,6 +158,8 @@ func CreateRole(t *testing.T, client *gophercloud.ServiceClient, c *roles.Create t.Logf("Successfully created role %s with ID %s", name, role.ID) + th.AssertEquals(t, role.Name, name) + return role, nil } @@ -173,6 +187,8 @@ func CreateRegion(t *testing.T, client *gophercloud.ServiceClient, c *regions.Cr t.Logf("Successfully created region %s", id) + th.AssertEquals(t, region.ID, id) + return region, nil } @@ -200,6 +216,8 @@ func CreateService(t *testing.T, client *gophercloud.ServiceClient, c *services. t.Logf("Successfully created service %s", service.ID) + th.AssertEquals(t, service.Extra["name"], name) + return service, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/policies_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/policies_test.go new file mode 100644 index 000000000000..3fb22bb22174 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/policies_test.go @@ -0,0 +1,143 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/identity/v3/policies" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestPoliciesList(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + allPages, err := policies.List(client, policies.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err := policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + + for _, policy := range allPolicies { + tools.PrintResource(t, policy) + } +} + +func TestPoliciesCRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + createOpts := policies.CreateOpts{ + Type: "application/json", + Blob: []byte("{'foobar_user': 'role:compute-user'}"), + Extra: map[string]interface{}{ + "description": "policy for foobar_user", + }, + } + + policy, err := policies.Create(client, &createOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, policy) + tools.PrintResource(t, policy.Extra) + + th.AssertEquals(t, policy.Type, createOpts.Type) + th.AssertEquals(t, policy.Blob, string(createOpts.Blob)) + th.AssertEquals(t, policy.Extra["description"], createOpts.Extra["description"]) + + var listOpts policies.ListOpts + + allPages, err := policies.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err := policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, p := range allPolicies { + tools.PrintResource(t, p) + tools.PrintResource(t, p.Extra) + + if p.ID == policy.ID { + found = true + } + } + + th.AssertEquals(t, true, found) + + listOpts.Filters = map[string]string{ + "type__contains": "json", + } + + allPages, err = policies.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err = policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + + found = false + for _, p := range allPolicies { + tools.PrintResource(t, p) + tools.PrintResource(t, p.Extra) + + if p.ID == policy.ID { + found = true + } + } + + th.AssertEquals(t, true, found) + + listOpts.Filters = map[string]string{ + "type__contains": "foobar", + } + + allPages, err = policies.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err = policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + + found = false + for _, p := range allPolicies { + tools.PrintResource(t, p) + tools.PrintResource(t, p.Extra) + + if p.ID == policy.ID { + found = true + } + } + + th.AssertEquals(t, false, found) + + gotPolicy, err := policies.Get(client, policy.ID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, policy, gotPolicy) + + updateOpts := policies.UpdateOpts{ + Type: "text/plain", + Blob: []byte("'foobar_user': 'role:compute-user'"), + Extra: map[string]interface{}{ + "description": "updated policy for foobar_user", + }, + } + + updatedPolicy, err := policies.Update(client, policy.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, updatedPolicy) + tools.PrintResource(t, updatedPolicy.Extra) + + th.AssertEquals(t, updatedPolicy.Type, updateOpts.Type) + th.AssertEquals(t, updatedPolicy.Blob, string(updateOpts.Blob)) + th.AssertEquals(t, updatedPolicy.Extra["description"], updateOpts.Extra["description"]) + + err = policies.Delete(client, policy.ID).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/projects_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/projects_test.go index 08a5cfdad4c8..38848be1ed71 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/projects_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/projects_test.go @@ -8,13 +8,14 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestProjectsList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) var iTrue bool = true listOpts := projects.ListOpts{ @@ -22,35 +23,76 @@ func TestProjectsList(t *testing.T) { } allPages, err := projects.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list projects: %v", err) - } + th.AssertNoErr(t, err) allProjects, err := projects.ExtractProjects(allPages) - if err != nil { - t.Fatalf("Unable to extract projects: %v", err) + th.AssertNoErr(t, err) + + var found bool + for _, project := range allProjects { + tools.PrintResource(t, project) + + if project.Name == "admin" { + found = true + } + } + + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "dmi", } + allPages, err = projects.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allProjects, err = projects.ExtractProjects(allPages) + th.AssertNoErr(t, err) + + found = false for _, project := range allProjects { tools.PrintResource(t, project) + + if project.Name == "admin" { + found = true + } } + + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "foo", + } + + allPages, err = projects.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allProjects, err = projects.ExtractProjects(allPages) + th.AssertNoErr(t, err) + + found = false + for _, project := range allProjects { + tools.PrintResource(t, project) + + if project.Name == "admin" { + found = true + } + } + + th.AssertEquals(t, found, false) } func TestProjectsGet(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := projects.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list projects: %v", err) - } + th.AssertNoErr(t, err) allProjects, err := projects.ExtractProjects(allPages) - if err != nil { - t.Fatalf("Unable to extract projects: %v", err) - } + th.AssertNoErr(t, err) project := allProjects[0] p, err := projects.Get(client, project.ID).Extract() @@ -59,40 +101,42 @@ func TestProjectsGet(t *testing.T) { } tools.PrintResource(t, p) + + th.AssertEquals(t, project.Name, p.Name) } func TestProjectsCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) project, err := CreateProject(t, client, nil) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, project.ID) tools.PrintResource(t, project) - var iFalse bool = false + description := "" + iFalse := false updateOpts := projects.UpdateOpts{ - Enabled: &iFalse, + Description: &description, + Enabled: &iFalse, } updatedProject, err := projects.Update(client, project.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update project: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, updatedProject) + th.AssertEquals(t, updatedProject.Description, description) + th.AssertEquals(t, updatedProject.Enabled, iFalse) } func TestProjectsDomain(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) var iTrue = true createOpts := projects.CreateOpts{ @@ -100,9 +144,7 @@ func TestProjectsDomain(t *testing.T) { } projectDomain, err := CreateProject(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, projectDomain.ID) tools.PrintResource(t, projectDomain) @@ -112,34 +154,30 @@ func TestProjectsDomain(t *testing.T) { } project, err := CreateProject(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, project.ID) tools.PrintResource(t, project) + th.AssertEquals(t, project.DomainID, projectDomain.ID) + var iFalse = false updateOpts := projects.UpdateOpts{ Enabled: &iFalse, } _, err = projects.Update(client, projectDomain.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to disable domain: %v") - } + th.AssertNoErr(t, err) } func TestProjectsNested(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) projectMain, err := CreateProject(t, client, nil) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, projectMain.ID) tools.PrintResource(t, projectMain) @@ -149,10 +187,10 @@ func TestProjectsNested(t *testing.T) { } project, err := CreateProject(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, project.ID) tools.PrintResource(t, project) + + th.AssertEquals(t, project.ParentID, projectMain.ID) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/reauth_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/reauth_test.go new file mode 100644 index 000000000000..27363db04ea7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/reauth_test.go @@ -0,0 +1,34 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack" + th "github.com/gophercloud/gophercloud/testhelper" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" +) + +func TestReauthAuthResultDeadlock(t *testing.T) { + clients.RequireAdmin(t) + + ao, err := openstack.AuthOptionsFromEnv() + th.AssertNoErr(t, err) + + ao.AllowReauth = true + + provider, err := openstack.AuthenticatedClient(ao) + th.AssertNoErr(t, err) + + provider.SetToken("this is not a valid token") + + client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) + pages, err := projects.List(client, nil).AllPages() + th.AssertNoErr(t, err) + _, err = projects.ExtractProjects(pages) + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/regions_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/regions_test.go index f98c23231425..f4a0b9456be3 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/regions_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/regions_test.go @@ -8,27 +8,24 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/regions" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestRegionsList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) listOpts := regions.ListOpts{ ParentRegionID: "RegionOne", } allPages, err := regions.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list regions: %v", err) - } + th.AssertNoErr(t, err) allRegions, err := regions.ExtractRegions(allPages) - if err != nil { - t.Fatalf("Unable to extract regions: %v", err) - } + th.AssertNoErr(t, err) for _, region := range allRegions { tools.PrintResource(t, region) @@ -36,35 +33,31 @@ func TestRegionsList(t *testing.T) { } func TestRegionsGet(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := regions.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list regions: %v", err) - } + th.AssertNoErr(t, err) allRegions, err := regions.ExtractRegions(allPages) - if err != nil { - t.Fatalf("Unable to extract regions: %v", err) - } + th.AssertNoErr(t, err) region := allRegions[0] p, err := regions.Get(client, region.ID).Extract() - if err != nil { - t.Fatalf("Unable to get region: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, p) + + th.AssertEquals(t, region.ID, p.ID) } func TestRegionsCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) createOpts := regions.CreateOpts{ ID: "testregion", @@ -76,16 +69,15 @@ func TestRegionsCRUD(t *testing.T) { // Create region in the default domain region, err := CreateRegion(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create region: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRegion(t, client, region.ID) tools.PrintResource(t, region) tools.PrintResource(t, region.Extra) + var description = "" updateOpts := regions.UpdateOpts{ - Description: "Region A for testing", + Description: &description, /* // Due to a bug in Keystone, the Extra column of the Region table // is not updatable, see: https://bugs.launchpad.net/keystone/+bug/1729933 @@ -98,10 +90,10 @@ func TestRegionsCRUD(t *testing.T) { } newRegion, err := regions.Update(client, region.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update region: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newRegion) tools.PrintResource(t, newRegion.Extra) + + th.AssertEquals(t, newRegion.Description, description) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go index be8e73f4e372..05708dcd575c 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go @@ -9,28 +9,26 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestRolesList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) listOpts := roles.ListOpts{ DomainID: "default", } allPages, err := roles.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list roles: %v", err) - } + th.AssertNoErr(t, err) allRoles, err := roles.ExtractRoles(allPages) - if err != nil { - t.Fatalf("Unable to extract roles: %v", err) - } + th.AssertNoErr(t, err) for _, role := range allRoles { tools.PrintResource(t, role) @@ -38,29 +36,25 @@ func TestRolesList(t *testing.T) { } func TestRolesGet(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) role, err := FindRole(t, client) - if err != nil { - t.Fatalf("Unable to find a role: %v", err) - } + th.AssertNoErr(t, err) p, err := roles.Get(client, role.ID).Extract() - if err != nil { - t.Fatalf("Unable to get role: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, p) } -func TestRoleCRUD(t *testing.T) { +func TestRolesCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) createOpts := roles.CreateOpts{ Name: "testrole", @@ -72,14 +66,33 @@ func TestRoleCRUD(t *testing.T) { // Create Role in the default domain role, err := CreateRole(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create role: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRole(t, client, role.ID) tools.PrintResource(t, role) tools.PrintResource(t, role.Extra) + listOpts := roles.ListOpts{ + DomainID: "default", + } + allPages, err := roles.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, r := range allRoles { + tools.PrintResource(t, r) + tools.PrintResource(t, r.Extra) + + if r.Name == role.Name { + found = true + } + } + + th.AssertEquals(t, found, true) + updateOpts := roles.UpdateOpts{ Extra: map[string]interface{}{ "description": "updated test role description", @@ -87,242 +100,610 @@ func TestRoleCRUD(t *testing.T) { } newRole, err := roles.Update(client, role.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update role: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newRole) tools.PrintResource(t, newRole.Extra) + + th.AssertEquals(t, newRole.Extra["description"], "updated test role description") } -func TestRoleAssignToUserOnProject(t *testing.T) { +func TestRolesFilterList(t *testing.T) { + clients.RequireAdmin(t) + + // For some reason this is not longer working. + // It might be a temporary issue. + clients.SkipRelease(t, "master") + clients.SkipRelease(t, "stable/queens") + clients.SkipRelease(t, "stable/rocky") + clients.SkipRelease(t, "stable/stein") + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an indentity client: %v", err) + th.AssertNoErr(t, err) + + createOpts := roles.CreateOpts{ + Name: "testrole", + DomainID: "default", + Extra: map[string]interface{}{ + "description": "test role description", + }, } - project, err := CreateProject(t, client, nil) - if err != nil { - t.Fatal("Unable to create a project") + // Create Role in the default domain + role, err := CreateRole(t, client, &createOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) + + var listOpts roles.ListOpts + listOpts.Filters = map[string]string{ + "name__contains": "TEST", } + + allPages, err := roles.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + found := false + for _, r := range allRoles { + tools.PrintResource(t, r) + tools.PrintResource(t, r.Extra) + + if r.Name == role.Name { + found = true + } + } + + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "reader", + } + + allPages, err = roles.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err = roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + found = false + for _, r := range allRoles { + tools.PrintResource(t, r) + tools.PrintResource(t, r.Extra) + + if r.Name == role.Name { + found = true + } + } + + th.AssertEquals(t, found, false) +} + +func TestRoleListAssignmentForUserOnProject(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + project, err := CreateProject(t, client, nil) + th.AssertNoErr(t, err) defer DeleteProject(t, client, project.ID) - role, err := FindRole(t, client) - if err != nil { - t.Fatalf("Unable to get a role: %v", err) + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) user, err := CreateUser(t, client, nil) - if err != nil { - t.Fatalf("Unable to create user: %v", err) + th.AssertNoErr(t, err) + defer DeleteUser(t, client, user.ID) + + t.Logf("Attempting to assign a role %s to a user %s on a project %s", + role.Name, user.Name, project.Name) + + assignOpts := roles.AssignOpts{ + UserID: user.ID, + ProjectID: project.ID, + } + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a user %s on a project %s", + role.Name, user.Name, project.Name) + + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + UserID: user.ID, + ProjectID: project.ID, + }) + + listAssignmentsOnResourceOpts := roles.ListAssignmentsOnResourceOpts{ + UserID: user.ID, + ProjectID: project.ID, + } + allPages, err := roles.ListAssignmentsOnResource(client, listAssignmentsOnResourceOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + t.Logf("Role assignments of user %s on project %s:", user.Name, project.Name) + var found bool + for _, _role := range allRoles { + tools.PrintResource(t, _role) + + if _role.ID == role.ID { + found = true + } } + + th.AssertEquals(t, found, true) +} + +func TestRoleListAssignmentForUserOnDomain(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + domain, err := CreateDomain(t, client, &domains.CreateOpts{ + Enabled: gophercloud.Disabled, + }) + th.AssertNoErr(t, err) + defer DeleteDomain(t, client, domain.ID) + + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", + } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) + + user, err := CreateUser(t, client, nil) + th.AssertNoErr(t, err) defer DeleteUser(t, client, user.ID) - t.Logf("Attempting to assign a role %s to a user %s on a project %s", role.Name, user.Name, project.Name) - err = roles.Assign(client, role.ID, roles.AssignOpts{ + t.Logf("Attempting to assign a role %s to a user %s on a domain %s", + role.Name, user.Name, domain.Name) + + assignOpts := roles.AssignOpts{ + UserID: user.ID, + DomainID: domain.ID, + } + + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a user %s on a domain %s", + role.Name, user.Name, domain.Name) + + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + UserID: user.ID, + DomainID: domain.ID, + }) + + listAssignmentsOnResourceOpts := roles.ListAssignmentsOnResourceOpts{ + UserID: user.ID, + DomainID: domain.ID, + } + allPages, err := roles.ListAssignmentsOnResource(client, listAssignmentsOnResourceOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + t.Logf("Role assignments of user %s on domain %s:", user.Name, domain.Name) + var found bool + for _, _role := range allRoles { + tools.PrintResource(t, _role) + + if _role.ID == role.ID { + found = true + } + } + + th.AssertEquals(t, found, true) +} + +func TestRoleListAssignmentForGroupOnProject(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + project, err := CreateProject(t, client, nil) + th.AssertNoErr(t, err) + defer DeleteProject(t, client, project.ID) + + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", + } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) + + groupCreateOpts := &groups.CreateOpts{ + DomainID: "default", + } + group, err := CreateGroup(t, client, groupCreateOpts) + th.AssertNoErr(t, err) + defer DeleteGroup(t, client, group.ID) + + t.Logf("Attempting to assign a role %s to a group %s on a project %s", + role.Name, group.Name, project.Name) + + assignOpts := roles.AssignOpts{ + GroupID: group.ID, + ProjectID: project.ID, + } + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a group %s on a project %s", + role.Name, group.Name, project.Name) + + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + GroupID: group.ID, + ProjectID: project.ID, + }) + + listAssignmentsOnResourceOpts := roles.ListAssignmentsOnResourceOpts{ + GroupID: group.ID, + ProjectID: project.ID, + } + allPages, err := roles.ListAssignmentsOnResource(client, listAssignmentsOnResourceOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + t.Logf("Role assignments of group %s on project %s:", group.Name, project.Name) + var found bool + for _, _role := range allRoles { + tools.PrintResource(t, _role) + + if _role.ID == role.ID { + found = true + } + } + + th.AssertEquals(t, found, true) +} + +func TestRoleListAssignmentForGroupOnDomain(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + domain, err := CreateDomain(t, client, &domains.CreateOpts{ + Enabled: gophercloud.Disabled, + }) + th.AssertNoErr(t, err) + defer DeleteDomain(t, client, domain.ID) + + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", + } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) + + groupCreateOpts := &groups.CreateOpts{ + DomainID: "default", + } + group, err := CreateGroup(t, client, groupCreateOpts) + th.AssertNoErr(t, err) + defer DeleteGroup(t, client, group.ID) + + t.Logf("Attempting to assign a role %s to a group %s on a domain %s", + role.Name, group.Name, domain.Name) + + assignOpts := roles.AssignOpts{ + GroupID: group.ID, + DomainID: domain.ID, + } + + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a group %s on a domain %s", + role.Name, group.Name, domain.Name) + + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + GroupID: group.ID, + DomainID: domain.ID, + }) + + listAssignmentsOnResourceOpts := roles.ListAssignmentsOnResourceOpts{ + GroupID: group.ID, + DomainID: domain.ID, + } + allPages, err := roles.ListAssignmentsOnResource(client, listAssignmentsOnResourceOpts).AllPages() + th.AssertNoErr(t, err) + + allRoles, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + + t.Logf("Role assignments of group %s on domain %s:", group.Name, domain.Name) + var found bool + for _, _role := range allRoles { + tools.PrintResource(t, _role) + + if _role.ID == role.ID { + found = true + } + } + + th.AssertEquals(t, found, true) +} + +func TestRolesAssignToUserOnProject(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + project, err := CreateProject(t, client, nil) + th.AssertNoErr(t, err) + defer DeleteProject(t, client, project.ID) + + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", + } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) + + user, err := CreateUser(t, client, nil) + th.AssertNoErr(t, err) + defer DeleteUser(t, client, user.ID) + + t.Logf("Attempting to assign a role %s to a user %s on a project %s", + role.Name, user.Name, project.Name) + + assignOpts := roles.AssignOpts{ UserID: user.ID, ProjectID: project.ID, - }).ExtractErr() - if err != nil { - t.Fatalf("Unable to assign a role to a user on a project: %v", err) } - t.Logf("Successfully assigned a role %s to a user %s on a project %s", role.Name, user.Name, project.Name) + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a user %s on a project %s", + role.Name, user.Name, project.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ UserID: user.ID, ProjectID: project.ID, }) - allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + lao := roles.ListAssignmentsOpts{ RoleID: role.ID, ScopeProjectID: project.ID, UserID: user.ID, - }).AllPages() - if err != nil { - t.Fatalf("Unable to list role assignments: %v", err) } + allPages, err := roles.ListAssignments(client, lao).AllPages() + th.AssertNoErr(t, err) + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) - if err != nil { - t.Fatalf("Unable to extract role assignments: %v", err) - } + th.AssertNoErr(t, err) t.Logf("Role assignments of user %s on project %s:", user.Name, project.Name) + var found bool for _, roleAssignment := range allRoleAssignments { tools.PrintResource(t, roleAssignment) + + if roleAssignment.Role.ID == role.ID { + found = true + } } + + th.AssertEquals(t, found, true) } -func TestRoleAssignToUserOnDomain(t *testing.T) { +func TestRolesAssignToUserOnDomain(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an indentity client: %v", err) - } + th.AssertNoErr(t, err) domain, err := CreateDomain(t, client, &domains.CreateOpts{ Enabled: gophercloud.Disabled, }) - if err != nil { - t.Fatal("Unable to create a domain") - } + th.AssertNoErr(t, err) defer DeleteDomain(t, client, domain.ID) - role, err := FindRole(t, client) - if err != nil { - t.Fatalf("Unable to get a role: %v", err) + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) user, err := CreateUser(t, client, nil) - if err != nil { - t.Fatalf("Unable to create user: %v", err) - } + th.AssertNoErr(t, err) defer DeleteUser(t, client, user.ID) - t.Logf("Attempting to assign a role %s to a user %s on a domain %s", role.Name, user.Name, domain.Name) - err = roles.Assign(client, role.ID, roles.AssignOpts{ + t.Logf("Attempting to assign a role %s to a user %s on a domain %s", + role.Name, user.Name, domain.Name) + + assignOpts := roles.AssignOpts{ UserID: user.ID, DomainID: domain.ID, - }).ExtractErr() - if err != nil { - t.Fatalf("Unable to assign a role to a user on a domain: %v", err) } - t.Logf("Successfully assigned a role %s to a user %s on a domain %s", role.Name, user.Name, domain.Name) + + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a user %s on a domain %s", + role.Name, user.Name, domain.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ UserID: user.ID, DomainID: domain.ID, }) - allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + lao := roles.ListAssignmentsOpts{ RoleID: role.ID, ScopeDomainID: domain.ID, UserID: user.ID, - }).AllPages() - if err != nil { - t.Fatalf("Unable to list role assignments: %v", err) } + allPages, err := roles.ListAssignments(client, lao).AllPages() + th.AssertNoErr(t, err) + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) - if err != nil { - t.Fatalf("Unable to extract role assignments: %v", err) - } + th.AssertNoErr(t, err) t.Logf("Role assignments of user %s on domain %s:", user.Name, domain.Name) + var found bool for _, roleAssignment := range allRoleAssignments { tools.PrintResource(t, roleAssignment) + + if roleAssignment.Role.ID == role.ID { + found = true + } } + + th.AssertEquals(t, found, true) } -func TestRoleAssignToGroupOnDomain(t *testing.T) { +func TestRolesAssignToGroupOnDomain(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an indentity client: %v", err) - } + th.AssertNoErr(t, err) domain, err := CreateDomain(t, client, &domains.CreateOpts{ Enabled: gophercloud.Disabled, }) - if err != nil { - t.Fatal("Unable to create a domain") - } + th.AssertNoErr(t, err) defer DeleteDomain(t, client, domain.ID) - role, err := FindRole(t, client) - if err != nil { - t.Fatalf("Unable to get a role: %v", err) + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) - group, err := CreateGroup(t, client, nil) - if err != nil { - t.Fatalf("Unable to create group: %v", err) + groupCreateOpts := &groups.CreateOpts{ + DomainID: "default", } + group, err := CreateGroup(t, client, groupCreateOpts) + th.AssertNoErr(t, err) defer DeleteGroup(t, client, group.ID) - t.Logf("Attempting to assign a role %s to a group %s on a domain %s", role.Name, group.Name, domain.Name) - err = roles.Assign(client, role.ID, roles.AssignOpts{ + t.Logf("Attempting to assign a role %s to a group %s on a domain %s", + role.Name, group.Name, domain.Name) + + assignOpts := roles.AssignOpts{ GroupID: group.ID, DomainID: domain.ID, - }).ExtractErr() - if err != nil { - t.Fatalf("Unable to assign a role to a group on a domain: %v", err) } - t.Logf("Successfully assigned a role %s to a group %s on a domain %s", role.Name, group.Name, domain.Name) + + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a group %s on a domain %s", + role.Name, group.Name, domain.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ GroupID: group.ID, DomainID: domain.ID, }) - allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + lao := roles.ListAssignmentsOpts{ RoleID: role.ID, ScopeDomainID: domain.ID, GroupID: group.ID, - }).AllPages() - if err != nil { - t.Fatalf("Unable to list role assignments: %v", err) } + allPages, err := roles.ListAssignments(client, lao).AllPages() + th.AssertNoErr(t, err) + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) - if err != nil { - t.Fatalf("Unable to extract role assignments: %v", err) - } + th.AssertNoErr(t, err) t.Logf("Role assignments of group %s on domain %s:", group.Name, domain.Name) + var found bool for _, roleAssignment := range allRoleAssignments { tools.PrintResource(t, roleAssignment) + + if roleAssignment.Role.ID == role.ID { + found = true + } } + + th.AssertEquals(t, found, true) } -func TestRoleAssignToGroupOnProject(t *testing.T) { +func TestRolesAssignToGroupOnProject(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an indentity client: %v", err) - } + th.AssertNoErr(t, err) project, err := CreateProject(t, client, nil) - if err != nil { - t.Fatal("Unable to create a project") - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, project.ID) - role, err := FindRole(t, client) - if err != nil { - t.Fatalf("Unable to get a role: %v", err) + roleCreateOpts := roles.CreateOpts{ + DomainID: "default", } + role, err := CreateRole(t, client, &roleCreateOpts) + th.AssertNoErr(t, err) + defer DeleteRole(t, client, role.ID) - group, err := CreateGroup(t, client, nil) - if err != nil { - t.Fatalf("Unable to create group: %v", err) + groupCreateOpts := &groups.CreateOpts{ + DomainID: "default", } + group, err := CreateGroup(t, client, groupCreateOpts) + th.AssertNoErr(t, err) defer DeleteGroup(t, client, group.ID) - t.Logf("Attempting to assign a role %s to a group %s on a project %s", role.Name, group.Name, project.Name) - err = roles.Assign(client, role.ID, roles.AssignOpts{ + t.Logf("Attempting to assign a role %s to a group %s on a project %s", + role.Name, group.Name, project.Name) + + assignOpts := roles.AssignOpts{ GroupID: group.ID, ProjectID: project.ID, - }).ExtractErr() - if err != nil { - t.Fatalf("Unable to assign a role to a group on a project: %v", err) } - t.Logf("Successfully assigned a role %s to a group %s on a project %s", role.Name, group.Name, project.Name) + err = roles.Assign(client, role.ID, assignOpts).ExtractErr() + th.AssertNoErr(t, err) + + t.Logf("Successfully assigned a role %s to a group %s on a project %s", + role.Name, group.Name, project.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ GroupID: group.ID, ProjectID: project.ID, }) - allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + lao := roles.ListAssignmentsOpts{ RoleID: role.ID, ScopeProjectID: project.ID, GroupID: group.ID, - }).AllPages() - if err != nil { - t.Fatalf("Unable to list role assignments: %v", err) } + allPages, err := roles.ListAssignments(client, lao).AllPages() + th.AssertNoErr(t, err) + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) - if err != nil { - t.Fatalf("Unable to extract role assignments: %v", err) - } + th.AssertNoErr(t, err) t.Logf("Role assignments of group %s on project %s:", group.Name, project.Name) + var found bool for _, roleAssignment := range allRoleAssignments { tools.PrintResource(t, roleAssignment) + + if roleAssignment.Role.ID == role.ID { + found = true + } } + + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/service_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/service_test.go index ffd7e4d1fb5c..7e072ce3a43e 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/service_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/service_test.go @@ -8,39 +8,42 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/services" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestServicesList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) listOpts := services.ListOpts{ ServiceType: "identity", } allPages, err := services.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list services: %v", err) - } + th.AssertNoErr(t, err) allServices, err := services.ExtractServices(allPages) - if err != nil { - t.Fatalf("Unable to extract services: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, service := range allServices { tools.PrintResource(t, service) + + if service.Type == "identity" { + found = true + } } + th.AssertEquals(t, found, true) } func TestServicesCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) createOpts := services.CreateOpts{ Type: "testing", @@ -51,9 +54,7 @@ func TestServicesCRUD(t *testing.T) { // Create service in the default domain service, err := CreateService(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create service: %v", err) - } + th.AssertNoErr(t, err) defer DeleteService(t, client, service.ID) tools.PrintResource(t, service) @@ -68,10 +69,10 @@ func TestServicesCRUD(t *testing.T) { } newService, err := services.Update(client, service.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update service: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newService) tools.PrintResource(t, newService.Extra) + + th.AssertEquals(t, newService.Extra["description"], "Test Users") } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/token_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/token_test.go index 0f471f776b27..ff6e91d49f2a 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/token_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/token_test.go @@ -9,18 +9,17 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestGetToken(t *testing.T) { +func TestTokensGet(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) ao, err := openstack.AuthOptionsFromEnv() - if err != nil { - t.Fatalf("Unable to obtain environment auth options: %v", err) - } + th.AssertNoErr(t, err) authOptions := tokens.AuthOptions{ Username: ao.Username, @@ -29,32 +28,22 @@ func TestGetToken(t *testing.T) { } token, err := tokens.Create(client, &authOptions).Extract() - if err != nil { - t.Fatalf("Unable to get token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, token) catalog, err := tokens.Get(client, token.ID).ExtractServiceCatalog() - if err != nil { - t.Fatalf("Unable to get catalog from token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, catalog) user, err := tokens.Get(client, token.ID).ExtractUser() - if err != nil { - t.Fatalf("Unable to get user from token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, user) roles, err := tokens.Get(client, token.ID).ExtractRoles() - if err != nil { - t.Fatalf("Unable to get roles from token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, roles) project, err := tokens.Get(client, token.ID).ExtractProject() - if err != nil { - t.Fatalf("Unable to get project from token: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, project) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go index 3ba1e87cf57b..2e283b5ec0c3 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go @@ -10,13 +10,14 @@ import ( "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/openstack/identity/v3/users" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestUsersList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) var iTrue bool = true listOpts := users.ListOpts{ @@ -24,62 +25,104 @@ func TestUsersList(t *testing.T) { } allPages, err := users.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list users: %v", err) - } + th.AssertNoErr(t, err) allUsers, err := users.ExtractUsers(allPages) - if err != nil { - t.Fatalf("Unable to extract users: %v", err) + th.AssertNoErr(t, err) + + var found bool + for _, user := range allUsers { + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + if user.Name == "admin" { + found = true + } } + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "dmi", + } + + allPages, err = users.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allUsers, err = users.ExtractUsers(allPages) + th.AssertNoErr(t, err) + + found = false for _, user := range allUsers { tools.PrintResource(t, user) tools.PrintResource(t, user.Extra) + + if user.Name == "admin" { + found = true + } } + + th.AssertEquals(t, found, true) + + listOpts.Filters = map[string]string{ + "name__contains": "foo", + } + + allPages, err = users.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allUsers, err = users.ExtractUsers(allPages) + th.AssertNoErr(t, err) + + found = false + for _, user := range allUsers { + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + if user.Name == "admin" { + found = true + } + } + + th.AssertEquals(t, found, false) } func TestUsersGet(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := users.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list users: %v", err) - } + th.AssertNoErr(t, err) allUsers, err := users.ExtractUsers(allPages) - if err != nil { - t.Fatalf("Unable to extract users: %v", err) - } + th.AssertNoErr(t, err) user := allUsers[0] p, err := users.Get(client, user.ID).Extract() - if err != nil { - t.Fatalf("Unable to get user: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, p) + + th.AssertEquals(t, user.Name, p.Name) } func TestUserCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } + th.AssertNoErr(t, err) project, err := CreateProject(t, client, nil) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer DeleteProject(t, client, project.ID) tools.PrintResource(t, project) createOpts := users.CreateOpts{ DefaultProjectID: project.ID, + Description: "test description", Password: "foobar", DomainID: "default", Options: map[users.Option]interface{}{ @@ -95,17 +138,22 @@ func TestUserCRUD(t *testing.T) { } user, err := CreateUser(t, client, &createOpts) - if err != nil { - t.Fatalf("Unable to create user: %v", err) - } + th.AssertNoErr(t, err) defer DeleteUser(t, client, user.ID) tools.PrintResource(t, user) tools.PrintResource(t, user.Extra) + th.AssertEquals(t, user.Description, createOpts.Description) + th.AssertEquals(t, user.DomainID, createOpts.DomainID) + iFalse := false + name := "newtestuser" + description := "" updateOpts := users.UpdateOpts{ - Enabled: &iFalse, + Name: name, + Description: &description, + Enabled: &iFalse, Options: map[users.Option]interface{}{ users.MultiFactorAuthRules: nil, }, @@ -115,108 +163,183 @@ func TestUserCRUD(t *testing.T) { } newUser, err := users.Update(client, user.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update user: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newUser) tools.PrintResource(t, newUser.Extra) + + th.AssertEquals(t, newUser.Name, name) + th.AssertEquals(t, newUser.Description, description) + th.AssertEquals(t, newUser.Enabled, iFalse) + th.AssertEquals(t, newUser.Extra["disabled_reason"], "DDOS") } -func TestUsersListGroups(t *testing.T) { +func TestUserChangePassword(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) + th.AssertNoErr(t, err) + + createOpts := users.CreateOpts{ + Password: "secretsecret", + DomainID: "default", } - allUserPages, err := users.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list users: %v", err) + + user, err := CreateUser(t, client, &createOpts) + th.AssertNoErr(t, err) + defer DeleteUser(t, client, user.ID) + + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + changePasswordOpts := users.ChangePasswordOpts{ + OriginalPassword: "secretsecret", + Password: "new_secretsecret", } + err = users.ChangePassword(client, user.ID, changePasswordOpts).ExtractErr() + th.AssertNoErr(t, err) +} - allUsers, err := users.ExtractUsers(allUserPages) - if err != nil { - t.Fatalf("Unable to extract users: %v", err) +func TestUsersGroups(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + createOpts := users.CreateOpts{ + Password: "foobar", + DomainID: "default", } - user := allUsers[0] + user, err := CreateUser(t, client, &createOpts) + th.AssertNoErr(t, err) + defer DeleteUser(t, client, user.ID) - allGroupPages, err := users.ListGroups(client, user.ID).AllPages() - if err != nil { - t.Fatalf("Unable to list groups: %v", err) + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + createGroupOpts := groups.CreateOpts{ + Name: "testgroup", + DomainID: "default", } + // Create Group in the default domain + group, err := CreateGroup(t, client, &createGroupOpts) + th.AssertNoErr(t, err) + defer DeleteGroup(t, client, group.ID) + + tools.PrintResource(t, group) + tools.PrintResource(t, group.Extra) + + err = users.AddToGroup(client, group.ID, user.ID).ExtractErr() + th.AssertNoErr(t, err) + + allGroupPages, err := users.ListGroups(client, user.ID).AllPages() + th.AssertNoErr(t, err) + allGroups, err := groups.ExtractGroups(allGroupPages) - if err != nil { - t.Fatalf("Unable to extract groups: %v", err) - } + th.AssertNoErr(t, err) - for _, group := range allGroups { - tools.PrintResource(t, group) - tools.PrintResource(t, group.Extra) - } -} + var found bool + for _, g := range allGroups { + tools.PrintResource(t, g) + tools.PrintResource(t, g.Extra) -func TestUsersListProjects(t *testing.T) { - client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } - allUserPages, err := users.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list users: %v", err) + if g.ID == group.ID { + found = true + } } + th.AssertEquals(t, found, true) + + found = false + allUserPages, err := users.ListInGroup(client, group.ID, nil).AllPages() + th.AssertNoErr(t, err) + allUsers, err := users.ExtractUsers(allUserPages) - if err != nil { - t.Fatalf("Unable to extract users: %v", err) + th.AssertNoErr(t, err) + + for _, u := range allUsers { + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + if u.ID == user.ID { + found = true + } } - user := allUsers[0] + th.AssertEquals(t, found, true) - allProjectPages, err := users.ListProjects(client, user.ID).AllPages() + ok, err := users.IsMemberOfGroup(client, group.ID, user.ID).Extract() if err != nil { - t.Fatalf("Unable to list projects: %v", err) + t.Fatalf("Unable to check whether user belongs to group: %v", err) + } + if !ok { + t.Fatalf("User %s is expected to be a member of group %s", user.ID, group.ID) } - allProjects, err := projects.ExtractProjects(allProjectPages) - if err != nil { - t.Fatalf("Unable to extract projects: %v", err) + err = users.RemoveFromGroup(client, group.ID, user.ID).ExtractErr() + th.AssertNoErr(t, err) + + allGroupPages, err = users.ListGroups(client, user.ID).AllPages() + th.AssertNoErr(t, err) + + allGroups, err = groups.ExtractGroups(allGroupPages) + th.AssertNoErr(t, err) + + found = false + for _, g := range allGroups { + tools.PrintResource(t, g) + tools.PrintResource(t, g.Extra) + + if g.ID == group.ID { + found = true + } } - for _, project := range allProjects { - tools.PrintResource(t, project) + th.AssertEquals(t, found, false) + + found = false + allUserPages, err = users.ListInGroup(client, group.ID, nil).AllPages() + th.AssertNoErr(t, err) + + allUsers, err = users.ExtractUsers(allUserPages) + th.AssertNoErr(t, err) + + for _, u := range allUsers { + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + + if u.ID == user.ID { + found = true + } } + + th.AssertEquals(t, found, false) + } -func TestUsersListInGroup(t *testing.T) { +func TestUsersListProjects(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v", err) - } - allGroupPages, err := groups.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list groups: %v", err) - } + th.AssertNoErr(t, err) - allGroups, err := groups.ExtractGroups(allGroupPages) - if err != nil { - t.Fatalf("Unable to extract groups: %v", err) - } + allUserPages, err := users.List(client, nil).AllPages() + th.AssertNoErr(t, err) - group := allGroups[0] + allUsers, err := users.ExtractUsers(allUserPages) + th.AssertNoErr(t, err) - allUserPages, err := users.ListInGroup(client, group.ID, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list users: %v", err) - } + user := allUsers[0] - allUsers, err := users.ExtractUsers(allUserPages) - if err != nil { - t.Fatalf("Unable to extract users: %v", err) - } + allProjectPages, err := users.ListProjects(client, user.ID).AllPages() + th.AssertNoErr(t, err) - for _, user := range allUsers { - tools.PrintResource(t, user) - tools.PrintResource(t, user.Extra) + allProjects, err := projects.ExtractProjects(allProjectPages) + th.AssertNoErr(t, err) + + for _, project := range allProjects { + tools.PrintResource(t, project) } } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imagedata_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imagedata_test.go new file mode 100644 index 000000000000..d19c38d4a9e6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imagedata_test.go @@ -0,0 +1,29 @@ +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestImageStage(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + image, err := CreateEmptyImage(t, client) + th.AssertNoErr(t, err) + defer DeleteImage(t, client, image) + + imageFileName := tools.RandomString("image_", 8) + imageFilepath := "/tmp/" + imageFileName + imageURL := ImportImageURL + + err = DownloadImageFileFromURL(t, imageURL, imageFilepath) + th.AssertNoErr(t, err) + defer DeleteImageFile(t, imageFilepath) + + err = StageImage(t, client, imageFilepath, image.ID) + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageimport_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageimport_test.go new file mode 100644 index 000000000000..e3f95d6c9a2c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageimport_test.go @@ -0,0 +1,33 @@ +// +build acceptance imageservice imageimport + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestGetImportInfo(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + importInfo, err := GetImportInfo(t, client) + th.AssertNoErr(t, err) + + tools.PrintResource(t, importInfo) +} + +func TestCreateImport(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + image, err := CreateEmptyImage(t, client) + th.AssertNoErr(t, err) + defer DeleteImage(t, client, image) + + err = ImportImage(t, client, image.ID) + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/images_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/images_test.go index 04926109f679..17069c3dc688 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/images_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/images_test.go @@ -10,13 +10,12 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestImagesListEachPage(t *testing.T) { client, err := clients.NewImageServiceV2Client() - if err != nil { - t.Fatalf("Unable to create an image service client: %v", err) - } + th.AssertNoErr(t, err) listOpts := images.ListOpts{ Limit: 1, @@ -40,69 +39,54 @@ func TestImagesListEachPage(t *testing.T) { func TestImagesListAllPages(t *testing.T) { client, err := clients.NewImageServiceV2Client() - if err != nil { - t.Fatalf("Unable to create an image service client: %v", err) - } + th.AssertNoErr(t, err) - listOpts := images.ListOpts{ - Limit: 1, - } + image, err := CreateEmptyImage(t, client) + th.AssertNoErr(t, err) + defer DeleteImage(t, client, image) + + listOpts := images.ListOpts{} allPages, err := images.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve all images: %v", err) - } + th.AssertNoErr(t, err) allImages, err := images.ExtractImages(allPages) - if err != nil { - t.Fatalf("Unable to extract images: %v", err) - } - - for _, image := range allImages { - tools.PrintResource(t, image) - tools.PrintResource(t, image.Properties) - } -} + th.AssertNoErr(t, err) -func TestImagesCreateDestroyEmptyImage(t *testing.T) { - client, err := clients.NewImageServiceV2Client() - if err != nil { - t.Fatalf("Unable to create an image service client: %v", err) - } + var found bool + for _, i := range allImages { + tools.PrintResource(t, i) + tools.PrintResource(t, i.Properties) - image, err := CreateEmptyImage(t, client) - if err != nil { - t.Fatalf("Unable to create empty image: %v", err) + if i.Name == image.Name { + found = true + } } - defer DeleteImage(t, client, image) - - tools.PrintResource(t, image) + th.AssertEquals(t, found, true) } func TestImagesListByDate(t *testing.T) { client, err := clients.NewImageServiceV2Client() - if err != nil { - t.Fatalf("Unable to create an image service client: %v", err) - } + th.AssertNoErr(t, err) date := time.Date(2014, 1, 1, 1, 1, 1, 0, time.UTC) listOpts := images.ListOpts{ Limit: 1, - CreatedAt: &images.ImageDateQuery{ + CreatedAtQuery: &images.ImageDateQuery{ Date: date, Filter: images.FilterGTE, }, } allPages, err := images.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve all images: %v", err) - } + th.AssertNoErr(t, err) allImages, err := images.ExtractImages(allPages) - if err != nil { - t.Fatalf("Unable to extract images: %v", err) + th.AssertNoErr(t, err) + + if len(allImages) == 0 { + t.Fatalf("Query resulted in no results") } for _, image := range allImages { @@ -113,21 +97,17 @@ func TestImagesListByDate(t *testing.T) { date = time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) listOpts = images.ListOpts{ Limit: 1, - CreatedAt: &images.ImageDateQuery{ + CreatedAtQuery: &images.ImageDateQuery{ Date: date, Filter: images.FilterGTE, }, } allPages, err = images.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve all images: %v", err) - } + th.AssertNoErr(t, err) allImages, err = images.ExtractImages(allPages) - if err != nil { - t.Fatalf("Unable to extract images: %v", err) - } + th.AssertNoErr(t, err) if len(allImages) > 0 { t.Fatalf("Expected 0 images, got %d", len(allImages)) @@ -136,15 +116,10 @@ func TestImagesListByDate(t *testing.T) { func TestImagesFilter(t *testing.T) { client, err := clients.NewImageServiceV2Client() - if err != nil { - t.Fatalf("Unable to create an image service client: %v", err) - } + th.AssertNoErr(t, err) image, err := CreateEmptyImage(t, client) - if err != nil { - t.Fatalf("Unable to create empty image: %v", err) - } - + th.AssertNoErr(t, err) defer DeleteImage(t, client, image) listOpts := images.ListOpts{ @@ -154,16 +129,55 @@ func TestImagesFilter(t *testing.T) { } allPages, err := images.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to retrieve all images: %v", err) - } + th.AssertNoErr(t, err) allImages, err := images.ExtractImages(allPages) - if err != nil { - t.Fatalf("Unable to extract images: %v", err) - } + th.AssertNoErr(t, err) if len(allImages) == 0 { t.Fatalf("Query resulted in no results") } } + +func TestImagesUpdate(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + image, err := CreateEmptyImage(t, client) + th.AssertNoErr(t, err) + defer DeleteImage(t, client, image) + + newTags := []string{"foo", "bar"} + + updateOpts := images.UpdateOpts{ + images.ReplaceImageName{NewName: image.Name + "foo"}, + images.ReplaceImageTags{NewTags: newTags}, + images.ReplaceImageMinDisk{NewMinDisk: 21}, + images.UpdateImageProperty{ + Op: images.AddOp, + Name: "hw_disk_bus", + Value: "scsi", + }, + images.UpdateImageProperty{ + Op: images.RemoveOp, + Name: "architecture", + }, + } + + newImage, err := images.Update(client, image.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newImage) + tools.PrintResource(t, newImage.Properties) + + th.AssertEquals(t, newImage.Name, image.Name+"foo") + th.AssertDeepEquals(t, newImage.Tags, newTags) + + // Because OpenStack is now adding additional properties automatically, + // it's not possible to do an easy AssertDeepEquals. + th.AssertEquals(t, newImage.Properties["hw_disk_bus"], "scsi") + + if _, ok := newImage.Properties["architecture"]; ok { + t.Fatal("architecture property still exists") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageservice.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageservice.go index 18af05ab3b3d..fd55741aa696 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageservice.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/imageservice.go @@ -3,11 +3,18 @@ package v2 import ( + "io" + "net/http" + "os" "testing" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateEmptyImage will create an image, but with no actual image data. @@ -39,8 +46,16 @@ func CreateEmptyImage(t *testing.T, client *gophercloud.ServiceClient) (*images. return image, err } - t.Logf("Created image %s: %#v", name, image) - return image, nil + newImage, err := images.Get(client, image.ID).Extract() + if err != nil { + return image, err + } + + t.Logf("Created image %s: %#v", name, newImage) + + th.CheckEquals(t, newImage.Name, name) + th.CheckEquals(t, newImage.Properties["architecture"], "x86_64") + return newImage, nil } // DeleteImage deletes an image. @@ -54,3 +69,102 @@ func DeleteImage(t *testing.T, client *gophercloud.ServiceClient, image *images. t.Logf("Deleted image: %s", image.ID) } + +// ImportImageURL contains an URL of a test image that can be imported. +const ImportImageURL = "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + +// CreateTask will create a task to import the CirrOS image. +// An error will be returned if a task couldn't be created. +func CreateTask(t *testing.T, client *gophercloud.ServiceClient, imageURL string) (*tasks.Task, error) { + t.Logf("Attempting to create an Imageservice import task with image: %s", imageURL) + opts := tasks.CreateOpts{ + Type: "import", + Input: map[string]interface{}{ + "image_properties": map[string]interface{}{ + "container_format": "bare", + "disk_format": "raw", + }, + "import_from_format": "raw", + "import_from": imageURL, + }, + } + task, err := tasks.Create(client, opts).Extract() + if err != nil { + return nil, err + } + + newTask, err := tasks.Get(client, task.ID).Extract() + if err != nil { + return nil, err + } + + return newTask, nil +} + +// GetImportInfo will retrieve Import API information. +func GetImportInfo(t *testing.T, client *gophercloud.ServiceClient) (*imageimport.ImportInfo, error) { + t.Log("Attempting to get the Imageservice Import API information") + importInfo, err := imageimport.Get(client).Extract() + if err != nil { + return nil, err + } + + return importInfo, nil +} + +// StageImage will stage local image file to the referenced remote queued image. +func StageImage(t *testing.T, client *gophercloud.ServiceClient, filepath, imageID string) error { + imageData, err := os.Open(filepath) + if err != nil { + return err + } + defer imageData.Close() + + return imagedata.Stage(client, imageID, imageData).ExtractErr() +} + +// DownloadImageFileFromURL will download an image from the specified URL and +// place it into the specified path. +func DownloadImageFileFromURL(t *testing.T, url, filepath string) error { + file, err := os.Create(filepath) + if err != nil { + return err + } + defer file.Close() + + t.Logf("Attempting to download image from %s", url) + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + size, err := io.Copy(file, resp.Body) + if err != nil { + return err + } + + t.Logf("Downloaded image with size of %d bytes in %s", size, filepath) + return nil +} + +// DeleteImageFile will delete local image file. +func DeleteImageFile(t *testing.T, filepath string) { + err := os.Remove(filepath) + if err != nil { + t.Fatalf("Unable to delete image file %s", filepath) + } + + t.Logf("Successfully deleted image file %s", filepath) +} + +// ImportImage will import image data from the remote source to the Imageservice. +func ImportImage(t *testing.T, client *gophercloud.ServiceClient, imageID string) error { + importOpts := imageimport.CreateOpts{ + Name: imageimport.WebDownloadMethod, + URI: ImportImageURL, + } + + t.Logf("Attempting to import image data for %s from %s", imageID, importOpts.URI) + return imageimport.Create(client, imageID, importOpts).ExtractErr() +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/tasks_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/tasks_test.go new file mode 100644 index 000000000000..1e0fbf3f2d72 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/imageservice/v2/tasks_test.go @@ -0,0 +1,63 @@ +// +build acceptance imageservice tasks + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestTasksListEachPage(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + listOpts := tasks.ListOpts{ + Limit: 1, + } + + pager := tasks.List(client, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + tasks, err := tasks.ExtractTasks(page) + th.AssertNoErr(t, err) + + for _, task := range tasks { + tools.PrintResource(t, task) + } + + return true, nil + }) +} + +func TestTasksListAllPages(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + listOpts := tasks.ListOpts{} + + allPages, err := tasks.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allTasks, err := tasks.ExtractTasks(allPages) + th.AssertNoErr(t, err) + + for _, i := range allTasks { + tools.PrintResource(t, i) + } +} + +func TestTaskCreate(t *testing.T) { + client, err := clients.NewImageServiceV2Client() + th.AssertNoErr(t, err) + + task, err := CreateTask(t, client, ImportImageURL) + if err != nil { + t.Fatalf("Unable to create an Imageservice task: %v", err) + } + + tools.PrintResource(t, task) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/acls_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/acls_test.go new file mode 100644 index 000000000000..54a123a2b231 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/acls_test.go @@ -0,0 +1,103 @@ +// +build acceptance keymanager acls + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestACLCRUD(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + payload := tools.RandomString("SUPERSECRET-", 8) + secret, err := CreateSecretWithPayload(t, client, payload) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + user := tools.RandomString("", 32) + users := []string{user} + iFalse := false + setOpts := acls.SetOpts{ + Type: "read", + Users: &users, + ProjectAccess: &iFalse, + } + + aclRef, err := acls.SetSecretACL(client, secretID, setOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, aclRef) + defer func() { + err := acls.DeleteSecretACL(client, secretID).ExtractErr() + th.AssertNoErr(t, err) + acl, err := acls.GetSecretACL(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, acl) + }() + + acl, err := acls.GetSecretACL(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, acl) + tools.PrintResource(t, (*acl)["read"].Created) + th.AssertEquals(t, len((*acl)["read"].Users), 1) + th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + + newUsers := []string{} + updateOpts := acls.SetOpts{ + Type: "read", + Users: &newUsers, + } + + aclRef, err = acls.UpdateSecretACL(client, secretID, updateOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, aclRef) + + acl, err = acls.GetSecretACL(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, acl) + tools.PrintResource(t, (*acl)["read"].Created) + th.AssertEquals(t, len((*acl)["read"].Users), 0) + th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + + container, err := CreateGenericContainer(t, client, secret) + th.AssertNoErr(t, err) + containerID, err := ParseID(container.ContainerRef) + th.AssertNoErr(t, err) + defer DeleteContainer(t, client, containerID) + + aclRef, err = acls.SetContainerACL(client, containerID, setOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, aclRef) + defer func() { + err := acls.DeleteContainerACL(client, containerID).ExtractErr() + th.AssertNoErr(t, err) + acl, err := acls.GetContainerACL(client, containerID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, acl) + }() + + acl, err = acls.GetContainerACL(client, containerID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, acl) + tools.PrintResource(t, (*acl)["read"].Created) + th.AssertEquals(t, len((*acl)["read"].Users), 1) + th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + + aclRef, err = acls.UpdateContainerACL(client, containerID, updateOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, aclRef) + + acl, err = acls.GetContainerACL(client, containerID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, acl) + tools.PrintResource(t, (*acl)["read"].Created) + th.AssertEquals(t, len((*acl)["read"].Users), 0) + th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/containers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/containers_test.go new file mode 100644 index 000000000000..2b5f4d01fe5d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/containers_test.go @@ -0,0 +1,188 @@ +// +build acceptance keymanager containers + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestGenericContainersCRUD(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + payload := tools.RandomString("SUPERSECRET-", 8) + secret, err := CreateSecretWithPayload(t, client, payload) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + container, err := CreateGenericContainer(t, client, secret) + th.AssertNoErr(t, err) + containerID, err := ParseID(container.ContainerRef) + th.AssertNoErr(t, err) + defer DeleteContainer(t, client, containerID) + + allPages, err := containers.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allContainers, err := containers.ExtractContainers(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allContainers { + if v.ContainerRef == container.ContainerRef { + found = true + } + } + + th.AssertEquals(t, found, true) +} + +func TestCertificateContainer(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + pass := tools.RandomString("", 16) + priv, cert, err := CreateCertificate(t, pass) + th.AssertNoErr(t, err) + + private, err := CreatePrivateSecret(t, client, priv) + th.AssertNoErr(t, err) + secretID, err := ParseID(private.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Private Payload: %s", string(payload)) + + certificate, err := CreateCertificateSecret(t, client, cert) + th.AssertNoErr(t, err) + secretID, err = ParseID(certificate.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err = secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Certificate Payload: %s", string(payload)) + + passphrase, err := CreatePassphraseSecret(t, client, pass) + th.AssertNoErr(t, err) + secretID, err = ParseID(passphrase.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err = secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Passphrase Payload: %s", string(payload)) + + container, err := CreateCertificateContainer(t, client, passphrase, private, certificate) + th.AssertNoErr(t, err) + containerID, err := ParseID(container.ContainerRef) + defer DeleteContainer(t, client, containerID) +} + +func TestRSAContainer(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + pass := tools.RandomString("", 16) + priv, pub, err := CreateRSAKeyPair(t, pass) + th.AssertNoErr(t, err) + + private, err := CreatePrivateSecret(t, client, priv) + th.AssertNoErr(t, err) + secretID, err := ParseID(private.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Private Payload: %s", string(payload)) + + public, err := CreatePublicSecret(t, client, pub) + th.AssertNoErr(t, err) + secretID, err = ParseID(public.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err = secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Public Payload: %s", string(payload)) + + passphrase, err := CreatePassphraseSecret(t, client, pass) + th.AssertNoErr(t, err) + secretID, err = ParseID(passphrase.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err = secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + t.Logf("Passphrase Payload: %s", string(payload)) + + container, err := CreateRSAContainer(t, client, passphrase, private, public) + th.AssertNoErr(t, err) + containerID, err := ParseID(container.ContainerRef) + defer DeleteContainer(t, client, containerID) +} + +func TestContainerConsumersCRUD(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + payload := tools.RandomString("SUPERSECRET-", 8) + secret, err := CreateSecretWithPayload(t, client, payload) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + container, err := CreateGenericContainer(t, client, secret) + th.AssertNoErr(t, err) + containerID, err := ParseID(container.ContainerRef) + th.AssertNoErr(t, err) + defer DeleteContainer(t, client, containerID) + + consumerName := tools.RandomString("CONSUMER-", 8) + consumerCreateOpts := containers.CreateConsumerOpts{ + Name: consumerName, + URL: "http://example.com", + } + + container, err = containers.CreateConsumer(client, containerID, consumerCreateOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, container.Consumers) + th.AssertEquals(t, len(container.Consumers), 1) + defer func() { + deleteOpts := containers.DeleteConsumerOpts{ + Name: consumerName, + URL: "http://example.com", + } + + container, err := containers.DeleteConsumer(client, containerID, deleteOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, len(container.Consumers), 0) + }() + + allPages, err := containers.ListConsumers(client, containerID, nil).AllPages() + th.AssertNoErr(t, err) + + allConsumers, err := containers.ExtractConsumers(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allConsumers { + if v.Name == consumerName { + found = true + } + } + + th.AssertEquals(t, found, true) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/keymanager.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/keymanager.go new file mode 100644 index 000000000000..c7c72ef7c264 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/keymanager.go @@ -0,0 +1,716 @@ +package v1 + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/pem" + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// CreateAsymmetric Order will create a random asymmetric order. +// An error will be returned if the order could not be created. +func CreateAsymmetricOrder(t *testing.T, client *gophercloud.ServiceClient) (*orders.Order, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create order %s", name) + + expiration := time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) + createOpts := orders.CreateOpts{ + Type: orders.AsymmetricOrder, + Meta: orders.MetaOpts{ + Name: name, + Algorithm: "rsa", + BitLength: 2048, + Mode: "cbc", + Expiration: &expiration, + }, + } + + order, err := orders.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + orderID, err := ParseID(order.OrderRef) + if err != nil { + return nil, err + } + + err = WaitForOrder(client, orderID) + th.AssertNoErr(t, err) + + order, err = orders.Get(client, orderID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, order) + tools.PrintResource(t, order.Meta.Expiration) + + th.AssertEquals(t, order.Meta.Name, name) + th.AssertEquals(t, order.Type, "asymmetric") + + return order, nil +} + +// CreateCertificateContainer will create a random certificate container. +// An error will be returned if the container could not be created. +func CreateCertificateContainer(t *testing.T, client *gophercloud.ServiceClient, passphrase, private, certificate *secrets.Secret) (*containers.Container, error) { + containerName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create container %s", containerName) + + createOpts := containers.CreateOpts{ + Type: containers.CertificateContainer, + Name: containerName, + SecretRefs: []containers.SecretRef{ + { + Name: "certificate", + SecretRef: certificate.SecretRef, + }, + { + Name: "private_key", + SecretRef: private.SecretRef, + }, + { + Name: "private_key_passphrase", + SecretRef: passphrase.SecretRef, + }, + }, + } + + container, err := containers.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created container: %s", container.ContainerRef) + + containerID, err := ParseID(container.ContainerRef) + if err != nil { + return nil, err + } + + container, err = containers.Get(client, containerID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, container) + + th.AssertEquals(t, container.Name, containerName) + th.AssertEquals(t, container.Type, "certificate") + + return container, nil +} + +// CreateKeyOrder will create a random key order. +// An error will be returned if the order could not be created. +func CreateKeyOrder(t *testing.T, client *gophercloud.ServiceClient) (*orders.Order, error) { + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create order %s", name) + + expiration := time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) + createOpts := orders.CreateOpts{ + Type: orders.KeyOrder, + Meta: orders.MetaOpts{ + Name: name, + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + Expiration: &expiration, + }, + } + + order, err := orders.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + orderID, err := ParseID(order.OrderRef) + if err != nil { + return nil, err + } + + order, err = orders.Get(client, orderID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, order) + tools.PrintResource(t, order.Meta.Expiration) + + th.AssertEquals(t, order.Meta.Name, name) + th.AssertEquals(t, order.Type, "key") + + return order, nil +} + +// CreateRSAContainer will create a random RSA container. +// An error will be returned if the container could not be created. +func CreateRSAContainer(t *testing.T, client *gophercloud.ServiceClient, passphrase, private, public *secrets.Secret) (*containers.Container, error) { + containerName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create container %s", containerName) + + createOpts := containers.CreateOpts{ + Type: containers.RSAContainer, + Name: containerName, + SecretRefs: []containers.SecretRef{ + { + Name: "public_key", + SecretRef: public.SecretRef, + }, + { + Name: "private_key", + SecretRef: private.SecretRef, + }, + { + Name: "private_key_passphrase", + SecretRef: passphrase.SecretRef, + }, + }, + } + + container, err := containers.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created container: %s", container.ContainerRef) + + containerID, err := ParseID(container.ContainerRef) + if err != nil { + return nil, err + } + + container, err = containers.Get(client, containerID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, container) + + th.AssertEquals(t, container.Name, containerName) + th.AssertEquals(t, container.Type, "rsa") + + return container, nil +} + +// CreateCertificateSecret will create a random certificate secret. An error +// will be returned if the secret could not be created. +func CreateCertificateSecret(t *testing.T, client *gophercloud.ServiceClient, cert []byte) (*secrets.Secret, error) { + b64Cert := base64.StdEncoding.EncodeToString(cert) + + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create public key %s", name) + + createOpts := secrets.CreateOpts{ + Name: name, + SecretType: secrets.CertificateSecret, + Payload: b64Cert, + PayloadContentType: "application/octet-stream", + PayloadContentEncoding: "base64", + Algorithm: "rsa", + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, name) + th.AssertEquals(t, secret.Algorithm, "rsa") + + return secret, nil +} + +// CreateEmptySecret will create a random secret with no payload. An error will +// be returned if the secret could not be created. +func CreateEmptySecret(t *testing.T, client *gophercloud.ServiceClient) (*secrets.Secret, error) { + secretName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create secret %s", secretName) + + createOpts := secrets.CreateOpts{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + Name: secretName, + SecretType: secrets.OpaqueSecret, + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, secretName) + th.AssertEquals(t, secret.Algorithm, "aes") + + return secret, nil +} + +// CreateGenericContainer will create a random generic container with a +// specified secret. An error will be returned if the container could not +// be created. +func CreateGenericContainer(t *testing.T, client *gophercloud.ServiceClient, secret *secrets.Secret) (*containers.Container, error) { + containerName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create container %s", containerName) + + createOpts := containers.CreateOpts{ + Type: containers.GenericContainer, + Name: containerName, + SecretRefs: []containers.SecretRef{ + { + Name: secret.Name, + SecretRef: secret.SecretRef, + }, + }, + } + + container, err := containers.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created container: %s", container.ContainerRef) + + containerID, err := ParseID(container.ContainerRef) + if err != nil { + return nil, err + } + + container, err = containers.Get(client, containerID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, container) + + th.AssertEquals(t, container.Name, containerName) + th.AssertEquals(t, container.Type, "generic") + + return container, nil +} + +// CreatePassphraseSecret will create a random passphrase secret. +// An error will be returned if the secret could not be created. +func CreatePassphraseSecret(t *testing.T, client *gophercloud.ServiceClient, passphrase string) (*secrets.Secret, error) { + secretName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create secret %s", secretName) + + createOpts := secrets.CreateOpts{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + Name: secretName, + Payload: passphrase, + PayloadContentType: "text/plain", + SecretType: secrets.PassphraseSecret, + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, secretName) + th.AssertEquals(t, secret.Algorithm, "aes") + + return secret, nil +} + +// CreatePublicSecret will create a random public secret. An error +// will be returned if the secret could not be created. +func CreatePublicSecret(t *testing.T, client *gophercloud.ServiceClient, pub []byte) (*secrets.Secret, error) { + b64Cert := base64.StdEncoding.EncodeToString(pub) + + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create public key %s", name) + + createOpts := secrets.CreateOpts{ + Name: name, + SecretType: secrets.PublicSecret, + Payload: b64Cert, + PayloadContentType: "application/octet-stream", + PayloadContentEncoding: "base64", + Algorithm: "rsa", + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, name) + th.AssertEquals(t, secret.Algorithm, "rsa") + + return secret, nil +} + +// CreatePrivateSecret will create a random private secret. An error +// will be returned if the secret could not be created. +func CreatePrivateSecret(t *testing.T, client *gophercloud.ServiceClient, priv []byte) (*secrets.Secret, error) { + b64Cert := base64.StdEncoding.EncodeToString(priv) + + name := tools.RandomString("TESTACC-", 8) + t.Logf("Attempting to create public key %s", name) + + createOpts := secrets.CreateOpts{ + Name: name, + SecretType: secrets.PrivateSecret, + Payload: b64Cert, + PayloadContentType: "application/octet-stream", + PayloadContentEncoding: "base64", + Algorithm: "rsa", + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, name) + th.AssertEquals(t, secret.Algorithm, "rsa") + + return secret, nil +} + +// CreateSecretWithPayload will create a random secret with a given payload. +// An error will be returned if the secret could not be created. +func CreateSecretWithPayload(t *testing.T, client *gophercloud.ServiceClient, payload string) (*secrets.Secret, error) { + secretName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create secret %s", secretName) + + expiration := time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) + createOpts := secrets.CreateOpts{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + Name: secretName, + Payload: payload, + PayloadContentType: "text/plain", + SecretType: secrets.OpaqueSecret, + Expiration: &expiration, + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, secretName) + th.AssertEquals(t, secret.Algorithm, "aes") + th.AssertEquals(t, secret.Expiration, expiration) + + return secret, nil +} + +// CreateSymmetricSecret will create a random symmetric secret. An error +// will be returned if the secret could not be created. +func CreateSymmetricSecret(t *testing.T, client *gophercloud.ServiceClient) (*secrets.Secret, error) { + name := tools.RandomString("TESTACC-", 8) + key := tools.RandomString("", 256) + b64Key := base64.StdEncoding.EncodeToString([]byte(key)) + + t.Logf("Attempting to create symmetric key %s", name) + + createOpts := secrets.CreateOpts{ + Name: name, + SecretType: secrets.SymmetricSecret, + Payload: b64Key, + PayloadContentType: "application/octet-stream", + PayloadContentEncoding: "base64", + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created secret: %s", secret.SecretRef) + + secretID, err := ParseID(secret.SecretRef) + if err != nil { + return nil, err + } + + secret, err = secrets.Get(client, secretID).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, secret) + + th.AssertEquals(t, secret.Name, name) + th.AssertEquals(t, secret.Algorithm, "aes") + + return secret, nil +} + +// DeleteContainer will delete a container. A fatal error will occur if the +// container could not be deleted. This works best when used as a deferred +// function. +func DeleteContainer(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete container %s", id) + + err := containers.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Could not delete container: %s", err) + } + + t.Logf("Successfully deleted container %s", id) +} + +// DeleteOrder will delete an order. A fatal error will occur if the +// order could not be deleted. This works best when used as a deferred +// function. +func DeleteOrder(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete order %s", id) + + err := orders.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Could not delete order: %s", err) + } + + t.Logf("Successfully deleted order %s", id) +} + +// DeleteSecret will delete a secret. A fatal error will occur if the secret +// could not be deleted. This works best when used as a deferred function. +func DeleteSecret(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete secret %s", id) + + err := secrets.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Could not delete secret: %s", err) + } + + t.Logf("Successfully deleted secret %s", id) +} + +func ParseID(ref string) (string, error) { + parts := strings.Split(ref, "/") + if len(parts) < 2 { + return "", fmt.Errorf("Could not parse %s", ref) + } + + return parts[len(parts)-1], nil +} + +// CreateCertificate will create a random certificate. A fatal error will +// be returned if creation failed. +// https://golang.org/src/crypto/tls/generate_cert.go +func CreateCertificate(t *testing.T, passphrase string) ([]byte, []byte, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + + if passphrase != "" { + block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256) + if err != nil { + return nil, nil, err + } + } + + keyPem := pem.EncodeToMemory(block) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, err + } + + tpl := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Some Org"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(5, 0, 0), + BasicConstraintsValid: true, + } + + cert, err := x509.CreateCertificate(rand.Reader, &tpl, &tpl, &key.PublicKey, key) + if err != nil { + return nil, nil, err + } + + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert, + }) + + return keyPem, certPem, nil +} + +// CreateRSAKeyPair will create a random RSA key pair. An error will be +// returned if the pair could not be created. +func CreateRSAKeyPair(t *testing.T, passphrase string) ([]byte, []byte, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + + if passphrase != "" { + block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256) + if err != nil { + return nil, nil, err + } + } + + keyPem := pem.EncodeToMemory(block) + + asn1Bytes, err := asn1.Marshal(key.PublicKey) + if err != nil { + return nil, nil, err + } + + block = &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: asn1Bytes, + } + + pubPem := pem.EncodeToMemory(block) + + return keyPem, pubPem, nil +} + +func WaitForOrder(client *gophercloud.ServiceClient, orderID string) error { + return tools.WaitFor(func() (bool, error) { + order, err := orders.Get(client, orderID).Extract() + if err != nil { + return false, err + } + + if order.SecretRef != "" { + return true, nil + } + + if order.ContainerRef != "" { + return true, nil + } + + if order.Status == "ERROR" { + return false, fmt.Errorf("Order %s in ERROR state", orderID) + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/orders_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/orders_test.go new file mode 100644 index 000000000000..de44debe9123 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/orders_test.go @@ -0,0 +1,84 @@ +// +build acceptance keymanager orders + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestOrdersCRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + order, err := CreateKeyOrder(t, client) + th.AssertNoErr(t, err) + orderID, err := ParseID(order.OrderRef) + th.AssertNoErr(t, err) + defer DeleteOrder(t, client, orderID) + + secretID, err := ParseID(order.SecretRef) + th.AssertNoErr(t, err) + + payloadOpts := secrets.GetPayloadOpts{ + PayloadContentType: "application/octet-stream", + } + payload, err := secrets.GetPayload(client, secretID, payloadOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, payload) + + allPages, err := orders.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allOrders, err := orders.ExtractOrders(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allOrders { + if v.OrderRef == order.OrderRef { + found = true + } + } + + th.AssertEquals(t, found, true) +} + +func TestOrdersAsymmetric(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + order, err := CreateAsymmetricOrder(t, client) + th.AssertNoErr(t, err) + orderID, err := ParseID(order.OrderRef) + th.AssertNoErr(t, err) + defer DeleteOrder(t, client, orderID) + + containerID, err := ParseID(order.ContainerRef) + th.AssertNoErr(t, err) + + container, err := containers.Get(client, containerID).Extract() + th.AssertNoErr(t, err) + + for _, v := range container.SecretRefs { + secretID, err := ParseID(v.SecretRef) + th.AssertNoErr(t, err) + + payloadOpts := secrets.GetPayloadOpts{ + PayloadContentType: "application/octet-stream", + } + + payload, err := secrets.GetPayload(client, secretID, payloadOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, string(payload)) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/secrets_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/secrets_test.go new file mode 100644 index 000000000000..dd00e569adb1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/keymanager/v1/secrets_test.go @@ -0,0 +1,242 @@ +// +build acceptance keymanager secrets + +package v1 + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestSecretsCRUD(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + payload := tools.RandomString("SUPERSECRET-", 8) + secret, err := CreateSecretWithPayload(t, client, payload) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + // Test payload retrieval + actual, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, payload, string(actual)) + + // Test listing secrets + createdQuery := &secrets.DateQuery{ + Date: time.Date(2049, 6, 7, 1, 2, 3, 0, time.UTC), + Filter: secrets.DateFilterLT, + } + + listOpts := secrets.ListOpts{ + CreatedQuery: createdQuery, + } + + allPages, err := secrets.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allSecrets, err := secrets.ExtractSecrets(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allSecrets { + if v.SecretRef == secret.SecretRef { + found = true + } + } + + th.AssertEquals(t, found, true) +} + +func TestSecretsDelayedPayload(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + secret, err := CreateEmptySecret(t, client) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload := tools.RandomString("SUPERSECRET-", 8) + updateOpts := secrets.UpdateOpts{ + ContentType: "text/plain", + Payload: payload, + } + + err = secrets.Update(client, secretID, updateOpts).ExtractErr() + th.AssertNoErr(t, err) + + // Test payload retrieval + actual, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, payload, string(actual)) +} + +func TestSecretsMetadataCRUD(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + payload := tools.RandomString("SUPERSECRET-", 8) + secret, err := CreateSecretWithPayload(t, client, payload) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + // Create some metadata + createOpts := secrets.MetadataOpts{ + "foo": "bar", + "something": "something else", + } + + ref, err := secrets.CreateMetadata(client, secretID, createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ref["metadata_ref"], secret.SecretRef+"/metadata") + + // Get the metadata + metadata, err := secrets.GetMetadata(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, metadata) + th.AssertEquals(t, metadata["foo"], "bar") + th.AssertEquals(t, metadata["something"], "something else") + + // Add a single metadatum + metadatumOpts := secrets.MetadatumOpts{ + Key: "bar", + Value: "baz", + } + + err = secrets.CreateMetadatum(client, secretID, metadatumOpts).ExtractErr() + th.AssertNoErr(t, err) + + metadata, err = secrets.GetMetadata(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, metadata) + th.AssertEquals(t, len(metadata), 3) + th.AssertEquals(t, metadata["foo"], "bar") + th.AssertEquals(t, metadata["something"], "something else") + th.AssertEquals(t, metadata["bar"], "baz") + + // Update a metadatum + metadatumOpts.Key = "foo" + metadatumOpts.Value = "foo" + + metadatum, err := secrets.UpdateMetadatum(client, secretID, metadatumOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, metadatum) + th.AssertDeepEquals(t, metadatum.Key, "foo") + th.AssertDeepEquals(t, metadatum.Value, "foo") + + metadata, err = secrets.GetMetadata(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, metadata) + th.AssertEquals(t, len(metadata), 3) + th.AssertEquals(t, metadata["foo"], "foo") + th.AssertEquals(t, metadata["something"], "something else") + th.AssertEquals(t, metadata["bar"], "baz") + + // Delete a metadatum + err = secrets.DeleteMetadatum(client, secretID, "foo").ExtractErr() + th.AssertNoErr(t, err) + + metadata, err = secrets.GetMetadata(client, secretID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, metadata) + th.AssertEquals(t, len(metadata), 2) + th.AssertEquals(t, metadata["something"], "something else") + th.AssertEquals(t, metadata["bar"], "baz") +} + +func TestSymmetricSecret(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + secret, err := CreateSymmetricSecret(t, client) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, string(payload)) +} + +func TestCertificateSecret(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + pass := tools.RandomString("", 16) + cert, _, err := CreateCertificate(t, pass) + th.AssertNoErr(t, err) + + secret, err := CreateCertificateSecret(t, client, cert) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, string(payload)) +} + +func TestPrivateSecret(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + pass := tools.RandomString("", 16) + priv, _, err := CreateCertificate(t, pass) + th.AssertNoErr(t, err) + + secret, err := CreatePrivateSecret(t, client, priv) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, string(payload)) +} + +func TestPublicSecret(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + _, pub, err := CreateRSAKeyPair(t, "") + th.AssertNoErr(t, err) + + secret, err := CreatePublicSecret(t, client, pub) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, string(payload)) +} + +func TestPassphraseSecret(t *testing.T) { + client, err := clients.NewKeyManagerV1Client() + th.AssertNoErr(t, err) + + pass := tools.RandomString("", 16) + secret, err := CreatePassphraseSecret(t, client, pass) + th.AssertNoErr(t, err) + secretID, err := ParseID(secret.SecretRef) + th.AssertNoErr(t, err) + defer DeleteSecret(t, client, secretID) + + payload, err := secrets.GetPayload(client, secretID, nil).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, string(payload)) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/amphorae_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/amphorae_test.go new file mode 100644 index 000000000000..0e3369c49aa3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/amphorae_test.go @@ -0,0 +1,32 @@ +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae" +) + +func TestAmphoraeList(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewLoadBalancerV2Client() + if err != nil { + t.Fatalf("Unable to create a loadbalancer client: %v", err) + } + + allPages, err := amphorae.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list amphorae: %v", err) + } + + allAmphorae, err := amphorae.ExtractAmphorae(allPages) + if err != nil { + t.Fatalf("Unable to extract amphorae: %v", err) + } + + for _, amphora := range allAmphorae { + tools.PrintResource(t, amphora) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/l7policies_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/l7policies_test.go new file mode 100644 index 000000000000..d6f092587053 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/l7policies_test.go @@ -0,0 +1,32 @@ +// +build acceptance networking loadbalancer l7policies + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" +) + +func TestL7PoliciesList(t *testing.T) { + client, err := clients.NewLoadBalancerV2Client() + if err != nil { + t.Fatalf("Unable to create a loadbalancer client: %v", err) + } + + allPages, err := l7policies.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list l7policies: %v", err) + } + + allL7Policies, err := l7policies.ExtractL7Policies(allPages) + if err != nil { + t.Fatalf("Unable to extract l7policies: %v", err) + } + + for _, policy := range allL7Policies { + tools.PrintResource(t, policy) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/listeners_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/listeners_test.go new file mode 100644 index 000000000000..f643676544b8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/listeners_test.go @@ -0,0 +1,32 @@ +// +build acceptance networking loadbalancer listeners + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" +) + +func TestListenersList(t *testing.T) { + client, err := clients.NewLoadBalancerV2Client() + if err != nil { + t.Fatalf("Unable to create a loadbalancer client: %v", err) + } + + allPages, err := listeners.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list listeners: %v", err) + } + + allListeners, err := listeners.ExtractListeners(allPages) + if err != nil { + t.Fatalf("Unable to extract listeners: %v", err) + } + + for _, listener := range allListeners { + tools.PrintResource(t, listener) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancer.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancer.go new file mode 100644 index 000000000000..880e29403b02 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancer.go @@ -0,0 +1,463 @@ +package v2 + +import ( + "fmt" + "strings" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +const loadbalancerActiveTimeoutSeconds = 600 +const loadbalancerDeleteTimeoutSeconds = 600 + +// CreateListener will create a listener for a given load balancer on a random +// port with a random name. An error will be returned if the listener could not +// be created. +func CreateListener(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer) (*listeners.Listener, error) { + listenerName := tools.RandomString("TESTACCT-", 8) + listenerDescription := tools.RandomString("TESTACCT-DESC-", 8) + listenerPort := tools.RandomInt(1, 100) + + t.Logf("Attempting to create listener %s on port %d", listenerName, listenerPort) + + createOpts := listeners.CreateOpts{ + Name: listenerName, + Description: listenerDescription, + LoadbalancerID: lb.ID, + Protocol: listeners.ProtocolTCP, + ProtocolPort: listenerPort, + } + + listener, err := listeners.Create(client, createOpts).Extract() + if err != nil { + return listener, err + } + + t.Logf("Successfully created listener %s", listenerName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return listener, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, listener.Name, listenerName) + th.AssertEquals(t, listener.Description, listenerDescription) + th.AssertEquals(t, listener.Loadbalancers[0].ID, lb.ID) + th.AssertEquals(t, listener.Protocol, string(listeners.ProtocolTCP)) + th.AssertEquals(t, listener.ProtocolPort, listenerPort) + + return listener, nil +} + +// CreateLoadBalancer will create a load balancer with a random name on a given +// subnet. An error will be returned if the loadbalancer could not be created. +func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetID string, tags []string) (*loadbalancers.LoadBalancer, error) { + lbName := tools.RandomString("TESTACCT-", 8) + lbDescription := tools.RandomString("TESTACCT-DESC-", 8) + + t.Logf("Attempting to create loadbalancer %s on subnet %s", lbName, subnetID) + + createOpts := loadbalancers.CreateOpts{ + Name: lbName, + Description: lbDescription, + VipSubnetID: subnetID, + AdminStateUp: gophercloud.Enabled, + } + if len(tags) > 0 { + createOpts.Tags = tags + } + + lb, err := loadbalancers.Create(client, createOpts).Extract() + if err != nil { + return lb, err + } + + t.Logf("Successfully created loadbalancer %s on subnet %s", lbName, subnetID) + t.Logf("Waiting for loadbalancer %s to become active", lbName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return lb, err + } + + t.Logf("LoadBalancer %s is active", lbName) + + th.AssertEquals(t, lb.Name, lbName) + th.AssertEquals(t, lb.Description, lbDescription) + th.AssertEquals(t, lb.VipSubnetID, subnetID) + th.AssertEquals(t, lb.AdminStateUp, true) + + if len(tags) > 0 { + th.AssertDeepEquals(t, lb.Tags, tags) + } + + return lb, nil +} + +// CreateMember will create a member with a random name, port, address, and +// weight. An error will be returned if the member could not be created. +func CreateMember(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer, pool *pools.Pool, subnetID, subnetCIDR string) (*pools.Member, error) { + memberName := tools.RandomString("TESTACCT-", 8) + memberPort := tools.RandomInt(100, 1000) + memberWeight := tools.RandomInt(1, 10) + + cidrParts := strings.Split(subnetCIDR, "/") + subnetParts := strings.Split(cidrParts[0], ".") + memberAddress := fmt.Sprintf("%s.%s.%s.%d", subnetParts[0], subnetParts[1], subnetParts[2], tools.RandomInt(10, 100)) + + t.Logf("Attempting to create member %s", memberName) + + createOpts := pools.CreateMemberOpts{ + Name: memberName, + ProtocolPort: memberPort, + Weight: &memberWeight, + Address: memberAddress, + SubnetID: subnetID, + } + + t.Logf("Member create opts: %#v", createOpts) + + member, err := pools.CreateMember(client, pool.ID, createOpts).Extract() + if err != nil { + return member, err + } + + t.Logf("Successfully created member %s", memberName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return member, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, member.Name, memberName) + + return member, nil +} + +// CreateMonitor will create a monitor with a random name for a specific pool. +// An error will be returned if the monitor could not be created. +func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer, pool *pools.Pool) (*monitors.Monitor, error) { + monitorName := tools.RandomString("TESTACCT-", 8) + + t.Logf("Attempting to create monitor %s", monitorName) + + createOpts := monitors.CreateOpts{ + PoolID: pool.ID, + Name: monitorName, + Delay: 10, + Timeout: 5, + MaxRetries: 5, + Type: monitors.TypePING, + } + + monitor, err := monitors.Create(client, createOpts).Extract() + if err != nil { + return monitor, err + } + + t.Logf("Successfully created monitor: %s", monitorName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return monitor, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, monitor.Name, monitorName) + th.AssertEquals(t, monitor.Type, monitors.TypePING) + + return monitor, nil +} + +// CreatePool will create a pool with a random name with a specified listener +// and loadbalancer. An error will be returned if the pool could not be +// created. +func CreatePool(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer) (*pools.Pool, error) { + poolName := tools.RandomString("TESTACCT-", 8) + poolDescription := tools.RandomString("TESTACCT-DESC-", 8) + + t.Logf("Attempting to create pool %s", poolName) + + createOpts := pools.CreateOpts{ + Name: poolName, + Description: poolDescription, + Protocol: pools.ProtocolTCP, + LoadbalancerID: lb.ID, + LBMethod: pools.LBMethodLeastConnections, + } + + pool, err := pools.Create(client, createOpts).Extract() + if err != nil { + return pool, err + } + + t.Logf("Successfully created pool %s", poolName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return pool, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, pool.Name, poolName) + th.AssertEquals(t, pool.Description, poolDescription) + th.AssertEquals(t, pool.Protocol, string(pools.ProtocolTCP)) + th.AssertEquals(t, pool.Loadbalancers[0].ID, lb.ID) + th.AssertEquals(t, pool.LBMethod, string(pools.LBMethodLeastConnections)) + + return pool, nil +} + +// CreateL7Policy will create a l7 policy with a random name with a specified listener +// and loadbalancer. An error will be returned if the l7 policy could not be +// created. +func CreateL7Policy(t *testing.T, client *gophercloud.ServiceClient, listener *listeners.Listener, lb *loadbalancers.LoadBalancer) (*l7policies.L7Policy, error) { + policyName := tools.RandomString("TESTACCT-", 8) + policyDescription := tools.RandomString("TESTACCT-DESC-", 8) + + t.Logf("Attempting to create l7 policy %s", policyName) + + createOpts := l7policies.CreateOpts{ + Name: policyName, + Description: policyDescription, + ListenerID: listener.ID, + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + } + + policy, err := l7policies.Create(client, createOpts).Extract() + if err != nil { + return policy, err + } + + t.Logf("Successfully created l7 policy %s", policyName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return policy, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, policy.Name, policyName) + th.AssertEquals(t, policy.Description, policyDescription) + th.AssertEquals(t, policy.ListenerID, listener.ID) + th.AssertEquals(t, policy.Action, string(l7policies.ActionRedirectToURL)) + th.AssertEquals(t, policy.RedirectURL, "http://www.example.com") + + return policy, nil +} + +// CreateL7Rule creates a l7 rule for specified l7 policy. +func CreateL7Rule(t *testing.T, client *gophercloud.ServiceClient, policyID string, lb *loadbalancers.LoadBalancer) (*l7policies.Rule, error) { + t.Logf("Attempting to create l7 rule for policy %s", policyID) + + createOpts := l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeStartWith, + Value: "/api", + } + + rule, err := l7policies.CreateRule(client, policyID, createOpts).Extract() + if err != nil { + return rule, err + } + + t.Logf("Successfully created l7 rule for policy %s", policyID) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return rule, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, rule.RuleType, string(l7policies.TypePath)) + th.AssertEquals(t, rule.CompareType, string(l7policies.CompareTypeStartWith)) + th.AssertEquals(t, rule.Value, "/api") + + return rule, nil +} + +// DeleteL7Policy will delete a specified l7 policy. A fatal error will occur if +// the l7 policy could not be deleted. This works best when used as a deferred +// function. +func DeleteL7Policy(t *testing.T, client *gophercloud.ServiceClient, lbID, policyID string) { + t.Logf("Attempting to delete l7 policy %s", policyID) + + if err := l7policies.Delete(client, policyID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete l7 policy: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted l7 policy %s", policyID) +} + +// DeleteL7Rule will delete a specified l7 rule. A fatal error will occur if +// the l7 rule could not be deleted. This works best when used as a deferred +// function. +func DeleteL7Rule(t *testing.T, client *gophercloud.ServiceClient, lbID, policyID, ruleID string) { + t.Logf("Attempting to delete l7 rule %s", ruleID) + + if err := l7policies.DeleteRule(client, policyID, ruleID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete l7 rule: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted l7 rule %s", ruleID) +} + +// DeleteListener will delete a specified listener. A fatal error will occur if +// the listener could not be deleted. This works best when used as a deferred +// function. +func DeleteListener(t *testing.T, client *gophercloud.ServiceClient, lbID, listenerID string) { + t.Logf("Attempting to delete listener %s", listenerID) + + if err := listeners.Delete(client, listenerID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete listener: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted listener %s", listenerID) +} + +// DeleteMember will delete a specified member. A fatal error will occur if the +// member could not be deleted. This works best when used as a deferred +// function. +func DeleteMember(t *testing.T, client *gophercloud.ServiceClient, lbID, poolID, memberID string) { + t.Logf("Attempting to delete member %s", memberID) + + if err := pools.DeleteMember(client, poolID, memberID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete member: %s", memberID) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted member %s", memberID) +} + +// DeleteLoadBalancer will delete a specified loadbalancer. A fatal error will +// occur if the loadbalancer could not be deleted. This works best when used +// as a deferred function. +func DeleteLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, lbID string) { + t.Logf("Attempting to delete loadbalancer %s", lbID) + + deleteOpts := loadbalancers.DeleteOpts{ + Cascade: false, + } + + if err := loadbalancers.Delete(client, lbID, deleteOpts).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete loadbalancer: %v", err) + } + } + + t.Logf("Waiting for loadbalancer %s to delete", lbID) + + if err := WaitForLoadBalancerState(client, lbID, "DELETED", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Loadbalancer did not delete in time: %s", err) + } + + t.Logf("Successfully deleted loadbalancer %s", lbID) +} + +// CascadeDeleteLoadBalancer will perform a cascading delete on a loadbalancer. +// A fatal error will occur if the loadbalancer could not be deleted. This works +// best when used as a deferred function. +func CascadeDeleteLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, lbID string) { + t.Logf("Attempting to cascade delete loadbalancer %s", lbID) + + deleteOpts := loadbalancers.DeleteOpts{ + Cascade: true, + } + + if err := loadbalancers.Delete(client, lbID, deleteOpts).ExtractErr(); err != nil { + t.Fatalf("Unable to cascade delete loadbalancer: %v", err) + } + + t.Logf("Waiting for loadbalancer %s to cascade delete", lbID) + + if err := WaitForLoadBalancerState(client, lbID, "DELETED", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Loadbalancer did not delete in time.") + } + + t.Logf("Successfully deleted loadbalancer %s", lbID) +} + +// DeleteMonitor will delete a specified monitor. A fatal error will occur if +// the monitor could not be deleted. This works best when used as a deferred +// function. +func DeleteMonitor(t *testing.T, client *gophercloud.ServiceClient, lbID, monitorID string) { + t.Logf("Attempting to delete monitor %s", monitorID) + + if err := monitors.Delete(client, monitorID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete monitor: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted monitor %s", monitorID) +} + +// DeletePool will delete a specified pool. A fatal error will occur if the +// pool could not be deleted. This works best when used as a deferred function. +func DeletePool(t *testing.T, client *gophercloud.ServiceClient, lbID, poolID string) { + t.Logf("Attempting to delete pool %s", poolID) + + if err := pools.Delete(client, poolID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete pool: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted pool %s", poolID) +} + +// WaitForLoadBalancerState will wait until a loadbalancer reaches a given state. +func WaitForLoadBalancerState(client *gophercloud.ServiceClient, lbID, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := loadbalancers.Get(client, lbID).Extract() + if err != nil { + if httpStatus, ok := err.(gophercloud.ErrDefault404); ok { + if httpStatus.Actual == 404 { + if status == "DELETED" { + return true, nil + } + } + } + return false, err + } + + if current.ProvisioningStatus == status { + return true, nil + } + + if current.ProvisioningStatus == "ERROR" { + return false, fmt.Errorf("Load balancer is in ERROR state") + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go new file mode 100644 index 000000000000..e4bae39582f3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go @@ -0,0 +1,496 @@ +// +build acceptance networking loadbalancer loadbalancers + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestLoadbalancersList(t *testing.T) { + client, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + allPages, err := loadbalancers.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allLoadbalancers, err := loadbalancers.ExtractLoadBalancers(allPages) + th.AssertNoErr(t, err) + + for _, lb := range allLoadbalancers { + tools.PrintResource(t, lb) + } +} + +func TestLoadbalancersListByTags(t *testing.T) { + netClient, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + lbClient, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + network, err := networking.CreateNetwork(t, netClient) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, netClient, network.ID) + + subnet, err := networking.CreateSubnet(t, netClient, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, netClient, subnet.ID) + + // Add "test" tag intentionally to test the "not-tags" parameter. Because "test" tag is also used in other test + // cases, we use "test" tag to exclude load balancers created by other test case. + tags := []string{"tag1", "tag2", "test"} + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags) + th.AssertNoErr(t, err) + defer DeleteLoadBalancer(t, lbClient, lb.ID) + + tags = []string{"tag1"} + listOpts := loadbalancers.ListOpts{ + Tags: tags, + } + allPages, err := loadbalancers.List(lbClient, listOpts).AllPages() + th.AssertNoErr(t, err) + allLoadbalancers, err := loadbalancers.ExtractLoadBalancers(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(allLoadbalancers)) + + tags = []string{"test"} + listOpts = loadbalancers.ListOpts{ + TagsNot: tags, + } + allPages, err = loadbalancers.List(lbClient, listOpts).AllPages() + th.AssertNoErr(t, err) + allLoadbalancers, err = loadbalancers.ExtractLoadBalancers(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(allLoadbalancers)) + + tags = []string{"tag1", "tag3"} + listOpts = loadbalancers.ListOpts{ + TagsAny: tags, + } + allPages, err = loadbalancers.List(lbClient, listOpts).AllPages() + th.AssertNoErr(t, err) + allLoadbalancers, err = loadbalancers.ExtractLoadBalancers(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(allLoadbalancers)) + + tags = []string{"tag1", "test"} + listOpts = loadbalancers.ListOpts{ + TagsNotAny: tags, + } + allPages, err = loadbalancers.List(lbClient, listOpts).AllPages() + th.AssertNoErr(t, err) + allLoadbalancers, err = loadbalancers.ExtractLoadBalancers(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(allLoadbalancers)) +} + +func TestLoadbalancersCRUD(t *testing.T) { + netClient, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + lbClient, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + network, err := networking.CreateNetwork(t, netClient) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, netClient, network.ID) + + subnet, err := networking.CreateSubnet(t, netClient, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, netClient, subnet.ID) + + tags := []string{"test"} + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags) + th.AssertNoErr(t, err) + defer DeleteLoadBalancer(t, lbClient, lb.ID) + + lbDescription := "" + updateLoadBalancerOpts := loadbalancers.UpdateOpts{ + Description: &lbDescription, + } + _, err = loadbalancers.Update(lbClient, lb.ID, updateLoadBalancerOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newLB, err := loadbalancers.Get(lbClient, lb.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newLB) + + th.AssertEquals(t, newLB.Description, lbDescription) + + lbStats, err := loadbalancers.GetStats(lbClient, lb.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, lbStats) + + // Because of the time it takes to create a loadbalancer, + // this test will include some other resources. + + // Listener + listener, err := CreateListener(t, lbClient, lb) + th.AssertNoErr(t, err) + defer DeleteListener(t, lbClient, lb.ID, listener.ID) + + listenerName := "" + listenerDescription := "" + updateListenerOpts := listeners.UpdateOpts{ + Name: &listenerName, + Description: &listenerDescription, + } + _, err = listeners.Update(lbClient, listener.ID, updateListenerOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newListener, err := listeners.Get(lbClient, listener.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newListener) + + th.AssertEquals(t, newListener.Name, listenerName) + th.AssertEquals(t, newListener.Description, listenerDescription) + + listenerStats, err := listeners.GetStats(lbClient, listener.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, listenerStats) + + // L7 policy + policy, err := CreateL7Policy(t, lbClient, listener, lb) + th.AssertNoErr(t, err) + defer DeleteL7Policy(t, lbClient, lb.ID, policy.ID) + + newDescription := "" + updateL7policyOpts := l7policies.UpdateOpts{ + Description: &newDescription, + } + _, err = l7policies.Update(lbClient, policy.ID, updateL7policyOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newPolicy, err := l7policies.Get(lbClient, policy.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newPolicy) + + th.AssertEquals(t, newPolicy.Description, newDescription) + + // L7 rule + rule, err := CreateL7Rule(t, lbClient, newPolicy.ID, lb) + th.AssertNoErr(t, err) + defer DeleteL7Rule(t, lbClient, lb.ID, policy.ID, rule.ID) + + allPages, err := l7policies.ListRules(lbClient, policy.ID, l7policies.ListRulesOpts{}).AllPages() + th.AssertNoErr(t, err) + allRules, err := l7policies.ExtractRules(allPages) + th.AssertNoErr(t, err) + for _, rule := range allRules { + tools.PrintResource(t, rule) + } + + updateL7ruleOpts := l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", + } + _, err = l7policies.UpdateRule(lbClient, policy.ID, rule.ID, updateL7ruleOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newRule, err := l7policies.GetRule(lbClient, newPolicy.ID, rule.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newRule) + + // Pool + pool, err := CreatePool(t, lbClient, lb) + th.AssertNoErr(t, err) + defer DeletePool(t, lbClient, lb.ID, pool.ID) + + poolName := "" + poolDescription := "" + updatePoolOpts := pools.UpdateOpts{ + Name: &poolName, + Description: &poolDescription, + } + _, err = pools.Update(lbClient, pool.ID, updatePoolOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newPool, err := pools.Get(lbClient, pool.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newPool) + th.AssertEquals(t, newPool.Name, poolName) + th.AssertEquals(t, newPool.Description, poolDescription) + + // Update L7policy to redirect to pool + newRedirectURL := "" + updateL7policyOpts = l7policies.UpdateOpts{ + Action: l7policies.ActionRedirectToPool, + RedirectPoolID: &newPool.ID, + RedirectURL: &newRedirectURL, + } + _, err = l7policies.Update(lbClient, policy.ID, updateL7policyOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newPolicy, err = l7policies.Get(lbClient, policy.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newPolicy) + + th.AssertEquals(t, newPolicy.Description, newDescription) + th.AssertEquals(t, newPolicy.Action, string(l7policies.ActionRedirectToPool)) + th.AssertEquals(t, newPolicy.RedirectPoolID, newPool.ID) + th.AssertEquals(t, newPolicy.RedirectURL, newRedirectURL) + + // Workaround for proper delete order + defer DeleteL7Policy(t, lbClient, lb.ID, policy.ID) + defer DeleteL7Rule(t, lbClient, lb.ID, policy.ID, rule.ID) + + // Update listener's default pool ID + updateListenerOpts = listeners.UpdateOpts{ + DefaultPoolID: &pool.ID, + } + _, err = listeners.Update(lbClient, listener.ID, updateListenerOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newListener, err = listeners.Get(lbClient, listener.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newListener) + + th.AssertEquals(t, newListener.DefaultPoolID, pool.ID) + + // Remove listener's default pool ID + emptyPoolID := "" + updateListenerOpts = listeners.UpdateOpts{ + DefaultPoolID: &emptyPoolID, + } + _, err = listeners.Update(lbClient, listener.ID, updateListenerOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newListener, err = listeners.Get(lbClient, listener.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newListener) + + th.AssertEquals(t, newListener.DefaultPoolID, "") + + // Member + member, err := CreateMember(t, lbClient, lb, newPool, subnet.ID, subnet.CIDR) + th.AssertNoErr(t, err) + defer DeleteMember(t, lbClient, lb.ID, pool.ID, member.ID) + + memberName := "" + newWeight := tools.RandomInt(11, 100) + updateMemberOpts := pools.UpdateMemberOpts{ + Name: &memberName, + Weight: &newWeight, + } + _, err = pools.UpdateMember(lbClient, pool.ID, member.ID, updateMemberOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newMember, err := pools.GetMember(lbClient, pool.ID, member.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newMember) + th.AssertEquals(t, newMember.Name, memberName) + + newWeight = tools.RandomInt(11, 100) + memberOpts := pools.BatchUpdateMemberOpts{ + Address: member.Address, + ProtocolPort: member.ProtocolPort, + Weight: &newWeight, + } + batchMembers := []pools.BatchUpdateMemberOpts{memberOpts} + if err := pools.BatchUpdateMembers(lbClient, pool.ID, batchMembers).ExtractErr(); err != nil { + t.Fatalf("Unable to batch update members") + } + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newMember, err = pools.GetMember(lbClient, pool.ID, member.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newMember) + + // Monitor + monitor, err := CreateMonitor(t, lbClient, lb, newPool) + th.AssertNoErr(t, err) + defer DeleteMonitor(t, lbClient, lb.ID, monitor.ID) + + monName := "" + newDelay := tools.RandomInt(20, 30) + updateMonitorOpts := monitors.UpdateOpts{ + Name: &monName, + Delay: newDelay, + } + _, err = monitors.Update(lbClient, monitor.ID, updateMonitorOpts).Extract() + th.AssertNoErr(t, err) + + if err = WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newMonitor, err := monitors.Get(lbClient, monitor.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newMonitor) + + th.AssertEquals(t, newMonitor.Name, monName) + th.AssertEquals(t, newMonitor.Delay, newDelay) +} + +func TestLoadbalancersCascadeCRUD(t *testing.T) { + netClient, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + lbClient, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + network, err := networking.CreateNetwork(t, netClient) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, netClient, network.ID) + + subnet, err := networking.CreateSubnet(t, netClient, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, netClient, subnet.ID) + + tags := []string{"test"} + lb, err := CreateLoadBalancer(t, lbClient, subnet.ID, tags) + th.AssertNoErr(t, err) + defer CascadeDeleteLoadBalancer(t, lbClient, lb.ID) + + newLB, err := loadbalancers.Get(lbClient, lb.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newLB) + + // Because of the time it takes to create a loadbalancer, + // this test will include some other resources. + + // Listener + listener, err := CreateListener(t, lbClient, lb) + th.AssertNoErr(t, err) + + listenerDescription := "Some listener description" + updateListenerOpts := listeners.UpdateOpts{ + Description: &listenerDescription, + } + _, err = listeners.Update(lbClient, listener.ID, updateListenerOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newListener, err := listeners.Get(lbClient, listener.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newListener) + + // Pool + pool, err := CreatePool(t, lbClient, lb) + th.AssertNoErr(t, err) + + poolDescription := "Some pool description" + updatePoolOpts := pools.UpdateOpts{ + Description: &poolDescription, + } + _, err = pools.Update(lbClient, pool.ID, updatePoolOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newPool, err := pools.Get(lbClient, pool.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newPool) + + // Member + member, err := CreateMember(t, lbClient, lb, newPool, subnet.ID, subnet.CIDR) + th.AssertNoErr(t, err) + + newWeight := tools.RandomInt(11, 100) + updateMemberOpts := pools.UpdateMemberOpts{ + Weight: &newWeight, + } + _, err = pools.UpdateMember(lbClient, pool.ID, member.ID, updateMemberOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newMember, err := pools.GetMember(lbClient, pool.ID, member.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newMember) + + // Monitor + monitor, err := CreateMonitor(t, lbClient, lb, newPool) + th.AssertNoErr(t, err) + + newDelay := tools.RandomInt(20, 30) + updateMonitorOpts := monitors.UpdateOpts{ + Delay: newDelay, + } + _, err = monitors.Update(lbClient, monitor.ID, updateMonitorOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newMonitor, err := monitors.Get(lbClient, monitor.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newMonitor) + +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/monitors_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/monitors_test.go new file mode 100644 index 000000000000..93688fdb2e1b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/monitors_test.go @@ -0,0 +1,32 @@ +// +build acceptance networking loadbalancer monitors + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" +) + +func TestMonitorsList(t *testing.T) { + client, err := clients.NewLoadBalancerV2Client() + if err != nil { + t.Fatalf("Unable to create a loadbalancer client: %v", err) + } + + allPages, err := monitors.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list monitors: %v", err) + } + + allMonitors, err := monitors.ExtractMonitors(allPages) + if err != nil { + t.Fatalf("Unable to extract monitors: %v", err) + } + + for _, monitor := range allMonitors { + tools.PrintResource(t, monitor) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/pkg.go similarity index 100% rename from vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/pkg.go rename to vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/pkg.go diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/pools_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/pools_test.go new file mode 100644 index 000000000000..f7ec2a4ac491 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/loadbalancer/v2/pools_test.go @@ -0,0 +1,32 @@ +// +build acceptance networking loadbalancer pools + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" +) + +func TestPoolsList(t *testing.T) { + client, err := clients.NewLoadBalancerV2Client() + if err != nil { + t.Fatalf("Unable to create a loadbalancer client: %v", err) + } + + allPages, err := pools.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list pools: %v", err) + } + + allPools, err := pools.ExtractPools(allPages) + if err != nil { + t.Fatalf("Unable to extract pools: %v", err) + } + + for _, pool := range allPools { + tools.PrintResource(t, pool) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/claims_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/claims_test.go new file mode 100644 index 000000000000..4ffb9229e9ba --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/claims_test.go @@ -0,0 +1,63 @@ +// +build acceptance messaging claims + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/messaging/v2/claims" +) + +func TestCRUDClaim(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734c" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + + client, err = clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + for i := 0; i < 3; i++ { + CreateMessage(t, client, createdQueueName) + } + + clientID = "3381af92-2b9e-11e3-b191-7186130073dd" + claimedMessages, err := CreateClaim(t, client, createdQueueName) + claimIDs, _ := ExtractIDs(claimedMessages) + + tools.PrintResource(t, claimedMessages) + + updateOpts := claims.UpdateOpts{ + TTL: 600, + Grace: 500, + } + + for _, claimID := range claimIDs { + t.Logf("Attempting to update claim: %s", claimID) + updateErr := claims.Update(client, createdQueueName, claimID, updateOpts).ExtractErr() + + if updateErr != nil { + t.Fatalf("Unable to update claim %s: %v", claimID, err) + } else { + t.Logf("Successfully updated claim: %s", claimID) + } + + updatedClaim, getErr := GetClaim(t, client, createdQueueName, claimID) + if getErr != nil { + t.Fatalf("Unable to retrieve claim %s: %v", claimID, getErr) + } + + tools.PrintResource(t, updatedClaim) + DeleteClaim(t, client, createdQueueName, claimID) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/message_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/message_test.go new file mode 100644 index 000000000000..f3c558116e02 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/message_test.go @@ -0,0 +1,301 @@ +// +build acceptance messaging messages + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/messaging/v2/messages" + "github.com/gophercloud/gophercloud/pagination" +) + +func TestListMessages(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-718613007343" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + for i := 0; i < 3; i++ { + CreateMessage(t, client, createdQueueName) + } + + // Use a different client/clientID in order to see messages on the Queue + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + client, err = clients.NewMessagingV2Client(clientID) + + listOpts := messages.ListOpts{} + + pager := messages.List(client, createdQueueName, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, err := messages.ExtractMessages(page) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + tools.PrintResource(t, message) + } + + return true, nil + }) +} + +func TestCreateMessages(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734c" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + CreateMessage(t, client, createdQueueName) +} + +func TestGetMessages(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-718613007343" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + CreateMessage(t, client, createdQueueName) + CreateMessage(t, client, createdQueueName) + + // Use a different client/clientID in order to see messages on the Queue + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + client, err = clients.NewMessagingV2Client(clientID) + + listOpts := messages.ListOpts{} + + var messageIDs []string + + pager := messages.List(client, createdQueueName, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, err := messages.ExtractMessages(page) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + messageIDs = append(messageIDs, message.ID) + } + + return true, nil + }) + + getMessageOpts := messages.GetMessagesOpts{ + IDs: messageIDs, + } + t.Logf("Attempting to get messages from queue %s with ids: %v", createdQueueName, messageIDs) + messagesList, err := messages.GetMessages(client, createdQueueName, getMessageOpts).Extract() + if err != nil { + t.Fatalf("Unable to get messages from queue: %s", createdQueueName) + } + + tools.PrintResource(t, messagesList) +} + +func TestGetMessage(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-718613007343" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + CreateMessage(t, client, createdQueueName) + + // Use a different client/clientID in order to see messages on the Queue + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + client, err = clients.NewMessagingV2Client(clientID) + + listOpts := messages.ListOpts{} + + var messageIDs []string + + pager := messages.List(client, createdQueueName, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, err := messages.ExtractMessages(page) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + messageIDs = append(messageIDs, message.ID) + } + + return true, nil + }) + + for _, messageID := range messageIDs { + t.Logf("Attempting to get message from queue %s: %s", createdQueueName, messageID) + message, getErr := messages.Get(client, createdQueueName, messageID).Extract() + if getErr != nil { + t.Fatalf("Unable to get message from queue %s: %s", createdQueueName, messageID) + } + tools.PrintResource(t, message) + } +} + +func TestDeleteMessagesIDs(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-718613007343" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + CreateMessage(t, client, createdQueueName) + CreateMessage(t, client, createdQueueName) + + // Use a different client/clientID in order to see messages on the Queue + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + + client, err = clients.NewMessagingV2Client(clientID) + + listOpts := messages.ListOpts{} + + var messageIDs []string + + pager := messages.List(client, createdQueueName, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, err := messages.ExtractMessages(page) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + messageIDs = append(messageIDs, message.ID) + tools.PrintResource(t, message) + } + + return true, nil + }) + + deleteOpts := messages.DeleteMessagesOpts{ + IDs: messageIDs, + } + + t.Logf("Attempting to delete messages: %v", messageIDs) + deleteErr := messages.DeleteMessages(client, createdQueueName, deleteOpts).ExtractErr() + if deleteErr != nil { + t.Fatalf("Unable to delete messages: %v", deleteErr) + } + + t.Logf("Attempting to list messages.") + messageList, err := ListMessages(t, client, createdQueueName) + + if len(messageList) > 0 { + t.Fatalf("Did not delete all specified messages in the queue.") + } +} + +func TestDeleteMessagesPop(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-718613007343" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + for i := 0; i < 5; i++ { + CreateMessage(t, client, createdQueueName) + } + + // Use a different client/clientID in order to see messages on the Queue + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + + client, err = clients.NewMessagingV2Client(clientID) + + messageList, err := ListMessages(t, client, createdQueueName) + + messagesNumber := len(messageList) + popNumber := 3 + + PopOpts := messages.PopMessagesOpts{ + Pop: popNumber, + } + + t.Logf("Attempting to Pop last %v messages.", popNumber) + popMessages, deleteErr := messages.PopMessages(client, createdQueueName, PopOpts).Extract() + if deleteErr != nil { + t.Fatalf("Unable to Pop messages: %v", deleteErr) + } + + tools.PrintResource(t, popMessages) + + messageList, err = ListMessages(t, client, createdQueueName) + if len(messageList) != messagesNumber-popNumber { + t.Fatalf("Unable to Pop specified number of messages.") + } +} + +func TestDeleteMessage(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-718613007343" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + CreateMessage(t, client, createdQueueName) + + // Use a different client/clientID in order to see messages on the Queue + clientID = "3381af92-2b9e-11e3-b191-71861300734d" + client, err = clients.NewMessagingV2Client(clientID) + + listOpts := messages.ListOpts{} + + var messageIDs []string + + pager := messages.List(client, createdQueueName, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, err := messages.ExtractMessages(page) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + messageIDs = append(messageIDs, message.ID) + } + + return true, nil + }) + + for _, messageID := range messageIDs { + t.Logf("Attempting to delete message from queue %s: %s", createdQueueName, messageID) + deleteOpts := messages.DeleteOpts{} + deleteErr := messages.Delete(client, createdQueueName, messageID, deleteOpts).ExtractErr() + if deleteErr != nil { + t.Fatalf("Unable to delete message from queue %s: %s", createdQueueName, messageID) + } else { + t.Logf("Successfully deleted message: %s", messageID) + } + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/messaging.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/messaging.go new file mode 100644 index 000000000000..f782770b704e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/messaging.go @@ -0,0 +1,173 @@ +package v2 + +import ( + "strings" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/messaging/v2/claims" + "github.com/gophercloud/gophercloud/openstack/messaging/v2/messages" + "github.com/gophercloud/gophercloud/openstack/messaging/v2/queues" + "github.com/gophercloud/gophercloud/pagination" +) + +func CreateQueue(t *testing.T, client *gophercloud.ServiceClient) (string, error) { + queueName := tools.RandomString("ACPTTEST", 5) + + t.Logf("Attempting to create Queue: %s", queueName) + + createOpts := queues.CreateOpts{ + QueueName: queueName, + MaxMessagesPostSize: 262143, + DefaultMessageTTL: 3700, + DefaultMessageDelay: 25, + DeadLetterQueueMessagesTTL: 3500, + MaxClaimCount: 10, + Extra: map[string]interface{}{"description": "Test Queue for Gophercloud acceptance tests."}, + } + + createErr := queues.Create(client, createOpts).ExtractErr() + if createErr != nil { + t.Fatalf("Unable to create Queue: %v", createErr) + } + + GetQueue(t, client, queueName) + + t.Logf("Created Queue: %s", queueName) + return queueName, nil +} + +func DeleteQueue(t *testing.T, client *gophercloud.ServiceClient, queueName string) { + t.Logf("Attempting to delete Queue: %s", queueName) + err := queues.Delete(client, queueName).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete Queue %s: %v", queueName, err) + } + + t.Logf("Deleted Queue: %s", queueName) +} + +func GetQueue(t *testing.T, client *gophercloud.ServiceClient, queueName string) (queues.QueueDetails, error) { + t.Logf("Attempting to get Queue: %s", queueName) + queue, err := queues.Get(client, queueName).Extract() + if err != nil { + t.Fatalf("Unable to get Queue %s: %v", queueName, err) + } + return queue, nil +} + +func CreateShare(t *testing.T, client *gophercloud.ServiceClient, queueName string) (queues.QueueShare, error) { + t.Logf("Attempting to create share for queue: %s", queueName) + + shareOpts := queues.ShareOpts{ + Paths: []queues.SharePath{queues.PathMessages}, + Methods: []queues.ShareMethod{queues.MethodPost}, + } + + share, err := queues.Share(client, queueName, shareOpts).Extract() + + return share, err +} + +func CreateMessage(t *testing.T, client *gophercloud.ServiceClient, queueName string) (messages.ResourceList, error) { + t.Logf("Attempting to add message to Queue: %s", queueName) + createOpts := messages.BatchCreateOpts{ + messages.CreateOpts{ + TTL: 300, + Body: map[string]interface{}{"Key": tools.RandomString("ACPTTEST", 8)}, + }, + } + + resource, err := messages.Create(client, queueName, createOpts).Extract() + if err != nil { + t.Fatalf("Unable to add message to queue %s: %v", queueName, err) + } else { + t.Logf("Successfully added message to queue: %s", queueName) + } + + return resource, err +} + +func ListMessages(t *testing.T, client *gophercloud.ServiceClient, queueName string) ([]messages.Message, error) { + listOpts := messages.ListOpts{} + var allMessages []messages.Message + var listErr error + + t.Logf("Attempting to list messages on queue: %s", queueName) + pager := messages.List(client, queueName, listOpts) + err := pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, listErr = messages.ExtractMessages(page) + if listErr != nil { + t.Fatalf("Unable to extract messages: %v", listErr) + } + + for _, message := range allMessages { + tools.PrintResource(t, message) + } + + return true, nil + }) + return allMessages, err +} + +func CreateClaim(t *testing.T, client *gophercloud.ServiceClient, queueName string) ([]claims.Messages, error) { + createOpts := claims.CreateOpts{} + + t.Logf("Attempting to create claim on queue: %s", queueName) + claimedMessages, err := claims.Create(client, queueName, createOpts).Extract() + tools.PrintResource(t, claimedMessages) + if err != nil { + t.Fatalf("Unable to create claim: %v", err) + } + + return claimedMessages, err +} + +func GetClaim(t *testing.T, client *gophercloud.ServiceClient, queueName string, claimID string) (*claims.Claim, error) { + t.Logf("Attempting to get claim: %s", claimID) + claim, err := claims.Get(client, queueName, claimID).Extract() + if err != nil { + t.Fatalf("Unable to get claim: %s", claimID) + } + + return claim, err +} + +func DeleteClaim(t *testing.T, client *gophercloud.ServiceClient, queueName string, claimID string) error { + t.Logf("Attempting to delete claim: %s", claimID) + err := claims.Delete(client, queueName, claimID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete claim: %s", claimID) + } + t.Logf("Sucessfully deleted claim: %s", claimID) + + return err +} + +func ExtractIDs(claim []claims.Messages) ([]string, []string) { + var claimIDs []string + var messageID []string + + for _, msg := range claim { + parts := strings.Split(msg.Href, "?claim_id=") + if len(parts) == 2 { + pieces := strings.Split(parts[0], "/") + if len(pieces) > 0 { + messageID = append(messageID, pieces[len(pieces)-1]) + } + claimIDs = append(claimIDs, parts[1]) + } + } + encountered := map[string]bool{} + for v := range claimIDs { + encountered[claimIDs[v]] = true + } + + var uniqueClaimIDs []string + + for key := range encountered { + uniqueClaimIDs = append(uniqueClaimIDs, key) + } + return uniqueClaimIDs, messageID +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/queue_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/queue_test.go new file mode 100644 index 000000000000..166f46e83dce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/messaging/v2/queue_test.go @@ -0,0 +1,154 @@ +// +build acceptance messaging queues + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/messaging/v2/queues" + "github.com/gophercloud/gophercloud/pagination" +) + +func TestCRUDQueues(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734d" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + createdQueue, err := queues.Get(client, createdQueueName).Extract() + + tools.PrintResource(t, createdQueue) + tools.PrintResource(t, createdQueue.Extra) + + updateOpts := queues.BatchUpdateOpts{ + queues.UpdateOpts{ + Op: "replace", + Path: "/metadata/_max_claim_count", + Value: 15, + }, + queues.UpdateOpts{ + Op: "replace", + Path: "/metadata/description", + Value: "Updated description for queues acceptance test.", + }, + } + + t.Logf("Attempting to update Queue: %s", createdQueueName) + updateResult, updateErr := queues.Update(client, createdQueueName, updateOpts).Extract() + if updateErr != nil { + t.Fatalf("Unable to update Queue %s: %v", createdQueueName, updateErr) + } + + updatedQueue, err := GetQueue(t, client, createdQueueName) + + tools.PrintResource(t, updateResult) + tools.PrintResource(t, updatedQueue) + tools.PrintResource(t, updatedQueue.Extra) +} + +func TestListQueues(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734d" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + firstQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, firstQueueName) + + secondQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, secondQueueName) + + listOpts := queues.ListOpts{ + Limit: 10, + Detailed: true, + } + + pager := queues.List(client, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allQueues, err := queues.ExtractQueues(page) + if err != nil { + t.Fatalf("Unable to extract Queues: %v", err) + } + + for _, queue := range allQueues { + tools.PrintResource(t, queue) + } + + return true, nil + }) +} + +func TestStatQueue(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734c" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + createdQueueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, createdQueueName) + + queueStats, err := queues.GetStats(client, createdQueueName).Extract() + if err != nil { + t.Fatalf("Unable to stat queue: %v", err) + } + + tools.PrintResource(t, queueStats) +} + +func TestShare(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734c" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + queueName, err := CreateQueue(t, client) + if err != nil { + t.Logf("Unable to create queue for share.") + } + defer DeleteQueue(t, client, queueName) + + t.Logf("Attempting to create share for queue: %s", queueName) + share, shareErr := CreateShare(t, client, queueName) + if shareErr != nil { + t.Fatalf("Unable to create share: %v", shareErr) + } + + tools.PrintResource(t, share) +} + +func TestPurge(t *testing.T) { + clientID := "3381af92-2b9e-11e3-b191-71861300734c" + + client, err := clients.NewMessagingV2Client(clientID) + if err != nil { + t.Fatalf("Unable to create a messaging service client: %v", err) + } + + queueName, err := CreateQueue(t, client) + defer DeleteQueue(t, client, queueName) + + purgeOpts := queues.PurgeOpts{ + ResourceTypes: []queues.PurgeResource{ + queues.ResourceMessages, + }, + } + + t.Logf("Attempting to purge queue: %s", queueName) + purgeErr := queues.Purge(client, queueName, purgeOpts).ExtractErr() + if purgeErr != nil { + t.Fatalf("Unable to purge queue %s: %v", queueName, purgeErr) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/apiversion_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/apiversion_test.go index c6f8f261b35e..2fb4a23210db 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/apiversion_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/apiversion_test.go @@ -39,7 +39,7 @@ func TestAPIResourcesList(t *testing.T) { allPages, err := apiversions.ListVersionResources(client, "v2.0").AllPages() if err != nil { - t.Fatalf("Unable to list api version reosources: %v", err) + t.Fatalf("Unable to list api version resources: %v", err) } allVersionResources, err := apiversions.ExtractVersionResources(allPages) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/agents_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/agents_test.go new file mode 100644 index 000000000000..972b78451339 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/agents_test.go @@ -0,0 +1,28 @@ +// +build acceptance networking agents + +package agents + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestAgentsRead(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + allPages, err := agents.List(client, agents.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + + allAgents, err := agents.ExtractAgents(allPages) + th.AssertNoErr(t, err) + + for _, agent := range allAgents { + t.Logf("Retrieved Networking V2 agent: %s", agent.ID) + tools.PrintResource(t, agent) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/doc.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/doc.go new file mode 100644 index 000000000000..0a76caef2611 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/agents/doc.go @@ -0,0 +1,2 @@ +// agents acceptance tests +package agents diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/attributestags_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/attributestags_test.go new file mode 100644 index 000000000000..96dc418bd8b4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/attributestags_test.go @@ -0,0 +1,131 @@ +// +build acceptance networking tags + +package extensions + +import ( + "fmt" + "sort" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func createNetworkWithTags(t *testing.T, client *gophercloud.ServiceClient, tags []string) (network *networks.Network) { + // Create Network + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + + tagReplaceAllOpts := attributestags.ReplaceAllOpts{ + // docs say list of tags, but it's a set e.g no duplicates + Tags: tags, + } + rtags, err := attributestags.ReplaceAll(client, "networks", network.ID, tagReplaceAllOpts).Extract() + th.AssertNoErr(t, err) + sort.Strings(rtags) // Ensure ordering, older OpenStack versions aren't sorted... + th.AssertDeepEquals(t, rtags, tags) + + // Verify the tags are also set in the object Get response + gnetwork, err := networks.Get(client, network.ID).Extract() + th.AssertNoErr(t, err) + rtags = gnetwork.Tags + sort.Strings(rtags) + th.AssertDeepEquals(t, rtags, tags) + return network +} + +func TestTags(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create Network + network := createNetworkWithTags(t, client, []string{"a", "b", "c"}) + defer networking.DeleteNetwork(t, client, network.ID) + + // Add a tag + err = attributestags.Add(client, "networks", network.ID, "d").ExtractErr() + th.AssertNoErr(t, err) + + // Delete a tag + err = attributestags.Delete(client, "networks", network.ID, "a").ExtractErr() + th.AssertNoErr(t, err) + + // Verify expected tags are set in the List response + tags, err := attributestags.List(client, "networks", network.ID).Extract() + th.AssertNoErr(t, err) + sort.Strings(tags) + th.AssertDeepEquals(t, []string{"b", "c", "d"}, tags) + + // Confirm tags exist/don't exist + exists, err := attributestags.Confirm(client, "networks", network.ID, "d").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, true, exists) + noexists, err := attributestags.Confirm(client, "networks", network.ID, "a").Extract() + th.AssertEquals(t, false, noexists) + + // Delete all tags + err = attributestags.DeleteAll(client, "networks", network.ID).ExtractErr() + th.AssertNoErr(t, err) + tags, err = attributestags.List(client, "networks", network.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(tags)) +} + +func listNetworkWithTagOpts(t *testing.T, client *gophercloud.ServiceClient, listOpts networks.ListOpts) (ids []string) { + allPages, err := networks.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + allNetworks, err := networks.ExtractNetworks(allPages) + th.AssertNoErr(t, err) + for _, network := range allNetworks { + ids = append(ids, network.ID) + } + return ids +} + +func TestQueryByTags(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create a random tag to ensure we only get networks created + // by this test + testtag := tools.RandomString("zzz-tag-", 8) + + // Create Networks + network1 := createNetworkWithTags( + t, client, []string{"a", "b", "c", testtag}) + defer networking.DeleteNetwork(t, client, network1.ID) + + network2 := createNetworkWithTags( + t, client, []string{"b", "c", "d", testtag}) + defer networking.DeleteNetwork(t, client, network2.ID) + + // Tags - Networks that match all tags will be returned + listOpts := networks.ListOpts{ + Tags: fmt.Sprintf("a,b,c,%s", testtag)} + ids := listNetworkWithTagOpts(t, client, listOpts) + th.AssertDeepEquals(t, []string{network1.ID}, ids) + + // TagsAny - Networks that match any tag will be returned + listOpts = networks.ListOpts{ + SortKey: "id", SortDir: "asc", + TagsAny: fmt.Sprintf("a,b,c,%s", testtag)} + ids = listNetworkWithTagOpts(t, client, listOpts) + expected_ids := []string{network1.ID, network2.ID} + sort.Strings(expected_ids) + th.AssertDeepEquals(t, expected_ids, ids) + + // NotTags - Networks that match all tags will be excluded + listOpts = networks.ListOpts{Tags: testtag, NotTags: "a,b,c"} + ids = listNetworkWithTagOpts(t, client, listOpts) + th.AssertDeepEquals(t, []string{network2.ID}, ids) + + // NotTagsAny - Networks that match any tag will be excluded. + listOpts = networks.ListOpts{Tags: testtag, NotTagsAny: "d"} + ids = listNetworkWithTagOpts(t, client, listOpts) + th.AssertDeepEquals(t, []string{network1.ID}, ids) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns.go new file mode 100644 index 000000000000..dba9dfed9bd1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns.go @@ -0,0 +1,135 @@ +package dns + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// PortWithDNSExt represents a port with the DNS fields +type PortWithDNSExt struct { + ports.Port + dns.PortDNSExt +} + +// FloatingIPWithDNSExt represents a floating IP with the DNS fields +type FloatingIPWithDNSExt struct { + floatingips.FloatingIP + dns.FloatingIPDNSExt +} + +// NetworkWithDNSExt represents a network with the DNS fields +type NetworkWithDNSExt struct { + networks.Network + dns.NetworkDNSExt +} + +// CreatePortDNS will create a port with a DNS name on the specified subnet. An +// error will be returned if the port could not be created. +func CreatePortDNS(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID, dnsName string) (*PortWithDNSExt, error) { + portName := tools.RandomString("TESTACC-", 8) + portDescription := tools.RandomString("TESTACC-PORT-DESC-", 8) + iFalse := true + + t.Logf("Attempting to create port: %s", portName) + + portCreateOpts := ports.CreateOpts{ + NetworkID: networkID, + Name: portName, + Description: portDescription, + AdminStateUp: &iFalse, + FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, + } + + createOpts := dns.PortCreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + DNSName: dnsName, + } + + var port PortWithDNSExt + + err := ports.Create(client, createOpts).ExtractInto(&port) + if err != nil { + return &port, err + } + + t.Logf("Successfully created port: %s", portName) + + th.AssertEquals(t, port.Name, portName) + th.AssertEquals(t, port.Description, portDescription) + th.AssertEquals(t, port.DNSName, dnsName) + + return &port, nil +} + +// CreateFloatingIPDNS creates a floating IP with the DNS extension on a given +// network and port. An error will be returned if the creation failed. +func CreateFloatingIPDNS(t *testing.T, client *gophercloud.ServiceClient, networkID, portID, dnsName, dnsDomain string) (*FloatingIPWithDNSExt, error) { + t.Logf("Attempting to create floating IP on port: %s", portID) + + fipDescription := "Test floating IP" + fipCreateOpts := &floatingips.CreateOpts{ + Description: fipDescription, + FloatingNetworkID: networkID, + PortID: portID, + } + + createOpts := dns.FloatingIPCreateOptsExt{ + CreateOptsBuilder: fipCreateOpts, + DNSName: dnsName, + DNSDomain: dnsDomain, + } + + var floatingIP FloatingIPWithDNSExt + err := floatingips.Create(client, createOpts).ExtractInto(&floatingIP) + if err != nil { + return &floatingIP, err + } + + t.Logf("Created floating IP.") + + th.AssertEquals(t, floatingIP.Description, fipDescription) + th.AssertEquals(t, floatingIP.FloatingNetworkID, networkID) + th.AssertEquals(t, floatingIP.PortID, portID) + th.AssertEquals(t, floatingIP.DNSName, dnsName) + th.AssertEquals(t, floatingIP.DNSDomain, dnsDomain) + + return &floatingIP, err +} + +// CreateNetworkDNS will create a network with a DNS domain set. +// An error will be returned if the network could not be created. +func CreateNetworkDNS(t *testing.T, client *gophercloud.ServiceClient, dnsDomanin string) (*NetworkWithDNSExt, error) { + networkName := tools.RandomString("TESTACC-", 8) + networkCreateOpts := networks.CreateOpts{ + Name: networkName, + AdminStateUp: gophercloud.Enabled, + } + + createOpts := dns.NetworkCreateOptsExt{ + CreateOptsBuilder: networkCreateOpts, + DNSDomain: dnsDomanin, + } + + t.Logf("Attempting to create network: %s", networkName) + + var network NetworkWithDNSExt + + err := networks.Create(client, createOpts).ExtractInto(&network) + if err != nil { + return &network, err + } + + t.Logf("Successfully created network.") + + th.AssertEquals(t, network.Name, networkName) + th.AssertEquals(t, network.DNSDomain, dnsDomanin) + + return &network, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns_test.go new file mode 100644 index 000000000000..0cb62551c510 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/dns/dns_test.go @@ -0,0 +1,266 @@ +// +build acceptance networking + +package dns + +import ( + "os" + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/common/extensions" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestDNSPortCRUDL(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + extension, err := extensions.Get(client, "dns-integration").Extract() + if err != nil { + t.Skip("This test requires dns-integration Neutron extension") + } + tools.PrintResource(t, extension) + + // Create Network + networkDNSDomain := "local." + network, err := CreateNetworkDNS(t, client, networkDNSDomain) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + // Create Subnet + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, client, subnet.ID) + + // Create port + portDNSName := "port" + port, err := CreatePortDNS(t, client, network.ID, subnet.ID, portDNSName) + th.AssertNoErr(t, err) + defer networking.DeletePort(t, client, port.ID) + + tools.PrintResource(t, port) + + if os.Getenv("OS_BRANCH") == "stable/mitaka" { + // List port successfully + var listOpts ports.ListOptsBuilder + listOpts = dns.PortListOptsExt{ + ListOptsBuilder: ports.ListOpts{}, + DNSName: portDNSName, + } + var listedPorts []PortWithDNSExt + i := 0 + err = ports.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + i++ + err := ports.ExtractPortsInto(page, &listedPorts) + if err != nil { + t.Errorf("Failed to extract ports: %v", err) + return false, err + } + + tools.PrintResource(t, listedPorts) + + th.AssertEquals(t, 1, len(listedPorts)) + th.CheckDeepEquals(t, *port, listedPorts[0]) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, i) + + // List port unsuccessfully + listOpts = dns.PortListOptsExt{ + ListOptsBuilder: ports.ListOpts{}, + DNSName: "foo", + } + i = 0 + err = ports.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + i++ + err := ports.ExtractPortsInto(page, &listedPorts) + if err != nil { + t.Errorf("Failed to extract ports: %v", err) + return false, err + } + + tools.PrintResource(t, listedPorts) + + th.AssertEquals(t, 1, len(listedPorts)) + th.CheckDeepEquals(t, *port, listedPorts[0]) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, i) + } + + // Get port + var getPort PortWithDNSExt + err = ports.Get(client, port.ID).ExtractInto(&getPort) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getPort) + th.AssertDeepEquals(t, *port, getPort) + + // Update port + newPortName := "" + newPortDescription := "" + newDNSName := "" + portUpdateOpts := ports.UpdateOpts{ + Name: &newPortName, + Description: &newPortDescription, + } + var updateOpts ports.UpdateOptsBuilder + updateOpts = dns.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + DNSName: &newDNSName, + } + + var newPort PortWithDNSExt + err = ports.Update(client, port.ID, updateOpts).ExtractInto(&newPort) + th.AssertNoErr(t, err) + + tools.PrintResource(t, newPort) + th.AssertEquals(t, newPort.Description, newPortName) + th.AssertEquals(t, newPort.Description, newPortDescription) + th.AssertEquals(t, newPort.DNSName, newDNSName) + + // Get updated port + var getNewPort PortWithDNSExt + err = ports.Get(client, port.ID).ExtractInto(&getNewPort) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getNewPort) + // workaround for update race condition + newPort.DNSAssignment = nil + getNewPort.DNSAssignment = nil + th.AssertDeepEquals(t, newPort, getNewPort) +} + +func TestDNSFloatingIPCRDL(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + extension, err := extensions.Get(client, "dns-integration").Extract() + if err != nil { + t.Skip("This test requires dns-integration Neutron extension") + } + tools.PrintResource(t, extension) + + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + // Create Network + networkDNSDomain := "local." + network, err := CreateNetworkDNS(t, client, networkDNSDomain) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + // Create Subnet + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, client, subnet.ID) + + // Create Router + router, err := layer3.CreateExternalRouter(t, client) + th.AssertNoErr(t, err) + defer layer3.DeleteRouter(t, client, router.ID) + + // Create router interface + routerPort, err := networking.CreatePort(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + _, err = layer3.CreateRouterInterface(t, client, routerPort.ID, router.ID) + th.AssertNoErr(t, err) + defer layer3.DeleteRouterInterface(t, client, routerPort.ID, router.ID) + + // Create port + portDNSName := "port" + port, err := CreatePortDNS(t, client, network.ID, subnet.ID, portDNSName) + th.AssertNoErr(t, err) + defer networking.DeletePort(t, client, port.ID) + + tools.PrintResource(t, port) + + // Create floating IP + fipDNSName := "fip" + fipDNSDomain := "local." + fip, err := CreateFloatingIPDNS(t, client, choices.ExternalNetworkID, port.ID, fipDNSName, fipDNSDomain) + th.AssertNoErr(t, err) + defer layer3.DeleteFloatingIP(t, client, fip.ID) + + // Get floating IP + var getFip FloatingIPWithDNSExt + err = floatingips.Get(client, fip.ID).ExtractInto(&getFip) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getFip) + th.AssertDeepEquals(t, *fip, getFip) +} + +func TestDNSNetwork(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + extension, err := extensions.Get(client, "dns-integration").Extract() + if err != nil { + t.Skip("This test requires dns-integration Neutron extension") + } + tools.PrintResource(t, extension) + + // Create Network + networkDNSDomain := "local." + network, err := CreateNetworkDNS(t, client, networkDNSDomain) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + // Get network + var getNetwork NetworkWithDNSExt + err = networks.Get(client, network.ID).ExtractInto(&getNetwork) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getNetwork) + th.AssertDeepEquals(t, *network, getNetwork) + + // Update network + newNetworkName := "" + newNetworkDescription := "" + newNetworkDNSDomain := "" + networkUpdateOpts := networks.UpdateOpts{ + Name: &newNetworkName, + Description: &newNetworkDescription, + } + var updateOpts networks.UpdateOptsBuilder + updateOpts = dns.NetworkUpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + DNSDomain: &newNetworkDNSDomain, + } + + var newNetwork NetworkWithDNSExt + err = networks.Update(client, network.ID, updateOpts).ExtractInto(&newNetwork) + th.AssertNoErr(t, err) + + tools.PrintResource(t, newNetwork) + th.AssertEquals(t, newNetwork.Description, newNetworkName) + th.AssertEquals(t, newNetwork.Description, newNetworkDescription) + th.AssertEquals(t, newNetwork.DNSDomain, newNetworkDNSDomain) + + // Get updated network + var getNewNetwork NetworkWithDNSExt + err = networks.Get(client, network.ID).ExtractInto(&getNewNetwork) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getNewNetwork) + th.AssertDeepEquals(t, newNetwork, getNewNetwork) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go index 06068322e133..a05d0ed78fe2 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go @@ -10,12 +10,14 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateExternalNetwork will create an external network. An error will be // returned if the creation failed. func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*networks.Network, error) { networkName := tools.RandomString("TESTACC-", 8) + networkDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create external network: %s", networkName) @@ -24,6 +26,7 @@ func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*ne networkCreateOpts := networks.CreateOpts{ Name: networkName, + Description: networkDescription, AdminStateUp: &adminStateUp, } @@ -39,6 +42,9 @@ func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*ne t.Logf("Created external network: %s", networkName) + th.AssertEquals(t, network.Name, networkName) + th.AssertEquals(t, network.Description, networkDescription) + return network, nil } @@ -46,6 +52,7 @@ func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*ne // attached. An error will be returned if the port could not be created. func CreatePortWithSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID, secGroupID string) (*ports.Port, error) { portName := tools.RandomString("TESTACC-", 8) + portDescription := tools.RandomString("TESTACC-DESC-", 8) iFalse := false t.Logf("Attempting to create port: %s", portName) @@ -53,6 +60,7 @@ func CreatePortWithSecurityGroup(t *testing.T, client *gophercloud.ServiceClient createOpts := ports.CreateOpts{ NetworkID: networkID, Name: portName, + Description: portDescription, AdminStateUp: &iFalse, FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, SecurityGroups: &[]string{secGroupID}, @@ -65,6 +73,10 @@ 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) + return port, nil } @@ -72,11 +84,13 @@ func CreatePortWithSecurityGroup(t *testing.T, client *gophercloud.ServiceClient // An error will be returned if one was failed to be created. func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*groups.SecGroup, error) { secGroupName := tools.RandomString("TESTACC-", 8) + secGroupDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create security group: %s", secGroupName) createOpts := groups.CreateOpts{ - Name: secGroupName, + Name: secGroupName, + Description: secGroupDescription, } secGroup, err := groups.Create(client, createOpts).Extract() @@ -86,6 +100,9 @@ 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) + return secGroup, nil } @@ -95,10 +112,12 @@ func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*grou func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, secGroupID string) (*rules.SecGroupRule, error) { t.Logf("Attempting to create security group rule in group: %s", secGroupID) + description := "Rule description" fromPort := tools.RandomInt(80, 89) toPort := tools.RandomInt(90, 99) createOpts := rules.CreateOpts{ + Description: description, Direction: "ingress", EtherType: "IPv4", SecGroupID: secGroupID, @@ -114,6 +133,9 @@ func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, se t.Logf("Created security group rule: %s", rule.ID) + th.AssertEquals(t, rule.SecGroupID, secGroupID) + th.AssertEquals(t, rule.Description, description) + return rule, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go index 89d378ee7c6d..4779491fda49 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/firewall_test.go @@ -10,128 +10,104 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestFirewallList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } - - allPages, err := firewalls.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list firewalls: %v", err) - } - - allFirewalls, err := firewalls.ExtractFirewalls(allPages) - if err != nil { - t.Fatalf("Unable to extract firewalls: %v", err) - } - - for _, firewall := range allFirewalls { - tools.PrintResource(t, firewall) - } -} - func TestFirewallCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) router, err := layer3.CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router.ID) rule, err := CreateRule(t, client) - if err != nil { - t.Fatalf("Unable to create rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRule(t, client, rule.ID) tools.PrintResource(t, rule) policy, err := CreatePolicy(t, client, rule.ID) - if err != nil { - t.Fatalf("Unable to create policy: %v", err) - } + th.AssertNoErr(t, err) defer DeletePolicy(t, client, policy.ID) tools.PrintResource(t, policy) firewall, err := CreateFirewall(t, client, policy.ID) - if err != nil { - t.Fatalf("Unable to create firewall: %v", err) - } + th.AssertNoErr(t, err) defer DeleteFirewall(t, client, firewall.ID) tools.PrintResource(t, firewall) - updateOpts := firewalls.UpdateOpts{ + fwName := "" + fwDescription := "" + fwUpdateOpts := firewalls.UpdateOpts{ + Name: &fwName, + Description: &fwDescription, PolicyID: policy.ID, - Description: "Some firewall description", } - _, err = firewalls.Update(client, firewall.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update firewall: %v", err) - } + _, err = firewalls.Update(client, firewall.ID, fwUpdateOpts).Extract() + th.AssertNoErr(t, err) newFirewall, err := firewalls.Get(client, firewall.ID).Extract() - if err != nil { - t.Fatalf("Unable to get firewall: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newFirewall) + th.AssertEquals(t, newFirewall.Name, fwName) + th.AssertEquals(t, newFirewall.Description, fwDescription) + th.AssertEquals(t, newFirewall.PolicyID, policy.ID) + + allPages, err := firewalls.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allFirewalls, err := firewalls.ExtractFirewalls(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, firewall := range allFirewalls { + if firewall.ID == newFirewall.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } func TestFirewallCRUDRouter(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) router, err := layer3.CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router.ID) rule, err := CreateRule(t, client) - if err != nil { - t.Fatalf("Unable to create rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRule(t, client, rule.ID) tools.PrintResource(t, rule) policy, err := CreatePolicy(t, client, rule.ID) - if err != nil { - t.Fatalf("Unable to create policy: %v", err) - } + th.AssertNoErr(t, err) defer DeletePolicy(t, client, policy.ID) tools.PrintResource(t, policy) firewall, err := CreateFirewallOnRouter(t, client, policy.ID, router.ID) - if err != nil { - t.Fatalf("Unable to create firewall: %v", err) - } + th.AssertNoErr(t, err) defer DeleteFirewall(t, client, firewall.ID) tools.PrintResource(t, firewall) router2, err := layer3.CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router2.ID) + description := "Some firewall description" firewallUpdateOpts := firewalls.UpdateOpts{ PolicyID: policy.ID, - Description: "Some firewall description", + Description: &description, } updateOpts := routerinsertion.UpdateOptsExt{ @@ -140,57 +116,44 @@ func TestFirewallCRUDRouter(t *testing.T) { } _, err = firewalls.Update(client, firewall.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update firewall: %v", err) - } + th.AssertNoErr(t, err) newFirewall, err := firewalls.Get(client, firewall.ID).Extract() - if err != nil { - t.Fatalf("Unable to get firewall: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newFirewall) } func TestFirewallCRUDRemoveRouter(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) router, err := layer3.CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router.ID) rule, err := CreateRule(t, client) - if err != nil { - t.Fatalf("Unable to create rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRule(t, client, rule.ID) tools.PrintResource(t, rule) policy, err := CreatePolicy(t, client, rule.ID) - if err != nil { - t.Fatalf("Unable to create policy: %v", err) - } + th.AssertNoErr(t, err) defer DeletePolicy(t, client, policy.ID) tools.PrintResource(t, policy) firewall, err := CreateFirewallOnRouter(t, client, policy.ID, router.ID) - if err != nil { - t.Fatalf("Unable to create firewall: %v", err) - } + th.AssertNoErr(t, err) defer DeleteFirewall(t, client, firewall.ID) tools.PrintResource(t, firewall) + description := "Some firewall description" firewallUpdateOpts := firewalls.UpdateOpts{ PolicyID: policy.ID, - Description: "Some firewall description", + Description: &description, } updateOpts := routerinsertion.UpdateOptsExt{ @@ -199,14 +162,10 @@ func TestFirewallCRUDRemoveRouter(t *testing.T) { } _, err = firewalls.Update(client, firewall.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update firewall: %v", err) - } + th.AssertNoErr(t, err) newFirewall, err := firewalls.Get(client, firewall.ID).Extract() - if err != nil { - t.Fatalf("Unable to get firewall: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newFirewall) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/fwaas.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/fwaas.go index 83aa1a400fc0..66065bd2c816 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/fwaas.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/fwaas.go @@ -11,18 +11,21 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateFirewall will create a Firewaill with a random name and a specified // policy ID. An error will be returned if the firewall could not be created. func CreateFirewall(t *testing.T, client *gophercloud.ServiceClient, policyID string) (*firewalls.Firewall, error) { firewallName := tools.RandomString("TESTACC-", 8) + firewallDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create firewall %s", firewallName) iTrue := true createOpts := firewalls.CreateOpts{ Name: firewallName, + Description: firewallDescription, PolicyID: policyID, AdminStateUp: &iTrue, } @@ -39,6 +42,9 @@ func CreateFirewall(t *testing.T, client *gophercloud.ServiceClient, policyID st t.Logf("Successfully created firewall %s", firewallName) + th.AssertEquals(t, firewall.Name, firewallName) + th.AssertEquals(t, firewall.Description, firewallDescription) + return firewall, nil } @@ -47,12 +53,14 @@ func CreateFirewall(t *testing.T, client *gophercloud.ServiceClient, policyID st // returned if the firewall could not be created. func CreateFirewallOnRouter(t *testing.T, client *gophercloud.ServiceClient, policyID string, routerID string) (*firewalls.Firewall, error) { firewallName := tools.RandomString("TESTACC-", 8) + firewallDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create firewall %s", firewallName) firewallCreateOpts := firewalls.CreateOpts{ - Name: firewallName, - PolicyID: policyID, + Name: firewallName, + Description: firewallDescription, + PolicyID: policyID, } createOpts := routerinsertion.CreateOptsExt{ @@ -72,6 +80,9 @@ func CreateFirewallOnRouter(t *testing.T, client *gophercloud.ServiceClient, pol t.Logf("Successfully created firewall %s", firewallName) + th.AssertEquals(t, firewall.Name, firewallName) + th.AssertEquals(t, firewall.Description, firewallDescription) + return firewall, nil } @@ -79,11 +90,13 @@ func CreateFirewallOnRouter(t *testing.T, client *gophercloud.ServiceClient, pol // rule. An error will be returned if the rule could not be created. func CreatePolicy(t *testing.T, client *gophercloud.ServiceClient, ruleID string) (*policies.Policy, error) { policyName := tools.RandomString("TESTACC-", 8) + policyDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create policy %s", policyName) createOpts := policies.CreateOpts{ - Name: policyName, + Name: policyName, + Description: policyDescription, Rules: []string{ ruleID, }, @@ -96,6 +109,10 @@ func CreatePolicy(t *testing.T, client *gophercloud.ServiceClient, ruleID string t.Logf("Successfully created policy %s", policyName) + th.AssertEquals(t, policy.Name, policyName) + th.AssertEquals(t, policy.Description, policyDescription) + th.AssertEquals(t, len(policy.Rules), 1) + return policy, nil } @@ -129,6 +146,13 @@ func CreateRule(t *testing.T, client *gophercloud.ServiceClient) (*rules.Rule, e t.Logf("Rule %s successfully created", ruleName) + th.AssertEquals(t, rule.Name, ruleName) + th.AssertEquals(t, rule.Protocol, rules.ProtocolTCP) + th.AssertEquals(t, rule.SourceIPAddress, sourceAddress) + th.AssertEquals(t, rule.SourcePort, sourcePort) + th.AssertEquals(t, rule.DestinationIPAddress, destinationAddress) + th.AssertEquals(t, rule.DestinationPort, destinationPort) + return rule, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go deleted file mode 100644 index 206bf3313a8d..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package fwaas diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go index 3220d821a3c5..ab0d7c9008da 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/policy_test.go @@ -8,64 +8,54 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestPolicyList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } - - allPages, err := policies.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list policies: %v", err) - } - - allPolicies, err := policies.ExtractPolicies(allPages) - if err != nil { - t.Fatalf("Unable to extract policies: %v", err) - } - - for _, policy := range allPolicies { - tools.PrintResource(t, policy) - } -} - func TestPolicyCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) rule, err := CreateRule(t, client) - if err != nil { - t.Fatalf("Unable to create rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRule(t, client, rule.ID) tools.PrintResource(t, rule) policy, err := CreatePolicy(t, client, rule.ID) - if err != nil { - t.Fatalf("Unable to create policy: %v", err) - } + th.AssertNoErr(t, err) defer DeletePolicy(t, client, policy.ID) tools.PrintResource(t, policy) + name := "" + description := "" updateOpts := policies.UpdateOpts{ - Description: "Some policy description", + Name: &name, + Description: &description, } _, err = policies.Update(client, policy.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update policy: %v", err) - } + th.AssertNoErr(t, err) newPolicy, err := policies.Get(client, policy.ID).Extract() - if err != nil { - t.Fatalf("Unable to get policy: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPolicy) + th.AssertEquals(t, newPolicy.Name, name) + th.AssertEquals(t, newPolicy.Description, description) + + allPages, err := policies.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allPolicies, err := policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, policy := range allPolicies { + if policy.ID == newPolicy.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go index 4521a60b81e6..6f5968b30c7a 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/fwaas/rule_test.go @@ -8,39 +8,15 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestRuleList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } - - allPages, err := rules.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list rules: %v", err) - } - - allRules, err := rules.ExtractRules(allPages) - if err != nil { - t.Fatalf("Unable to extract rules: %v", err) - } - - for _, rule := range allRules { - tools.PrintResource(t, rule) - } -} - func TestRuleCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) rule, err := CreateRule(t, client) - if err != nil { - t.Fatalf("Unable to create rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRule(t, client, rule.ID) tools.PrintResource(t, rule) @@ -51,14 +27,25 @@ func TestRuleCRUD(t *testing.T) { } _, err = rules.Update(client, rule.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update rule: %v", err) - } + th.AssertNoErr(t, err) newRule, err := rules.Get(client, rule.ID).Extract() - if err != nil { - t.Fatalf("Unable to get rule: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newRule) + + allPages, err := rules.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allRules, err := rules.ExtractRules(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, rule := range allRules { + if rule.ID == newRule.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go new file mode 100644 index 000000000000..4d7cff353806 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go @@ -0,0 +1,53 @@ +// +build acceptance networking layer3 addressscopes + +package layer3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestAddressScopesCRUD(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create an address-scope + addressScope, err := CreateAddressScope(t, client) + th.AssertNoErr(t, err) + defer DeleteAddressScope(t, client, addressScope.ID) + + tools.PrintResource(t, addressScope) + + newName := tools.RandomString("TESTACC-", 8) + updateOpts := &addressscopes.UpdateOpts{ + Name: &newName, + } + + _, err = addressscopes.Update(client, addressScope.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + newAddressScope, err := addressscopes.Get(client, addressScope.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newAddressScope) + th.AssertEquals(t, newAddressScope.Name, newName) + + allPages, err := addressscopes.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allAddressScopes, err := addressscopes.ExtractAddressScopes(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, addressScope := range allAddressScopes { + if addressScope.ID == newAddressScope.ID { + found = true + } + } + + th.AssertEquals(t, found, true) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go index b38d7283ca05..ac50e9efaf1b 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go @@ -3,133 +3,192 @@ package layer3 import ( - "os" "testing" "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestLayer3FloatingIPsList(t *testing.T) { +func TestLayer3FloatingIPsCreateDelete(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a compute client: %v", err) - } + th.AssertNoErr(t, err) - listOpts := floatingips.ListOpts{ - Status: "DOWN", - } - allPages, err := floatingips.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list floating IPs: %v", 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) + + newFip, err := floatingips.Get(client, fip.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newFip) + + allPages, err := floatingips.List(client, floatingips.ListOpts{}).AllPages() + th.AssertNoErr(t, err) allFIPs, err := floatingips.ExtractFloatingIPs(allPages) - if err != nil { - t.Fatalf("Unable to extract floating IPs: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, fip := range allFIPs { - tools.PrintResource(t, fip) + if fip.ID == newFip.ID { + found = true + } } + + th.AssertEquals(t, found, true) } -func TestLayer3FloatingIPsCreateDelete(t *testing.T) { +func TestLayer3FloatingIPsExternalCreateDelete(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a compute client: %v", err) - } + th.AssertNoErr(t, err) choices, err := clients.AcceptanceTestChoicesFromEnv() - if err != nil { - t.Fatalf("Unable to get choices: %v", err) - } + th.AssertNoErr(t, err) - netid, err := networks.IDFromName(client, choices.NetworkName) - if err != nil { - t.Fatalf("Unable to find network id: %v", err) - } + // Create Network + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) - subnet, err := networking.CreateSubnet(t, client, netid) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, client, subnet.ID) router, err := CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRouter(t, client, router.ID) - port, err := networking.CreatePort(t, client, netid, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + port, err := networking.CreatePort(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + // not required, since "DeleteRouterInterface" above removes the port + // defer networking.DeletePort(t, client, port.ID) _, err = CreateRouterInterface(t, client, port.ID, router.ID) - if err != nil { - t.Fatalf("Unable to create router interface: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRouterInterface(t, client, port.ID, router.ID) fip, err := CreateFloatingIP(t, client, choices.ExternalNetworkID, port.ID) - if err != nil { - t.Fatalf("Unable to create floating IP: %v", err) - } + th.AssertNoErr(t, err) defer DeleteFloatingIP(t, client, fip.ID) newFip, err := floatingips.Get(client, fip.ID).Extract() - if err != nil { - t.Fatalf("Unable to get floating ip: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newFip) // Disassociate the floating IP updateOpts := floatingips.UpdateOpts{ - PortID: nil, + PortID: new(string), + } + + _, err = floatingips.Update(client, fip.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + newFip, err = floatingips.Get(client, fip.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newFip) + + th.AssertEquals(t, newFip.PortID, "") +} + +func TestLayer3FloatingIPsWithFixedIPsExternalCreateDelete(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + // Create Network + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, client, subnet.ID) + + router, err := CreateExternalRouter(t, client) + th.AssertNoErr(t, err) + defer DeleteRouter(t, client, router.ID) + + port, err := networking.CreatePortWithMultipleFixedIPs(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + defer networking.DeletePort(t, client, port.ID) + + var fixedIPs []string + for _, fixedIP := range port.FixedIPs { + fixedIPs = append(fixedIPs, fixedIP.IPAddress) + } + + iface, err := CreateRouterInterfaceOnSubnet(t, client, subnet.ID, router.ID) + th.AssertNoErr(t, err) + defer DeleteRouterInterface(t, client, iface.PortID, router.ID) + + fip, err := CreateFloatingIPWithFixedIP(t, client, choices.ExternalNetworkID, port.ID, fixedIPs[0]) + th.AssertNoErr(t, err) + defer DeleteFloatingIP(t, client, fip.ID) + + newFip, err := floatingips.Get(client, fip.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newFip) + + // Associate the floating IP with another fixed IP + updateOpts := floatingips.UpdateOpts{ + PortID: &port.ID, + FixedIP: fixedIPs[1], } - newFip, err = floatingips.Update(client, fip.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to disassociate floating IP: %v", err) + _, err = floatingips.Update(client, fip.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + newFip, err = floatingips.Get(client, fip.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newFip) + + th.AssertEquals(t, newFip.FixedIP, fixedIPs[1]) + + // Disassociate the floating IP + updateOpts = floatingips.UpdateOpts{ + PortID: new(string), } + + _, err = floatingips.Update(client, fip.ID, updateOpts).Extract() + th.AssertNoErr(t, err) } func TestLayer3FloatingIPsCreateDeleteBySubnetID(t *testing.T) { - username := os.Getenv("OS_USERNAME") - if username != "admin" { - t.Skip("must be admin to run this test") - } + clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a compute client: %v", err) - } + th.AssertNoErr(t, err) choices, err := clients.AcceptanceTestChoicesFromEnv() - if err != nil { - t.Fatalf("Unable to get choices: %v", err) - } + th.AssertNoErr(t, err) listOpts := subnets.ListOpts{ NetworkID: choices.ExternalNetworkID, + IPVersion: 4, } subnetPages, err := subnets.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list subnets: %v", err) - } + th.AssertNoErr(t, err) allSubnets, err := subnets.ExtractSubnets(subnetPages) - if err != nil { - t.Fatalf("Unable to extract subnets: %v", err) - } + th.AssertNoErr(t, err) createOpts := floatingips.CreateOpts{ FloatingNetworkID: choices.ExternalNetworkID, @@ -137,9 +196,7 @@ func TestLayer3FloatingIPsCreateDeleteBySubnetID(t *testing.T) { } fip, err := floatingips.Create(client, createOpts).Extract() - if err != nil { - t.Fatalf("Unable to create floating IP: %v") - } + th.AssertNoErr(t, err) tools.PrintResource(t, fip) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go index 3d017be889a0..abc562c83704 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go @@ -3,12 +3,15 @@ package layer3 import ( "testing" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateFloatingIP creates a floating IP on a given network and port. An error @@ -16,7 +19,9 @@ import ( func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, networkID, portID string) (*floatingips.FloatingIP, error) { t.Logf("Attempting to create floating IP on port: %s", portID) + fipDescription := "Test floating IP" createOpts := &floatingips.CreateOpts{ + Description: fipDescription, FloatingNetworkID: networkID, PortID: portID, } @@ -28,6 +33,34 @@ func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, networkID t.Logf("Created floating IP.") + th.AssertEquals(t, floatingIP.Description, fipDescription) + + return floatingIP, err +} + +// CreateFloatingIPWithFixedIP creates a floating IP on a given network and port with a +// defined fixed IP. An error will be returned if the creation failed. +func CreateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient, networkID, portID, fixedIP string) (*floatingips.FloatingIP, error) { + t.Logf("Attempting to create floating IP on port: %s and address: %s", portID, fixedIP) + + fipDescription := "Test floating IP" + createOpts := &floatingips.CreateOpts{ + Description: fipDescription, + FloatingNetworkID: networkID, + PortID: portID, + FixedIP: fixedIP, + } + + floatingIP, err := floatingips.Create(client, createOpts).Extract() + if err != nil { + return floatingIP, err + } + + t.Logf("Created floating IP.") + + th.AssertEquals(t, floatingIP.Description, fipDescription) + th.AssertEquals(t, floatingIP.FixedIP, fixedIP) + return floatingIP, err } @@ -42,6 +75,7 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou } routerName := tools.RandomString("TESTACC-", 8) + routerDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create external router: %s", routerName) @@ -54,6 +88,7 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou createOpts := routers.CreateOpts{ Name: routerName, + Description: routerDescription, AdminStateUp: &adminStateUp, GatewayInfo: &gatewayInfo, } @@ -69,6 +104,9 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou t.Logf("Created router: %s", routerName) + th.AssertEquals(t, router.Name, routerName) + th.AssertEquals(t, router.Description, routerDescription) + return router, nil } @@ -76,18 +114,15 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou // returned if the creation failed. func CreateRouter(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*routers.Router, error) { routerName := tools.RandomString("TESTACC-", 8) + routerDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create router: %s", routerName) adminStateUp := true - gatewayInfo := routers.GatewayInfo{ - NetworkID: networkID, - } - createOpts := routers.CreateOpts{ Name: routerName, + Description: routerDescription, AdminStateUp: &adminStateUp, - GatewayInfo: &gatewayInfo, } router, err := routers.Create(client, createOpts).Extract() @@ -101,6 +136,9 @@ func CreateRouter(t *testing.T, client *gophercloud.ServiceClient, networkID str t.Logf("Created router: %s", routerName) + th.AssertEquals(t, router.Name, routerName) + th.AssertEquals(t, router.Description, routerDescription) + return router, nil } @@ -126,6 +164,28 @@ func CreateRouterInterface(t *testing.T, client *gophercloud.ServiceClient, port return iface, nil } +// CreateRouterInterfaceOnSubnet will attach a subnet to a router. An error will be +// returned if the operation fails. +func CreateRouterInterfaceOnSubnet(t *testing.T, client *gophercloud.ServiceClient, subnetID, routerID string) (*routers.InterfaceInfo, error) { + t.Logf("Attempting to add subnet %s to router %s", subnetID, routerID) + + aiOpts := routers.AddInterfaceOpts{ + SubnetID: subnetID, + } + + iface, err := routers.AddInterface(client, routerID, aiOpts).Extract() + if err != nil { + return iface, err + } + + if err := WaitForRouterInterfaceToAttach(client, iface.PortID, 60); err != nil { + return iface, err + } + + t.Logf("Successfully added subnet %s to router %s", subnetID, routerID) + return iface, nil +} + // DeleteRouter deletes a router of a specified ID. A fatal error will occur // if the deletion failed. This works best when used as a deferred function. func DeleteRouter(t *testing.T, client *gophercloud.ServiceClient, routerID string) { @@ -248,3 +308,40 @@ func WaitForRouterInterfaceToDetach(client *gophercloud.ServiceClient, routerInt return false, nil }) } + +// CreateAddressScope will create an address-scope. An error will be returned if +// the address-scope could not be created. +func CreateAddressScope(t *testing.T, client *gophercloud.ServiceClient) (*addressscopes.AddressScope, error) { + addressScopeName := tools.RandomString("TESTACC-", 8) + createOpts := addressscopes.CreateOpts{ + Name: addressScopeName, + IPVersion: 4, + } + + t.Logf("Attempting to create an address-scope: %s", addressScopeName) + + addressScope, err := addressscopes.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Successfully created the addressscopes.") + + th.AssertEquals(t, addressScope.Name, addressScopeName) + th.AssertEquals(t, addressScope.IPVersion, int(gophercloud.IPv4)) + + return addressScope, nil +} + +// DeleteAddressScope will delete an address-scope with the specified ID. +// A fatal error will occur if the delete was not successful. +func DeleteAddressScope(t *testing.T, client *gophercloud.ServiceClient, addressScopeID string) { + t.Logf("Attempting to delete the address-scope: %s", addressScopeID) + + err := addressscopes.Delete(client, addressScopeID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete address-scope %s: %v", addressScopeID, err) + } + + t.Logf("Deleted address-scope: %s", addressScopeID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go index 4be922e9f560..73390757b451 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go @@ -9,91 +9,141 @@ import ( networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" - "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestLayer3RouterList(t *testing.T) { +func TestLayer3RouterCreateDelete(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) + 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) + + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" + updateOpts := routers.UpdateOpts{ + Name: newName, + Description: &newDescription, } + _, err = routers.Update(client, router.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + newRouter, err := routers.Get(client, router.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newRouter) + th.AssertEquals(t, newRouter.Name, newName) + th.AssertEquals(t, newRouter.Description, newDescription) + listOpts := routers.ListOpts{} allPages, err := routers.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list routers: %v", err) - } + th.AssertNoErr(t, err) allRouters, err := routers.ExtractRouters(allPages) - if err != nil { - t.Fatalf("Unable to extract routers: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, router := range allRouters { - tools.PrintResource(t, router) + if router.ID == newRouter.ID { + found = true + } } + + th.AssertEquals(t, found, true) } func TestLayer3ExternalRouterCreateDelete(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) router, err := CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRouter(t, client, router.ID) tools.PrintResource(t, router) + efi := []routers.ExternalFixedIP{} + for _, extIP := range router.GatewayInfo.ExternalFixedIPs { + efi = append(efi, + routers.ExternalFixedIP{ + IPAddress: extIP.IPAddress, + SubnetID: extIP.SubnetID, + }, + ) + } + // Add a new external router IP + efi = append(efi, + routers.ExternalFixedIP{ + SubnetID: router.GatewayInfo.ExternalFixedIPs[0].SubnetID, + }, + ) + + enableSNAT := true + gatewayInfo := routers.GatewayInfo{ + NetworkID: router.GatewayInfo.NetworkID, + EnableSNAT: &enableSNAT, + ExternalFixedIPs: efi, + } + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" updateOpts := routers.UpdateOpts{ - Name: newName, + Name: newName, + Description: &newDescription, + GatewayInfo: &gatewayInfo, } _, err = routers.Update(client, router.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update router: %v", err) - } + th.AssertNoErr(t, err) newRouter, err := routers.Get(client, router.ID).Extract() - if err != nil { - t.Fatalf("Unable to get router: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newRouter) + th.AssertEquals(t, newRouter.Name, newName) + th.AssertEquals(t, newRouter.Description, newDescription) + th.AssertEquals(t, *newRouter.GatewayInfo.EnableSNAT, enableSNAT) + th.AssertDeepEquals(t, newRouter.GatewayInfo.ExternalFixedIPs, efi) + + // Test Gateway removal + updateOpts = routers.UpdateOpts{ + GatewayInfo: &routers.GatewayInfo{}, + } + + newRouter, err = routers.Update(client, router.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, newRouter.GatewayInfo, routers.GatewayInfo{}) } func TestLayer3RouterInterface(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a compute client: %v", err) - } + clients.RequireAdmin(t) - choices, err := clients.AcceptanceTestChoicesFromEnv() - if err != nil { - t.Fatalf("Unable to get choices: %v", err) - } + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) - netid, err := networks.IDFromName(client, choices.NetworkName) - if err != nil { - t.Fatalf("Unable to find network id: %v", err) - } + // Create Network + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) - subnet, err := networking.CreateSubnet(t, client, netid) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, client, subnet.ID) tools.PrintResource(t, subnet) router, err := CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRouter(t, client, router.ID) aiOpts := routers.AddInterfaceOpts{ @@ -101,9 +151,7 @@ func TestLayer3RouterInterface(t *testing.T) { } iface, err := routers.AddInterface(client, router.ID, aiOpts).Extract() - if err != nil { - t.Fatalf("Failed to add interface to router: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, router) tools.PrintResource(t, iface) @@ -113,7 +161,5 @@ func TestLayer3RouterInterface(t *testing.T) { } _, err = routers.RemoveInterface(client, router.ID, riOpts).Extract() - if err != nil { - t.Fatalf("Failed to remove interface from router: %v", err) - } + th.AssertNoErr(t, err) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/members_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/members_test.go index 75dec83986d7..c57bc7ecc6d7 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/members_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/members_test.go @@ -71,12 +71,12 @@ func TestMembersCRUD(t *testing.T) { _, err = members.Update(client, member.ID, updateOpts).Extract() if err != nil { - t.Fatalf("Unable to update member: %v") + t.Fatalf("Unable to update member: %v", err) } newMember, err := members.Get(client, member.ID).Extract() if err != nil { - t.Fatalf("Unable to get member: %v") + t.Fatalf("Unable to get member: %v", err) } tools.PrintResource(t, newMember) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/monitors_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/monitors_test.go index 56b413afb0df..31ce3fad980e 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/monitors_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/monitors_test.go @@ -51,12 +51,12 @@ func TestMonitorsCRUD(t *testing.T) { _, err = monitors.Update(client, monitor.ID, updateOpts).Extract() if err != nil { - t.Fatalf("Unable to update monitor: %v") + t.Fatalf("Unable to update monitor: %v", err) } newMonitor, err := monitors.Get(client, monitor.ID).Extract() if err != nil { - t.Fatalf("Unable to get monitor: %v") + t.Fatalf("Unable to get monitor: %v", err) } tools.PrintResource(t, newMonitor) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pkg.go deleted file mode 100644 index f5a7df7b7515..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package lbaas diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pools_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pools_test.go index b53237c0e374..e1eb9406782a 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pools_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pools_test.go @@ -64,12 +64,12 @@ func TestPoolsCRUD(t *testing.T) { _, err = pools.Update(client, pool.ID, updateOpts).Extract() if err != nil { - t.Fatalf("Unable to update pool: %v") + t.Fatalf("Unable to update pool: %v", err) } newPool, err := pools.Get(client, pool.ID).Extract() if err != nil { - t.Fatalf("Unable to get pool: %v") + t.Fatalf("Unable to get pool: %v", err) } tools.PrintResource(t, newPool) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/vips_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/vips_test.go index a63dc63c6cee..4e54170e70ac 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/vips_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/vips_test.go @@ -71,12 +71,12 @@ func TestVIPsCRUD(t *testing.T) { _, err = vips.Update(client, vip.ID, updateOpts).Extract() if err != nil { - t.Fatalf("Unable to update vip: %v") + t.Fatalf("Unable to update vip: %v", err) } newVIP, err := vips.Get(client, vip.ID).Extract() if err != nil { - t.Fatalf("Unable to get vip: %v") + t.Fatalf("Unable to get vip: %v", err) } tools.PrintResource(t, newVIP) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/l7policies_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/l7policies_test.go new file mode 100644 index 000000000000..fc7c3d9e3607 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/l7policies_test.go @@ -0,0 +1,32 @@ +// +build acceptance networking loadbalancer l7policies + +package lbaas_v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" +) + +func TestL7PoliciesList(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a loadbalancer client: %v", err) + } + + allPages, err := l7policies.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list l7policies: %v", err) + } + + allL7Policies, err := l7policies.ExtractL7Policies(allPages) + if err != nil { + t.Fatalf("Unable to extract l7policies: %v", err) + } + + for _, policy := range allL7Policies { + tools.PrintResource(t, policy) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/lbaas_v2.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/lbaas_v2.go index 093f835b9b26..db84121104e8 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/lbaas_v2.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/lbaas_v2.go @@ -7,28 +7,32 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" ) -const loadbalancerActiveTimeoutSeconds = 300 -const loadbalancerDeleteTimeoutSeconds = 300 +const loadbalancerActiveTimeoutSeconds = 600 +const loadbalancerDeleteTimeoutSeconds = 600 // CreateListener will create a listener for a given load balancer on a random // port with a random name. An error will be returned if the listener could not // be created. func CreateListener(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer) (*listeners.Listener, error) { listenerName := tools.RandomString("TESTACCT-", 8) + listenerDescription := tools.RandomString("TESTACCT-DESC-", 8) listenerPort := tools.RandomInt(1, 100) t.Logf("Attempting to create listener %s on port %d", listenerName, listenerPort) createOpts := listeners.CreateOpts{ Name: listenerName, + Description: listenerDescription, LoadbalancerID: lb.ID, - Protocol: "TCP", + Protocol: listeners.ProtocolHTTP, ProtocolPort: listenerPort, } @@ -40,9 +44,15 @@ func CreateListener(t *testing.T, client *gophercloud.ServiceClient, lb *loadbal t.Logf("Successfully created listener %s", listenerName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - return listener, fmt.Errorf("Timed out waiting for loadbalancer to become active") + return listener, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) } + th.AssertEquals(t, listener.Name, listenerName) + th.AssertEquals(t, listener.Description, listenerDescription) + th.AssertEquals(t, listener.Loadbalancers[0].ID, lb.ID) + th.AssertEquals(t, listener.Protocol, string(listeners.ProtocolHTTP)) + th.AssertEquals(t, listener.ProtocolPort, listenerPort) + return listener, nil } @@ -50,11 +60,13 @@ func CreateListener(t *testing.T, client *gophercloud.ServiceClient, lb *loadbal // subnet. An error will be returned if the loadbalancer could not be created. func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetID string) (*loadbalancers.LoadBalancer, error) { lbName := tools.RandomString("TESTACCT-", 8) + lbDescription := tools.RandomString("TESTACCT-DESC-", 8) t.Logf("Attempting to create loadbalancer %s on subnet %s", lbName, subnetID) createOpts := loadbalancers.CreateOpts{ Name: lbName, + Description: lbDescription, VipSubnetID: subnetID, AdminStateUp: gophercloud.Enabled, } @@ -73,6 +85,11 @@ func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetI t.Logf("LoadBalancer %s is active", lbName) + th.AssertEquals(t, lb.Name, lbName) + th.AssertEquals(t, lb.Description, lbDescription) + th.AssertEquals(t, lb.VipSubnetID, subnetID) + th.AssertEquals(t, lb.AdminStateUp, true) + return lb, nil } @@ -92,7 +109,7 @@ func CreateMember(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalan createOpts := pools.CreateMemberOpts{ Name: memberName, ProtocolPort: memberPort, - Weight: memberWeight, + Weight: &memberWeight, Address: memberAddress, SubnetID: subnetID, } @@ -107,9 +124,11 @@ func CreateMember(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalan t.Logf("Successfully created member %s", memberName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - return member, fmt.Errorf("Timed out waiting for loadbalancer to become active") + return member, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) } + th.AssertEquals(t, member.Name, memberName) + return member, nil } @@ -126,7 +145,7 @@ func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbala Delay: 10, Timeout: 5, MaxRetries: 5, - Type: "PING", + Type: monitors.TypePING, } monitor, err := monitors.Create(client, createOpts).Extract() @@ -137,9 +156,12 @@ func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbala t.Logf("Successfully created monitor: %s", monitorName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - return monitor, fmt.Errorf("Timed out waiting for loadbalancer to become active") + return monitor, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) } + th.AssertEquals(t, monitor.Name, monitorName) + th.AssertEquals(t, monitor.Type, monitors.TypePING) + return monitor, nil } @@ -148,12 +170,14 @@ func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbala // created. func CreatePool(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer) (*pools.Pool, error) { poolName := tools.RandomString("TESTACCT-", 8) + poolDescription := tools.RandomString("TESTACCT-DESC-", 8) t.Logf("Attempting to create pool %s", poolName) createOpts := pools.CreateOpts{ Name: poolName, - Protocol: pools.ProtocolTCP, + Description: poolDescription, + Protocol: pools.ProtocolHTTP, LoadbalancerID: lb.ID, LBMethod: pools.LBMethodLeastConnections, } @@ -166,12 +190,121 @@ func CreatePool(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalance t.Logf("Successfully created pool %s", poolName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - return pool, fmt.Errorf("Timed out waiting for loadbalancer to become active") + return pool, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) } + th.AssertEquals(t, pool.Name, poolName) + th.AssertEquals(t, pool.Description, poolDescription) + th.AssertEquals(t, pool.Protocol, string(pools.ProtocolHTTP)) + th.AssertEquals(t, pool.Loadbalancers[0].ID, lb.ID) + th.AssertEquals(t, pool.LBMethod, string(pools.LBMethodLeastConnections)) + return pool, nil } +// CreateL7Policy will create a l7 policy with a random name with a specified listener +// and loadbalancer. An error will be returned if the l7 policy could not be +// created. +func CreateL7Policy(t *testing.T, client *gophercloud.ServiceClient, listener *listeners.Listener, lb *loadbalancers.LoadBalancer) (*l7policies.L7Policy, error) { + policyName := tools.RandomString("TESTACCT-", 8) + policyDescription := tools.RandomString("TESTACCT-DESC-", 8) + + t.Logf("Attempting to create l7 policy %s on the %s listener ID", policyName, listener.ID) + + createOpts := l7policies.CreateOpts{ + Name: policyName, + Description: policyDescription, + ListenerID: listener.ID, + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + } + + policy, err := l7policies.Create(client, createOpts).Extract() + if err != nil { + return policy, err + } + + t.Logf("Successfully created l7 policy %s", policyName) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return policy, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, policy.Name, policyName) + th.AssertEquals(t, policy.Description, policyDescription) + th.AssertEquals(t, policy.ListenerID, listener.ID) + th.AssertEquals(t, policy.Action, string(l7policies.ActionRedirectToURL)) + th.AssertEquals(t, policy.RedirectURL, "http://www.example.com") + + return policy, nil +} + +// CreateL7Rule creates a l7 rule for specified l7 policy. +func CreateL7Rule(t *testing.T, client *gophercloud.ServiceClient, policyID string, lb *loadbalancers.LoadBalancer) (*l7policies.Rule, error) { + t.Logf("Attempting to create l7 rule for policy %s", policyID) + + createOpts := l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeStartWith, + Value: "/api", + } + + rule, err := l7policies.CreateRule(client, policyID, createOpts).Extract() + if err != nil { + return rule, err + } + + t.Logf("Successfully created l7 rule for policy %s", policyID) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + return rule, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + } + + th.AssertEquals(t, rule.RuleType, string(l7policies.TypePath)) + th.AssertEquals(t, rule.CompareType, string(l7policies.CompareTypeStartWith)) + th.AssertEquals(t, rule.Value, "/api") + + return rule, nil +} + +// DeleteL7Policy will delete a specified l7 policy. A fatal error will occur if +// the l7 policy could not be deleted. This works best when used as a deferred +// function. +func DeleteL7Policy(t *testing.T, client *gophercloud.ServiceClient, lbID, policyID string) { + t.Logf("Attempting to delete l7 policy %s", policyID) + + if err := l7policies.Delete(client, policyID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete l7 policy: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted l7 policy %s", policyID) +} + +// DeleteL7Rule will delete a specified l7 rule. A fatal error will occur if +// the l7 rule could not be deleted. This works best when used as a deferred +// function. +func DeleteL7Rule(t *testing.T, client *gophercloud.ServiceClient, lbID, policyID, ruleID string) { + t.Logf("Attempting to delete l7 rule %s", ruleID) + + if err := l7policies.DeleteRule(client, policyID, ruleID).ExtractErr(); err != nil { + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete l7 rule: %v", err) + } + } + + if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) + } + + t.Logf("Successfully deleted l7 rule %s", ruleID) +} + // DeleteListener will delete a specified listener. A fatal error will occur if // the listener could not be deleted. This works best when used as a deferred // function. @@ -179,11 +312,13 @@ func DeleteListener(t *testing.T, client *gophercloud.ServiceClient, lbID, liste t.Logf("Attempting to delete listener %s", listenerID) if err := listeners.Delete(client, listenerID).ExtractErr(); err != nil { - t.Fatalf("Unable to delete listener: %v", err) + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete listener: %v", err) + } } if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - t.Fatalf("Timed out waiting for loadbalancer to become active") + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) } t.Logf("Successfully deleted listener %s", listenerID) @@ -196,11 +331,13 @@ func DeleteMember(t *testing.T, client *gophercloud.ServiceClient, lbID, poolID, t.Logf("Attempting to delete member %s", memberID) if err := pools.DeleteMember(client, poolID, memberID).ExtractErr(); err != nil { - t.Fatalf("Unable to delete member: %s", memberID) + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete member: %s", memberID) + } } if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - t.Fatalf("Timed out waiting for loadbalancer to become active") + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) } t.Logf("Successfully deleted member %s", memberID) @@ -213,13 +350,15 @@ func DeleteLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, lbID st t.Logf("Attempting to delete loadbalancer %s", lbID) if err := loadbalancers.Delete(client, lbID).ExtractErr(); err != nil { - t.Fatalf("Unable to delete loadbalancer: %v", err) + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete loadbalancer: %v", err) + } } t.Logf("Waiting for loadbalancer %s to delete", lbID) if err := WaitForLoadBalancerState(client, lbID, "DELETED", loadbalancerActiveTimeoutSeconds); err != nil { - t.Fatalf("Loadbalancer did not delete in time.") + t.Fatalf("Loadbalancer did not delete in time: %s", err) } t.Logf("Successfully deleted loadbalancer %s", lbID) @@ -232,11 +371,13 @@ func DeleteMonitor(t *testing.T, client *gophercloud.ServiceClient, lbID, monito t.Logf("Attempting to delete monitor %s", monitorID) if err := monitors.Delete(client, monitorID).ExtractErr(); err != nil { - t.Fatalf("Unable to delete monitor: %v", err) + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete monitor: %v", err) + } } if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - t.Fatalf("Timed out waiting for loadbalancer to become active") + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) } t.Logf("Successfully deleted monitor %s", monitorID) @@ -248,11 +389,13 @@ func DeletePool(t *testing.T, client *gophercloud.ServiceClient, lbID, poolID st t.Logf("Attempting to delete pool %s", poolID) if err := pools.Delete(client, poolID).ExtractErr(); err != nil { - t.Fatalf("Unable to delete pool: %v", err) + if _, ok := err.(gophercloud.ErrDefault404); !ok { + t.Fatalf("Unable to delete pool: %v", err) + } } if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { - t.Fatalf("Timed out waiting for loadbalancer to become active") + t.Fatalf("Timed out waiting for loadbalancer to become active: %s", err) } t.Logf("Successfully deleted pool %s", poolID) @@ -277,6 +420,10 @@ func WaitForLoadBalancerState(client *gophercloud.ServiceClient, lbID, status st return true, nil } + if current.ProvisioningStatus == "ERROR" { + return false, fmt.Errorf("Load balancer is in ERROR state") + } + return false, nil }) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/listeners_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/listeners_test.go index 2d2dd036954e..30136b04941e 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/listeners_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/listeners_test.go @@ -1,4 +1,4 @@ -// +build acceptance networking lbaas_v2 listeners +// +build acceptance networking loadbalancer listeners package lbaas_v2 @@ -13,7 +13,7 @@ import ( func TestListenersList(t *testing.T) { client, err := clients.NewNetworkV2Client() if err != nil { - t.Fatalf("Unable to create a network client: %v", err) + t.Fatalf("Unable to create a loadbalancer client: %v", err) } allPages, err := listeners.List(client, nil).AllPages() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/loadbalancers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/loadbalancers_test.go index 26064f0c28c3..49184277de5f 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/loadbalancers_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/loadbalancers_test.go @@ -8,27 +8,23 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestLoadbalancersList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := loadbalancers.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list loadbalancers: %v", err) - } + th.AssertNoErr(t, err) allLoadbalancers, err := loadbalancers.ExtractLoadBalancers(allPages) - if err != nil { - t.Fatalf("Unable to extract loadbalancers: %v", err) - } + th.AssertNoErr(t, err) for _, lb := range allLoadbalancers { tools.PrintResource(t, lb) @@ -37,291 +33,264 @@ func TestLoadbalancersList(t *testing.T) { func TestLoadbalancersCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) network, err := networking.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteNetwork(t, client, network.ID) subnet, err := networking.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, client, subnet.ID) lb, err := CreateLoadBalancer(t, client, subnet.ID) - if err != nil { - t.Fatalf("Unable to create loadbalancer: %v", err) - } + th.AssertNoErr(t, err) defer DeleteLoadBalancer(t, client, lb.ID) - newLB, err := loadbalancers.Get(client, lb.ID).Extract() - if err != nil { - t.Fatalf("Unable to get loadbalancer: %v", err) + lbDescription := "" + updateLoadBalancerOpts := loadbalancers.UpdateOpts{ + Description: &lbDescription, } + _, err = loadbalancers.Update(client, lb.ID, updateLoadBalancerOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") + } + + newLB, err := loadbalancers.Get(client, lb.ID).Extract() + th.AssertNoErr(t, err) tools.PrintResource(t, newLB) + th.AssertEquals(t, newLB.Description, lbDescription) + + lbStats, err := loadbalancers.GetStats(client, lb.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, lbStats) + // Because of the time it takes to create a loadbalancer, // this test will include some other resources. // Listener listener, err := CreateListener(t, client, lb) - if err != nil { - t.Fatalf("Unable to create listener: %v", err) - } + th.AssertNoErr(t, err) defer DeleteListener(t, client, lb.ID, listener.ID) + listenerName := "" + listenerDescription := "" updateListenerOpts := listeners.UpdateOpts{ - Description: "Some listener description", + Name: &listenerName, + Description: &listenerDescription, } _, err = listeners.Update(client, listener.ID, updateListenerOpts).Extract() - if err != nil { - t.Fatalf("Unable to update listener") - } + th.AssertNoErr(t, err) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } newListener, err := listeners.Get(client, listener.ID).Extract() - if err != nil { - t.Fatalf("Unable to get listener") - } + th.AssertNoErr(t, err) tools.PrintResource(t, newListener) - // Pool - pool, err := CreatePool(t, client, lb) - if err != nil { - t.Fatalf("Unable to create pool: %v", err) - } - defer DeletePool(t, client, lb.ID, pool.ID) + th.AssertEquals(t, newListener.Name, listenerName) + th.AssertEquals(t, newListener.Description, listenerDescription) - updatePoolOpts := pools.UpdateOpts{ - Description: "Some pool description", - } - _, err = pools.Update(client, pool.ID, updatePoolOpts).Extract() - if err != nil { - t.Fatalf("Unable to update pool") + // L7 policy + policy, err := CreateL7Policy(t, client, listener, lb) + th.AssertNoErr(t, err) + defer DeleteL7Policy(t, client, lb.ID, policy.ID) + + newDescription := "" + updateL7policyOpts := l7policies.UpdateOpts{ + Description: &newDescription, } + _, err = l7policies.Update(client, policy.ID, updateL7policyOpts).Extract() + th.AssertNoErr(t, err) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newPool, err := pools.Get(client, pool.ID).Extract() - if err != nil { - t.Fatalf("Unable to get pool") - } + newPolicy, err := l7policies.Get(client, policy.ID).Extract() + th.AssertNoErr(t, err) - tools.PrintResource(t, newPool) + tools.PrintResource(t, newPolicy) - // Member - member, err := CreateMember(t, client, lb, newPool, subnet.ID, subnet.CIDR) - if err != nil { - t.Fatalf("Unable to create member: %v", err) - } - defer DeleteMember(t, client, lb.ID, pool.ID, member.ID) + th.AssertEquals(t, newPolicy.Description, newDescription) - newWeight := tools.RandomInt(11, 100) - updateMemberOpts := pools.UpdateMemberOpts{ - Weight: newWeight, + // L7 rule + rule, err := CreateL7Rule(t, client, newPolicy.ID, lb) + th.AssertNoErr(t, err) + defer DeleteL7Rule(t, client, lb.ID, policy.ID, rule.ID) + + allPages, err := l7policies.ListRules(client, policy.ID, l7policies.ListRulesOpts{}).AllPages() + th.AssertNoErr(t, err) + allRules, err := l7policies.ExtractRules(allPages) + th.AssertNoErr(t, err) + for _, rule := range allRules { + tools.PrintResource(t, rule) } - _, err = pools.UpdateMember(client, pool.ID, member.ID, updateMemberOpts).Extract() - if err != nil { - t.Fatalf("Unable to update pool") + + /* NOT supported on F5 driver */ + updateL7ruleOpts := l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", } + _, err = l7policies.UpdateRule(client, policy.ID, rule.ID, updateL7ruleOpts).Extract() + th.AssertNoErr(t, err) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newMember, err := pools.GetMember(client, pool.ID, member.ID).Extract() - if err != nil { - t.Fatalf("Unable to get member") - } + newRule, err := l7policies.GetRule(client, newPolicy.ID, rule.ID).Extract() + th.AssertNoErr(t, err) - tools.PrintResource(t, newMember) + tools.PrintResource(t, newRule) - // Monitor - monitor, err := CreateMonitor(t, client, lb, newPool) - if err != nil { - t.Fatalf("Unable to create monitor: %v", err) - } - defer DeleteMonitor(t, client, lb.ID, monitor.ID) + // Pool + pool, err := CreatePool(t, client, lb) + th.AssertNoErr(t, err) + defer DeletePool(t, client, lb.ID, pool.ID) - newDelay := tools.RandomInt(20, 30) - updateMonitorOpts := monitors.UpdateOpts{ - Delay: newDelay, - } - _, err = monitors.Update(client, monitor.ID, updateMonitorOpts).Extract() - if err != nil { - t.Fatalf("Unable to update monitor") + poolName := "" + poolDescription := "" + updatePoolOpts := pools.UpdateOpts{ + Name: &poolName, + Description: &poolDescription, } + _, err = pools.Update(client, pool.ID, updatePoolOpts).Extract() + th.AssertNoErr(t, err) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newMonitor, err := monitors.Get(client, monitor.ID).Extract() - if err != nil { - t.Fatalf("Unable to get monitor") - } - - tools.PrintResource(t, newMonitor) - -} - -func TestOctaviaLoadbalancersCRUD(t *testing.T) { - netClient, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + newPool, err := pools.Get(client, pool.ID).Extract() + th.AssertNoErr(t, err) - lbClient, err := clients.NewLoadBalancerV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + tools.PrintResource(t, newPool) + th.AssertEquals(t, newPool.Name, poolName) + th.AssertEquals(t, newPool.Description, poolDescription) - network, err := networking.CreateNetwork(t, netClient) - if err != nil { - t.Fatalf("Unable to create network: %v", err) + // Update L7policy to redirect to pool + newRedirectURL := "" + updateL7policyOpts = l7policies.UpdateOpts{ + Action: l7policies.ActionRedirectToPool, + RedirectPoolID: &newPool.ID, + RedirectURL: &newRedirectURL, } - defer networking.DeleteNetwork(t, netClient, network.ID) + _, err = l7policies.Update(client, policy.ID, updateL7policyOpts).Extract() + th.AssertNoErr(t, err) - subnet, err := networking.CreateSubnet(t, netClient, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + t.Fatalf("Timed out waiting for loadbalancer to become active") } - defer networking.DeleteSubnet(t, netClient, subnet.ID) - lb, err := CreateLoadBalancer(t, lbClient, subnet.ID) - if err != nil { - t.Fatalf("Unable to create loadbalancer: %v", err) - } - defer func() { - t.Logf("Running cascading delete on Octavia LB...") - err := loadbalancers.CascadingDelete(lbClient, lb.ID).ExtractErr() - if err != nil { - t.Fatalf("Error running cascading delete: %v", err) - } - }() - - newLB, err := loadbalancers.Get(lbClient, lb.ID).Extract() - if err != nil { - t.Fatalf("Unable to get loadbalancer: %v", err) - } + newPolicy, err = l7policies.Get(client, policy.ID).Extract() + th.AssertNoErr(t, err) - tools.PrintResource(t, newLB) + tools.PrintResource(t, newPolicy) - // Because of the time it takes to create a loadbalancer, - // this test will include some other resources. + th.AssertEquals(t, newPolicy.Description, newDescription) + th.AssertEquals(t, newPolicy.Action, string(l7policies.ActionRedirectToPool)) + th.AssertEquals(t, newPolicy.RedirectPoolID, newPool.ID) + th.AssertEquals(t, newPolicy.RedirectURL, newRedirectURL) - // Listener - listener, err := CreateListener(t, lbClient, lb) - if err != nil { - t.Fatalf("Unable to create listener: %v", err) - } + // Workaround for proper delete order + defer DeleteL7Policy(t, client, lb.ID, policy.ID) + defer DeleteL7Rule(t, client, lb.ID, policy.ID, rule.ID) - updateListenerOpts := listeners.UpdateOpts{ - Description: "Some listener description", - } - _, err = listeners.Update(lbClient, listener.ID, updateListenerOpts).Extract() - if err != nil { - t.Fatalf("Unable to update listener") + // Update listener's default pool ID + updateListenerOpts = listeners.UpdateOpts{ + DefaultPoolID: &pool.ID, } + _, err = listeners.Update(client, listener.ID, updateListenerOpts).Extract() + th.AssertNoErr(t, err) - if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newListener, err := listeners.Get(lbClient, listener.ID).Extract() - if err != nil { - t.Fatalf("Unable to get listener") - } + newListener, err = listeners.Get(client, listener.ID).Extract() + th.AssertNoErr(t, err) tools.PrintResource(t, newListener) - // Pool - pool, err := CreatePool(t, lbClient, lb) - if err != nil { - t.Fatalf("Unable to create pool: %v", err) - } + th.AssertEquals(t, newListener.DefaultPoolID, pool.ID) - updatePoolOpts := pools.UpdateOpts{ - Description: "Some pool description", - } - _, err = pools.Update(lbClient, pool.ID, updatePoolOpts).Extract() - if err != nil { - t.Fatalf("Unable to update pool") + // Remove listener's default pool ID + emptyPoolID := "" + updateListenerOpts = listeners.UpdateOpts{ + DefaultPoolID: &emptyPoolID, } + _, err = listeners.Update(client, listener.ID, updateListenerOpts).Extract() + th.AssertNoErr(t, err) - if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newPool, err := pools.Get(lbClient, pool.ID).Extract() - if err != nil { - t.Fatalf("Unable to get pool") - } + newListener, err = listeners.Get(client, listener.ID).Extract() + th.AssertNoErr(t, err) - tools.PrintResource(t, newPool) + tools.PrintResource(t, newListener) + + th.AssertEquals(t, newListener.DefaultPoolID, "") // Member - member, err := CreateMember(t, lbClient, lb, newPool, subnet.ID, subnet.CIDR) - if err != nil { - t.Fatalf("Unable to create member: %v", err) - } + member, err := CreateMember(t, client, lb, newPool, subnet.ID, subnet.CIDR) + th.AssertNoErr(t, err) + defer DeleteMember(t, client, lb.ID, pool.ID, member.ID) + memberName := "" newWeight := tools.RandomInt(11, 100) updateMemberOpts := pools.UpdateMemberOpts{ - Weight: newWeight, - } - _, err = pools.UpdateMember(lbClient, pool.ID, member.ID, updateMemberOpts).Extract() - if err != nil { - t.Fatalf("Unable to update pool") + Name: &memberName, + Weight: &newWeight, } + _, err = pools.UpdateMember(client, pool.ID, member.ID, updateMemberOpts).Extract() + th.AssertNoErr(t, err) - if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newMember, err := pools.GetMember(lbClient, pool.ID, member.ID).Extract() - if err != nil { - t.Fatalf("Unable to get member") - } + newMember, err := pools.GetMember(client, pool.ID, member.ID).Extract() + th.AssertNoErr(t, err) tools.PrintResource(t, newMember) + th.AssertEquals(t, newMember.Name, memberName) + th.AssertEquals(t, newMember.Weight, newWeight) // Monitor - monitor, err := CreateMonitor(t, lbClient, lb, newPool) - if err != nil { - t.Fatalf("Unable to create monitor: %v", err) - } + monitor, err := CreateMonitor(t, client, lb, newPool) + th.AssertNoErr(t, err) + defer DeleteMonitor(t, client, lb.ID, monitor.ID) + monName := "" newDelay := tools.RandomInt(20, 30) updateMonitorOpts := monitors.UpdateOpts{ + Name: &monName, Delay: newDelay, } - _, err = monitors.Update(lbClient, monitor.ID, updateMonitorOpts).Extract() - if err != nil { - t.Fatalf("Unable to update monitor") - } + _, err = monitors.Update(client, monitor.ID, updateMonitorOpts).Extract() + th.AssertNoErr(t, err) - if err := WaitForLoadBalancerState(lbClient, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { + if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil { t.Fatalf("Timed out waiting for loadbalancer to become active") } - newMonitor, err := monitors.Get(lbClient, monitor.ID).Extract() - if err != nil { - t.Fatalf("Unable to get monitor") - } + newMonitor, err := monitors.Get(client, monitor.ID).Extract() + th.AssertNoErr(t, err) tools.PrintResource(t, newMonitor) + th.AssertEquals(t, newMonitor.Name, monName) + th.AssertEquals(t, newMonitor.Delay, newDelay) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/monitors_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/monitors_test.go index b312370722db..84b0c867d70d 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/monitors_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/monitors_test.go @@ -1,4 +1,4 @@ -// +build acceptance networking lbaas_v2 monitors +// +build acceptance networking loadbalancer monitors package lbaas_v2 @@ -13,7 +13,7 @@ import ( func TestMonitorsList(t *testing.T) { client, err := clients.NewNetworkV2Client() if err != nil { - t.Fatalf("Unable to create a network client: %v", err) + t.Fatalf("Unable to create a loadbalancer client: %v", err) } allPages, err := monitors.List(client, nil).AllPages() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pkg.go deleted file mode 100644 index 24b7482a56cf..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package lbaas_v2 diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pools_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pools_test.go index b4f55a0f63f5..bcab7fd55c68 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pools_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas_v2/pools_test.go @@ -1,4 +1,4 @@ -// +build acceptance networking lbaas_v2 pools +// +build acceptance networking loadbalancer pools package lbaas_v2 @@ -13,7 +13,7 @@ import ( func TestPoolsList(t *testing.T) { client, err := clients.NewNetworkV2Client() if err != nil { - t.Fatalf("Unable to create a network client: %v", err) + t.Fatalf("Unable to create a loadbalancer client: %v", err) } allPages, err := pools.List(client, nil).AllPages() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu.go new file mode 100644 index 000000000000..36c06f9e8d7f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu.go @@ -0,0 +1,61 @@ +package mtu + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +type NetworkMTU struct { + networks.Network + mtu.NetworkMTUExt +} + +// CreateNetworkWithMTU will create a network with custom MTU. An error will be +// returned if the creation failed. +func CreateNetworkWithMTU(t *testing.T, client *gophercloud.ServiceClient, networkMTU *int) (*NetworkMTU, error) { + networkName := tools.RandomString("TESTACC-", 8) + networkDescription := tools.RandomString("TESTACC-DESC-", 8) + + t.Logf("Attempting to create a network with custom MTU: %s", networkName) + + adminStateUp := true + + var createOpts networks.CreateOptsBuilder + createOpts = networks.CreateOpts{ + Name: networkName, + Description: networkDescription, + AdminStateUp: &adminStateUp, + } + + if *networkMTU > 0 { + createOpts = mtu.CreateOptsExt{ + CreateOptsBuilder: createOpts, + MTU: *networkMTU, + } + } + + var network NetworkMTU + + err := networks.Create(client, createOpts).ExtractInto(&network) + if err != nil { + return &network, err + } + + t.Logf("Created a network with custom MTU: %s", networkName) + + th.AssertEquals(t, network.Name, networkName) + th.AssertEquals(t, network.Description, networkDescription) + th.AssertEquals(t, network.AdminStateUp, adminStateUp) + if *networkMTU > 0 { + th.AssertEquals(t, network.MTU, *networkMTU) + } else { + *networkMTU = network.MTU + } + + return &network, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go new file mode 100644 index 000000000000..76647296150f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go @@ -0,0 +1,133 @@ +// +build acceptance networking + +package mtu + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/common/extensions" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestMTUNetworkCRUDL(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + extension, err := extensions.Get(client, "net-mtu").Extract() + if err != nil { + t.Skip("This test requires net-mtu Neutron extension") + } + tools.PrintResource(t, extension) + + mtuWritable, _ := extensions.Get(client, "net-mtu-writable").Extract() + tools.PrintResource(t, mtuWritable) + + // Create Network + var networkMTU int + if mtuWritable != nil { + networkMTU = 1449 + } + network, err := CreateNetworkWithMTU(t, client, &networkMTU) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + // MTU filtering is supported only in read-only MTU extension + // https://bugs.launchpad.net/neutron/+bug/1818317 + if mtuWritable == nil { + // List network successfully + var listOpts networks.ListOptsBuilder + listOpts = mtu.ListOptsExt{ + ListOptsBuilder: networks.ListOpts{}, + MTU: networkMTU, + } + var listedNetworks []NetworkMTU + i := 0 + err = networks.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + i++ + err := networks.ExtractNetworksInto(page, &listedNetworks) + if err != nil { + t.Errorf("Failed to extract networks: %v", err) + return false, err + } + + tools.PrintResource(t, listedNetworks) + + th.AssertEquals(t, 1, len(listedNetworks)) + th.CheckDeepEquals(t, *network, listedNetworks[0]) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, i) + + // List network unsuccessfully + listOpts = mtu.ListOptsExt{ + ListOptsBuilder: networks.ListOpts{}, + MTU: 1, + } + i = 0 + err = networks.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + i++ + err := networks.ExtractNetworksInto(page, &listedNetworks) + if err != nil { + t.Errorf("Failed to extract networks: %v", err) + return false, err + } + + tools.PrintResource(t, listedNetworks) + + th.AssertEquals(t, 1, len(listedNetworks)) + th.CheckDeepEquals(t, *network, listedNetworks[0]) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, i) + } + + // Get network + var getNetwork NetworkMTU + err = networks.Get(client, network.ID).ExtractInto(&getNetwork) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getNetwork) + th.AssertDeepEquals(t, *network, getNetwork) + + if mtuWritable != nil { + // Update network + newNetworkDescription := "" + newNetworkMTU := 1350 + networkUpdateOpts := networks.UpdateOpts{ + Description: &newNetworkDescription, + } + var updateOpts networks.UpdateOptsBuilder + updateOpts = mtu.UpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + MTU: newNetworkMTU, + } + + var newNetwork NetworkMTU + err = networks.Update(client, network.ID, updateOpts).ExtractInto(&newNetwork) + th.AssertNoErr(t, err) + + tools.PrintResource(t, newNetwork) + th.AssertEquals(t, newNetwork.Description, newNetworkDescription) + th.AssertEquals(t, newNetwork.MTU, newNetworkMTU) + + // Get updated network + var getNewNetwork NetworkMTU + err = networks.Get(client, network.ID).ExtractInto(&getNewNetwork) + th.AssertNoErr(t, err) + + tools.PrintResource(t, getNewNetwork) + th.AssertDeepEquals(t, newNetwork, getNewNetwork) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/networkipavailabilities/networkipavailabilities_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/networkipavailabilities/networkipavailabilities_test.go new file mode 100644 index 000000000000..cfebed0a308f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/networkipavailabilities/networkipavailabilities_test.go @@ -0,0 +1,31 @@ +// +build acceptance networking networkipavailabilities + +package networkipavailabilities + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNetworkIPAvailabilityList(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + allPages, err := networkipavailabilities.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allAvailabilities, err := networkipavailabilities.ExtractNetworkIPAvailabilities(allPages) + th.AssertNoErr(t, err) + + for _, availability := range allAvailabilities { + for _, subnet := range availability.SubnetIPAvailabilities { + tools.PrintResource(t, subnet) + tools.PrintResource(t, subnet.TotalIPs) + tools.PrintResource(t, subnet.UsedIPs) + } + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/pkg.go deleted file mode 100644 index aeec0fa756e7..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package extensions diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/pkg.go deleted file mode 100644 index 5dae1b16605e..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package portsbinding diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding.go index 5b7dc90787e6..ef3179e6a9dc 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" ) // PortWithBindingExt represents a port with the binding fields @@ -17,8 +18,9 @@ type PortWithBindingExt struct { // CreatePortsbinding will create a port on the specified subnet. An error will be // returned if the port could not be created. -func CreatePortsbinding(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID, hostID string) (PortWithBindingExt, error) { +func CreatePortsbinding(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID, hostID string, profile map[string]interface{}) (PortWithBindingExt, error) { portName := tools.RandomString("TESTACC-", 8) + portDescription := tools.RandomString("TESTACC-PORT-DESC-", 8) iFalse := false t.Logf("Attempting to create port: %s", portName) @@ -26,6 +28,7 @@ func CreatePortsbinding(t *testing.T, client *gophercloud.ServiceClient, network portCreateOpts := ports.CreateOpts{ NetworkID: networkID, Name: portName, + Description: portDescription, AdminStateUp: &iFalse, FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, } @@ -33,6 +36,7 @@ func CreatePortsbinding(t *testing.T, client *gophercloud.ServiceClient, network createOpts := portsbinding.CreateOptsExt{ CreateOptsBuilder: portCreateOpts, HostID: hostID, + Profile: profile, } var s PortWithBindingExt @@ -44,5 +48,8 @@ func CreatePortsbinding(t *testing.T, client *gophercloud.ServiceClient, network t.Logf("Successfully created port: %s", portName) + th.AssertEquals(t, s.Name, portName) + th.AssertEquals(t, s.Description, portDescription) + return s, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go index 6ce205925acf..d699c786a647 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go @@ -8,50 +8,73 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestPortsbindingCRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := networking.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := networking.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, client, subnet.ID) // Define a host hostID := "localhost" + profile := map[string]interface{}{"foo": "bar"} // Create port - port, err := CreatePortsbinding(t, client, network.ID, subnet.ID, hostID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + port, err := CreatePortsbinding(t, client, network.ID, subnet.ID, hostID, profile) + th.AssertNoErr(t, err) defer networking.DeletePort(t, client, port.ID) tools.PrintResource(t, port) + th.AssertEquals(t, port.HostID, hostID) + th.AssertEquals(t, port.VNICType, "normal") + th.AssertDeepEquals(t, port.Profile, profile) // Update port - newPortName := tools.RandomString("TESTACC-", 8) + newPortName := "" + newPortDescription := "" + newHostID := "127.0.0.1" + newProfile := map[string]interface{}{} updateOpts := ports.UpdateOpts{ - Name: newPortName, + Name: &newPortName, + Description: &newPortDescription, } - newPort, err := ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) + + var finalUpdateOpts ports.UpdateOptsBuilder + + finalUpdateOpts = portsbinding.UpdateOptsExt{ + UpdateOptsBuilder: updateOpts, + HostID: &newHostID, + VNICType: "baremetal", + Profile: newProfile, } + var newPort PortWithBindingExt + + _, err = ports.Update(client, port.ID, finalUpdateOpts).Extract() + th.AssertNoErr(t, err) + + // Read the updated port + err = ports.Get(client, port.ID).ExtractInto(&newPort) + th.AssertNoErr(t, err) + tools.PrintResource(t, newPort) + th.AssertEquals(t, newPort.Description, newPortName) + th.AssertEquals(t, newPort.Description, newPortDescription) + th.AssertEquals(t, newPort.HostID, newHostID) + th.AssertEquals(t, newPort.VNICType, "baremetal") + th.AssertDeepEquals(t, newPort.Profile, newProfile) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/provider_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/provider_test.go index 44726ce27e45..45893fbd337b 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/provider_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/provider_test.go @@ -9,26 +9,21 @@ import ( networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestNetworksProviderCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create a network network, err := networking.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteNetwork(t, client, network.ID) getResult := networks.Get(client, network.ID) newNetwork, err := getResult.Extract() - if err != nil { - t.Fatalf("Unable to extract network: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newNetwork) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/qos/ruletypes/ruletypes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/qos/ruletypes/ruletypes_test.go new file mode 100644 index 000000000000..e14844b56aea --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/qos/ruletypes/ruletypes_test.go @@ -0,0 +1,31 @@ +package ruletypes + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes" +) + +func TestListRuleTypes(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network client: %v", err) + return + } + + page, err := ruletypes.ListRuleTypes(client).AllPages() + if err != nil { + t.Fatalf("Failed to list rule types pages: %v", err) + return + } + + ruleTypes, err := ruletypes.ExtractRuleTypes(page) + if err != nil { + t.Fatalf("Failed to list rule types: %v", err) + return + } + + tools.PrintResource(t, ruleTypes) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/pkg.go deleted file mode 100644 index f682aeab0642..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package rbacpolicies diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go index 46903dff78fe..b6e80d242026 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go @@ -5,6 +5,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateRBACPolicy will create a rbac-policy. An error will be returned if the @@ -25,6 +26,9 @@ func CreateRBACPolicy(t *testing.T, client *gophercloud.ServiceClient, tenantID, } t.Logf("Successfully created rbac_policy") + + th.AssertEquals(t, rbacPolicy.ObjectID, networkID) + return rbacPolicy, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go index db838d2816a6..b07bab1718e1 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go @@ -3,7 +3,6 @@ package rbacpolicies import ( - "os" "testing" "github.com/gophercloud/gophercloud/acceptance/clients" @@ -11,56 +10,42 @@ import ( networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestRBACPolicyCRUD(t *testing.T) { - username := os.Getenv("OS_USERNAME") - if username != "admin" { - t.Skip("must be admin to run this test") - } + clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create a network network, err := networking.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteNetwork(t, client, network.ID) tools.PrintResource(t, network) identityClient, err := clients.NewIdentityV3Client() - if err != nil { - t.Fatalf("Unable to obtain an identity client: %v") - } + th.AssertNoErr(t, err) // Create a project/tenant project, err := projects.CreateProject(t, identityClient, nil) - if err != nil { - t.Fatalf("Unable to create project: %v", err) - } + th.AssertNoErr(t, err) defer projects.DeleteProject(t, identityClient, project.ID) tools.PrintResource(t, project) // Create a rbac-policy rbacPolicy, err := CreateRBACPolicy(t, client, project.ID, network.ID) - if err != nil { - t.Fatalf("Unable to create rbac-policy: %v", err) - } + th.AssertNoErr(t, err) defer DeleteRBACPolicy(t, client, rbacPolicy.ID) tools.PrintResource(t, rbacPolicy) // Create another project/tenant for rbac-update project2, err := projects.CreateProject(t, identityClient, nil) - if err != nil { - t.Fatalf("Unable to create project2: %v", err) - } + th.AssertNoErr(t, err) defer projects.DeleteProject(t, identityClient, project2.ID) tools.PrintResource(t, project2) @@ -71,25 +56,21 @@ func TestRBACPolicyCRUD(t *testing.T) { } _, err = rbacpolicies.Update(client, rbacPolicy.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update rbac-policy: %v", err) - } + th.AssertNoErr(t, err) // Get the rbac-policy by ID t.Logf("Get rbac_policy by ID") newrbacPolicy, err := rbacpolicies.Get(client, rbacPolicy.ID).Extract() - if err != nil { - t.Fatalf("Unable to retrieve rbac policy: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newrbacPolicy) } func TestRBACPolicyList(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) type rbacPolicy struct { rbacpolicies.RBACPolicy @@ -98,14 +79,10 @@ func TestRBACPolicyList(t *testing.T) { var allRBACPolicies []rbacPolicy allPages, err := rbacpolicies.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list rbac policies: %v", err) - } + th.AssertNoErr(t, err) err = rbacpolicies.ExtractRBACPolicesInto(allPages, &allRBACPolicies) - if err != nil { - t.Fatalf("Unable to extract rbac policies: %v", err) - } + th.AssertNoErr(t, err) for _, rbacpolicy := range allRBACPolicies { tools.PrintResource(t, rbacpolicy) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/security_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/security_test.go index 3810a4201413..3bf44a807148 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/security_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/security_test.go @@ -9,96 +9,76 @@ import ( networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestSecurityGroupsList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } - - listOpts := groups.ListOpts{} - allPages, err := groups.List(client, listOpts).AllPages() - if err != nil { - t.Fatalf("Unable to list groups: %v", err) - } - - allGroups, err := groups.ExtractGroups(allPages) - if err != nil { - t.Fatalf("Unable to extract groups: %v", err) - } - - for _, group := range allGroups { - tools.PrintResource(t, group) - } -} - func TestSecurityGroupsCreateUpdateDelete(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) group, err := CreateSecurityGroup(t, client) - if err != nil { - t.Fatalf("Unable to create security group: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSecurityGroup(t, client, group.ID) rule, err := CreateSecurityGroupRule(t, client, group.ID) - if err != nil { - t.Fatalf("Unable to create security group rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSecurityGroupRule(t, client, rule.ID) tools.PrintResource(t, group) + var name = "Update group" + var description = "" updateOpts := groups.UpdateOpts{ - Description: "A security group", + Name: name, + Description: &description, } newGroup, err := groups.Update(client, group.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update security group: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newGroup) + th.AssertEquals(t, newGroup.Name, name) + th.AssertEquals(t, newGroup.Description, description) + + listOpts := groups.ListOpts{} + allPages, err := groups.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + allGroups, err := groups.ExtractGroups(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, group := range allGroups { + if group.ID == newGroup.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } func TestSecurityGroupsPort(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) network, err := networking.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteNetwork(t, client, network.ID) subnet, err := networking.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeleteSubnet(t, client, subnet.ID) group, err := CreateSecurityGroup(t, client) - if err != nil { - t.Fatalf("Unable to create security group: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSecurityGroup(t, client, group.ID) rule, err := CreateSecurityGroupRule(t, client, group.ID) - if err != nil { - t.Fatalf("Unable to create security group rule: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSecurityGroupRule(t, client, rule.ID) port, err := CreatePortWithSecurityGroup(t, client, network.ID, subnet.ID, group.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer networking.DeletePort(t, client, port.ID) tools.PrintResource(t, port) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go index fdf6318d52f9..6380264b1069 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go @@ -6,18 +6,22 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateSubnetPool will create a subnetpool. An error will be returned if the // subnetpool could not be created. func CreateSubnetPool(t *testing.T, client *gophercloud.ServiceClient) (*subnetpools.SubnetPool, error) { subnetPoolName := tools.RandomString("TESTACC-", 8) + subnetPoolDescription := tools.RandomString("TESTACC-DESC-", 8) subnetPoolPrefixes := []string{ "10.0.0.0/8", } createOpts := subnetpools.CreateOpts{ - Name: subnetPoolName, - Prefixes: subnetPoolPrefixes, + Name: subnetPoolName, + Description: subnetPoolDescription, + Prefixes: subnetPoolPrefixes, + DefaultPrefixLen: 24, } t.Logf("Attempting to create a subnetpool: %s", subnetPoolName) @@ -28,6 +32,10 @@ func CreateSubnetPool(t *testing.T, client *gophercloud.ServiceClient) (*subnetp } t.Logf("Successfully created the subnetpool.") + + th.AssertEquals(t, subnetPool.Name, subnetPoolName) + th.AssertEquals(t, subnetPool.Description, subnetPoolDescription) + return subnetPool, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go index bdef6e67942d..e5c9c320ed08 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go @@ -8,58 +8,49 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestSubnetPoolsCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create a subnetpool subnetPool, err := CreateSubnetPool(t, client) - if err != nil { - t.Fatalf("Unable to create a subnetpool: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnetPool(t, client, subnetPool.ID) tools.PrintResource(t, subnetPool) newName := tools.RandomString("TESTACC-", 8) + newDescription := "" updateOpts := &subnetpools.UpdateOpts{ - Name: newName, + Name: newName, + Description: &newDescription, } _, err = subnetpools.Update(client, subnetPool.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update the subnetpool: %v", err) - } + th.AssertNoErr(t, err) newSubnetPool, err := subnetpools.Get(client, subnetPool.ID).Extract() - if err != nil { - t.Fatalf("Unable to get subnetpool: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newSubnetPool) -} - -func TestSubnetPoolsList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertEquals(t, newSubnetPool.Name, newName) + th.AssertEquals(t, newSubnetPool.Description, newDescription) allPages, err := subnetpools.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list subnetpools: %v", err) - } + th.AssertNoErr(t, err) allSubnetPools, err := subnetpools.ExtractSubnetPools(allPages) - if err != nil { - t.Fatalf("Unable to extract subnetpools: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, subnetpool := range allSubnetPools { - tools.PrintResource(t, subnetpool) + if subnetpool.ID == newSubnetPool.ID { + found = true + } } + + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks.go new file mode 100644 index 000000000000..18fc920fd64e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks.go @@ -0,0 +1,46 @@ +package trunks + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" +) + +func CreateTrunk(t *testing.T, client *gophercloud.ServiceClient, parentPortID string, subportIDs ...string) (trunk *trunks.Trunk, err error) { + trunkName := tools.RandomString("TESTACC-", 8) + iTrue := true + opts := trunks.CreateOpts{ + Name: trunkName, + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + PortID: parentPortID, + } + + opts.Subports = make([]trunks.Subport, len(subportIDs)) + for id, subportID := range subportIDs { + opts.Subports[id] = trunks.Subport{ + SegmentationID: id + 1, + SegmentationType: "vlan", + PortID: subportID, + } + } + + t.Logf("Attempting to create trunk: %s", opts.Name) + trunk, err = trunks.Create(client, opts).Extract() + if err == nil { + t.Logf("Successfully created trunk") + } + return +} + +func DeleteTrunk(t *testing.T, client *gophercloud.ServiceClient, trunkID string) { + t.Logf("Attempting to delete trunk: %s", trunkID) + err := trunks.Delete(client, trunkID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete trunk %s: %v", trunkID, err) + } + + t.Logf("Deleted trunk: %s", trunkID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go new file mode 100644 index 000000000000..7f562d65ce64 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go @@ -0,0 +1,286 @@ +// +build acceptance trunks + +package trunks + +import ( + "sort" + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + v2 "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestTrunkCRUD(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network client: %v", err) + } + + // Create Network + network, err := v2.CreateNetwork(t, client) + if err != nil { + t.Fatalf("Unable to create network: %v", 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + defer DeleteTrunk(t, client, trunk.ID) + + _, err = trunks.Get(client, trunk.ID).Extract() + if err != nil { + t.Fatalf("Unable to get trunk: %v", err) + } + + // Update Trunk + name := "" + description := "" + updateOpts := trunks.UpdateOpts{ + Name: &name, + Description: &description, + } + updatedTrunk, err := trunks.Update(client, trunk.ID, updateOpts).Extract() + if err != nil { + t.Fatalf("Unable to update trunk: %v", err) + } + + if trunk.Name == updatedTrunk.Name { + t.Fatalf("Trunk name was not updated correctly") + } + + if trunk.Description == updatedTrunk.Description { + t.Fatalf("Trunk description was not updated correctly") + } + + th.AssertDeepEquals(t, updatedTrunk.Name, name) + th.AssertDeepEquals(t, updatedTrunk.Description, description) + + // Get subports + subports, err := trunks.GetSubports(client, trunk.ID).Extract() + if err != nil { + t.Fatalf("Unable to get subports from the Trunk: %v", err) + } + th.AssertDeepEquals(t, trunk.Subports[0], subports[0]) + th.AssertDeepEquals(t, trunk.Subports[1], subports[1]) + + tools.PrintResource(t, trunk) +} + +func TestTrunkList(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network client: %v", err) + } + + allPages, err := trunks.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list trunks: %v", err) + } + + allTrunks, err := trunks.ExtractTrunks(allPages) + if err != nil { + t.Fatalf("Unable to extract trunks: %v", err) + } + + for _, trunk := range allTrunks { + tools.PrintResource(t, trunk) + } +} + +func TestTrunkSubportOperation(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network client: %v", err) + } + + // Create Network + network, err := v2.CreateNetwork(t, client) + if err != nil { + t.Fatalf("Unable to create network: %v", 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + defer DeleteTrunk(t, client, trunk.ID) + + // Add subports to the trunk + addSubportsOpts := trunks.AddSubportsOpts{ + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: subport1.ID, + }, + { + SegmentationID: 11, + SegmentationType: "vlan", + PortID: subport2.ID, + }, + }, + } + updatedTrunk, err := trunks.AddSubports(client, trunk.ID, addSubportsOpts).Extract() + if err != nil { + t.Fatalf("Unable to add subports to the Trunk: %v", 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]) + + // Remove the Subports from the trunk + subRemoveOpts := trunks.RemoveSubportsOpts{ + Subports: []trunks.RemoveSubport{ + {PortID: subport1.ID}, + {PortID: subport2.ID}, + }, + } + updatedAgainTrunk, err := trunks.RemoveSubports(client, trunk.ID, subRemoveOpts).Extract() + if err != nil { + t.Fatalf("Unable to remove subports from the Trunk: %v", 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) + } + + // Create Network + network, err := v2.CreateNetwork(t, client) + if err != nil { + t.Fatalf("Unable to create network: %v", 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + defer DeleteTrunk(t, client, trunk.ID) + + tagReplaceAllOpts := attributestags.ReplaceAllOpts{ + // docs say list of tags, but it's a set e.g no duplicates + Tags: []string{"a", "b", "c"}, + } + tags, err := attributestags.ReplaceAll(client, "trunks", trunk.ID, tagReplaceAllOpts).Extract() + if err != nil { + t.Fatalf("Unable to set trunk tags: %v", err) + } + + gtrunk, err := trunks.Get(client, trunk.ID).Extract() + if err != nil { + t.Fatalf("Unable to get trunk: %v", err) + } + tags = gtrunk.Tags + sort.Strings(tags) // Ensure ordering, older OpenStack versions aren't sorted... + th.AssertDeepEquals(t, []string{"a", "b", "c"}, tags) + + // Add a tag + err = attributestags.Add(client, "trunks", trunk.ID, "d").ExtractErr() + th.AssertNoErr(t, err) + + // Delete a tag + err = attributestags.Delete(client, "trunks", trunk.ID, "a").ExtractErr() + th.AssertNoErr(t, err) + + // Verify expected tags are set in the List response + tags, err = attributestags.List(client, "trunks", trunk.ID).Extract() + th.AssertNoErr(t, err) + sort.Strings(tags) + th.AssertDeepEquals(t, []string{"b", "c", "d"}, tags) + + // Delete all tags + err = attributestags.DeleteAll(client, "trunks", trunk.ID).ExtractErr() + th.AssertNoErr(t, err) + tags, err = attributestags.List(client, "trunks", trunk.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(tags)) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go new file mode 100644 index 000000000000..e91ada32d669 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go @@ -0,0 +1,106 @@ +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// VLANTransparentNetwork represents OpenStack V2 Networking Network with the +// "vlan-transparent" extension enabled. +type VLANTransparentNetwork struct { + networks.Network + vlantransparent.TransparentExt +} + +// ListVLANTransparentNetworks will list networks with the "vlan-transparent" +// extension. An error will be returned networks could not be listed. +func ListVLANTransparentNetworks(t *testing.T, client *gophercloud.ServiceClient) ([]*VLANTransparentNetwork, error) { + iTrue := true + networkListOpts := networks.ListOpts{} + listOpts := vlantransparent.ListOptsExt{ + ListOptsBuilder: networkListOpts, + VLANTransparent: &iTrue, + } + + var allNetworks []*VLANTransparentNetwork + + t.Log("Attempting to list VLAN-transparent networks") + + allPages, err := networks.List(client, listOpts).AllPages() + if err != nil { + return nil, err + } + err = networks.ExtractNetworksInto(allPages, &allNetworks) + if err != nil { + return nil, err + } + + t.Log("Successfully retrieved networks.") + + return allNetworks, nil +} + +// CreateVLANTransparentNetwork will create a network with the +// "vlan-transparent" extension. An error will be returned if the network could +// not be created. +func CreateVLANTransparentNetwork(t *testing.T, client *gophercloud.ServiceClient) (*VLANTransparentNetwork, error) { + networkName := tools.RandomString("TESTACC-", 8) + networkCreateOpts := networks.CreateOpts{ + Name: networkName, + } + + iTrue := true + createOpts := vlantransparent.CreateOptsExt{ + CreateOptsBuilder: &networkCreateOpts, + VLANTransparent: &iTrue, + } + + t.Logf("Attempting to create a VLAN-transparent network: %s", networkName) + + var network VLANTransparentNetwork + err := networks.Create(client, createOpts).ExtractInto(&network) + if err != nil { + return nil, err + } + + t.Logf("Successfully created the network.") + + th.AssertEquals(t, networkName, network.Name) + + return &network, nil +} + +// UpdateVLANTransparentNetwork will update a network with the +// "vlan-transparent" extension. An error will be returned if the network could +// not be updated. +func UpdateVLANTransparentNetwork(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*VLANTransparentNetwork, error) { + networkName := tools.RandomString("TESTACC-NEW-", 6) + networkUpdateOpts := networks.UpdateOpts{ + Name: &networkName, + } + + iFalse := false + updateOpts := vlantransparent.UpdateOptsExt{ + UpdateOptsBuilder: &networkUpdateOpts, + VLANTransparent: &iFalse, + } + + t.Logf("Attempting to update a VLAN-transparent network: %s", networkID) + + var network VLANTransparentNetwork + err := networks.Update(client, networkID, updateOpts).ExtractInto(&network) + if err != nil { + return nil, err + } + + t.Logf("Successfully updated the network.") + + th.AssertEquals(t, networkName, network.Name) + + return &network, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go new file mode 100644 index 000000000000..248ab04ffaca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go @@ -0,0 +1,45 @@ +// +build acceptance networking vlantransparent + +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + networkingv2 "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/acceptance/tools" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestVLANTransparentCRUD(t *testing.T) { + t.Skip("We don't have VLAN transparent extension in OpenLab.") + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create a VLAN transparent network. + network, err := CreateVLANTransparentNetwork(t, client) + th.AssertNoErr(t, err) + defer networkingv2.DeleteNetwork(t, client, network.ID) + + tools.PrintResource(t, network) + + // Update the created VLAN transparent network. + newNetwork, err := UpdateVLANTransparentNetwork(t, client, network.ID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, newNetwork) + + // Check that the created VLAN transparent network exists. + vlanTransparentNetworks, err := ListVLANTransparentNetworks(t, client) + th.AssertNoErr(t, err) + + var found bool + for _, vlanTransparentNetwork := range vlanTransparentNetworks { + if vlanTransparentNetwork.ID == network.ID { + found = true + } + } + + th.AssertEquals(t, found, true) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go index 853065d2190e..c45daf0e43bf 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go @@ -8,23 +8,18 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestGroupList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := endpointgroups.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list endpoint groups: %v", err) - } + th.AssertNoErr(t, err) allGroups, err := endpointgroups.ExtractEndpointGroups(allPages) - if err != nil { - t.Fatalf("Unable to extract endpoint groups: %v", err) - } + th.AssertNoErr(t, err) for _, group := range allGroups { tools.PrintResource(t, group) @@ -33,21 +28,15 @@ func TestGroupList(t *testing.T) { func TestGroupCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) group, err := CreateEndpointGroup(t, client) - if err != nil { - t.Fatalf("Unable to create Endpoint group: %v", err) - } + th.AssertNoErr(t, err) defer DeleteEndpointGroup(t, client, group.ID) tools.PrintResource(t, group) newGroup, err := endpointgroups.Get(client, group.ID).Extract() - if err != nil { - t.Fatalf("Unable to retrieve Endpoint group: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newGroup) updatedName := "updatedname" @@ -57,9 +46,6 @@ func TestGroupCRUD(t *testing.T) { Description: &updatedDescription, } updatedGroup, err := endpointgroups.Update(client, group.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update endpoint group: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, updatedGroup) - } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go index 2efa1e1b65f8..c14c9fb5e3b4 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go @@ -8,23 +8,18 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestIKEPolicyList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := ikepolicies.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list IKE policies: %v", err) - } + th.AssertNoErr(t, err) allPolicies, err := ikepolicies.ExtractPolicies(allPages) - if err != nil { - t.Fatalf("Unable to extract IKE policies: %v", err) - } + th.AssertNoErr(t, err) for _, policy := range allPolicies { tools.PrintResource(t, policy) @@ -33,22 +28,16 @@ func TestIKEPolicyList(t *testing.T) { func TestIKEPolicyCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) policy, err := CreateIKEPolicy(t, client) - if err != nil { - t.Fatalf("Unable to create IKE policy: %v", err) - } + th.AssertNoErr(t, err) defer DeleteIKEPolicy(t, client, policy.ID) tools.PrintResource(t, policy) newPolicy, err := ikepolicies.Get(client, policy.ID).Extract() - if err != nil { - t.Fatalf("Unable to get IKE policy: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPolicy) updatedName := "updatedname" @@ -61,9 +50,6 @@ func TestIKEPolicyCRUD(t *testing.T) { }, } updatedPolicy, err := ikepolicies.Update(client, policy.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update IKE policy: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, updatedPolicy) - } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go index 2fdee7dd92cb..7589590ee457 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go @@ -8,23 +8,18 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestIPSecPolicyList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := ipsecpolicies.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list IPSec policies: %v", err) - } + th.AssertNoErr(t, err) allPolicies, err := ipsecpolicies.ExtractPolicies(allPages) - if err != nil { - t.Fatalf("Unable to extract policies: %v", err) - } + th.AssertNoErr(t, err) for _, policy := range allPolicies { tools.PrintResource(t, policy) @@ -33,14 +28,10 @@ func TestIPSecPolicyList(t *testing.T) { func TestIPSecPolicyCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) policy, err := CreateIPSecPolicy(t, client) - if err != nil { - t.Fatalf("Unable to create IPSec policy: %v", err) - } + th.AssertNoErr(t, err) defer DeleteIPSecPolicy(t, client, policy.ID) tools.PrintResource(t, policy) @@ -50,14 +41,10 @@ func TestIPSecPolicyCRUD(t *testing.T) { } policy, err = ipsecpolicies.Update(client, policy.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update IPSec policy: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, policy) newPolicy, err := ipsecpolicies.Get(client, policy.ID).Extract() - if err != nil { - t.Fatalf("Unable to get IPSec policy: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPolicy) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go index f88aa7611dfa..d7bc05ce5eed 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go @@ -9,23 +9,18 @@ import ( layer3 "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestServiceList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := services.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list services: %v", err) - } + th.AssertNoErr(t, err) allServices, err := services.ExtractServices(allPages) - if err != nil { - t.Fatalf("Unable to extract services: %v", err) - } + th.AssertNoErr(t, err) for _, service := range allServices { tools.PrintResource(t, service) @@ -34,26 +29,18 @@ func TestServiceList(t *testing.T) { func TestServiceCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) router, err := layer3.CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router.ID) service, err := CreateService(t, client, router.ID) - if err != nil { - t.Fatalf("Unable to create service: %v", err) - } + th.AssertNoErr(t, err) defer DeleteService(t, client, service.ID) newService, err := services.Get(client, service.ID).Extract() - if err != nil { - t.Fatalf("Unable to get service: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, service) tools.PrintResource(t, newService) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go index 5bf756074734..72d025ea7a55 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go @@ -8,27 +8,21 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" networks "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2" layer3 "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" - "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestConnectionList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) allPages, err := siteconnections.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list IPSec site connections: %v", err) - } + th.AssertNoErr(t, err) allConnections, err := siteconnections.ExtractConnections(allPages) - if err != nil { - t.Fatalf("Unable to extract IPSec site connections: %v", err) - } + th.AssertNoErr(t, err) for _, connection := range allConnections { tools.PrintResource(t, connection) @@ -37,28 +31,20 @@ func TestConnectionList(t *testing.T) { func TestConnectionCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := networks.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer networks.DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := networks.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer networks.DeleteSubnet(t, client, subnet.ID) router, err := layer3.CreateExternalRouter(t, client) - if err != nil { - t.Fatalf("Unable to create router: %v", err) - } + th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router.ID) // Link router and subnet @@ -67,9 +53,7 @@ func TestConnectionCRUD(t *testing.T) { } _, err = routers.AddInterface(client, router.ID, aiOpts).Extract() - if err != nil { - t.Fatalf("Failed to add interface to router: %v", err) - } + th.AssertNoErr(t, err) defer func() { riOpts := routers.RemoveInterfaceOpts{ SubnetID: subnet.ID, @@ -79,47 +63,32 @@ func TestConnectionCRUD(t *testing.T) { // Create all needed resources for the connection service, err := CreateService(t, client, router.ID) - if err != nil { - t.Fatalf("Unable to create service: %v", err) - } + th.AssertNoErr(t, err) defer DeleteService(t, client, service.ID) ikepolicy, err := CreateIKEPolicy(t, client) - if err != nil { - t.Fatalf("Unable to create IKE policy: %v", err) - } + th.AssertNoErr(t, err) defer DeleteIKEPolicy(t, client, ikepolicy.ID) ipsecpolicy, err := CreateIPSecPolicy(t, client) - if err != nil { - t.Fatalf("Unable to create IPSec Policy: %v", err) - } + th.AssertNoErr(t, err) defer DeleteIPSecPolicy(t, client, ipsecpolicy.ID) peerEPGroup, err := CreateEndpointGroup(t, client) - if err != nil { - t.Fatalf("Unable to create Endpoint Group with CIDR endpoints: %v", err) - } + th.AssertNoErr(t, err) defer DeleteEndpointGroup(t, client, peerEPGroup.ID) localEPGroup, err := CreateEndpointGroupWithSubnet(t, client, subnet.ID) - if err != nil { - t.Fatalf("Unable to create Endpoint Group with subnet endpoints: %v", err) - } + th.AssertNoErr(t, err) defer DeleteEndpointGroup(t, client, localEPGroup.ID) conn, err := CreateSiteConnection(t, client, ikepolicy.ID, ipsecpolicy.ID, service.ID, peerEPGroup.ID, localEPGroup.ID) - if err != nil { - t.Fatalf("Unable to create IPSec Site Connection: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSiteConnection(t, client, conn.ID) newConnection, err := siteconnections.Get(client, conn.ID).Extract() - if err != nil { - t.Fatalf("Unable to get connection: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, conn) tools.PrintResource(t, newConnection) - } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go index 2194a4c70e07..2ba6a0905016 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go @@ -10,6 +10,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections" + th "github.com/gophercloud/gophercloud/testhelper" ) // CreateService will create a Service with a random name and a specified router ID @@ -32,6 +33,8 @@ func CreateService(t *testing.T, client *gophercloud.ServiceClient, routerID str t.Logf("Successfully created service %s", serviceName) + th.AssertEquals(t, service.Name, serviceName) + return service, nil } @@ -67,6 +70,8 @@ func CreateIPSecPolicy(t *testing.T, client *gophercloud.ServiceClient) (*ipsecp t.Logf("Successfully created IPSec policy %s", policyName) + th.AssertEquals(t, policy.Name, policyName) + return policy, nil } @@ -90,6 +95,8 @@ func CreateIKEPolicy(t *testing.T, client *gophercloud.ServiceClient) (*ikepolic t.Logf("Successfully created IKE policy %s", policyName) + th.AssertEquals(t, policy.Name, policyName) + return policy, nil } @@ -143,6 +150,8 @@ func CreateEndpointGroup(t *testing.T, client *gophercloud.ServiceClient) (*endp t.Logf("Successfully created group %s", groupName) + th.AssertEquals(t, group.Name, groupName) + return group, nil } @@ -168,6 +177,8 @@ func CreateEndpointGroupWithCIDR(t *testing.T, client *gophercloud.ServiceClient t.Logf("Successfully created group %s", groupName) t.Logf("%v", group) + th.AssertEquals(t, group.Name, groupName) + return group, nil } @@ -207,6 +218,8 @@ func CreateEndpointGroupWithSubnet(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created group %s", groupName) + th.AssertEquals(t, group.Name, groupName) + return group, nil } @@ -239,6 +252,8 @@ func CreateSiteConnection(t *testing.T, client *gophercloud.ServiceClient, ikepo t.Logf("Successfully created IPSec Site Connection %s", connectionName) + th.AssertEquals(t, connection.Name, connectionName) + return connection, nil } @@ -254,5 +269,4 @@ func DeleteSiteConnection(t *testing.T, client *gophercloud.ServiceClient, siteC } t.Logf("Deleted site connection: %s", siteConnectionID) - } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networking.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networking.go index b5dcb6d2c763..6fc87f28d8cb 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networking.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networking.go @@ -11,14 +11,23 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" + th "github.com/gophercloud/gophercloud/testhelper" ) +// PortWithExtraDHCPOpts represents a port with extra DHCP options configuration. +type PortWithExtraDHCPOpts struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt +} + // CreateNetwork will create basic network. An error will be returned if the // network could not be created. func CreateNetwork(t *testing.T, client *gophercloud.ServiceClient) (*networks.Network, error) { networkName := tools.RandomString("TESTACC-", 8) + networkDescription := tools.RandomString("TESTACC-DESC-", 8) createOpts := networks.CreateOpts{ Name: networkName, + Description: networkDescription, AdminStateUp: gophercloud.Enabled, } @@ -30,6 +39,10 @@ func CreateNetwork(t *testing.T, client *gophercloud.ServiceClient) (*networks.N } t.Logf("Successfully created network.") + + th.AssertEquals(t, network.Name, networkName) + th.AssertEquals(t, network.Description, networkDescription) + return network, nil } @@ -56,6 +69,9 @@ func CreateNetworkWithoutPortSecurity(t *testing.T, client *gophercloud.ServiceC } t.Logf("Successfully created network.") + + th.AssertEquals(t, network.Name, networkName) + return network, nil } @@ -63,12 +79,14 @@ func CreateNetworkWithoutPortSecurity(t *testing.T, client *gophercloud.ServiceC // returned if the port could not be created. func CreatePort(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID string) (*ports.Port, error) { portName := tools.RandomString("TESTACC-", 8) + portDescription := tools.RandomString("TESTACC-DESC-", 8) t.Logf("Attempting to create port: %s", portName) createOpts := ports.CreateOpts{ NetworkID: networkID, Name: portName, + Description: portDescription, AdminStateUp: gophercloud.Enabled, FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, } @@ -89,6 +107,9 @@ func CreatePort(t *testing.T, client *gophercloud.ServiceClient, networkID, subn t.Logf("Successfully created port: %s", portName) + th.AssertEquals(t, port.Name, portName) + th.AssertEquals(t, port.Description, portDescription) + return newPort, nil } @@ -124,6 +145,8 @@ func CreatePortWithNoSecurityGroup(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created port: %s", portName) + th.AssertEquals(t, port.Name, portName) + return newPort, nil } @@ -163,6 +186,94 @@ func CreatePortWithoutPortSecurity(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created port: %s", portName) + th.AssertEquals(t, port.Name, portName) + + return newPort, nil +} + +// CreatePortWithExtraDHCPOpts will create a port with DHCP options on the +// specified subnet. An error will be returned if the port could not be created. +func CreatePortWithExtraDHCPOpts(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID string) (*PortWithExtraDHCPOpts, error) { + portName := tools.RandomString("TESTACC-", 8) + + t.Logf("Attempting to create port: %s", portName) + + portCreateOpts := ports.CreateOpts{ + NetworkID: networkID, + Name: portName, + AdminStateUp: gophercloud.Enabled, + FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, + } + + createOpts := extradhcpopts.CreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + ExtraDHCPOpts: []extradhcpopts.CreateExtraDHCPOpt{ + { + OptName: "test_option_1", + OptValue: "test_value_1", + }, + }, + } + port := &PortWithExtraDHCPOpts{} + + err := ports.Create(client, createOpts).ExtractInto(port) + if err != nil { + return nil, err + } + + if err := WaitForPortToCreate(client, port.ID, 60); err != nil { + return nil, err + } + + err = ports.Get(client, port.ID).ExtractInto(port) + if err != nil { + return port, err + } + + t.Logf("Successfully created port: %s", portName) + + return port, nil +} + +// CreatePortWithMultipleFixedIPs will create a port with two FixedIPs on the +// specified subnet. An error will be returned if the port could not be created. +func CreatePortWithMultipleFixedIPs(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID string) (*ports.Port, error) { + portName := tools.RandomString("TESTACC-", 8) + portDescription := tools.RandomString("TESTACC-DESC-", 8) + + t.Logf("Attempting to create port with two fixed IPs: %s", portName) + + createOpts := ports.CreateOpts{ + NetworkID: networkID, + Name: portName, + Description: portDescription, + AdminStateUp: gophercloud.Enabled, + FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}, ports.IP{SubnetID: subnetID}}, + } + + port, err := ports.Create(client, createOpts).Extract() + if err != nil { + return port, err + } + + if err := WaitForPortToCreate(client, port.ID, 60); err != nil { + return port, err + } + + newPort, err := ports.Get(client, port.ID).Extract() + if err != nil { + return newPort, err + } + + t.Logf("Successfully created port: %s", portName) + + th.AssertEquals(t, port.Name, portName) + th.AssertEquals(t, port.Description, portDescription) + + if len(port.FixedIPs) != 2 { + t.Fatalf("Failed to create a port with two fixed IPs: %s", portName) + } + return newPort, nil } @@ -170,16 +281,18 @@ func CreatePortWithoutPortSecurity(t *testing.T, client *gophercloud.ServiceClie // will be returned if the subnet could not be created. func CreateSubnet(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*subnets.Subnet, error) { subnetName := tools.RandomString("TESTACC-", 8) + subnetDescription := tools.RandomString("TESTACC-DESC-", 8) subnetOctet := tools.RandomInt(1, 250) subnetCIDR := fmt.Sprintf("192.168.%d.0/24", subnetOctet) subnetGateway := fmt.Sprintf("192.168.%d.1", subnetOctet) createOpts := subnets.CreateOpts{ - NetworkID: networkID, - CIDR: subnetCIDR, - IPVersion: 4, - Name: subnetName, - EnableDHCP: gophercloud.Disabled, - GatewayIP: &subnetGateway, + NetworkID: networkID, + CIDR: subnetCIDR, + IPVersion: 4, + Name: subnetName, + Description: subnetDescription, + EnableDHCP: gophercloud.Disabled, + GatewayIP: &subnetGateway, } t.Logf("Attempting to create subnet: %s", subnetName) @@ -190,6 +303,12 @@ func CreateSubnet(t *testing.T, client *gophercloud.ServiceClient, networkID str } t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnet.Description, subnetDescription) + th.AssertEquals(t, subnet.GatewayIP, subnetGateway) + th.AssertEquals(t, subnet.CIDR, subnetCIDR) + return subnet, nil } @@ -200,6 +319,8 @@ func CreateSubnetWithDefaultGateway(t *testing.T, client *gophercloud.ServiceCli subnetName := tools.RandomString("TESTACC-", 8) subnetOctet := tools.RandomInt(1, 250) subnetCIDR := fmt.Sprintf("192.168.%d.0/24", subnetOctet) + defaultGateway := fmt.Sprintf("192.168.%d.1", subnetOctet) + createOpts := subnets.CreateOpts{ NetworkID: networkID, CIDR: subnetCIDR, @@ -216,6 +337,11 @@ func CreateSubnetWithDefaultGateway(t *testing.T, client *gophercloud.ServiceCli } t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnet.GatewayIP, defaultGateway) + th.AssertEquals(t, subnet.CIDR, subnetCIDR) + return subnet, nil } @@ -252,6 +378,11 @@ func CreateSubnetWithNoGateway(t *testing.T, client *gophercloud.ServiceClient, } t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnet.GatewayIP, "") + th.AssertEquals(t, subnet.CIDR, subnetCIDR) + return subnet, nil } @@ -278,6 +409,66 @@ func CreateSubnetWithSubnetPool(t *testing.T, client *gophercloud.ServiceClient, } t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnet.CIDR, subnetCIDR) + + return subnet, nil +} + +// CreateSubnetWithSubnetPoolNoCIDR will create a subnet associated with the +// provided subnetpool on the specified Network ID. +// An error will be returned if the subnet or the subnetpool could not be created. +func CreateSubnetWithSubnetPoolNoCIDR(t *testing.T, client *gophercloud.ServiceClient, networkID string, subnetPoolID string) (*subnets.Subnet, error) { + subnetName := tools.RandomString("TESTACC-", 8) + createOpts := subnets.CreateOpts{ + NetworkID: networkID, + IPVersion: 4, + Name: subnetName, + EnableDHCP: gophercloud.Disabled, + SubnetPoolID: subnetPoolID, + } + + t.Logf("Attempting to create subnet: %s", subnetName) + + subnet, err := subnets.Create(client, createOpts).Extract() + if err != nil { + return subnet, err + } + + t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + + return subnet, nil +} + +// CreateSubnetWithSubnetPoolPrefixlen will create a subnet associated with the +// provided subnetpool on the specified Network ID and with overwritten +// prefixlen instead of the default subnetpool prefixlen. +// An error will be returned if the subnet or the subnetpool could not be created. +func CreateSubnetWithSubnetPoolPrefixlen(t *testing.T, client *gophercloud.ServiceClient, networkID string, subnetPoolID string) (*subnets.Subnet, error) { + subnetName := tools.RandomString("TESTACC-", 8) + createOpts := subnets.CreateOpts{ + NetworkID: networkID, + IPVersion: 4, + Name: subnetName, + EnableDHCP: gophercloud.Disabled, + SubnetPoolID: subnetPoolID, + Prefixlen: 12, + } + + t.Logf("Attempting to create subnet: %s", subnetName) + + subnet, err := subnets.Create(client, createOpts).Extract() + if err != nil { + return subnet, err + } + + t.Logf("Successfully created subnet.") + + th.AssertEquals(t, subnet.Name, subnetName) + return subnet, nil } @@ -337,53 +528,3 @@ func WaitForPortToCreate(client *gophercloud.ServiceClient, portID string, secs return false, nil }) } - -// PortWithExtraDHCPOpts represents a port with extra DHCP options configuration. -type PortWithExtraDHCPOpts struct { - ports.Port - extradhcpopts.ExtraDHCPOptsExt -} - -// CreatePortWithExtraDHCPOpts will create a port with DHCP options on the -// specified subnet. An error will be returned if the port could not be created. -func CreatePortWithExtraDHCPOpts(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID string) (*PortWithExtraDHCPOpts, error) { - portName := tools.RandomString("TESTACC-", 8) - - t.Logf("Attempting to create port: %s", portName) - - portCreateOpts := ports.CreateOpts{ - NetworkID: networkID, - Name: portName, - AdminStateUp: gophercloud.Enabled, - FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, - } - - createOpts := extradhcpopts.CreateOptsExt{ - CreateOptsBuilder: portCreateOpts, - ExtraDHCPOpts: []extradhcpopts.CreateExtraDHCPOpt{ - { - OptName: "test_option_1", - OptValue: "test_value_1", - }, - }, - } - port := &PortWithExtraDHCPOpts{} - - err := ports.Create(client, createOpts).ExtractInto(port) - if err != nil { - return nil, err - } - - if err := WaitForPortToCreate(client, port.ID, 60); err != nil { - return nil, err - } - - err = ports.Get(client, port.ID).ExtractInto(port) - if err != nil { - return port, err - } - - t.Logf("Successfully created port: %s", portName) - - return port, nil -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networks_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networks_test.go index ab4c2b1ce91b..e00ab0a32eee 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networks_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/networks_test.go @@ -7,76 +7,120 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsecurity" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestNetworksList(t *testing.T) { +func TestNetworksExternalList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) + + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) type networkWithExt struct { networks.Network - portsecurity.PortSecurityExt + external.NetworkExternalExt } var allNetworks []networkWithExt - allPages, err := networks.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list networks: %v", err) + iTrue := true + networkListOpts := networks.ListOpts{ + ID: choices.ExternalNetworkID, + } + listOpts := external.ListOptsExt{ + ListOptsBuilder: networkListOpts, + External: &iTrue, } + allPages, err := networks.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + err = networks.ExtractNetworksInto(allPages, &allNetworks) - if err != nil { - t.Fatalf("Unable to extract networks: %v", err) - } + th.AssertNoErr(t, err) + var found bool for _, network := range allNetworks { - tools.PrintResource(t, network) + if network.External == true && network.ID == choices.ExternalNetworkID { + found = true + } + } + + th.AssertEquals(t, found, true) + + iFalse := false + networkListOpts = networks.ListOpts{ + ID: choices.ExternalNetworkID, + } + listOpts = external.ListOptsExt{ + ListOptsBuilder: networkListOpts, + External: &iFalse, } + + allPages, err = networks.List(client, listOpts).AllPages() + th.AssertNoErr(t, err) + + v, err := networks.ExtractNetworks(allPages) + th.AssertEquals(t, len(v), 0) } func TestNetworksCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create a network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) tools.PrintResource(t, network) newName := tools.RandomString("TESTACC-", 8) + newDescription := "" updateOpts := &networks.UpdateOpts{ - Name: newName, + Name: &newName, + Description: &newDescription, } _, err = networks.Update(client, network.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update network: %v", err) - } + th.AssertNoErr(t, err) newNetwork, err := networks.Get(client, network.ID).Extract() - if err != nil { - t.Fatalf("Unable to retrieve network: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newNetwork) + th.AssertEquals(t, newNetwork.Name, newName) + th.AssertEquals(t, newNetwork.Description, newDescription) + + type networkWithExt struct { + networks.Network + portsecurity.PortSecurityExt + } + + var allNetworks []networkWithExt + + allPages, err := networks.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + err = networks.ExtractNetworksInto(allPages, &allNetworks) + th.AssertNoErr(t, err) + + var found bool + for _, network := range allNetworks { + if network.ID == newNetwork.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } func TestNetworksPortSecurityCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create a network without port security network, err := CreateNetworkWithoutPortSecurity(t, client) @@ -91,9 +135,7 @@ func TestNetworksPortSecurityCRUD(t *testing.T) { } err = networks.Get(client, network.ID).ExtractInto(&networkWithExtensions) - if err != nil { - t.Fatalf("Unable to retrieve network: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, networkWithExtensions) @@ -105,9 +147,7 @@ func TestNetworksPortSecurityCRUD(t *testing.T) { } err = networks.Update(client, network.ID, updateOpts).ExtractInto(&networkWithExtensions) - if err != nil { - t.Fatalf("Unable to update network: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, networkWithExtensions) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/ports_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/ports_test.go index 5b820d7e57ec..b50edbb0f1cd 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/ports_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/ports_test.go @@ -11,54 +11,26 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsecurity" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestPortsList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } - - allPages, err := ports.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list ports: %v", err) - } - - allPorts, err := ports.ExtractPorts(allPages) - if err != nil { - t.Fatalf("Unable to extract ports: %v", err) - } - - for _, port := range allPorts { - tools.PrintResource(t, port) - } -} - func TestPortsCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create port port, err := CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) if len(port.SecurityGroups) != 1 { @@ -68,52 +40,60 @@ func TestPortsCRUD(t *testing.T) { tools.PrintResource(t, port) // Update port - newPortName := tools.RandomString("TESTACC-", 8) + newPortName := "" + newPortDescription := "" updateOpts := ports.UpdateOpts{ - Name: newPortName, + Name: &newPortName, + Description: &newPortDescription, } newPort, err := ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) + + th.AssertEquals(t, newPort.Name, newPortName) + th.AssertEquals(t, newPort.Description, newPortDescription) + + allPages, err := ports.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allPorts, err := ports.ExtractPorts(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, port := range allPorts { + if port.ID == newPort.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } func TestPortsRemoveSecurityGroups(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create port port, err := CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) tools.PrintResource(t, port) // Create a Security Group group, err := extensions.CreateSecurityGroup(t, client) - if err != nil { - t.Fatalf("Unable to create security group: %v", err) - } + th.AssertNoErr(t, err) defer extensions.DeleteSecurityGroup(t, client, group.ID) // Add the group to the port @@ -121,18 +101,14 @@ func TestPortsRemoveSecurityGroups(t *testing.T) { SecurityGroups: &[]string{group.ID}, } newPort, err := ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) // Remove the group updateOpts = ports.UpdateOpts{ SecurityGroups: &[]string{}, } newPort, err = ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) @@ -143,36 +119,26 @@ func TestPortsRemoveSecurityGroups(t *testing.T) { func TestPortsDontAlterSecurityGroups(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create a Security Group group, err := extensions.CreateSecurityGroup(t, client) - if err != nil { - t.Fatalf("Unable to create security group: %v", err) - } + th.AssertNoErr(t, err) defer extensions.DeleteSecurityGroup(t, client, group.ID) // Create port port, err := CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) tools.PrintResource(t, port) @@ -182,18 +148,15 @@ func TestPortsDontAlterSecurityGroups(t *testing.T) { SecurityGroups: &[]string{group.ID}, } newPort, err := ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) // Update the port again + var name = "some_port" updateOpts = ports.UpdateOpts{ - Name: "some_port", + Name: &name, } newPort, err = ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) @@ -204,29 +167,21 @@ func TestPortsDontAlterSecurityGroups(t *testing.T) { func TestPortsWithNoSecurityGroup(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create port port, err := CreatePortWithNoSecurityGroup(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) tools.PrintResource(t, port) @@ -238,29 +193,21 @@ func TestPortsWithNoSecurityGroup(t *testing.T) { func TestPortsRemoveAddressPair(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create port port, err := CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) tools.PrintResource(t, port) @@ -272,18 +219,14 @@ func TestPortsRemoveAddressPair(t *testing.T) { }, } newPort, err := ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) // Remove the address pair updateOpts = ports.UpdateOpts{ AllowedAddressPairs: &[]ports.AddressPair{}, } newPort, err = ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) @@ -294,29 +237,21 @@ func TestPortsRemoveAddressPair(t *testing.T) { func TestPortsDontUpdateAllowedAddressPairs(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create port port, err := CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) tools.PrintResource(t, port) @@ -328,20 +263,17 @@ func TestPortsDontUpdateAllowedAddressPairs(t *testing.T) { }, } newPort, err := ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) // Remove the address pair + var name = "some_port" updateOpts = ports.UpdateOpts{ - Name: "some_port", + Name: &name, } newPort, err = ports.Update(client, port.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) @@ -352,29 +284,21 @@ func TestPortsDontUpdateAllowedAddressPairs(t *testing.T) { func TestPortsPortSecurityCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create port port, err := CreatePortWithoutPortSecurity(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) var portWithExt struct { @@ -383,9 +307,7 @@ func TestPortsPortSecurityCRUD(t *testing.T) { } err = ports.Get(client, port.ID).ExtractInto(&portWithExt) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, portWithExt) @@ -397,38 +319,28 @@ func TestPortsPortSecurityCRUD(t *testing.T) { } err = ports.Update(client, port.ID, updateOpts).ExtractInto(&portWithExt) - if err != nil { - t.Fatalf("Unable to update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, portWithExt) } func TestPortsWithExtraDHCPOptsCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create a Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create a network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create a Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create a subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) // Create a port with extra DHCP options. port, err := CreatePortWithExtraDHCPOpts(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create a port: %v", err) - } + th.AssertNoErr(t, err) defer DeletePort(t, client, port.ID) tools.PrintResource(t, port) @@ -436,7 +348,7 @@ func TestPortsWithExtraDHCPOptsCRUD(t *testing.T) { // Update the port with extra DHCP options. newPortName := tools.RandomString("TESTACC-", 8) portUpdateOpts := ports.UpdateOpts{ - Name: newPortName, + Name: &newPortName, } existingOpt := port.ExtraDHCPOpts[0] @@ -458,9 +370,7 @@ func TestPortsWithExtraDHCPOptsCRUD(t *testing.T) { newPort := &PortWithExtraDHCPOpts{} err = ports.Update(client, port.ID, updateOpts).ExtractInto(newPort) - if err != nil { - t.Fatalf("Could not update port: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newPort) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/subnets_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/subnets_test.go index ee665286ed87..8b9629345173 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/subnets_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/subnets_test.go @@ -11,88 +11,71 @@ import ( subnetpools "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/subnetpools" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" + th "github.com/gophercloud/gophercloud/testhelper" ) -func TestSubnetsList(t *testing.T) { - client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } - - allPages, err := subnets.List(client, nil).AllPages() - if err != nil { - t.Fatalf("Unable to list subnets: %v", err) - } - - allSubnets, err := subnets.ExtractSubnets(allPages) - if err != nil { - t.Fatalf("Unable to extract subnets: %v", err) - } - - for _, subnet := range allSubnets { - tools.PrintResource(t, subnet) - } -} - func TestSubnetCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) tools.PrintResource(t, subnet) // Update Subnet newSubnetName := tools.RandomString("TESTACC-", 8) + newSubnetDescription := "" updateOpts := subnets.UpdateOpts{ - Name: newSubnetName, + Name: &newSubnetName, + Description: &newSubnetDescription, } _, err = subnets.Update(client, subnet.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update subnet: %v", err) - } + th.AssertNoErr(t, err) // Get subnet newSubnet, err := subnets.Get(client, subnet.ID).Extract() - if err != nil { - t.Fatalf("Unable to get subnet: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, newSubnet) + th.AssertEquals(t, newSubnet.Name, newSubnetName) + th.AssertEquals(t, newSubnet.Description, newSubnetDescription) + + allPages, err := subnets.List(client, nil).AllPages() + th.AssertNoErr(t, err) + + allSubnets, err := subnets.ExtractSubnets(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, subnet := range allSubnets { + if subnet.ID == newSubnet.ID { + found = true + } + } + + th.AssertEquals(t, found, true) } func TestSubnetsDefaultGateway(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnetWithDefaultGateway(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) tools.PrintResource(t, subnet) @@ -107,9 +90,7 @@ func TestSubnetsDefaultGateway(t *testing.T) { } newSubnet, err := subnets.Update(client, subnet.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update subnet") - } + th.AssertNoErr(t, err) if newSubnet.GatewayIP != "" { t.Fatalf("Gateway was not updated correctly") @@ -118,22 +99,16 @@ func TestSubnetsDefaultGateway(t *testing.T) { func TestSubnetsNoGateway(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := CreateSubnetWithNoGateway(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) tools.PrintResource(t, subnet) @@ -149,9 +124,7 @@ func TestSubnetsNoGateway(t *testing.T) { } newSubnet, err := subnets.Update(client, subnet.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update subnet") - } + th.AssertNoErr(t, err) if newSubnet.GatewayIP == "" { t.Fatalf("Gateway was not updated correctly") @@ -160,29 +133,73 @@ func TestSubnetsNoGateway(t *testing.T) { func TestSubnetsWithSubnetPool(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Create Network network, err := CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer DeleteNetwork(t, client, network.ID) // Create SubnetPool subnetPool, err := subnetpools.CreateSubnetPool(t, client) - if err != nil { - t.Fatalf("Unable to create subnet pool: %v", err) - } + th.AssertNoErr(t, err) defer subnetpools.DeleteSubnetPool(t, client, subnetPool.ID) // Create Subnet subnet, err := CreateSubnetWithSubnetPool(t, client, network.ID, subnetPool.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) + th.AssertNoErr(t, err) + defer DeleteSubnet(t, client, subnet.ID) + + tools.PrintResource(t, subnet) + + if subnet.GatewayIP == "" { + t.Fatalf("A subnet pool was not associated.") + } +} + +func TestSubnetsWithSubnetPoolNoCIDR(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create Network + network, err := CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer DeleteNetwork(t, client, network.ID) + + // Create SubnetPool + subnetPool, err := subnetpools.CreateSubnetPool(t, client) + th.AssertNoErr(t, err) + defer subnetpools.DeleteSubnetPool(t, client, subnetPool.ID) + + // Create Subnet + subnet, err := CreateSubnetWithSubnetPoolNoCIDR(t, client, network.ID, subnetPool.ID) + th.AssertNoErr(t, err) + defer DeleteSubnet(t, client, subnet.ID) + + tools.PrintResource(t, subnet) + + if subnet.GatewayIP == "" { + t.Fatalf("A subnet pool was not associated.") } +} + +func TestSubnetsWithSubnetPoolPrefixlen(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create Network + network, err := CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer DeleteNetwork(t, client, network.ID) + + // Create SubnetPool + subnetPool, err := subnetpools.CreateSubnetPool(t, client) + th.AssertNoErr(t, err) + defer subnetpools.DeleteSubnetPool(t, client, subnetPool.ID) + + // Create Subnet + subnet, err := CreateSubnetWithSubnetPoolPrefixlen(t, client, network.ID, subnetPool.ID) + th.AssertNoErr(t, err) defer DeleteSubnet(t, client, subnet.ID) tools.PrintResource(t, subnet) @@ -190,4 +207,60 @@ func TestSubnetsWithSubnetPool(t *testing.T) { if subnet.GatewayIP == "" { t.Fatalf("A subnet pool was not associated.") } + + cidrParts := strings.Split(subnet.CIDR, "/") + if len(cidrParts) != 2 { + t.Fatalf("Got invalid CIDR for subnet '%s': %s", subnet.ID, subnet.CIDR) + } + + if cidrParts[1] != "12" { + t.Fatalf("Got invalid prefix length for subnet '%s': wanted 12 but got '%s'", subnet.ID, cidrParts[1]) + } +} + +func TestSubnetDNSNameservers(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create Network + network, err := CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer DeleteNetwork(t, client, network.ID) + + // Create Subnet + subnet, err := CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer DeleteSubnet(t, client, subnet.ID) + + tools.PrintResource(t, subnet) + + // Update Subnet + dnsNameservers := []string{"1.1.1.1"} + updateOpts := subnets.UpdateOpts{ + DNSNameservers: &dnsNameservers, + } + _, err = subnets.Update(client, subnet.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + // Get subnet + newSubnet, err := subnets.Get(client, subnet.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newSubnet) + th.AssertEquals(t, len(newSubnet.DNSNameservers), 1) + + // Update Subnet again + dnsNameservers = []string{} + updateOpts = subnets.UpdateOpts{ + DNSNameservers: &dnsNameservers, + } + _, err = subnets.Update(client, subnet.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + // Get subnet + newSubnet, err = subnets.Get(client, subnet.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, newSubnet) + th.AssertEquals(t, len(newSubnet.DNSNameservers), 0) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go index 5673aa10c91f..8a86762f06e4 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go @@ -77,16 +77,33 @@ func TestContainers(t *testing.T) { th.AssertNoErr(t, updateres.Err) // After the tests are done, delete the metadata that was set. defer func() { - tempMap := make(map[string]string) - for k := range metadata { - tempMap[k] = "" + temp := []string{} + for k, _ := range metadata { + temp = append(temp, k) } - res := containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: tempMap}) + res := containers.Update(client, cNames[0], &containers.UpdateOpts{RemoveMetadata: temp}) th.AssertNoErr(t, res.Err) + + // confirm the metadata was removed + getOpts := containers.GetOpts{ + Newest: true, + } + + cm, err := containers.Get(client, cNames[0], getOpts).ExtractMetadata() + th.AssertNoErr(t, err) + for k, _ := range metadata { + if _, ok := cm[k]; ok { + t.Errorf("Unexpected custom metadata with key: %s", k) + } + } }() // Retrieve a container's metadata. - cm, err := containers.Get(client, cNames[0]).ExtractMetadata() + getOpts := containers.GetOpts{ + Newest: true, + } + + cm, err := containers.Get(client, cNames[0], getOpts).ExtractMetadata() th.AssertNoErr(t, err) for k := range metadata { if cm[k] != metadata[strings.Title(k)] { diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/objects_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/objects_test.go index 1335d0cbc19f..ed3def62a392 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/objects_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/objectstorage/v1/objects_test.go @@ -97,7 +97,10 @@ func TestObjects(t *testing.T) { th.AssertNoErr(t, err) // Download the another object that was create above. - downloadres = objects.Download(client, cName, oNames[1], nil) + downloadOpts := objects.DownloadOpts{ + Newest: true, + } + downloadres = objects.Download(client, cName, oNames[1], downloadOpts) th.AssertNoErr(t, downloadres.Err) o2Content, err := downloadres.ExtractContent() th.AssertNoErr(t, err) @@ -127,7 +130,10 @@ func TestObjects(t *testing.T) { }() // Retrieve an object's metadata. - om, err := objects.Get(client, cName, oNames[0], nil).ExtractMetadata() + getOpts := objects.GetOpts{ + Newest: true, + } + om, err := objects.Get(client, cName, oNames[0], getOpts).ExtractMetadata() th.AssertNoErr(t, err) for k := range metadata { if om[k] != metadata[strings.Title(k)] { diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go index 1b4866291636..03806a36d6e1 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/buildinfo_test.go @@ -5,14 +5,14 @@ package v1 import ( "testing" + "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/openstack/orchestration/v1/buildinfo" th "github.com/gophercloud/gophercloud/testhelper" ) func TestBuildInfo(t *testing.T) { - // Create a provider client for making the HTTP requests. - // See common.go in this directory for more information. - client := newClient(t) + client, err := clients.NewOrchestrationV1Client() + th.AssertNoErr(t, err) bi, err := buildinfo.Get(client).Extract() th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/common.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/common.go deleted file mode 100644 index 4eec2e3156c8..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/common.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build acceptance - -package v1 - -import ( - "fmt" - "os" - "testing" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" - th "github.com/gophercloud/gophercloud/testhelper" -) - -var template = fmt.Sprintf(` -{ - "heat_template_version": "2013-05-23", - "description": "Simple template to test heat commands", - "parameters": {}, - "resources": { - "hello_world": { - "type":"OS::Nova::Server", - "properties": { - "flavor": "%s", - "image": "%s", - "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" - } - } - } -}`, os.Getenv("OS_FLAVOR_ID"), os.Getenv("OS_IMAGE_ID")) - -func newClient(t *testing.T) *gophercloud.ServiceClient { - ao, err := openstack.AuthOptionsFromEnv() - th.AssertNoErr(t, err) - - client, err := openstack.AuthenticatedClient(ao) - th.AssertNoErr(t, err) - - c, err := openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) - th.AssertNoErr(t, err) - return c -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json deleted file mode 100644 index 11cfc8053428..000000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/hello-compute.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "heat_template_version": "2013-05-23", - "resources": { - "compute_instance": { - "type": "OS::Nova::Server", - "properties": { - "flavor": "m1.small", - "image": "cirros-0.3.2-x86_64-disk", - "name": "Single Compute Instance" - } - } - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/orchestration.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/orchestration.go new file mode 100644 index 000000000000..698153793174 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/orchestration.go @@ -0,0 +1,115 @@ +package v1 + +import ( + "fmt" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +const basicTemplateResourceName = "secgroup_1" +const basicTemplate = ` + { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "resources": { + "secgroup_1": { + "type": "OS::Neutron::SecurityGroup", + "properties": { + "description": "Gophercloud test", + "name": "secgroup_1" + } + } + } + } +` + +const validateTemplate = ` + { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + }, + "resources": { + "hello_world": { + "type": "OS::Nova::Server", + "properties": { + "key_name": "heat_key", + "flavor": { + "get_param": "flavor" + }, + "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", + "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n" + } + } + } + } +` + +// CreateStack will create a heat stack with a randomly generated name. +// An error will be returned if the stack failed to be created. +func CreateStack(t *testing.T, client *gophercloud.ServiceClient) (*stacks.RetrievedStack, error) { + stackName := tools.RandomString("ACCPTEST", 8) + t.Logf("Attempting to create stack %s", stackName) + + template := new(stacks.Template) + template.Bin = []byte(basicTemplate) + + createOpts := stacks.CreateOpts{ + Name: stackName, + Timeout: 60, + TemplateOpts: template, + DisableRollback: gophercloud.Disabled, + } + + stack, err := stacks.Create(client, createOpts).Extract() + th.AssertNoErr(t, err) + + if err := WaitForStackStatus(client, stackName, stack.ID, "CREATE_COMPLETE"); err != nil { + return nil, err + } + + newStack, err := stacks.Get(client, stackName, stack.ID).Extract() + return newStack, err +} + +// DeleteStack deletes a stack via its ID. +// A fatal error will occur if the stack failed to be deleted. This works +// best when used as a deferred function. +func DeleteStack(t *testing.T, client *gophercloud.ServiceClient, stackName, stackID string) { + t.Logf("Attempting to delete stack %s (%s)", stackName, stackID) + + err := stacks.Delete(client, stackName, stackID).ExtractErr() + if err != nil { + t.Fatalf("Failed to delete stack %s: %s", stackID, err) + } + + t.Logf("Deleted stack: %s", stackID) +} + +// WaitForStackStatus will wait until a stack has reached a certain status. +func WaitForStackStatus(client *gophercloud.ServiceClient, stackName, stackID, status string) error { + return tools.WaitFor(func() (bool, error) { + latest, err := stacks.Get(client, stackName, stackID).Extract() + if err != nil { + return false, err + } + + if latest.Status == status { + return true, nil + } + + if latest.Status == "ERROR" { + return false, fmt.Errorf("Stack in ERROR state") + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go index 4be4bf676a51..53c5970a21de 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackevents_test.go @@ -5,64 +5,35 @@ package v1 import ( "testing" - "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents" - "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks" - "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" ) func TestStackEvents(t *testing.T) { - // Create a provider client for making the HTTP requests. - // See common.go in this directory for more information. - client := newClient(t) - - stackName := "postman_stack_2" - resourceName := "hello_world" - var eventID string + client, err := clients.NewOrchestrationV1Client() + th.AssertNoErr(t, err) - createOpts := stacks.CreateOpts{ - Name: stackName, - Template: template, - Timeout: 5, - } - stack, err := stacks.Create(client, createOpts).Extract() + stack, err := CreateStack(t, client) th.AssertNoErr(t, err) - t.Logf("Created stack: %+v\n", stack) - defer func() { - err := stacks.Delete(client, stackName, stack.ID).ExtractErr() - th.AssertNoErr(t, err) - t.Logf("Deleted stack (%s)", stackName) - }() - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName, stack.ID).Extract() - if err != nil { - return false, err - } - if getStack.Status == "CREATE_COMPLETE" { - return true, nil - } - return false, nil - }) + defer DeleteStack(t, client, stack.Name, stack.ID) - err = stackevents.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) { - events, err := stackevents.ExtractEvents(page) - th.AssertNoErr(t, err) - t.Logf("listed events: %+v\n", events) - eventID = events[0].ID - return false, nil - }) + allPages, err := stackevents.List(client, stack.Name, stack.ID, nil).AllPages() th.AssertNoErr(t, err) + allEvents, err := stackevents.ExtractEvents(allPages) + th.AssertNoErr(t, err) + + th.AssertEquals(t, len(allEvents), 4) - err = stackevents.ListResourceEvents(client, stackName, stack.ID, resourceName, nil).EachPage(func(page pagination.Page) (bool, error) { - resourceEvents, err := stackevents.ExtractEvents(page) + /* + allPages is currently broke + allPages, err = stackevents.ListResourceEvents(client, stack.Name, stack.ID, basicTemplateResourceName, nil).AllPages() + th.AssertNoErr(t, err) + allEvents, err = stackevents.ExtractEvents(allPages) th.AssertNoErr(t, err) - t.Logf("listed resource events: %+v\n", resourceEvents) - return false, nil - }) - th.AssertNoErr(t, err) - event, err := stackevents.Get(client, stackName, stack.ID, resourceName, eventID).Extract() - th.AssertNoErr(t, err) - t.Logf("retrieved event: %+v\n", event) + for _, v := range allEvents { + tools.PrintResource(t, v) + } + */ } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go index 50a0f0631116..02eb78bbab5b 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stackresources_test.go @@ -5,58 +5,52 @@ package v1 import ( "testing" - "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources" - "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks" - "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" ) func TestStackResources(t *testing.T) { - // Create a provider client for making the HTTP requests. - // See common.go in this directory for more information. - client := newClient(t) + client, err := clients.NewOrchestrationV1Client() + th.AssertNoErr(t, err) + + stack, err := CreateStack(t, client) + th.AssertNoErr(t, err) + defer DeleteStack(t, client, stack.Name, stack.ID) - stackName := "postman_stack_2" + resource, err := stackresources.Get(client, stack.Name, stack.ID, basicTemplateResourceName).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, resource) - createOpts := stacks.CreateOpts{ - Name: stackName, - Template: template, - Timeout: 5, + metadata, err := stackresources.Metadata(client, stack.Name, stack.ID, basicTemplateResourceName).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, metadata) + + markUnhealthyOpts := &stackresources.MarkUnhealthyOpts{ + MarkUnhealthy: true, + ResourceStatusReason: "Wrong security policy is detected.", } - stack, err := stacks.Create(client, createOpts).Extract() - th.AssertNoErr(t, err) - t.Logf("Created stack: %+v\n", stack) - defer func() { - err := stacks.Delete(client, stackName, stack.ID).ExtractErr() - th.AssertNoErr(t, err) - t.Logf("Deleted stack (%s)", stackName) - }() - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName, stack.ID).Extract() - if err != nil { - return false, err - } - if getStack.Status == "CREATE_COMPLETE" { - return true, nil - } - return false, nil - }) - resourceName := "hello_world" - resource, err := stackresources.Get(client, stackName, stack.ID, resourceName).Extract() + err = stackresources.MarkUnhealthy(client, stack.Name, stack.ID, basicTemplateResourceName, markUnhealthyOpts).ExtractErr() th.AssertNoErr(t, err) - t.Logf("Got stack resource: %+v\n", resource) - metadata, err := stackresources.Metadata(client, stackName, stack.ID, resourceName).Extract() + unhealthyResource, err := stackresources.Get(client, stack.Name, stack.ID, basicTemplateResourceName).Extract() th.AssertNoErr(t, err) - t.Logf("Got stack resource metadata: %+v\n", metadata) + th.AssertEquals(t, "CHECK_FAILED", unhealthyResource.Status) + tools.PrintResource(t, unhealthyResource) - err = stackresources.List(client, stackName, stack.ID, stackresources.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { - resources, err := stackresources.ExtractResources(page) - th.AssertNoErr(t, err) - t.Logf("resources: %+v\n", resources) - return false, nil - }) + allPages, err := stackresources.List(client, stack.Name, stack.ID, nil).AllPages() + th.AssertNoErr(t, err) + allResources, err := stackresources.ExtractResources(allPages) th.AssertNoErr(t, err) + + var found bool + for _, v := range allResources { + if v.Name == basicTemplateResourceName { + found = true + } + } + + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go index c87cc5d00e2a..a0738d5a9755 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacks_test.go @@ -5,149 +5,47 @@ package v1 import ( "testing" - "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks" - "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" ) -func TestStacks(t *testing.T) { - // Create a provider client for making the HTTP requests. - // See common.go in this directory for more information. - client := newClient(t) - - stackName1 := "gophercloud-test-stack-2" - createOpts := stacks.CreateOpts{ - Name: stackName1, - Template: template, - Timeout: 5, - } - stack, err := stacks.Create(client, createOpts).Extract() +func TestStacksCRUD(t *testing.T) { + client, err := clients.NewOrchestrationV1Client() th.AssertNoErr(t, err) - t.Logf("Created stack: %+v\n", stack) - defer func() { - err := stacks.Delete(client, stackName1, stack.ID).ExtractErr() - th.AssertNoErr(t, err) - t.Logf("Deleted stack (%s)", stackName1) - }() - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() - if err != nil { - return false, err - } - if getStack.Status == "CREATE_COMPLETE" { - return true, nil - } - return false, nil - }) - updateOpts := stacks.UpdateOpts{ - Template: template, - Timeout: 20, - } - err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr() + createdStack, err := CreateStack(t, client) th.AssertNoErr(t, err) - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() - if err != nil { - return false, err - } - if getStack.Status == "UPDATE_COMPLETE" { - return true, nil - } - return false, nil - }) + defer DeleteStack(t, client, createdStack.Name, createdStack.ID) - t.Logf("Updated stack") + tools.PrintResource(t, createdStack) + tools.PrintResource(t, createdStack.CreationTime) - err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) { - stackList, err := stacks.ExtractStacks(page) - th.AssertNoErr(t, err) - - t.Logf("Got stack list: %+v\n", stackList) + template := new(stacks.Template) + template.Bin = []byte(basicTemplate) + updateOpts := stacks.UpdateOpts{ + TemplateOpts: template, + Timeout: 20, + } - return true, nil - }) + err = stacks.Update(client, createdStack.Name, createdStack.ID, updateOpts).ExtractErr() th.AssertNoErr(t, err) - getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() + err = WaitForStackStatus(client, createdStack.Name, createdStack.ID, "UPDATE_COMPLETE") th.AssertNoErr(t, err) - t.Logf("Got stack: %+v\n", getStack) - abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract() + var found bool + allPages, err := stacks.List(client, nil).AllPages() th.AssertNoErr(t, err) - t.Logf("Abandonded stack %+v\n", abandonedStack) + allStacks, err := stacks.ExtractStacks(allPages) th.AssertNoErr(t, err) -} - -// Test using the updated interface -func TestStacksNewTemplateFormat(t *testing.T) { - // Create a provider client for making the HTTP requests. - // See common.go in this directory for more information. - client := newClient(t) - stackName1 := "gophercloud-test-stack-2" - templateOpts := new(osStacks.Template) - templateOpts.Bin = []byte(template) - createOpts := osStacks.CreateOpts{ - Name: stackName1, - TemplateOpts: templateOpts, - Timeout: 5, - } - stack, err := stacks.Create(client, createOpts).Extract() - th.AssertNoErr(t, err) - t.Logf("Created stack: %+v\n", stack) - defer func() { - err := stacks.Delete(client, stackName1, stack.ID).ExtractErr() - th.AssertNoErr(t, err) - t.Logf("Deleted stack (%s)", stackName1) - }() - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() - if err != nil { - return false, err + for _, v := range allStacks { + if v.ID == createdStack.ID { + found = true } - if getStack.Status == "CREATE_COMPLETE" { - return true, nil - } - return false, nil - }) - - updateOpts := osStacks.UpdateOpts{ - TemplateOpts: templateOpts, - Timeout: 20, } - err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr() - th.AssertNoErr(t, err) - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() - if err != nil { - return false, err - } - if getStack.Status == "UPDATE_COMPLETE" { - return true, nil - } - return false, nil - }) - - t.Logf("Updated stack") - - err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) { - stackList, err := osStacks.ExtractStacks(page) - th.AssertNoErr(t, err) - t.Logf("Got stack list: %+v\n", stackList) - - return true, nil - }) - th.AssertNoErr(t, err) - - getStack, err := stacks.Get(client, stackName1, stack.ID).Extract() - th.AssertNoErr(t, err) - t.Logf("Got stack: %+v\n", getStack) - - abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract() - th.AssertNoErr(t, err) - t.Logf("Abandonded stack %+v\n", abandonedStack) - th.AssertNoErr(t, err) + th.AssertEquals(t, found, true) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go index 9992e0c044e5..ba00d88a9f58 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/orchestration/v1/stacktemplates_test.go @@ -5,71 +5,34 @@ package v1 import ( "testing" - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacktemplates" th "github.com/gophercloud/gophercloud/testhelper" ) -func TestStackTemplates(t *testing.T) { - // Create a provider client for making the HTTP requests. - // See common.go in this directory for more information. - client := newClient(t) +func TestStackTemplatesCRUD(t *testing.T) { + client, err := clients.NewOrchestrationV1Client() + th.AssertNoErr(t, err) - stackName := "postman_stack_2" + stack, err := CreateStack(t, client) + th.AssertNoErr(t, err) + defer DeleteStack(t, client, stack.Name, stack.ID) - createOpts := stacks.CreateOpts{ - Name: stackName, - Template: template, - Timeout: 5, - } - stack, err := stacks.Create(client, createOpts).Extract() + tmpl, err := stacktemplates.Get(client, stack.Name, stack.ID).Extract() th.AssertNoErr(t, err) - t.Logf("Created stack: %+v\n", stack) - defer func() { - err := stacks.Delete(client, stackName, stack.ID).ExtractErr() - th.AssertNoErr(t, err) - t.Logf("Deleted stack (%s)", stackName) - }() - err = gophercloud.WaitFor(60, func() (bool, error) { - getStack, err := stacks.Get(client, stackName, stack.ID).Extract() - if err != nil { - return false, err - } - if getStack.Status == "CREATE_COMPLETE" { - return true, nil - } - return false, nil - }) + tools.PrintResource(t, tmpl) +} - tmpl, err := stacktemplates.Get(client, stackName, stack.ID).Extract() +func TestStackTemplatesValidate(t *testing.T) { + client, err := clients.NewOrchestrationV1Client() th.AssertNoErr(t, err) - t.Logf("retrieved template: %+v\n", tmpl) - validateOpts := osStacktemplates.ValidateOpts{ - Template: `{"heat_template_version": "2013-05-23", - "description": "Simple template to test heat commands", - "parameters": { - "flavor": { - "default": "m1.tiny", - "type": "string", - }, - }, - "resources": { - "hello_world": { - "type": "OS::Nova::Server", - "properties": { - "key_name": "heat_key", - "flavor": { - "get_param": "flavor", - }, - "image": "ad091b52-742f-469e-8f3c-fd81cadf0743", - "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n", - }, - }, - }, - }`} + validateOpts := stacktemplates.ValidateOpts{ + Template: validateTemplate, + } + validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract() th.AssertNoErr(t, err) - t.Logf("validated template: %+v\n", validatedTemplate) + tools.PrintResource(t, validatedTemplate) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages.go new file mode 100644 index 000000000000..3bc24e960da6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages.go @@ -0,0 +1,19 @@ +package messages + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages" +) + +// DeleteMessage will delete a message. An error will occur if +// the message was unable to be deleted. +func DeleteMessage(t *testing.T, client *gophercloud.ServiceClient, message *messages.Message) { + err := messages.Delete(client, message.ID).ExtractErr() + if err != nil { + t.Fatalf("Failed to delete message %s: %v", message.ID, err) + } + + t.Logf("Deleted message: %s", message.ID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages_test.go new file mode 100644 index 000000000000..faf8fc66eca2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/messages_test.go @@ -0,0 +1,105 @@ +package messages + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages" +) + +const requestID = "req-6f52cd8b-25a1-42cf-b497-7babf70f55f4" +const minimumManilaMessagesMicroVersion = "2.37" + +func TestMessageList(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = minimumManilaMessagesMicroVersion + + allPages, err := messages.List(client, messages.ListOpts{}).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve messages: %v", err) + } + + allMessages, err := messages.ExtractMessages(allPages) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + tools.PrintResource(t, message) + } +} + +// The test creates 2 messages and verifies that only the one(s) with +// a particular name are being listed +func TestMessageListFiltering(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = minimumManilaMessagesMicroVersion + + options := messages.ListOpts{ + RequestID: requestID, + } + + allPages, err := messages.List(client, options).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve messages: %v", err) + } + + allMessages, err := messages.ExtractMessages(allPages) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + for _, listedMessage := range allMessages { + if listedMessage.RequestID != options.RequestID { + t.Fatalf("The request id of the message was expected to be %s", options.RequestID) + } + tools.PrintResource(t, listedMessage) + } +} + +// Create a message and update the name and description. Get the ity +// service and verify that the name and description have been updated +func TestMessageDelete(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create shared file system client: %v", err) + } + client.Microversion = minimumManilaMessagesMicroVersion + + options := messages.ListOpts{ + RequestID: requestID, + } + + allPages, err := messages.List(client, options).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve messages: %v", err) + } + + allMessages, err := messages.ExtractMessages(allPages) + if err != nil { + t.Fatalf("Unable to extract messages: %v", err) + } + + var messageID string + for _, listedMessage := range allMessages { + if listedMessage.RequestID != options.RequestID { + t.Fatalf("The request id of the message was expected to be %s", options.RequestID) + } + tools.PrintResource(t, listedMessage) + messageID = listedMessage.ID + } + + message, err := messages.Get(client, messageID).Extract() + if err != nil { + t.Fatalf("Unable to retrieve the message: %v", err) + } + + DeleteMessage(t, client, message) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/pkg.go new file mode 100644 index 000000000000..0046213058f2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/messages/pkg.go @@ -0,0 +1,3 @@ +// The v2 package contains acceptance tests for the Openstack Manila V2 messages package + +package messages diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices.go index 265323d47927..342a91789e12 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices.go @@ -16,11 +16,13 @@ func CreateSecurityService(t *testing.T, client *gophercloud.ServiceClient) (*se } securityServiceName := tools.RandomString("ACPTTEST", 16) + securityServiceDescription := tools.RandomString("ACPTTEST-DESC", 16) t.Logf("Attempting to create security service: %s", securityServiceName) createOpts := securityservices.CreateOpts{ - Name: securityServiceName, - Type: "kerberos", + Name: securityServiceName, + Description: securityServiceDescription, + Type: "kerberos", } securityService, err := securityservices.Create(client, createOpts).Extract() @@ -41,20 +43,3 @@ func DeleteSecurityService(t *testing.T, client *gophercloud.ServiceClient, secu t.Logf("Deleted security service: %s", securityService.ID) } - -// PrintSecurityService will print a security service and all of its attributes. -func PrintSecurityService(t *testing.T, securityService *securityservices.SecurityService) { - t.Logf("ID: %s", securityService.ID) - t.Logf("Project ID: %s", securityService.ProjectID) - t.Logf("Domain: %s", securityService.Domain) - t.Logf("Status: %s", securityService.Status) - t.Logf("Type: %s", securityService.Type) - t.Logf("Name: %s", securityService.Name) - t.Logf("Description: %s", securityService.Description) - t.Logf("DNS IP: %s", securityService.DNSIP) - t.Logf("User: %s", securityService.User) - t.Logf("Password: %s", securityService.Password) - t.Logf("Server: %s", securityService.Server) - t.Logf("Created at: %v", securityService.CreatedAt) - t.Logf("Updated at: %v", securityService.UpdatedAt) -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go index ecf8cc9a1c8b..34b8a79e79dd 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices" ) @@ -27,7 +28,11 @@ func TestSecurityServiceCreateDelete(t *testing.T) { t.Fatalf("Security service name was expeted to be: %s", securityService.Name) } - PrintSecurityService(t, securityService) + if newSecurityService.Description != securityService.Description { + t.Fatalf("Security service description was expeted to be: %s", securityService.Description) + } + + tools.PrintResource(t, securityService) defer DeleteSecurityService(t, client, securityService) } @@ -49,7 +54,7 @@ func TestSecurityServiceList(t *testing.T) { } for _, securityService := range allSecurityServices { - PrintSecurityService(t, &securityService) + tools.PrintResource(t, &securityService) } } @@ -91,7 +96,7 @@ func TestSecurityServiceListFiltering(t *testing.T) { if listedSecurityService.Name != securityService.Name { t.Fatalf("The name of the security service was expected to be %s", securityService.Name) } - PrintSecurityService(t, &listedSecurityService) + tools.PrintResource(t, &listedSecurityService) } } @@ -108,9 +113,11 @@ func TestSecurityServiceUpdate(t *testing.T) { t.Fatalf("Unable to create security service: %v", err) } + name := "NewName" + description := "" options := securityservices.UpdateOpts{ - Name: "NewName", - Description: "New security service description", + Name: &name, + Description: &description, Type: "ldap", } @@ -124,19 +131,19 @@ func TestSecurityServiceUpdate(t *testing.T) { t.Errorf("Unable to retrieve the security service: %v", err) } - if newSecurityService.Name != options.Name { - t.Fatalf("Security service name was expeted to be: %s", options.Name) + if newSecurityService.Name != name { + t.Fatalf("Security service name was expeted to be: %s", name) } - if newSecurityService.Description != options.Description { - t.Fatalf("Security service description was expeted to be: %s", options.Description) + if newSecurityService.Description != description { + t.Fatalf("Security service description was expeted to be: %s", description) } if newSecurityService.Type != options.Type { t.Fatalf("Security service type was expected to be: %s", options.Type) } - PrintSecurityService(t, securityService) + tools.PrintResource(t, securityService) defer DeleteSecurityService(t, client, securityService) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go index b0aefd8577be..55aad83eee24 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks" ) @@ -15,12 +16,19 @@ func CreateShareNetwork(t *testing.T, client *gophercloud.ServiceClient) (*share t.Skip("Skipping test that requires share network creation in short mode.") } + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + return nil, err + } + shareNetworkName := tools.RandomString("ACPTTEST", 16) t.Logf("Attempting to create share network: %s", shareNetworkName) createOpts := sharenetworks.CreateOpts{ - Name: shareNetworkName, - Description: "This is a shared network", + Name: shareNetworkName, + NeutronNetID: choices.NetworkID, + NeutronSubnetID: choices.SubnetID, + Description: "This is a shared network", } shareNetwork, err := sharenetworks.Create(client, createOpts).Extract() @@ -33,28 +41,11 @@ func CreateShareNetwork(t *testing.T, client *gophercloud.ServiceClient) (*share // DeleteShareNetwork will delete a share network. An error will occur if // the share network was unable to be deleted. -func DeleteShareNetwork(t *testing.T, client *gophercloud.ServiceClient, shareNetwork *sharenetworks.ShareNetwork) { - err := sharenetworks.Delete(client, shareNetwork.ID).ExtractErr() +func DeleteShareNetwork(t *testing.T, client *gophercloud.ServiceClient, shareNetworkID string) { + err := sharenetworks.Delete(client, shareNetworkID).ExtractErr() if err != nil { - t.Fatalf("Failed to delete share network %s: %v", shareNetwork.ID, err) + t.Fatalf("Failed to delete share network %s: %v", shareNetworkID, err) } - t.Logf("Deleted share network: %s", shareNetwork.ID) -} - -// PrintShareNetwork will print a share network and all of its attributes. -func PrintShareNetwork(t *testing.T, sharenetwork *sharenetworks.ShareNetwork) { - t.Logf("ID: %s", sharenetwork.ID) - t.Logf("Project ID: %s", sharenetwork.ProjectID) - t.Logf("Neutron network ID: %s", sharenetwork.NeutronNetID) - t.Logf("Neutron sub-network ID: %s", sharenetwork.NeutronSubnetID) - t.Logf("Nova network ID: %s", sharenetwork.NovaNetID) - t.Logf("Network type: %s", sharenetwork.NetworkType) - t.Logf("Segmentation ID: %d", sharenetwork.SegmentationID) - t.Logf("CIDR: %s", sharenetwork.CIDR) - t.Logf("IP version: %d", sharenetwork.IPVersion) - t.Logf("Name: %s", sharenetwork.Name) - t.Logf("Description: %s", sharenetwork.Description) - t.Logf("Created at: %v", sharenetwork.CreatedAt) - t.Logf("Updated at: %v", sharenetwork.UpdatedAt) + t.Logf("Deleted share network: %s", shareNetworkID) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go index 7bf760f8aef2..16b2bfbc9e52 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" @@ -29,9 +30,13 @@ func TestShareNetworkCreateDestroy(t *testing.T) { t.Fatalf("Share network name was expeted to be: %s", shareNetwork.Name) } - PrintShareNetwork(t, shareNetwork) + if newShareNetwork.Description != shareNetwork.Description { + t.Fatalf("Share network description was expeted to be: %s", shareNetwork.Description) + } + + tools.PrintResource(t, shareNetwork) - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) } // Create a share network and update the name and description. Get the share @@ -52,13 +57,15 @@ func TestShareNetworkUpdate(t *testing.T) { t.Errorf("Unable to retrieve shareNetwork: %v", err) } + name := "NewName" + description := "" options := sharenetworks.UpdateOpts{ - Name: "NewName", - Description: "New share network description", + Name: &name, + Description: &description, } - expectedShareNetwork.Name = options.Name - expectedShareNetwork.Description = options.Description + expectedShareNetwork.Name = name + expectedShareNetwork.Description = description _, err = sharenetworks.Update(client, shareNetwork.ID, options).Extract() if err != nil { @@ -75,9 +82,9 @@ func TestShareNetworkUpdate(t *testing.T) { th.CheckDeepEquals(t, expectedShareNetwork, updatedShareNetwork) - PrintShareNetwork(t, shareNetwork) + tools.PrintResource(t, shareNetwork) - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) } func TestShareNetworkListDetail(t *testing.T) { @@ -97,7 +104,7 @@ func TestShareNetworkListDetail(t *testing.T) { } for _, shareNetwork := range allShareNetworks { - PrintShareNetwork(t, &shareNetwork) + tools.PrintResource(t, &shareNetwork) } } @@ -113,13 +120,13 @@ func TestShareNetworkListFiltering(t *testing.T) { if err != nil { t.Fatalf("Unable to create share network: %v", err) } - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) shareNetwork, err = CreateShareNetwork(t, client) if err != nil { t.Fatalf("Unable to create share network: %v", err) } - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) options := sharenetworks.ListOpts{ Name: shareNetwork.Name, @@ -139,7 +146,7 @@ func TestShareNetworkListFiltering(t *testing.T) { if listedShareNetwork.Name != shareNetwork.Name { t.Fatalf("The name of the share network was expected to be %s", shareNetwork.Name) } - PrintShareNetwork(t, &listedShareNetwork) + tools.PrintResource(t, &listedShareNetwork) } } @@ -153,13 +160,13 @@ func TestShareNetworkListPagination(t *testing.T) { if err != nil { t.Fatalf("Unable to create share network: %v", err) } - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) shareNetwork, err = CreateShareNetwork(t, client) if err != nil { t.Fatalf("Unable to create share network: %v", err) } - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) count := 0 @@ -199,7 +206,7 @@ func TestShareNetworkAddRemoveSecurityService(t *testing.T) { if err != nil { t.Fatalf("Unable to create share network: %v", err) } - defer DeleteShareNetwork(t, client, shareNetwork) + defer DeleteShareNetwork(t, client, shareNetwork.ID) options := sharenetworks.AddSecurityServiceOpts{ SecurityServiceID: securityService.ID, @@ -219,5 +226,5 @@ func TestShareNetworkAddRemoveSecurityService(t *testing.T) { t.Errorf("Unable to remove security service: %v", err) } - PrintShareNetwork(t, shareNetwork) + tools.PrintResource(t, shareNetwork) } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares.go index 82f2f8d1ff1e..dee6abfe951a 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares.go @@ -1,12 +1,14 @@ package v2 import ( - "encoding/json" "fmt" + "strings" "testing" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" ) @@ -17,68 +19,145 @@ func CreateShare(t *testing.T, client *gophercloud.ServiceClient) (*shares.Share t.Skip("Skipping test that requres share creation in short mode.") } - choices, err := clients.AcceptanceTestChoicesFromEnv() - if err != nil { - t.Fatalf("Unable to fetch environment information") - } - - t.Logf("Share network id %s", choices.ShareNetworkID) + iTrue := true createOpts := shares.CreateOpts{ - Size: 1, - Name: "My Test Share", - ShareProto: "NFS", - ShareNetworkID: choices.ShareNetworkID, + Size: 1, + Name: "My Test Share", + Description: "My Test Description", + ShareProto: "NFS", + ShareType: "dhss_false", + IsPublic: &iTrue, } share, err := shares.Create(client, createOpts).Extract() if err != nil { - return share, err + t.Logf("Failed to create share") + return nil, err } - err = waitForStatus(client, share.ID, "available", 600) + err = waitForStatus(t, client, share.ID, "available", 600) if err != nil { + t.Logf("Failed to get %s share status", share.ID) + DeleteShare(t, client, share) return share, err } return share, nil } +// ListShares lists all shares that belong to this tenant's project. +// An error will be returned if the shares could not be listed.. +func ListShares(t *testing.T, client *gophercloud.ServiceClient) ([]shares.Share, error) { + r, err := shares.ListDetail(client, &shares.ListOpts{}).AllPages() + if err != nil { + return nil, err + } + + return shares.ExtractShares(r) +} + +// GrantAccess will grant access to an existing share. A fatal error will occur if +// this operation fails. +func GrantAccess(t *testing.T, client *gophercloud.ServiceClient, share *shares.Share) (*shares.AccessRight, error) { + return shares.GrantAccess(client, share.ID, shares.GrantAccessOpts{ + AccessType: "ip", + AccessTo: "0.0.0.0/32", + AccessLevel: "ro", + }).Extract() +} + +// RevokeAccess will revoke an exisiting access of a share. A fatal error will occur +// if this operation fails. +func RevokeAccess(t *testing.T, client *gophercloud.ServiceClient, share *shares.Share, accessRight *shares.AccessRight) error { + return shares.RevokeAccess(client, share.ID, shares.RevokeAccessOpts{ + AccessID: accessRight.ID, + }).ExtractErr() +} + +// GetAccessRightsSlice will retrieve all access rules assigned to a share. +// A fatal error will occur if this operation fails. +func GetAccessRightsSlice(t *testing.T, client *gophercloud.ServiceClient, share *shares.Share) ([]shares.AccessRight, error) { + return shares.ListAccessRights(client, share.ID).Extract() +} + // DeleteShare will delete a share. A fatal error will occur if the share // failed to be deleted. This works best when used as a deferred function. func DeleteShare(t *testing.T, client *gophercloud.ServiceClient, share *shares.Share) { err := shares.Delete(client, share.ID).ExtractErr() if err != nil { - t.Fatalf("Unable to delete share %s: %v", share.ID, err) + t.Errorf("Unable to delete share %s: %v", share.ID, err) } - t.Logf("Deleted share: %s", share.ID) + err = waitForStatus(t, client, share.ID, "deleted", 600) + if err != nil { + t.Errorf("Failed to wait for 'deleted' status for %s share: %v", share.ID, err) + } else { + t.Logf("Deleted share: %s", share.ID) + } } -// PrintShare prints some information of the share -func PrintShare(t *testing.T, share *shares.Share) { - asJSON, err := json.MarshalIndent(share, "", " ") +// ExtendShare extends the capacity of an existing share +func ExtendShare(t *testing.T, client *gophercloud.ServiceClient, share *shares.Share, newSize int) error { + return shares.Extend(client, share.ID, &shares.ExtendOpts{NewSize: newSize}).ExtractErr() +} + +// ShrinkShare shrinks the capacity of an existing share +func ShrinkShare(t *testing.T, client *gophercloud.ServiceClient, share *shares.Share, newSize int) error { + return shares.Shrink(client, share.ID, &shares.ShrinkOpts{NewSize: newSize}).ExtractErr() +} + +func PrintMessages(t *testing.T, c *gophercloud.ServiceClient, id string) error { + c.Microversion = "2.37" + + allPages, err := messages.List(c, messages.ListOpts{ResourceID: id}).AllPages() if err != nil { - t.Logf("Cannot print the contents of %s", share.ID) + return fmt.Errorf("Unable to retrieve messages: %v", err) + } + + allMessages, err := messages.ExtractMessages(allPages) + if err != nil { + return fmt.Errorf("Unable to extract messages: %v", err) + } + + for _, message := range allMessages { + tools.PrintResource(t, message) } - t.Logf("Share %s", string(asJSON)) + return nil } -func waitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { - return gophercloud.WaitFor(secs, func() (bool, error) { +func waitForStatus(t *testing.T, c *gophercloud.ServiceClient, id, status string, secs int) error { + err := gophercloud.WaitFor(secs, func() (bool, error) { current, err := shares.Get(c, id).Extract() if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + switch status { + case "deleted": + return true, nil + default: + return false, err + } + } return false, err } - if current.Status == "error" { - return true, fmt.Errorf("An error occurred") - } - if current.Status == status { return true, nil } + if strings.Contains(current.Status, "error") { + return true, fmt.Errorf("An error occurred, wrong status: %s", current.Status) + } + return false, nil }) + + if err != nil { + mErr := PrintMessages(t, c, id) + if mErr != nil { + return fmt.Errorf("Share status is '%s' and unable to get manila messages: %s", err, mErr) + } + } + + return err } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares_test.go index abb9b3aefad2..1aef7d6973c7 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/shares_test.go @@ -4,13 +4,15 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" + th "github.com/gophercloud/gophercloud/testhelper" ) func TestShareCreate(t *testing.T) { client, err := clients.NewSharedFileSystemV2Client() if err != nil { - t.Fatalf("Unable to create a sharedfs client: %v", err) + t.Fatalf("Unable to create a shared file system client: %v", err) } share, err := CreateShare(t, client) @@ -24,5 +26,181 @@ func TestShareCreate(t *testing.T) { if err != nil { t.Errorf("Unable to retrieve share: %v", err) } - PrintShare(t, created) + tools.PrintResource(t, created) +} + +func TestShareUpdate(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create shared file system client: %v", err) + } + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create share: %v", err) + } + + defer DeleteShare(t, client, share) + + expectedShare, err := shares.Get(client, share.ID).Extract() + if err != nil { + t.Errorf("Unable to retrieve share: %v", err) + } + + name := "NewName" + description := "" + iFalse := false + options := shares.UpdateOpts{ + DisplayName: &name, + DisplayDescription: &description, + IsPublic: &iFalse, + } + + expectedShare.Name = name + expectedShare.Description = description + expectedShare.IsPublic = iFalse + + _, err = shares.Update(client, share.ID, options).Extract() + if err != nil { + t.Errorf("Unable to update share: %v", err) + } + + updatedShare, err := shares.Get(client, share.ID).Extract() + if err != nil { + t.Errorf("Unable to retrieve share: %v", err) + } + + // Update time has to be set in order to get the assert equal to pass + expectedShare.UpdatedAt = updatedShare.UpdatedAt + + tools.PrintResource(t, share) + + th.CheckDeepEquals(t, expectedShare, updatedShare) +} + +func TestShareListDetail(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + ss, err := ListShares(t, client) + if err != nil { + t.Fatalf("Unable to list shares: %v", err) + } + + for i := range ss { + tools.PrintResource(t, &ss[i]) + } +} + +func TestGrantAndRevokeAccess(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = "2.7" + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + accessRight, err := GrantAccess(t, client, share) + if err != nil { + t.Fatalf("Unable to grant access: %v", err) + } + + tools.PrintResource(t, accessRight) + + if err = RevokeAccess(t, client, share, accessRight); err != nil { + t.Fatalf("Unable to revoke access: %v", err) + } +} + +func TestListAccessRights(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = "2.7" + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + _, err = GrantAccess(t, client, share) + if err != nil { + t.Fatalf("Unable to grant access: %v", err) + } + + rs, err := GetAccessRightsSlice(t, client, share) + if err != nil { + t.Fatalf("Unable to retrieve list of access rules for share %s: %v", share.ID, err) + } + + if len(rs) != 1 { + t.Fatalf("Unexpected number of access rules for share %s: got %d, expected 1", share.ID, len(rs)) + } + + t.Logf("Share %s has %d access rule(s):", share.ID, len(rs)) + + for _, r := range rs { + tools.PrintResource(t, &r) + } +} + +func TestExtendAndShrink(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = "2.7" + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + err = ExtendShare(t, client, share, 2) + if err != nil { + t.Fatalf("Unable to extend a share: %v", err) + } + + // We need to wait till the Extend operation is done + err = waitForStatus(t, client, share.ID, "available", 120) + if err != nil { + t.Fatalf("Share status error: %v", err) + } + + t.Logf("Share %s successfuly extended", share.ID) + + /* disable shrinking for the LVM dhss=false + err = ShrinkShare(t, client, share, 1) + if err != nil { + t.Fatalf("Unable to shrink a share: %v", err) + } + + // We need to wait till the Shrink operation is done + err = waitForStatus(t, client, share.ID, "available", 300) + if err != nil { + t.Fatalf("Share status error: %v", err) + } + + t.Logf("Share %s successfuly shrunk", share.ID) + */ } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes.go index 97b44bd0efee..4debc1fd331a 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes.go @@ -46,11 +46,3 @@ func DeleteShareType(t *testing.T, client *gophercloud.ServiceClient, shareType t.Logf("Deleted share type: %s", shareType.ID) } - -// PrintShareType will print a share type and all of its attributes. -func PrintShareType(t *testing.T, shareType *sharetypes.ShareType) { - t.Logf("Name: %s", shareType.Name) - t.Logf("ID: %s", shareType.ID) - t.Logf("OS share type access is public: %t", shareType.IsPublic) - t.Logf("Extra specs: %#v", shareType.ExtraSpecs) -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go index 9efe0359d1f1..e5cb2ac82a56 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes" ) @@ -18,7 +19,7 @@ func TestShareTypeCreateDestroy(t *testing.T) { t.Fatalf("Unable to create share type: %v", err) } - PrintShareType(t, shareType) + tools.PrintResource(t, shareType) defer DeleteShareType(t, client, shareType) } @@ -40,7 +41,7 @@ func TestShareTypeList(t *testing.T) { } for _, shareType := range allShareTypes { - PrintShareType(t, &shareType) + tools.PrintResource(t, &shareType) } } @@ -55,7 +56,7 @@ func TestShareTypeGetDefault(t *testing.T) { t.Fatalf("Unable to retrieve the default share type: %v", err) } - PrintShareType(t, shareType) + tools.PrintResource(t, shareType) } func TestShareTypeExtraSpecs(t *testing.T) { @@ -105,7 +106,7 @@ func TestShareTypeExtraSpecs(t *testing.T) { t.Fatalf("my_new_key was expected to be unset for Share type: %s", shareType.Name) } - PrintShareType(t, shareType) + tools.PrintResource(t, shareType) defer DeleteShareType(t, client, shareType) } @@ -155,7 +156,7 @@ func TestShareTypeAccess(t *testing.T) { t.Fatalf("No access should be left for the share type: %s", shareType.Name) } - PrintShareType(t, shareType) + tools.PrintResource(t, shareType) defer DeleteShareType(t, client, shareType) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots.go new file mode 100644 index 000000000000..852e8844c18f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots.go @@ -0,0 +1,101 @@ +package v2 + +import ( + "fmt" + "strings" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots" +) + +// CreateSnapshot will create a snapshot from the share ID with a name. An error will +// be returned if the snapshot could not be created +func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, shareID string) (*snapshots.Snapshot, error) { + if testing.Short() { + t.Skip("Skipping test that requres share creation in short mode.") + } + + createOpts := snapshots.CreateOpts{ + ShareID: shareID, + Name: "My Test Snapshot", + Description: "My Test Description", + } + + snapshot, err := snapshots.Create(client, createOpts).Extract() + if err != nil { + t.Logf("Failed to create snapshot") + return nil, err + } + + err = waitForSnapshotStatus(t, client, snapshot.ID, "available", 600) + if err != nil { + t.Logf("Failed to get %s snapshot status", snapshot.ID) + return snapshot, err + } + + return snapshot, nil +} + +// ListSnapshots lists all snapshots that belong to this tenant's project. +// An error will be returned if the snapshots could not be listed.. +func ListSnapshots(t *testing.T, client *gophercloud.ServiceClient) ([]snapshots.Snapshot, error) { + r, err := snapshots.ListDetail(client, &snapshots.ListOpts{}).AllPages() + if err != nil { + return nil, err + } + + return snapshots.ExtractSnapshots(r) +} + +// DeleteSnapshot will delete a snapshot. A fatal error will occur if the snapshot +// failed to be deleted. This works best when used as a deferred function. +func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) { + err := snapshots.Delete(client, snapshot.ID).ExtractErr() + if err != nil { + t.Errorf("Unable to delete snapshot %s: %v", snapshot.ID, err) + } + + err = waitForSnapshotStatus(t, client, snapshot.ID, "deleted", 600) + if err != nil { + t.Errorf("Failed to wait for 'deleted' status for %s snapshot: %v", snapshot.ID, err) + } else { + t.Logf("Deleted snapshot: %s", snapshot.ID) + } +} + +func waitForSnapshotStatus(t *testing.T, c *gophercloud.ServiceClient, id, status string, secs int) error { + err := gophercloud.WaitFor(secs, func() (bool, error) { + current, err := snapshots.Get(c, id).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + switch status { + case "deleted": + return true, nil + default: + return false, err + } + } + return false, err + } + + if current.Status == status { + return true, nil + } + + if strings.Contains(current.Status, "error") { + return true, fmt.Errorf("An error occurred, wrong status: %s", current.Status) + } + + return false, nil + }) + + if err != nil { + mErr := PrintMessages(t, c, id) + if mErr != nil { + return fmt.Errorf("Snapshot status is '%s' and unable to get manila messages: %s", err, mErr) + } + } + + return err +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go new file mode 100644 index 000000000000..d94055902be2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go @@ -0,0 +1,118 @@ +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestSnapshotCreate(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + snapshot, err := CreateSnapshot(t, client, share.ID) + if err != nil { + t.Fatalf("Unable to create a snapshot: %v", err) + } + + defer DeleteSnapshot(t, client, snapshot) + + created, err := snapshots.Get(client, snapshot.ID).Extract() + if err != nil { + t.Fatalf("Unable to retrieve a snapshot: %v", err) + } + + tools.PrintResource(t, created) +} + +func TestSnapshotUpdate(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create shared file system client: %v", err) + } + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create share: %v", err) + } + + defer DeleteShare(t, client, share) + + snapshot, err := CreateSnapshot(t, client, share.ID) + if err != nil { + t.Fatalf("Unable to create a snapshot: %v", err) + } + + defer DeleteSnapshot(t, client, snapshot) + + expectedSnapshot, err := snapshots.Get(client, snapshot.ID).Extract() + if err != nil { + t.Errorf("Unable to retrieve snapshot: %v", err) + } + + name := "NewName" + description := "" + options := snapshots.UpdateOpts{ + DisplayName: &name, + DisplayDescription: &description, + } + + expectedSnapshot.Name = name + expectedSnapshot.Description = description + + _, err = snapshots.Update(client, snapshot.ID, options).Extract() + if err != nil { + t.Errorf("Unable to update snapshot: %v", err) + } + + updatedSnapshot, err := snapshots.Get(client, snapshot.ID).Extract() + if err != nil { + t.Errorf("Unable to retrieve snapshot: %v", err) + } + + tools.PrintResource(t, snapshot) + + th.CheckDeepEquals(t, expectedSnapshot, updatedSnapshot) +} + +func TestSnapshotListDetail(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + snapshot, err := CreateSnapshot(t, client, share.ID) + if err != nil { + t.Fatalf("Unable to create a snapshot: %v", err) + } + + defer DeleteSnapshot(t, client, snapshot) + + ss, err := ListSnapshots(t, client) + if err != nil { + t.Fatalf("Unable to list snapshots: %v", err) + } + + for i := range ss { + tools.PrintResource(t, &ss[i]) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontrigger.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontrigger.go new file mode 100644 index 000000000000..20a7fd653eb0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontrigger.go @@ -0,0 +1,70 @@ +package v2 + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// CreateCronTrigger creates a cron trigger for the given workflow. +func CreateCronTrigger(t *testing.T, client *gophercloud.ServiceClient, workflow *workflows.Workflow) (*crontriggers.CronTrigger, error) { + crontriggerName := tools.RandomString("crontrigger_", 5) + t.Logf("Attempting to create cron trigger: %s", crontriggerName) + + firstExecution := time.Now().AddDate(1, 0, 0) + createOpts := crontriggers.CreateOpts{ + WorkflowID: workflow.ID, + Name: crontriggerName, + Pattern: "0 0 1 1 *", + WorkflowInput: map[string]interface{}{ + "msg": "Hello World!", + }, + FirstExecutionTime: &firstExecution, + } + crontrigger, err := crontriggers.Create(client, createOpts).Extract() + if err != nil { + return crontrigger, err + } + t.Logf("Cron trigger created: %s", crontriggerName) + th.AssertEquals(t, crontrigger.Name, crontriggerName) + return crontrigger, nil +} + +// DeleteCronTrigger deletes a cron trigger. +func DeleteCronTrigger(t *testing.T, client *gophercloud.ServiceClient, crontrigger *crontriggers.CronTrigger) { + err := crontriggers.Delete(client, crontrigger.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete cron trigger %s: %v", crontrigger.Name, err) + } + + t.Logf("Deleted crontrigger: %s", crontrigger.Name) +} + +// GetCronTrigger gets a cron trigger. +func GetCronTrigger(t *testing.T, client *gophercloud.ServiceClient, id string) (*crontriggers.CronTrigger, error) { + crontrigger, err := crontriggers.Get(client, id).Extract() + if err != nil { + t.Fatalf("Unable to get cron trigger %s: %v", id, err) + } + t.Logf("Cron trigger %s get", id) + return crontrigger, err +} + +// ListCronTriggers lists cron triggers. +func ListCronTriggers(t *testing.T, client *gophercloud.ServiceClient, opts crontriggers.ListOptsBuilder) ([]crontriggers.CronTrigger, error) { + allPages, err := crontriggers.List(client, opts).AllPages() + if err != nil { + t.Fatalf("Unable to list cron triggers: %v", err) + } + crontriggersList, err := crontriggers.ExtractCronTriggers(allPages) + if err != nil { + t.Fatalf("Unable to extract cron triggers: %v", err) + } + t.Logf("Cron triggers list found, length: %d", len(crontriggersList)) + return crontriggersList, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontriggers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontriggers_test.go new file mode 100644 index 000000000000..48642cd5f26e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/crontriggers_test.go @@ -0,0 +1,58 @@ +package v2 + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCronTriggersCreateGetDelete(t *testing.T) { + client, err := clients.NewWorkflowV2Client() + th.AssertNoErr(t, err) + + workflow, err := CreateWorkflow(t, client) + th.AssertNoErr(t, err) + defer DeleteWorkflow(t, client, workflow) + + trigger, err := CreateCronTrigger(t, client, workflow) + th.AssertNoErr(t, err) + defer DeleteCronTrigger(t, client, trigger) + + gettrigger, err := GetCronTrigger(t, client, trigger.ID) + th.AssertNoErr(t, err) + + th.AssertEquals(t, trigger.ID, gettrigger.ID) + + tools.PrintResource(t, trigger) +} + +func TestCronTriggersList(t *testing.T) { + client, err := clients.NewWorkflowV2Client() + th.AssertNoErr(t, err) + workflow, err := CreateWorkflow(t, client) + th.AssertNoErr(t, err) + defer DeleteWorkflow(t, client, workflow) + trigger, err := CreateCronTrigger(t, client, workflow) + th.AssertNoErr(t, err) + defer DeleteCronTrigger(t, client, trigger) + list, err := ListCronTriggers(t, client, &crontriggers.ListOpts{ + Name: &crontriggers.ListFilter{ + Filter: crontriggers.FilterEQ, + Value: trigger.Name, + }, + Pattern: &crontriggers.ListFilter{ + Value: "0 0 1 1 *", + }, + CreatedAt: &crontriggers.ListDateFilter{ + Filter: crontriggers.FilterGT, + Value: time.Now().AddDate(-1, 0, 0), + }, + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(list)) + tools.PrintResource(t, list) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/execution.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/execution.go new file mode 100644 index 000000000000..6eb6d048da18 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/execution.go @@ -0,0 +1,83 @@ +package v2 + +import ( + "fmt" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/executions" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// CreateExecution creates an execution for the given workflow. +func CreateExecution(t *testing.T, client *gophercloud.ServiceClient, workflow *workflows.Workflow) (*executions.Execution, error) { + executionDescription := tools.RandomString("execution_", 5) + + t.Logf("Attempting to create execution: %s", executionDescription) + createOpts := executions.CreateOpts{ + ID: executionDescription, + WorkflowID: workflow.ID, + WorkflowNamespace: workflow.Namespace, + Description: executionDescription, + Input: map[string]interface{}{ + "msg": "Hello World!", + }, + } + execution, err := executions.Create(client, createOpts).Extract() + if err != nil { + return execution, err + } + + t.Logf("Execution created: %s", executionDescription) + + th.AssertEquals(t, execution.Description, executionDescription) + + t.Logf("Wait for execution status SUCCESS: %s", executionDescription) + th.AssertNoErr(t, tools.WaitFor(func() (bool, error) { + latest, err := executions.Get(client, execution.ID).Extract() + if err != nil { + return false, err + } + + if latest.State == "SUCCESS" { + execution = latest + return true, nil + } + + if latest.State == "ERROR" { + return false, fmt.Errorf("Execution in ERROR state") + } + + return false, nil + })) + t.Logf("Execution success: %s", executionDescription) + + return execution, nil +} + +// DeleteExecution deletes an execution. +func DeleteExecution(t *testing.T, client *gophercloud.ServiceClient, execution *executions.Execution) { + err := executions.Delete(client, execution.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete executions %s: %v", execution.Description, err) + } + t.Logf("Deleted executions: %s", execution.Description) +} + +// ListExecutions lists the executions. +func ListExecutions(t *testing.T, client *gophercloud.ServiceClient, opts executions.ListOptsBuilder) ([]executions.Execution, error) { + allPages, err := executions.List(client, opts).AllPages() + if err != nil { + t.Fatalf("Unable to list executions: %v", err) + } + + executionsList, err := executions.ExtractExecutions(allPages) + if err != nil { + t.Fatalf("Unable to extract executions: %v", err) + } + + t.Logf("Executions list find, length: %d", len(executionsList)) + return executionsList, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/executions_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/executions_test.go new file mode 100644 index 000000000000..86c0dd858b8f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/executions_test.go @@ -0,0 +1,54 @@ +package v2 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/executions" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestExecutionsCreate(t *testing.T) { + client, err := clients.NewWorkflowV2Client() + th.AssertNoErr(t, err) + + workflow, err := CreateWorkflow(t, client) + th.AssertNoErr(t, err) + defer DeleteWorkflow(t, client, workflow) + + execution, err := CreateExecution(t, client, workflow) + th.AssertNoErr(t, err) + defer DeleteExecution(t, client, execution) + + tools.PrintResource(t, execution) +} + +func TestExecutionsList(t *testing.T) { + client, err := clients.NewWorkflowV2Client() + th.AssertNoErr(t, err) + + workflow, err := CreateWorkflow(t, client) + th.AssertNoErr(t, err) + defer DeleteWorkflow(t, client, workflow) + + execution, err := CreateExecution(t, client, workflow) + th.AssertNoErr(t, err) + defer DeleteExecution(t, client, execution) + + list, err := ListExecutions(t, client, &executions.ListOpts{ + Description: &executions.ListFilter{ + Value: execution.Description, + }, + CreatedAt: &executions.ListDateFilter{ + Filter: executions.FilterGTE, + Value: execution.CreatedAt, + }, + Input: execution.Input, + }) + + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(list)) + + tools.PrintResource(t, list) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflow.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflow.go new file mode 100644 index 000000000000..de95d0ca607c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflow.go @@ -0,0 +1,95 @@ +package v2 + +import ( + "fmt" + "strings" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows" + th "github.com/gophercloud/gophercloud/testhelper" +) + +// GetEchoWorkflowDefinition returns a simple workflow definition that does nothing except a simple "echo" command. +func GetEchoWorkflowDefinition(workflowName string) string { + return fmt.Sprintf(`--- +version: '2.0' + +%s: + description: Simple workflow example + type: direct + tags: + - tag1 + - tag2 + + input: + - msg + + tasks: + test: + action: std.echo output="<%% $.msg %%>"`, workflowName) +} + +// CreateWorkflow creates a workflow on Mistral API. +// The created workflow is a dummy workflow that performs a simple echo. +func CreateWorkflow(t *testing.T, client *gophercloud.ServiceClient) (*workflows.Workflow, error) { + workflowName := tools.RandomString("workflow_echo_", 5) + + definition := GetEchoWorkflowDefinition(workflowName) + + t.Logf("Attempting to create workflow: %s", workflowName) + + opts := &workflows.CreateOpts{ + Namespace: "some-namespace", + Scope: "private", + Definition: strings.NewReader(definition), + } + workflowList, err := workflows.Create(client, opts).Extract() + if err != nil { + return nil, err + } + th.AssertEquals(t, 1, len(workflowList)) + + workflow := workflowList[0] + + t.Logf("Workflow created: %s", workflowName) + + th.AssertEquals(t, workflowName, workflow.Name) + + return &workflow, nil +} + +// DeleteWorkflow deletes the given workflow. +func DeleteWorkflow(t *testing.T, client *gophercloud.ServiceClient, workflow *workflows.Workflow) { + err := workflows.Delete(client, workflow.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete workflows %s: %v", workflow.Name, err) + } + + t.Logf("Deleted workflow: %s", workflow.Name) +} + +// GetWorkflow gets a workflow. +func GetWorkflow(t *testing.T, client *gophercloud.ServiceClient, id string) (*workflows.Workflow, error) { + workflow, err := workflows.Get(client, id).Extract() + if err != nil { + t.Fatalf("Unable to get workflow %s: %v", id, err) + } + t.Logf("Workflow get: %s", workflow.Name) + return workflow, err +} + +// ListWorkflows lists the workflows. +func ListWorkflows(t *testing.T, client *gophercloud.ServiceClient, opts workflows.ListOptsBuilder) ([]workflows.Workflow, error) { + allPages, err := workflows.List(client, opts).AllPages() + if err != nil { + t.Fatalf("Unable to list workflows: %v", err) + } + workflowsList, err := workflows.ExtractWorkflows(allPages) + if err != nil { + t.Fatalf("Unable to extract workflows: %v", err) + } + t.Logf("Workflows list find, length: %d", len(workflowsList)) + return workflowsList, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflows_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflows_test.go new file mode 100644 index 000000000000..a5fdde6413b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/workflow/v2/workflows_test.go @@ -0,0 +1,46 @@ +package v2 + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestWorkflowsCreateGetDelete(t *testing.T) { + client, err := clients.NewWorkflowV2Client() + th.AssertNoErr(t, err) + + workflow, err := CreateWorkflow(t, client) + th.AssertNoErr(t, err) + defer DeleteWorkflow(t, client, workflow) + + workflowget, err := GetWorkflow(t, client, workflow.ID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, workflowget) +} + +func TestWorkflowsList(t *testing.T) { + client, err := clients.NewWorkflowV2Client() + th.AssertNoErr(t, err) + workflow, err := CreateWorkflow(t, client) + th.AssertNoErr(t, err) + defer DeleteWorkflow(t, client, workflow) + list, err := ListWorkflows(t, client, &workflows.ListOpts{ + Name: &workflows.ListFilter{ + Value: workflow.Name, + }, + Tags: []string{"tag1"}, + CreatedAt: &workflows.ListDateFilter{ + Filter: workflows.FilterGT, + Value: time.Now().AddDate(-1, 0, 0), + }, + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(list)) + tools.PrintResource(t, list) +} diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go index 4211470020ae..5ffa8d1e0a76 100644 --- a/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -81,6 +81,23 @@ type AuthOptions struct { // TokenID allows users to authenticate (possibly as another user) with an // authentication token ID. TokenID string `json:"-"` + + // Scope determines the scoping of the authentication request. + Scope *AuthScope `json:"-"` + + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` +} + +// AuthScope allows a created token to be limited to a specific domain or project. +type AuthScope struct { + ProjectID string + ProjectName string + DomainID string + DomainName string } // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder @@ -131,7 +148,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s type userReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` - Password string `json:"password"` + Password string `json:"password,omitempty"` Domain *domainReq `json:"domain,omitempty"` } @@ -143,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s ID string `json:"id"` } + type applicationCredentialReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + User *userReq `json:"user,omitempty"` + Secret *string `json:"secret,omitempty"` + } + type identityReq struct { - Methods []string `json:"methods"` - Password *passwordReq `json:"password,omitempty"` - Token *tokenReq `json:"token,omitempty"` + Methods []string `json:"methods"` + Password *passwordReq `json:"password,omitempty"` + Token *tokenReq `json:"token,omitempty"` + ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` } type authReq struct { @@ -183,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s req.Auth.Identity.Token = &tokenReq{ ID: opts.TokenID, } + + } else if opts.ApplicationCredentialID != "" { + // Configure the request for ApplicationCredentialID authentication. + // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 + // There are three kinds of possible application_credential requests + // 1. application_credential id + secret + // 2. application_credential name + secret + user_id + // 3. application_credential name + secret + username + domain_id / domain_name + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + ID: &opts.ApplicationCredentialID, + Secret: &opts.ApplicationCredentialSecret, + } + } else if opts.ApplicationCredentialName != "" { + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + + var userRequest *userReq + + if opts.UserID != "" { + // UserID could be used without the domain information + userRequest = &userReq{ + ID: &opts.UserID, + } + } + + if userRequest == nil && opts.Username == "" { + // Make sure that Username or UserID are provided + return nil, ErrUsernameOrUserID{} + } + + if userRequest == nil && opts.DomainID != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{ID: &opts.DomainID}, + } + } + + if userRequest == nil && opts.DomainName != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{Name: &opts.DomainName}, + } + } + + // Make sure that DomainID or DomainName are provided among Username + if userRequest == nil { + return nil, ErrDomainIDOrDomainName{} + } + + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + Name: &opts.ApplicationCredentialName, + User: userRequest, + Secret: &opts.ApplicationCredentialSecret, + } } else { - // If no password or token ID are available, authentication can't continue. + // If no password or token ID or ApplicationCredential are available, authentication can't continue. return nil, ErrMissingPassword{} } } else { @@ -263,85 +348,83 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - - var scope struct { - ProjectID string - ProjectName string - DomainID string - DomainName string - } - - if opts.TenantID != "" { - scope.ProjectID = opts.TenantID - } else { - if opts.TenantName != "" { - scope.ProjectName = opts.TenantName - scope.DomainID = opts.DomainID - scope.DomainName = opts.DomainName + // For backwards compatibility. + // If AuthOptions.Scope was not set, try to determine it. + // This works well for common scenarios. + if opts.Scope == nil { + opts.Scope = new(AuthScope) + if opts.TenantID != "" { + opts.Scope.ProjectID = opts.TenantID + } else { + if opts.TenantName != "" { + opts.Scope.ProjectName = opts.TenantName + opts.Scope.DomainID = opts.DomainID + opts.Scope.DomainName = opts.DomainName + } } } - if scope.ProjectName != "" { + if opts.Scope.ProjectName != "" { // ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectID may not be supplied. - if scope.DomainID == "" && scope.DomainName == "" { + if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { return nil, ErrScopeDomainIDOrDomainName{} } - if scope.ProjectID != "" { + if opts.Scope.ProjectID != "" { return nil, ErrScopeProjectIDOrProjectName{} } - if scope.DomainID != "" { + if opts.Scope.DomainID != "" { // ProjectName + DomainID return map[string]interface{}{ "project": map[string]interface{}{ - "name": &scope.ProjectName, - "domain": map[string]interface{}{"id": &scope.DomainID}, + "name": &opts.Scope.ProjectName, + "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, }, }, nil } - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { // ProjectName + DomainName return map[string]interface{}{ "project": map[string]interface{}{ - "name": &scope.ProjectName, - "domain": map[string]interface{}{"name": &scope.DomainName}, + "name": &opts.Scope.ProjectName, + "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, }, }, nil } - } else if scope.ProjectID != "" { + } else if opts.Scope.ProjectID != "" { // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. - if scope.DomainID != "" { + if opts.Scope.DomainID != "" { return nil, ErrScopeProjectIDAlone{} } - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { return nil, ErrScopeProjectIDAlone{} } // ProjectID return map[string]interface{}{ "project": map[string]interface{}{ - "id": &scope.ProjectID, + "id": &opts.Scope.ProjectID, }, }, nil - } else if scope.DomainID != "" { + } else if opts.Scope.DomainID != "" { // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { return nil, ErrScopeDomainIDOrDomainName{} } // DomainID return map[string]interface{}{ "domain": map[string]interface{}{ - "id": &scope.DomainID, + "id": &opts.Scope.DomainID, }, }, nil - } else if scope.DomainName != "" { + } else if opts.Scope.DomainName != "" { // DomainName return map[string]interface{}{ "domain": map[string]interface{}{ - "name": &scope.DomainName, + "name": &opts.Scope.DomainName, }, }, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/auth_result.go b/vendor/github.com/gophercloud/gophercloud/auth_result.go new file mode 100644 index 000000000000..2e4699b978ce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/auth_result.go @@ -0,0 +1,52 @@ +package gophercloud + +/* +AuthResult is the result from the request that was used to obtain a provider +client's Keystone token. It is returned from ProviderClient.GetAuthResult(). + +The following types satisfy this interface: + + github.com/gophercloud/gophercloud/openstack/identity/v2/tokens.CreateResult + github.com/gophercloud/gophercloud/openstack/identity/v3/tokens.CreateResult + +Usage example: + + import ( + "github.com/gophercloud/gophercloud" + tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + ) + + func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) { + r := providerClient.GetAuthResult() + if r == nil { + //ProviderClient did not use openstack.Authenticate(), e.g. because token + //was set manually with ProviderClient.SetToken() + return "", errors.New("no AuthResult available") + } + switch r := r.(type) { + case tokens2.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + case tokens3.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + default: + panic(fmt.Sprintf("got unexpected AuthResult type %t", r)) + } + } + +Both implementing types share a lot of methods by name, like ExtractUser() in +this example. But those methods cannot be part of the AuthResult interface +because the return types are different (in this case, type tokens2.User vs. +type tokens3.User). +*/ +type AuthResult interface { + ExtractTokenID() (string, error) +} diff --git a/vendor/github.com/gophercloud/gophercloud/doc.go b/vendor/github.com/gophercloud/gophercloud/doc.go index 30067aa35275..953ca822a97a 100644 --- a/vendor/github.com/gophercloud/gophercloud/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/doc.go @@ -9,20 +9,37 @@ 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}", - } + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(opts) - provider, err := openstack.AuthenticatedClient(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(opts) You may also use the openstack.AuthOptionsFromEnv() helper function. This function reads in standard environment variables frequently found in an @@ -39,16 +56,16 @@ 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"} + opts := gophercloud.EndpointOpts{Region: "RegionOne"} - client := openstack.NewComputeV2(provider, opts) + 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(client, "{serverId}").Extract() + server, err := servers.Get(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 @@ -56,11 +73,11 @@ 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(client, "{serverId}") + result := servers.Get(client, "{serverId}") - // Attempt to extract the disk configuration from the OS-DCF disk config - // extension: - config, err := diskconfig.ExtractGet(result) + // 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 @@ -68,17 +85,17 @@ 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(func (page pagination.Page) (bool, error) { - s, err := servers.ExtractServers(page) - if err != nil { - return false, err - } + err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } - // Handle the []servers.Server slice. + // Handle the []servers.Server slice. - // Return "false" or an error to prematurely stop fetching new pages. - return true, nil - }) + // 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: diff --git a/vendor/github.com/gophercloud/gophercloud/FAQ.md b/vendor/github.com/gophercloud/gophercloud/docs/FAQ.md similarity index 97% rename from vendor/github.com/gophercloud/gophercloud/FAQ.md rename to vendor/github.com/gophercloud/gophercloud/docs/FAQ.md index 88a366a288b9..4bc8a8a47009 100644 --- a/vendor/github.com/gophercloud/gophercloud/FAQ.md +++ b/vendor/github.com/gophercloud/gophercloud/docs/FAQ.md @@ -1,5 +1,9 @@ # Tips +## Handling Microversions + +Please see our dedicated document [here](MICROVERSIONS.md). + ## Implementing default logging and re-authentication attempts You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client diff --git a/vendor/github.com/gophercloud/gophercloud/docs/MICROVERSIONS.md b/vendor/github.com/gophercloud/gophercloud/docs/MICROVERSIONS.md new file mode 100644 index 000000000000..18b4d4a1766d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/MICROVERSIONS.md @@ -0,0 +1,79 @@ +# Microversions + +## Table of Contents + +* [Introduction](#introduction) +* [Client Configuration](#client-configuration) +* [Gophercloud Developer Information](#gophercloud-developer-information) +* [Application Developer Information](#application-developer-information) + +## Introduction + +Microversions are an OpenStack API ability which allows developers to add and +remove features while still retaining backwards compatibility for all prior +versions of the API. + +More information can be found here: + +> Note: these links are not an exhaustive reference for microversions. + +* http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html +* https://developer.openstack.org/api-guide/compute/microversions.html +* https://github.com/openstack/keystoneauth/blob/master/doc/source/using-sessions.rst + +## Client Configuration + +You can set a specific microversion on a Service Client by doing the following: + +```go +client, err := openstack.NewComputeV2(providerClient, nil) +client.Microversion = "2.52" +``` + +## Gophercloud Developer Information + +Microversions change several aspects about API interaction. + +### Existing Fields, New Values + +This is when an existing field behaves like an "enum" and a new valid value +is possible by setting the client's microversion to a specific version. + +An example of this can be seen with Nova/Compute's Server Group `policy` field +and the introduction of the [`soft-affinity`](https://developer.openstack.org/api-ref/compute/?expanded=create-server-group-detail#create-server-group) +value. + +Unless Gophercloud is limiting the valid values that are passed to the +Nova/Compute service, no changes are required in Gophercloud. + +### New Request Fields + +This is when a microversion enables a new field to be used in an API request. +When implementing this kind of change, it is imperative that the field has +the `omitempty` attribute set. If `omitempty` is not set, then the field will +be used for _all_ microversions and possibly cause an error from the API +service. You may need to use a pointer field in order for this to work. + +When adding a new field, please make sure to include a GoDoc comment about +what microversions the field is valid for. + +### New Response Fields + +This is when a microversion includes new fields in the API response. The +correct way of implementing this in Gophercloud is to _not_ add the field +to the resource's "result" struct (in the `results.go` file), but instead +add a custom "extract" method to a new `microversions.go` file. This is +to ensure that base API interaction does not break with the introduction +of new fields. + +### Modified Response Fields + +This is when a microversion modifies an existing field in an API response +to be formatted differently than the base API. Research is still ongoing +on how to best handle this scenario in Gophercloud. + +## Application Developer Information + +Gophercloud does not perform any validation checks on the API request to make +sure it is valid for a specific microversion. It is up to you to ensure that +the API request is using the correct fields and functions for the microversion. diff --git a/vendor/github.com/gophercloud/gophercloud/MIGRATING.md b/vendor/github.com/gophercloud/gophercloud/docs/MIGRATING.md similarity index 95% rename from vendor/github.com/gophercloud/gophercloud/MIGRATING.md rename to vendor/github.com/gophercloud/gophercloud/docs/MIGRATING.md index aa383c9cc9e3..ef1a2bb900ac 100644 --- a/vendor/github.com/gophercloud/gophercloud/MIGRATING.md +++ b/vendor/github.com/gophercloud/gophercloud/docs/MIGRATING.md @@ -16,7 +16,7 @@ * `servers.Reboot` now requires a `servers.RebootOpts` struct: ```golang - rebootOpts := &servers.RebootOpts{ + rebootOpts := servers.RebootOpts{ Type: servers.SoftReboot, } res := servers.Reboot(client, server.ID, rebootOpts) diff --git a/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md b/vendor/github.com/gophercloud/gophercloud/docs/STYLEGUIDE.md similarity index 95% rename from vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md rename to vendor/github.com/gophercloud/gophercloud/docs/STYLEGUIDE.md index 22a290094129..fb09b72f6abc 100644 --- a/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md +++ b/vendor/github.com/gophercloud/gophercloud/docs/STYLEGUIDE.md @@ -55,6 +55,8 @@ - The following should be used in most cases: + - `microversions.go`: contains all the response methods for fields or actions + added in a microversion. - `requests.go`: contains all the functions that make HTTP requests and the types associated with the HTTP request (parameters for URL, body, etc) - `results.go`: contains all the response objects and their methods @@ -77,3 +79,7 @@ last parameter an `interface` named `OptsBuilder` (eg `ListOptsBuilder`). This `interface` should have at the least a method named `ToQuery` (eg `ToServerListQuery`). + +### Microversions + +- Please see our dedicated document [here](MICROVERSIONS.md). diff --git a/vendor/github.com/gophercloud/gophercloud/assets/openlab.png b/vendor/github.com/gophercloud/gophercloud/docs/assets/openlab.png similarity index 100% rename from vendor/github.com/gophercloud/gophercloud/assets/openlab.png rename to vendor/github.com/gophercloud/gophercloud/docs/assets/openlab.png diff --git a/vendor/github.com/gophercloud/gophercloud/assets/vexxhost.png b/vendor/github.com/gophercloud/gophercloud/docs/assets/vexxhost.png similarity index 100% rename from vendor/github.com/gophercloud/gophercloud/assets/vexxhost.png rename to vendor/github.com/gophercloud/gophercloud/docs/assets/vexxhost.png diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/doc.go b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/doc.go new file mode 100644 index 000000000000..bbf39c542875 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/doc.go @@ -0,0 +1,13 @@ +/* +Package NAME manages and retrieves RESOURCE in the OpenStack SERVICE Service. + +Example to List RESOURCE + +Example to Create a RESOURCE + +Example to Update a RESOURCE + +Example to Delete a RESOURCE + +*/ +package RESOURCE diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/requests.go b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/requests.go new file mode 100644 index 000000000000..1c4bb4d3e90a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/requests.go @@ -0,0 +1,105 @@ +package RESOURCE + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToResourceListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { +} + +// ToResourceListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToResourceListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List retrieves a list of RESOURCES. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToResourceListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ResourcePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details of a RESOURCE. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToResourceCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a RESOURCE. +type CreateOpts struct { +} + +// ToResourceCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToResourceCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "resource") +} + +// Create creates a new RESOURCE. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToResourceCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete deletes a RESOURCE. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToResourceUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents parameters to update a RESOURCE. +type UpdateOpts struct { +} + +// ToUpdateCreateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToResourceUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "resource") +} + +// Update modifies the attributes of a RESOURCE. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToResourceUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/results.go b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/results.go new file mode 100644 index 000000000000..88272e1e1cf6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/results.go @@ -0,0 +1,83 @@ +package RESOURCE + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// RESOURCE represents... +type Resource struct { +} + +type commonResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a RESOURCE. +type GetResult struct { + commonResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a RESOURCE. +type CreateResult struct { + commonResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the result of an Update request. Call its Extract method to +// interpret it as a RESOURCE. +type UpdateResult struct { + commonResult +} + +// ResourcePage is a single page of RESOURCE results. +type ResourcePage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of RESOURCES contains any results. +func (r ResourcePage) IsEmpty() (bool, error) { + resources, err := ExtractResources(r) + return len(resources) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r ResourcePage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractResources returns a slice of Resources contained in a single page of +// results. +func ExtractResources(r pagination.Page) ([]Resource, error) { + var s struct { + Resources []Resource `json:"resources"` + } + err := (r.(ResourcePage)).ExtractInto(&s) + return s.Resources, err +} + +// Extract interprets any commonResult as a Resource. +func (r commonResult) Extract() (*Resource, error) { + var s struct { + Resource *Resource `json:"resource"` + } + err := r.ExtractInto(&s) + return s.Resource, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/fixtures.go new file mode 100644 index 000000000000..66e2a202a2e0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/fixtures.go @@ -0,0 +1,118 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/service/vN/resources" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListResult provides a single page of RESOURCE results. +const ListResult = ` +{ +} +` + +// GetResult provides a Get result. +const GetResult = ` +{ +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ +} +` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = ` +{ +} +` + +// UpdateResult provides an update result. +const UpdateResult = ` +{ +} +` + +// FirstResource is the first resource in the List request. +var FirstResource = resources.Resource{} + +// SecondResource is the second resource in the List request. +var SecondResource = resources.Resource{} + +// SecondResourceUpdated is how SecondResource should look after an Update. +var SecondResourceUpdated = resources.Resource{} + +// ExpectedResourcesSlice is the slice of resources expected to be returned from ListResult. +var ExpectedResourcesSlice = []resources.Resource{FirstResource, SecondResource} + +// HandleListResourceSuccessfully creates an HTTP handler at `/resources` on the +// test handler mux that responds with a list of two resources. +func HandleListResourceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/resources", 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, ListResult) + }) +} + +// HandleGetResourceSuccessfully creates an HTTP handler at `/resources` on the +// test handler mux that responds with a single resource. +func HandleGetResourceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/resources/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetResult) + }) +} + +// HandleCreateResourceSuccessfully creates an HTTP handler at `/resources` on the +// test handler mux that tests resource creation. +func HandleCreateResourceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/resources", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetResult) + }) +} + +// HandleDeleteResourceSuccessfully creates an HTTP handler at `/resources` on the +// test handler mux that tests resource deletion. +func HandleDeleteResourceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/resources/9fe1d3", 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) + }) +} + +// HandleUpdateResourceSuccessfully creates an HTTP handler at `/resources` on the +// test handler mux that tests resource update. +func HandleUpdateResourceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/resources/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, UpdateResult) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/requests_test.go new file mode 100644 index 000000000000..22f97a2269e0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/testing/requests_test.go @@ -0,0 +1,89 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/service/vN/resources" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListResources(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListResourcesSuccessfully(t) + + count := 0 + err := resources.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := resources.ExtractResources(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedResourcesSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, count, 1) +} + +func TestListResourcesAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListResourcesSuccessfully(t) + + allPages, err := resources.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := resources.ExtractResources(allPages) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedResourcesSlice, actual) +} + +func TestGetResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetResourceSuccessfully(t) + + actual, err := resources.Get(client.ServiceClient(), "9fe1d3").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, SecondResource, *actual) +} + +func TestCreateResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateResourceSuccessfully(t) + + createOpts := resources.CreateOpts{ + Name: "resource two", + } + + actual, err := resources.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, SecondResource, *actual) +} + +func TestDeleteResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteResourceSuccessfully(t) + + res := resources.Delete(client.ServiceClient(), "9fe1d3") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateResourceSuccessfully(t) + + updateOpts := resources.UpdateOpts{ + Description: "Staging Resource", + } + + actual, err := resources.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, SecondResourceUpdated, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/urls.go b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/urls.go new file mode 100644 index 000000000000..603b8124c911 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/.template/urls.go @@ -0,0 +1,23 @@ +package RESOURCE + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("resource") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("resource", id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("resource") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("resource", id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("resource", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/README.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/README.md new file mode 100644 index 000000000000..14950b2bd6a0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/README.md @@ -0,0 +1,12 @@ +Contributor Tutorial +==================== + +This tutorial is to help new contributors become familiar with the processes +used by the Gophercloud team when adding a new feature or fixing a bug. + +While we have a defined process for working on Gophercloud, we're very mindful +that everyone is new to this in the beginning. Please reach out for help or ask +for clarification if needed. No question is ever "dumb" or not worth our time +answering. + +To begin, go to [Step 1](step-01-introduction.md). diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-01-introduction.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-01-introduction.md new file mode 100644 index 000000000000..d806143d774e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-01-introduction.md @@ -0,0 +1,16 @@ +Step 1: Read Our Guides +======================== + +There are two introductory guides you should read before proceeding: + +* [CONTRIBUTING](/.github/CONTRIBUTING.md): The Contributing guide is a detailed + document which describes the different ways you can contribute to Gophercloud + and how to get started. This tutorial you're reading is very similar to that + guide, but presented in a different way. We still recommend you read it over. + +* [STYLE](/docs/STYLEGUIDE.md): The Style Guide documents coding conventions used + in the Gophercloud project. + +--- + +When you've finished reading those guides, proceed to [Step 2](step-02-issues.md). diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-02-issues.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-02-issues.md new file mode 100644 index 000000000000..a3ae2a237b54 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-02-issues.md @@ -0,0 +1,124 @@ +Step 2: Create an Issue +======================== + +Every patch / Pull Request requires a corresponding issue. If you're fixing +a bug for an existing issue, then there's no need to create a new issue. + +However, if no prior issue exists, you must create an issue. + +Reporting a Bug +--------------- + +When reporting a bug, please try to provide as much information as you +can. + +The following issues are good examples for reporting a bug: + +* https://github.com/gophercloud/gophercloud/issues/108 +* https://github.com/gophercloud/gophercloud/issues/212 +* https://github.com/gophercloud/gophercloud/issues/424 +* https://github.com/gophercloud/gophercloud/issues/588 +* https://github.com/gophercloud/gophercloud/issues/629 +* https://github.com/gophercloud/gophercloud/issues/647 + +Feature Request +--------------- + +If you've noticed that a feature is missing from Gophercloud, you'll also +need to create an issue before doing any work. This is start a discussion about +whether or not the feature should be included in Gophercloud. We don't want to +want to see you put in hours of work only to learn that the feature is out of +scope of the project. + +Feature requests can come in different forms: + +### Adding a Feature to Gophercloud Core + +The "core" of Gophercloud is the code which supports API requests and +responses: pagination, error handling, building request bodies, and parsing +response bodies are all examples of core code. + +Modifications to core will usually have the most amount of discussion than +other requests since a change to core will affect _all_ of Gophercloud. + +The following issues are examples of core change discussions: + +* https://github.com/gophercloud/gophercloud/issues/310 +* https://github.com/gophercloud/gophercloud/issues/613 +* https://github.com/gophercloud/gophercloud/issues/729 +* https://github.com/gophercloud/gophercloud/issues/713 + +### Adding a Missing Field + +If you've found a missing field in an existing struct, submit an issue to +request having it added. These kinds of issues are pretty easy to report +and resolve. + +You should also provide a link to the actual service's Python code which +defines the missing field. + +The following issues are examples of missing fields: + +* https://github.com/gophercloud/gophercloud/issues/620 +* https://github.com/gophercloud/gophercloud/issues/621 +* https://github.com/gophercloud/gophercloud/issues/658 + +There's one situation which can make adding fields more difficult: if the field +is part of an API extension rather than the base API itself. An example of this +can be seen in [this](https://github.com/gophercloud/gophercloud/issues/749) +issue. + +Here, a user reported fields missing in the `Get` function of +`networking/v2/networks`. The fields reported missing weren't missing at all, +they're just part of various Networking extensions located in +`networking/v2/extensions`. + +### Adding a Missing API Call + +If you've found a missing API action, create an issue with details of +the action. For example: + +* https://github.com/gophercloud/gophercloud/issues/715 +* https://github.com/gophercloud/gophercloud/issues/719 + +You'll want to make sure the API call is part of the upstream OpenStack project +and not an extension created by a third-party or vendor. Gophercloud only +supports the OpenStack projects proper. + +### Adding a Missing API Suite + +Adding support to a missing suite of API calls will require more than one Pull +Request. However, you can use a single issue for all PRs. + +Examples of issues which track the addition of a missing API suite are: + +* https://github.com/gophercloud/gophercloud/issues/539 +* https://github.com/gophercloud/gophercloud/issues/555 +* https://github.com/gophercloud/gophercloud/issues/571 +* https://github.com/gophercloud/gophercloud/issues/583 +* https://github.com/gophercloud/gophercloud/issues/605 + +Note how the issue breaks down the implementation by request types (Create, +Update, Delete, Get, List). + +Also note how these issues provide links to the service's Python code. These +links are not required for _issues_, but it's usually a good idea to provide +them, anyway. These links _are required_ for PRs and that will be covered in +detail in a later step of this tutorial. + +### Adding a Missing OpenStack Project + +These kinds of feature additions are large undertakings. Adding support for +an entire OpenStack project is something the Gophercloud team very much +appreciates, but you should be prepared for several weeks of work and +interaction with the Gophercloud team. + +An example of how to create an issue for an entire project can be seen +here: + +* https://github.com/gophercloud/gophercloud/issues/723 + +--- + +With all of the above in mind, proceed to [Step 3](step-03-code-hunting.md) to +learn about Code Hunting. diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-03-code-hunting.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-03-code-hunting.md new file mode 100644 index 000000000000..f773eec040fc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-03-code-hunting.md @@ -0,0 +1,104 @@ +Step 3: Code Hunting +==================== + +If you plan to submit a feature or bug fix to Gophercloud, you must be +able to prove your code correctly works with the OpenStack service in +question. + +Let's use the following issue as an example: +[https://github.com/gophercloud/gophercloud/issues/621](https://github.com/gophercloud/gophercloud/issues/621). +In this issue, there's a request being made to add support for +`availability_zone_hints` to the `networking/v2/networks` package. +Meaning, we want to change: + +```go +type Network struct { + ID string `json:"id"` + Name string `json:"name"` + AdminStateUp bool `json:"admin_state_up"` + Status string `json:"status"` + Subnets []string `json:"subnets"` + TenantID string `json:"tenant_id"` + Shared bool `json:"shared"` +} +``` + +to look like + +```go +type Network struct { + ID string `json:"id"` + Name string `json:"name"` + AdminStateUp bool `json:"admin_state_up"` + Status string `json:"status"` + Subnets []string `json:"subnets"` + TenantID string `json:"tenant_id"` + Shared bool `json:"shared"` + + AvailabilityZoneHints []string `json:"availability_zone_hints"` +} +``` + +We need to be sure that `availability_zone_hints` is a field which really does +exist in the OpenStack Neutron project and it's not a field which was added as +a customization to a single OpenStack cloud. + +In addition, we need to ensure that `availability_zone_hints` is really a +`[]string` and not a different kind of type. + +One way of verifying this is through the [OpenStack API reference +documentation](https://developer.openstack.org/api-ref/network/v2/). +However, the API docs might either be incorrect or they might not provide all of +the details we need to know in order to ensure this field is added correctly. + +> Note: when we say the API docs might be incorrect, we are _not_ implying +> that the API docs aren't useful or that the contributors who work on the API +> docs are wrong. OpenStack moves fast. Typos happen. Forgetting to update +> documentation happens. + +Since the OpenStack service itself correctly accepts and processes the fields, +the best source of information on how the field works is in the service code +itself. + +Continuing on with using #621 as an example, we can find the definition of +`availability_zone_hints` in the following piece of code: + +https://github.com/openstack/neutron/blob/8e9959725eda4063a318b4ba6af1e3494cad9e35/neutron/objects/network.py#L191 + +The above code confirms that `availability_zone_hints` is indeed part of the +`Network` object and that its type is a list of strings (`[]string`). + +This example is a best-case situation: the code is relatively easy to find +and it's simple to understand. However, there will be times when proving the +implementation in the service code is difficult. Make no mistake, this is _not_ +fun work. This can sometimes be more difficult than writing the actual patch +for Gophercloud. However, this is an essential step to ensuring the feature +or bug fix is correctly added to Gophercloud. + +Examples of good code hunting can be seen here: + +* https://github.com/gophercloud/gophercloud/issues/539 +* https://github.com/gophercloud/gophercloud/issues/555 +* https://github.com/gophercloud/gophercloud/issues/571 +* https://github.com/gophercloud/gophercloud/issues/583 +* https://github.com/gophercloud/gophercloud/issues/605 + +Code Hunting Tips +----------------- + +OpenStack projects differ from one to another. Code is organized in different +ways. However, the following tips should be useful across all projects. + +* The logic which implements Create and Delete actions is usually either located + in the "model" or "controller" portion of the code. + +* Use Github's search box to search for the exact field you're working on. + Review all results to gain a good understanding of everywhere the field is + used. + +* When adding a field, look for an object model or a schema of some sort. + +--- + +Proceed to [Step 4](step-04-acceptance-testing.md) to learn about Acceptance +Testing. diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-04-acceptance-testing.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-04-acceptance-testing.md new file mode 100644 index 000000000000..fe8271743960 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-04-acceptance-testing.md @@ -0,0 +1,27 @@ +Step 4: Acceptance Testing +========================== + +If we haven't started working on the feature or bug fix, why are we talking +about Acceptance Testing now? + +Before you implement a feature or bug fix, you _must_ be able to test your code +in a working OpenStack environment. Please do not submit code which you have +only tested with offline unit tests. + +Blindly submitting code is dangerous to the Gophercloud project. Developers +from all over the world use Gophercloud in many different projects. If you +submit code which is untested, it can cause these projects to break or become +unstable. + +And, to be frank, submitting untested code will inevitably cause someone else +to have to spend time fixing it. + +If you don't have an OpenStack environment to test with, we have lots of +documentation [here](/acceptance) to help you build your own small OpenStack +environment for testing. + +--- + +Once you've confirmed you are able to test your code, proceed to +[Step 5](step-05-pull-requests.md) to (finally!) start working on a Pull +Request. diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-05-pull-requests.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-05-pull-requests.md new file mode 100644 index 000000000000..cb340a92cac7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-05-pull-requests.md @@ -0,0 +1,189 @@ +Step 5: Writing the Code +======================== + +At this point, you should have: + +- [x] Identified a feature or bug fix +- [x] Opened an Issue about it +- [x] Located the project's service code which validates the feature or fix +- [x] Have an OpenStack environment available to test with + +Now it's time to write the actual code! We recommend reading over the +[CONTRIBUTING](/.github/CONTRIBUTING.md) guide again as a refresh. Notably +the [Getting Started](/.github/CONTRIBUTING.md#getting-started) section will +help you set up a `git` repository correctly. + +We encourage you to browse the existing Gophercloud code to find examples +of similar implementations. It would be a _very_ rare occurrence for you +to be implementing something that hasn't already been done. + +Use the existing packages as templates and mirror the style, naming, and +logic. + +Types of Pull Requests +---------------------- + +The amount of changes you plan to make will determine how much code you should +submit as Pull Requests. + +### A Single Bug Fix + +If you're implementing a single bug fix, then creating one `git` branch and +submitting one Pull Request is fine. + +### Adding a Single Field + +If you're adding a single field, then a single Pull Request is also fine. See +[#662](https://github.com/gophercloud/gophercloud/pull/662) as an example of +this. + +If you plan to add more than one missing field, you will need to open a Pull +Request for _each_ field. + +### Adding a Single API Call + +Single API calls can also be submitted as a single Pull Request. See +[#722](https://github.com/gophercloud/gophercloud/pull/722) as an example of +this. + +### Adding a Suite of API Calls + +If you're adding support for a "suite" of API calls (meaning: Create, Update, +Delete, Get), then you will need to create one Pull Request for _each_ call. + +The following Pull Requests are good examples of how to do this: + +* https://github.com/gophercloud/gophercloud/pull/584 +* https://github.com/gophercloud/gophercloud/pull/586 +* https://github.com/gophercloud/gophercloud/pull/587 +* https://github.com/gophercloud/gophercloud/pull/594 + +You can also use the provided [template](/docs/contributor-tutorial/.template) +as it contains a lot of the repeated boiler plate code seen in each resource. +However, please make sure to thoroughly review and edit it as needed. +Leaving templated portions in-place might be interpreted as rushing through +the work and will require further rounds of review to fix. + +### Adding an Entire OpenStack Project + +To add an entire OpenStack project, you must break each set of API calls into +individual Pull Requests. Implementing an entire project can be thought of as +implementing multiple API suites. + +An example of this can be seen from the Pull Requests referenced in +[#723](https://github.com/gophercloud/gophercloud/issues/723). + +What to Include in a Pull Request +--------------------------------- + +Each Pull Request should contain the following: + +1. The actual Go code to implement the feature or bug fix +2. Unit tests +3. Acceptance tests +4. Documentation + +Whether you want to bundle all of the above into a single commit or multiple +commits is up to you. Use your preferred style. + +### Unit Tests + +Unit tests should provide basic validation that your code works as intended. + +Please do not use JSON fixtures from the API reference documentation. Please +generate your own fixtures using the OpenStack environment you're +[testing](step-04-acceptance-testing.md) with. + +### Acceptance Tests + +Since unit tests are not run against an actual OpenStack environment, +acceptance tests can arguably be more important. The acceptance tests that you +include in your Pull Request should confirm that your implemented code works +as intended with an actual OpenStack environment. + +### Documentation + +All documentation in Gophercloud is done through in-line `godoc`. Please make +sure to document all fields, functions, and methods appropriately. In addition, +each package has a `doc.go` file which should be created or amended with +details of your Pull Request, where appropriate. + +Dealing with Related Pull Requests +---------------------------------- + +If you plan to open more than one Pull Request, it's only natural that code +from one Pull Request will be dependent on code from the prior Pull Request. + +There are two methods of handling this: + +### Create Independent Pull Requests + +With this method, each Pull Request has all of the code to fully implement +the code in question. Each Pull Request can be merged in any order because +it's self contained. + +Use the following `git` workflow to implement this method: + +```shell +$ git checkout master +$ git pull +$ git checkout -b identityv3-regions-create +$ (write your code) +$ git add . +$ git commit -m "Implementing Regions Create" + +$ git checkout master +$ git checkout -b identityv3-regions-update +$ (write your code) +$ git add . +$ git commit -m "Implementing Regions Update" +``` + +Advantages of this Method: + +* Pull Requests can be merged in any order +* Additional commits to one Pull Request are independent of other Pull Requests + +Disadvantages of this Method: + +* There will be _a lot_ of duplicate code in each Pull Request +* You will have to rebase all other Pull Requests and resolve a good amount of + merge conflicts. + +### Create a Chain of Pull Requests + +With this method, each Pull Request is based off of a previous Pull Request. +Pull Requests will have to be merged in a specific order since there is a +defined relationship. + +Use the following `git` workflow to implement this method: + +```shell +$ git checkout master +$ git pull +$ git checkout -b identityv3-regions-create +$ (write your code) +$ git add . +$ git commit -m "Implementing Regions Create" + +$ git checkout -b identityv3-regions-update +$ (write your code) +$ git add . +$ git commit -m "Implementing Regions Update" +``` + +Advantages of this Method: + +* Each Pull Request becomes smaller since you are building off of the last + +Disadvantages of this Method: + +* If a Pull Request requires changes, you will have to rebase _all_ child + Pull Requests based off of the parent. + +The choice of method is up to you. + +--- + +Once you have your code written, submit a Pull Request to Gophercloud and +proceed to [Step 6](step-06-code-review.md). diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-06-code-review.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-06-code-review.md new file mode 100644 index 000000000000..ac3b68808bda --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-06-code-review.md @@ -0,0 +1,93 @@ +Step 6: Code Review +=================== + +Once you've submitted a Pull Request, three things will happen automatically: + +1. Travis-CI will run a set of simple tests: + + a. Unit Tests + + b. Code Formatting checks + + c. `go vet` checks + +2. Coveralls will run a coverage test. +3. [OpenLab](https://openlabtesting.org/) will run acceptance tests. + +Depending on the results of the above, you might need to make additional +changes to your code. + +While you're working on the finishing touches to your code, it is helpful +to add a `[wip]` tag to the title of your Pull Request. + +You are most welcomed to take as much time as you need to work on your Pull +Request. As well, take advantage of the automatic testing that is done to +each commit. + +### Travis-CI + +If Travis reports code formatting issues, please make sure to run `gofmt` on all +of your code. Travis will also report errors with unit tests, so you should +ensure those are fixed, too. + +### Coveralls + +If Coveralls reports a decrease in test coverage, check and make sure you have +provided unit tests. A decrease in test coverage is _sometimes_ unavoidable and +ignorable. + +### OpenLab + +OpenLab does not yet run a full suite of acceptance tests, so it's possible +that the acceptance tests you've included were not run. When this happens, +a core member for Gophercloud will run the tests manually. + +There are times when a core reviewer does not have access to the resources +required to run the acceptance tests. When this happens, it is essential +that you've run them yourself (See [Step 4](step-04.md)). + +Request a Code Review +--------------------- + +When you feel your Pull Request is ready for review, please leave a comment +requesting a code review. If you don't explicitly ask for a code review, a +core member might not know the Pull Request is ready for review. + +Additionally, if there are parts of your implementation that you are unsure +about, please ask for help. We're more than happy to provide advice. + +During the code review process, a core member will review the code you've +submitted and either request changes or request additional information. +Generally these requests fall under the following categories: + +1. Code which needs to be reformatted (See our [Style Guide](/docs/STYLEGUIDE.md) + for conventions used. + +2. Requests for additional information about the validity of something. This + might happen because the included supporting service code URLs don't have + enough information. + +3. Missing unit tests or acceptance tests. + +Submitting Changes +------------------ + +If a code review requires changes to be submitted, please do not squash your +commits. Please only add new commits to the Pull Request. This is to help the +code reviewer see only the changes that were made. + +It's Never Personal +------------------- + +Code review is a healthy exercise where a new set of eyes can sometimes spot +items forgotten by the author. + +Please don't take change requests personally. Our intention is to ensure the +code is correct before merging. + +--- + +Once the code has been reviewed and approved, a core member will merge your +Pull Request. + +Please proceed to [Step 7](step-07-congratulations.md). diff --git a/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-07-congratulations.md b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-07-congratulations.md new file mode 100644 index 000000000000..e14b794143d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/docs/contributor-tutorial/step-07-congratulations.md @@ -0,0 +1,9 @@ +Step 7: Congratulations! +======================== + +At this point your code is merged and you've either fixed a bug or added a new +feature to Gophercloud! + +We completely understand that this has been a long process. We appreciate your +patience as well as the time you have taken for working on this. You've made +Gophercloud a better project with your work. diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go index 2466932efe48..0bcb3af7f00c 100644 --- a/vendor/github.com/gophercloud/gophercloud/errors.go +++ b/vendor/github.com/gophercloud/gophercloud/errors.go @@ -1,6 +1,9 @@ package gophercloud -import "fmt" +import ( + "fmt" + "strings" +) // BaseError is an error type that all other error types embed. type BaseError struct { @@ -43,6 +46,33 @@ func (e ErrInvalidInput) Error() string { return e.choseErrString() } +// ErrMissingEnvironmentVariable is the error when environment variable is required +// in a particular situation but not provided by the user +type ErrMissingEnvironmentVariable struct { + BaseError + EnvironmentVariable string +} + +func (e ErrMissingEnvironmentVariable) Error() string { + e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable) + return e.choseErrString() +} + +// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables +// is required in a particular situation but not provided by the user +type ErrMissingAnyoneOfEnvironmentVariables struct { + BaseError + EnvironmentVariables []string +} + +func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Missing one of the following environment variables [%s]", + strings.Join(e.EnvironmentVariables, ", "), + ) + return e.choseErrString() +} + // ErrUnexpectedResponseCode is returned by the Request method when a response code other than // those listed in OkCodes is encountered. type ErrUnexpectedResponseCode struct { @@ -92,6 +122,11 @@ type ErrDefault408 struct { ErrUnexpectedResponseCode } +// ErrDefault409 is the default error type returned on a 409 HTTP response code. +type ErrDefault409 struct { + ErrUnexpectedResponseCode +} + // ErrDefault429 is the default error type returned on a 429 HTTP response code. type ErrDefault429 struct { ErrUnexpectedResponseCode @@ -108,7 +143,11 @@ type ErrDefault503 struct { } func (e ErrDefault400) Error() string { - return "Invalid request due to incorrect syntax or missing required parameters." + e.DefaultErrString = fmt.Sprintf( + "Bad request with: [%s %s], error message: %s", + e.Method, e.URL, e.Body, + ) + return e.choseErrString() } func (e ErrDefault401) Error() string { return "Authentication failed" @@ -177,6 +216,12 @@ type Err408er interface { Error408(ErrUnexpectedResponseCode) error } +// Err409er is the interface resource error types implement to override the error message +// from a 409 error. +type Err409er interface { + Error409(ErrUnexpectedResponseCode) error +} + // Err429er is the interface resource error types implement to override the error message // from a 429 error. type Err429er interface { @@ -417,3 +462,10 @@ type ErrScopeEmpty struct{ BaseError } func (e ErrScopeEmpty) Error() string { return "You must provide either a Project or Domain in a Scope" } + +// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name +type ErrAppCredMissingSecret struct{ BaseError } + +func (e ErrAppCredMissingSecret) Error() string { + return "You must provide an Application Credential Secret" +} diff --git a/vendor/github.com/gophercloud/gophercloud/go.mod b/vendor/github.com/gophercloud/gophercloud/go.mod new file mode 100644 index 000000000000..d1ee3b472ec8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/go.mod @@ -0,0 +1,7 @@ +module github.com/gophercloud/gophercloud + +require ( + golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 + golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/vendor/github.com/gophercloud/gophercloud/go.sum b/vendor/github.com/gophercloud/gophercloud/go.sum new file mode 100644 index 000000000000..33cb0be8aa31 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/go.sum @@ -0,0 +1,8 @@ +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go index b5482ba8c9f7..0e8d90ff8262 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go @@ -13,15 +13,19 @@ AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME, -OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. +OS_PASSWORD and OS_PROJECT_ID. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings, -or an error will result. OS_TENANT_ID, OS_TENANT_NAME, OS_PROJECT_ID, and -OS_PROJECT_NAME are optional. +or an error will result. OS_PROJECT_ID, is optional. -OS_TENANT_ID and OS_TENANT_NAME are mutually exclusive to OS_PROJECT_ID and -OS_PROJECT_NAME. If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will -still be referred as "tenant" in Gophercloud. +OS_TENANT_ID and OS_TENANT_NAME are deprecated forms of OS_PROJECT_ID and +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. To use this function, first set the OS_* environment variables (for example, by sourcing an `openrc` file), then: @@ -38,6 +42,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { tenantName := os.Getenv("OS_TENANT_NAME") domainID := os.Getenv("OS_DOMAIN_ID") domainName := os.Getenv("OS_DOMAIN_NAME") + applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID") + applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME") + applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET") // If OS_PROJECT_ID is set, overwrite tenantID with the value. if v := os.Getenv("OS_PROJECT_ID"); v != "" { @@ -50,29 +57,68 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { } if authURL == "" { - err := gophercloud.ErrMissingInput{Argument: "authURL"} + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_AUTH_URL", + } return nilOptions, err } - if username == "" && userID == "" { - err := gophercloud.ErrMissingInput{Argument: "username"} + if userID == "" && username == "" { + // Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set + if applicationCredentialID == "" && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + return nilOptions, err + } + } + + if password == "" && applicationCredentialID == "" && applicationCredentialName == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_PASSWORD", + } return nilOptions, err } - if password == "" { - err := gophercloud.ErrMissingInput{Argument: "password"} + if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET", + } return nilOptions, err } + if domainID == "" && domainName == "" && tenantID == "" && tenantName != "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_PROJECT_ID", + } + return nilOptions, err + } + + if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" { + if userID == "" && username == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + } + if username != "" && domainID == "" && domainName == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"}, + } + } + } + ao := gophercloud.AuthOptions{ - IdentityEndpoint: authURL, - UserID: userID, - Username: username, - Password: password, - TenantID: tenantID, - TenantName: tenantName, - DomainID: domainID, - DomainName: domainName, + IdentityEndpoint: authURL, + UserID: userID, + Username: username, + Password: password, + TenantID: tenantID, + TenantName: tenantName, + DomainID: domainID, + DomainName: domainName, + ApplicationCredentialID: applicationCredentialID, + ApplicationCredentialName: applicationCredentialName, + ApplicationCredentialSecret: applicationCredentialSecret, } return ao, nil diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go new file mode 100644 index 000000000000..9f83357b2aa2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go @@ -0,0 +1,17 @@ +/* +Package noauth provides support for noauth bare metal endpoints. + +Example of obtaining and using a client: + + client, err := noauth.NewBareMetalNoAuth(noauth.EndpointOpts{ + IronicEndpoint: "http://localhost:6385/v1/", + }) + if err != nil { + panic(err) + } + + client.Microversion = "1.50" + + nodes.ListDetail(client, nodes.ListOpts{}) +*/ +package noauth diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go new file mode 100644 index 000000000000..b5a1b58f0d59 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go @@ -0,0 +1,36 @@ +package noauth + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// EndpointOpts specifies a "noauth" Ironic Endpoint. +type EndpointOpts struct { + // IronicEndpoint [required] is currently only used with "noauth" Ironic. + // An Ironic endpoint with "auth_strategy=noauth" is necessary, for example: + // http://ironic.example.com:6385/v1. + IronicEndpoint string +} + +func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + if eo.IronicEndpoint == "" { + return nil, fmt.Errorf("IronicEndpoint is required") + } + + sc.Endpoint = gophercloud.NormalizeURL(eo.IronicEndpoint) + sc.ProviderClient = client + return sc, nil +} + +// NewBareMetalNoAuth creates a ServiceClient that may be used to access a +// "noauth" bare metal service. +func NewBareMetalNoAuth(eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(&gophercloud.ProviderClient{}, eo) + + sc.Type = "baremetal" + + return sc, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/testing/requests_test.go new file mode 100644 index 000000000000..4d107b9ffe46 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/testing/requests_test.go @@ -0,0 +1,16 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetal/noauth" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNoAuth(t *testing.T) { + noauthClient, err := noauth.NewBareMetalNoAuth(noauth.EndpointOpts{ + IronicEndpoint: "http://ironic:6385/v1", + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, "", noauthClient.TokenID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go new file mode 100644 index 000000000000..7acb0021a700 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go @@ -0,0 +1,131 @@ +package allocations + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAllocationCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies allocation creation parameters +type CreateOpts struct { + // The requested resource class for the allocation. + ResourceClass string `json:"resource_class" required:"true"` + + // The list of nodes (names or UUIDs) that should be considered for this allocation. If not provided, all available nodes will be considered. + CandidateNodes []string `json:"candidate_nodes,omitempty"` + + // The unique name of the Allocation. + Name string `json:"name,omitempty"` + + // The list of requested traits for the allocation. + Traits []string `json:"traits,omitempty"` + + // The UUID for the resource. + UUID string `json:"uuid,omitempty"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]string `json:"extra,omitempty"` +} + +// ToAllocationCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToAllocationCreateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create requests a node to be created +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToAllocationCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, nil) + return +} + +type AllocationState string + +var ( + Allocating AllocationState = "allocating" + Active = "active" + Error = "error" +) + +// ListOptsBuilder allows extensions to add additional parameters to the List request. +type ListOptsBuilder interface { + ToAllocationListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through the API. +type ListOpts struct { + // Filter the list of allocations by the node UUID or name. + Node string `q:"node"` + + // Filter the list of returned nodes, and only return the ones with the specified resource class. + ResourceClass string `q:"resource_class"` + + // Filter the list of allocations by the allocation state, one of active, allocating or error. + State AllocationState `q:"state"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item + Marker string `q:"marker"` + + // Sorts the response by the requested sort direction. + // Valid value is asc (ascending) or desc (descending). Default is asc. + SortDir string `q:"sort_dir"` + + // Sorts the response by the this attribute value. Default is id. + SortKey string `q:"sort_key"` +} + +// ToAllocationListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAllocationListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list allocations accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToAllocationListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AllocationPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get requests the details of an allocation by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete requests the deletion of an allocation +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go new file mode 100644 index 000000000000..cbd2115523e9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go @@ -0,0 +1,114 @@ +package allocations + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Allocation struct { + // The UUID for the resource. + UUID string `json:"uuid"` + + // A list of UUIDs of the nodes that are candidates for this allocation. + CandidateNodes []string `json:"candidate_nodes"` + + // The error message for the allocation if it is in the error state, null otherwise. + LastError string `json:"last_error"` + + // The unique name of the allocation. + Name string `json:"name"` + + // The UUID of the node assigned to the allocation. Will be null if a node is not yet assigned. + NodeUUID string `json:"node_uuid"` + + // The current state of the allocation. One of: allocation, active, error + State string `json:"state"` + + // The resource class requested for the allocation. + ResourceClass string `json:"resource_class"` + + // The list of the traits requested for the allocation. + Traits []string `json:"traits"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]string `json:"extra"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. May be “null”. + UpdatedAt time.Time `json:"updated_at"` + + // A list of relative links. Includes the self and bookmark links. + Links []interface{} `json:"links"` +} + +type allocationResult struct { + gophercloud.Result +} + +func (r allocationResult) Extract() (*Allocation, error) { + var s Allocation + err := r.ExtractInto(&s) + return &s, err +} + +func (r allocationResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractAllocationsInto(r pagination.Page, v interface{}) error { + return r.(AllocationPage).Result.ExtractIntoSlicePtr(v, "allocations") +} + +// AllocationPage abstracts the raw results of making a List() request against +// the API. +type AllocationPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Allocation results. +func (r AllocationPage) IsEmpty() (bool, error) { + s, err := ExtractAllocations(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r AllocationPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"allocations_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractAllocations interprets the results of a single page from a List() call, +// producing a slice of Allocation entities. +func ExtractAllocations(r pagination.Page) ([]Allocation, error) { + var s []Allocation + err := ExtractAllocationsInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Allocation. +type GetResult struct { + allocationResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + allocationResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/fixtures.go new file mode 100644 index 000000000000..340a7c5c1706 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/fixtures.go @@ -0,0 +1,168 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const AllocationListBody = ` +{ + "allocations": [ + { + "candidate_nodes": [], + "created_at": "2019-02-20T09:43:58+00:00", + "extra": {}, + "last_error": null, + "links": [ + { + "href": "http://127.0.0.1:6385/v1/allocations/5344a3e2-978a-444e-990a-cbf47c62ef88", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/allocations/5344a3e2-978a-444e-990a-cbf47c62ef88", + "rel": "bookmark" + } + ], + "name": "allocation-1", + "node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d", + "resource_class": "bm-large", + "state": "active", + "traits": [], + "updated_at": "2019-02-20T09:43:58+00:00", + "uuid": "5344a3e2-978a-444e-990a-cbf47c62ef88" + }, + { + "candidate_nodes": [], + "created_at": "2019-02-20T09:43:58+00:00", + "extra": {}, + "last_error": "Failed to process allocation eff80f47-75f0-4d41-b1aa-cf07c201adac: no available nodes match the resource class bm-large.", + "links": [ + { + "href": "http://127.0.0.1:6385/v1/allocations/eff80f47-75f0-4d41-b1aa-cf07c201adac", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/allocations/eff80f47-75f0-4d41-b1aa-cf07c201adac", + "rel": "bookmark" + } + ], + "name": "allocation-2", + "node_uuid": null, + "resource_class": "bm-large", + "state": "error", + "traits": [ + "CUSTOM_GOLD" + ], + "updated_at": "2019-02-20T09:43:58+00:00", + "uuid": "eff80f47-75f0-4d41-b1aa-cf07c201adac" + } + ] +} +` + +const SingleAllocationBody = ` +{ + "candidate_nodes": ["344a3e2-978a-444e-990a-cbf47c62ef88"], + "created_at": "2019-02-20T09:43:58+00:00", + "extra": {}, + "last_error": null, + "links": [ + { + "href": "http://127.0.0.1:6385/v1/allocations/5344a3e2-978a-444e-990a-cbf47c62ef88", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/allocations/5344a3e2-978a-444e-990a-cbf47c62ef88", + "rel": "bookmark" + } + ], + "name": "allocation-1", + "node_uuid": null, + "resource_class": "baremetal", + "state": "allocating", + "traits": ["foo"], + "updated_at": null, + "uuid": "5344a3e2-978a-444e-990a-cbf47c62ef88" +}` + +var ( + createdAt, _ = time.Parse(time.RFC3339, "2019-02-20T09:43:58+00:00") + + Allocation1 = allocations.Allocation{ + UUID: "5344a3e2-978a-444e-990a-cbf47c62ef88", + CandidateNodes: []string{"344a3e2-978a-444e-990a-cbf47c62ef88"}, + Name: "allocation-1", + State: "allocating", + ResourceClass: "baremetal", + Traits: []string{"foo"}, + Extra: map[string]string{}, + CreatedAt: createdAt, + Links: []interface{}{map[string]interface{}{"href": "http://127.0.0.1:6385/v1/allocations/5344a3e2-978a-444e-990a-cbf47c62ef88", "rel": "self"}, map[string]interface{}{"href": "http://127.0.0.1:6385/allocations/5344a3e2-978a-444e-990a-cbf47c62ef88", "rel": "bookmark"}}, + } +) + +// HandleAllocationListSuccessfully sets up the test server to respond to a allocation List request. +func HandleAllocationListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, AllocationListBody) + + case "eff80f47-75f0-4d41-b1aa-cf07c201adac": + fmt.Fprintf(w, `{ "allocations": [] }`) + default: + t.Fatalf("/allocations invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleAllocationCreationSuccessfully sets up the test server to respond to a allocation creation request +// with a given response. +func HandleAllocationCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "name": "allocation-1", + "resource_class": "baremetal", + "candidate_nodes": ["344a3e2-978a-444e-990a-cbf47c62ef88"], + "traits": ["foo"] + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleAllocationDeletionSuccessfully sets up the test server to respond to a allocation deletion request. +func HandleAllocationDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/allocations/344a3e2-978a-444e-990a-cbf47c62ef88", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleAllocationGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/allocations/344a3e2-978a-444e-990a-cbf47c62ef88", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleAllocationBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/requests_test.go new file mode 100644 index 000000000000..6d7b228d08ac --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/testing/requests_test.go @@ -0,0 +1,79 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListAllocations(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAllocationListSuccessfully(t) + + pages := 0 + err := allocations.List(client.ServiceClient(), allocations.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := allocations.ExtractAllocations(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 allocations, got %d", len(actual)) + } + th.AssertEquals(t, "5344a3e2-978a-444e-990a-cbf47c62ef88", actual[0].UUID) + th.AssertEquals(t, "eff80f47-75f0-4d41-b1aa-cf07c201adac", actual[1].UUID) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestCreateAllocation(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAllocationCreationSuccessfully(t, SingleAllocationBody) + + actual, err := allocations.Create(client.ServiceClient(), allocations.CreateOpts{ + Name: "allocation-1", + ResourceClass: "baremetal", + CandidateNodes: []string{"344a3e2-978a-444e-990a-cbf47c62ef88"}, + Traits: []string{"foo"}, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, Allocation1, *actual) +} + +func TestDeleteAllocation(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAllocationDeletionSuccessfully(t) + + res := allocations.Delete(client.ServiceClient(), "344a3e2-978a-444e-990a-cbf47c62ef88") + th.AssertNoErr(t, res.Err) +} + +func TestGetAllocation(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAllocationGetSuccessfully(t) + + c := client.ServiceClient() + actual, err := allocations.Get(c, "344a3e2-978a-444e-990a-cbf47c62ef88").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, Allocation1, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go new file mode 100644 index 000000000000..7163bbe334c1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go @@ -0,0 +1,23 @@ +package allocations + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("allocations") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func resourceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("allocations", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/doc.go new file mode 100644 index 000000000000..252c01b76ad9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/doc.go @@ -0,0 +1,43 @@ +/* +Package drivers contains the functionality for Listing drivers, driver details, +driver properties and driver logical disk properties + +API reference: https://developer.openstack.org/api-ref/baremetal/#drivers-drivers + +Example to List Drivers + + drivers.ListDrivers(client.ServiceClient(), drivers.ListDriversOpts{}).EachPage(func(page pagination.Page) (bool, error) { + driversList, err := drivers.ExtractDrivers(page) + if err != nil { + return false, err + } + + for _, n := range driversList { + // Do something + } + + return true, nil + }) + +Example to Get single Driver Details + + showDriverDetails, err := drivers.GetDriverDetails(client, "ipmi").Extract() + if err != nil { + panic(err) + } + +Example to Get single Driver Properties + + showDriverProperties, err := drivers.GetDriverProperties(client, "ipmi").Extract() + if err != nil { + panic(err) + } + +Example to Get single Driver Logical Disk Properties + + showDriverDiskProperties, err := drivers.GetDriverDiskProperties(client, "ipmi").Extract() + if err != nil { + panic(err) + } +*/ +package drivers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/requests.go new file mode 100644 index 000000000000..ab962c8ef7eb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/requests.go @@ -0,0 +1,70 @@ +package drivers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListDriversOptsBuilder allows extensions to add additional parameters to the +// ListDrivers request. +type ListDriversOptsBuilder interface { + ToListDriversOptsQuery() (string, error) +} + +// ListDriversOpts defines query options that can be passed to ListDrivers +type ListDriversOpts struct { + // Provide detailed information about the drivers + Detail bool `q:"detail"` + + // Filter the list by the type of the driver + Type string `q:"type"` +} + +// ToListDriversOptsQuery formats a ListOpts into a query string +func (opts ListDriversOpts) ToListDriversOptsQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDrivers makes a request against the API to list all drivers +func ListDrivers(client *gophercloud.ServiceClient, opts ListDriversOptsBuilder) pagination.Pager { + url := driversURL(client) + if opts != nil { + query, err := opts.ToListDriversOptsQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return DriverPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// GetDriverDetails Shows details for a driver +func GetDriverDetails(client *gophercloud.ServiceClient, driverName string) (r GetDriverResult) { + _, r.Err = client.Get(driverDetailsURL(client, driverName), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// GetDriverProperties Shows the required and optional parameters that +// driverName expects to be supplied in the driver_info field for every +// Node it manages +func GetDriverProperties(client *gophercloud.ServiceClient, driverName string) (r GetPropertiesResult) { + _, r.Err = client.Get(driverPropertiesURL(client, driverName), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// GetDriverDiskProperties Show the required and optional parameters that +// driverName expects to be supplied in the node’s raid_config field, if a +// RAID configuration change is requested. +func GetDriverDiskProperties(client *gophercloud.ServiceClient, driverName string) (r GetDiskPropertiesResult) { + _, r.Err = client.Get(driverDiskPropertiesURL(client, driverName), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/results.go new file mode 100644 index 000000000000..424079c8eafd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/results.go @@ -0,0 +1,198 @@ +package drivers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type driverResult struct { + gophercloud.Result +} + +// Extract interprets any driverResult as a Driver, if possible. +func (r driverResult) Extract() (*Driver, error) { + var s Driver + err := r.ExtractInto(&s) + return &s, err +} + +func (r driverResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractDriversInto(r pagination.Page, v interface{}) error { + return r.(DriverPage).Result.ExtractIntoSlicePtr(v, "drivers") +} + +// Driver represents a driver in the OpenStack Bare Metal API. +type Driver struct { + // Name and Identifier of the driver + Name string `json:"name"` + + // A list of active hosts that support this driver + Hosts []string `json:"hosts"` + + // Type of this driver (“classic” or “dynamic”) + Type string `json:"type"` + + // The default bios interface used for a node with a dynamic driver, + // if no bios interface is specified for the node. + DefaultBiosInterface string `json:"default_bios_interface"` + + // The default boot interface used for a node with a dynamic driver, + // if no boot interface is specified for the node. + DefaultBootInterface string `json:"default_boot_interface"` + + // The default console interface used for a node with a dynamic driver, + // if no console interface is specified for the node. + DefaultConsoleInterface string `json:"default_console_interface"` + + // The default deploy interface used for a node with a dynamic driver, + // if no deploy interface is specified for the node. + DefaultDeployInterface string `json:"default_deploy_interface"` + + // The default inspection interface used for a node with a dynamic driver, + // if no inspection interface is specified for the node. + DefaultInspectInterface string `json:"default_inspect_interface"` + + // The default management interface used for a node with a dynamic driver, + // if no management interface is specified for the node. + DefaultManagementInterface string `json:"default_management_interface"` + + // The default network interface used for a node with a dynamic driver, + // if no network interface is specified for the node. + DefaultNetworkInterface string `json:"default_network_interface"` + + // The default power interface used for a node with a dynamic driver, + // if no power interface is specified for the node. + DefaultPowerInterface string `json:"default_power_interface"` + + // The default RAID interface used for a node with a dynamic driver, + // if no RAID interface is specified for the node. + DefaultRaidInterface string `json:"default_raid_interface"` + + // The default rescue interface used for a node with a dynamic driver, + // if no rescue interface is specified for the node. + DefaultRescueInterface string `json:"default_rescue_interface"` + + // The default storage interface used for a node with a dynamic driver, + // if no storage interface is specified for the node. + DefaultStorageInterface string `json:"default_storage_interface"` + + // The default vendor interface used for a node with a dynamic driver, + // if no vendor interface is specified for the node. + DefaultVendorInterface string `json:"default_vendor_interface"` + + // The enabled bios interfaces for this driver. + EnabledBiosInterfaces []string `json:"enabled_bios_interfaces"` + + // The enabled boot interfaces for this driver. + EnabledBootInterfaces []string `json:"enabled_boot_interfaces"` + + // The enabled console interfaces for this driver. + EnabledConsoleInterface []string `json:"enabled_console_interfaces"` + + // The enabled deploy interfaces for this driver. + EnabledDeployInterfaces []string `json:"enabled_deploy_interfaces"` + + // The enabled inspection interfaces for this driver. + EnabledInspectInterfaces []string `json:"enabled_inspect_interfaces"` + + // The enabled management interfaces for this driver. + EnabledManagementInterfaces []string `json:"enabled_management_interfaces"` + + // The enabled network interfaces for this driver. + EnabledNetworkInterfaces []string `json:"enabled_network_interfaces"` + + // The enabled power interfaces for this driver. + EnabledPowerInterfaces []string `json:"enabled_power_interfaces"` + + // The enabled rescue interfaces for this driver. + EnabledRescueInterfaces []string `json:"enabled_rescue_interfaces"` + + // The enabled RAID interfaces for this driver. + EnabledRaidInterfaces []string `json:"enabled_raid_interfaces"` + + // The enabled storage interfaces for this driver. + EnabledStorageInterfaces []string `json:"enabled_storage_interfaces"` + + // The enabled vendor interfaces for this driver. + EnabledVendorInterfaces []string `json:"enabled_vendor_interfaces"` + + //A list of relative links. Includes the self and bookmark links. + Links []interface{} `json:"links"` + + // A list of links to driver properties. + Properties []interface{} `json:"properties"` +} + +// DriverPage abstracts the raw results of making a ListDrivers() request +// against the API. +type DriverPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Driver results. +func (r DriverPage) IsEmpty() (bool, error) { + s, err := ExtractDrivers(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r DriverPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"drivers_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractDrivers interprets the results of a single page from ListDrivers() +// call, producing a slice of Driver entities. +func ExtractDrivers(r pagination.Page) ([]Driver, error) { + var s []Driver + err := ExtractDriversInto(r, &s) + return s, err +} + +// GetDriverResult is the response from a Get operation. +// Call its Extract method to interpret it as a Driver. +type GetDriverResult struct { + driverResult +} + +// DriverProperties represents driver properties in the OpenStack Bare Metal API. +type DriverProperties map[string]interface{} + +// Extract interprets any GetPropertiesResult as DriverProperties, if possible. +func (r GetPropertiesResult) Extract() (*DriverProperties, error) { + var s DriverProperties + err := r.ExtractInto(&s) + return &s, err +} + +// GetPropertiesResult is the response from a GetDriverProperties operation. +// Call its Extract method to interpret it as DriverProperties. +type GetPropertiesResult struct { + gophercloud.Result +} + +// DiskProperties represents driver disk properties in the OpenStack Bare Metal API. +type DiskProperties map[string]interface{} + +// Extract interprets any GetDiskPropertiesResult as DiskProperties, if possible. +func (r GetDiskPropertiesResult) Extract() (*DiskProperties, error) { + var s DiskProperties + err := r.ExtractInto(&s) + return &s, err +} + +// GetDiskPropertiesResult is the response from a GetDriverDiskProperties operation. +// Call its Extract method to interpret it as DiskProperties. +type GetDiskPropertiesResult struct { + gophercloud.Result +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/doc.go new file mode 100644 index 000000000000..266af92b04b4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/doc.go @@ -0,0 +1,2 @@ +// Package testing contains drivers unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/fixtures.go new file mode 100644 index 000000000000..a785b905c1b6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/fixtures.go @@ -0,0 +1,407 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListDriversBody contains the canned body of a drivers.ListDrivers response, without details. +const ListDriversBody = ` +{ + "drivers": [ + { + "hosts": [ + "897ab1dad809" + ], + "links": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/agent_ipmitool", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/agent_ipmitool", + "rel": "bookmark" + } + ], + "name": "agent_ipmitool", + "properties": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/agent_ipmitool/properties", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/agent_ipmitool/properties", + "rel": "bookmark" + } + ], + "type": "classic" + }, + { + "hosts": [ + "897ab1dad809" + ], + "links": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/fake", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/fake", + "rel": "bookmark" + } + ], + "name": "fake", + "properties": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/fake/properties", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/fake/properties", + "rel": "bookmark" + } + ], + "type": "classic" + }, + { + "hosts": [ + "897ab1dad809" + ], + "links": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/ipmi", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/ipmi", + "rel": "bookmark" + } + ], + "name": "ipmi", + "properties": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/ipmi/properties", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/ipmi/properties", + "rel": "bookmark" + } + ], + "type": "dynamic" + } + ] +} +` +const SingleDriverDetails = ` +{ + "default_bios_interface": "no-bios", + "default_boot_interface": "pxe", + "default_console_interface": "no-console", + "default_deploy_interface": "iscsi", + "default_inspect_interface": "no-inspect", + "default_management_interface": "ipmitool", + "default_network_interface": "flat", + "default_power_interface": "ipmitool", + "default_raid_interface": "no-raid", + "default_rescue_interface": "no-rescue", + "default_storage_interface": "noop", + "default_vendor_interface": "no-vendor", + "enabled_bios_interfaces": [ + "no-bios" + ], + "enabled_boot_interfaces": [ + "pxe" + ], + "enabled_console_interfaces": [ + "no-console" + ], + "enabled_deploy_interfaces": [ + "iscsi", + "direct" + ], + "enabled_inspect_interfaces": [ + "no-inspect" + ], + "enabled_management_interfaces": [ + "ipmitool" + ], + "enabled_network_interfaces": [ + "flat", + "noop" + ], + "enabled_power_interfaces": [ + "ipmitool" + ], + "enabled_raid_interfaces": [ + "no-raid", + "agent" + ], + "enabled_rescue_interfaces": [ + "no-rescue" + ], + "enabled_storage_interfaces": [ + "noop" + ], + "enabled_vendor_interfaces": [ + "no-vendor" + ], + "hosts": [ + "897ab1dad809" + ], + "links": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/ipmi", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/ipmi", + "rel": "bookmark" + } + ], + "name": "ipmi", + "properties": [ + { + "href": "http://127.0.0.1:6385/v1/drivers/ipmi/properties", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/drivers/ipmi/properties", + "rel": "bookmark" + } + ], + "type": "dynamic" +} +` + +const SingleDriverProperties = ` +{ + "deploy_forces_oob_reboot": "Whether Ironic should force a reboot of the Node via the out-of-band channel after deployment is complete. Provides compatibility with older deploy ramdisks. Defaults to False. Optional.", + "deploy_kernel": "UUID (from Glance) of the deployment kernel. Required.", + "deploy_ramdisk": "UUID (from Glance) of the ramdisk that is mounted at boot time. Required.", + "image_http_proxy": "URL of a proxy server for HTTP connections. Optional.", + "image_https_proxy": "URL of a proxy server for HTTPS connections. Optional.", + "image_no_proxy": "A comma-separated list of host names, IP addresses and domain names (with optional :port) that will be excluded from proxying. To denote a domain name, use a dot to prefix the domain name. This value will be ignored if ` + "``image_http_proxy`` and ``image_https_proxy``" + ` are not specified. Optional.", + "ipmi_address": "IP address or hostname of the node. Required.", + "ipmi_bridging": "bridging_type; default is \"no\". One of \"single\", \"dual\", \"no\". Optional.", + "ipmi_disable_boot_timeout": "By default ironic will send a raw IPMI command to disable the 60 second timeout for booting. Setting this option to False will NOT send that command; default value is True. Optional.", + "ipmi_force_boot_device": "Whether Ironic should specify the boot device to the BMC each time the server is turned on, eg. because the BMC is not capable of remembering the selected boot device across power cycles; default value is False. Optional.", + "ipmi_local_address": "local IPMB address for bridged requests. Used only if ipmi_bridging is set to \"single\" or \"dual\". Optional.", + "ipmi_password": "password. Optional.", + "ipmi_port": "remote IPMI RMCP port. Optional.", + "ipmi_priv_level": "privilege level; default is ADMINISTRATOR. One of ADMINISTRATOR, CALLBACK, OPERATOR, USER. Optional.", + "ipmi_protocol_version": "the version of the IPMI protocol; default is \"2.0\". One of \"1.5\", \"2.0\". Optional.", + "ipmi_target_address": "destination address for bridged request. Required only if ipmi_bridging is set to \"single\" or \"dual\".", + "ipmi_target_channel": "destination channel for bridged request. Required only if ipmi_bridging is set to \"single\" or \"dual\".", + "ipmi_terminal_port": "node's UDP port to connect to. Only required for console access.", + "ipmi_transit_address": "transit address for bridged request. Required only if ipmi_bridging is set to \"dual\".", + "ipmi_transit_channel": "transit channel for bridged request. Required only if ipmi_bridging is set to \"dual\".", + "ipmi_username": "username; default is NULL user. Optional." +} +` + +const SingleDriverDiskProperties = ` +{ + "controller": "Controller to use for this logical disk. If not specified, the driver will choose a suitable RAID controller on the bare metal node. Optional.", + "disk_type": "The type of disk preferred. Valid values are 'hdd' and 'ssd'. If this is not specified, disk type will not be a selection criterion for choosing backing physical disks. Optional.", + "interface_type": "The interface type of disk. Valid values are 'sata', 'scsi' and 'sas'. If this is not specified, interface type will not be a selection criterion for choosing backing physical disks. Optional.", + "is_root_volume": "Specifies whether this disk is a root volume. By default, this is False. Optional.", + "number_of_physical_disks": "Number of physical disks to use for this logical disk. By default, the driver uses the minimum number of disks required for that RAID level. Optional.", + "physical_disks": "The physical disks to use for this logical disk. If not specified, the driver will choose suitable physical disks to use. Optional.", + "raid_level": "RAID level for the logical disk. Valid values are 'JBOD', '0', '1', '2', '5', '6', '1+0', '5+0' and '6+0'. Required.", + "share_physical_disks": "Specifies whether other logical disks can share physical disks with this logical disk. By default, this is False. Optional.", + "size_gb": "Size in GiB (Integer) for the logical disk. Use 'MAX' as size_gb if this logical disk is supposed to use the rest of the space available. Required.", + "volume_name": "Name of the volume to be created. If this is not specified, it will be auto-generated. Optional." +} +` + +var ( + DriverAgentIpmitool = drivers.Driver{ + Name: "agent_ipmitool", + Type: "classic", + Hosts: []string{"897ab1dad809"}, + Links: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:6385/v1/drivers/agent_ipmitool", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://127.0.0.1:6385/drivers/agent_ipmitool", + "rel": "bookmark", + }, + }, + Properties: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:6385/v1/drivers/agent_ipmitool/properties", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://127.0.0.1:6385/drivers/agent_ipmitool/properties", + "rel": "bookmark", + }, + }, + } + + DriverFake = drivers.Driver{ + Name: "fake", + Type: "classic", + Hosts: []string{"897ab1dad809"}, + Links: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:6385/v1/drivers/fake", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://127.0.0.1:6385/drivers/fake", + "rel": "bookmark", + }, + }, + Properties: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:6385/v1/drivers/fake/properties", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://127.0.0.1:6385/drivers/fake/properties", + "rel": "bookmark", + }, + }, + } + + DriverIpmi = drivers.Driver{ + Name: "ipmi", + Type: "dynamic", + Hosts: []string{"897ab1dad809"}, + DefaultBiosInterface: "no-bios", + DefaultBootInterface: "pxe", + DefaultConsoleInterface: "no-console", + DefaultDeployInterface: "iscsi", + DefaultInspectInterface: "no-inspect", + DefaultManagementInterface: "ipmitool", + DefaultNetworkInterface: "flat", + DefaultPowerInterface: "ipmitool", + DefaultRaidInterface: "no-raid", + DefaultRescueInterface: "no-rescue", + DefaultStorageInterface: "noop", + DefaultVendorInterface: "no-vendor", + EnabledBiosInterfaces: []string{"no-bios"}, + EnabledBootInterfaces: []string{"pxe"}, + EnabledConsoleInterface: []string{"no-console"}, + EnabledDeployInterfaces: []string{"iscsi", "direct"}, + EnabledInspectInterfaces: []string{"no-inspect"}, + EnabledManagementInterfaces: []string{"ipmitool"}, + EnabledNetworkInterfaces: []string{"flat", "noop"}, + EnabledPowerInterfaces: []string{"ipmitool"}, + EnabledRescueInterfaces: []string{"no-rescue"}, + EnabledRaidInterfaces: []string{"no-raid", "agent"}, + EnabledStorageInterfaces: []string{"noop"}, + EnabledVendorInterfaces: []string{"no-vendor"}, + Links: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:6385/v1/drivers/ipmi", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://127.0.0.1:6385/drivers/ipmi", + "rel": "bookmark", + }, + }, + Properties: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:6385/v1/drivers/ipmi/properties", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://127.0.0.1:6385/drivers/ipmi/properties", + "rel": "bookmark", + }, + }, + } + + DriverIpmiToolProperties = drivers.DriverProperties{ + "deploy_forces_oob_reboot": "Whether Ironic should force a reboot of the Node via the out-of-band channel after deployment is complete. Provides compatibility with older deploy ramdisks. Defaults to False. Optional.", + "deploy_kernel": "UUID (from Glance) of the deployment kernel. Required.", + "deploy_ramdisk": "UUID (from Glance) of the ramdisk that is mounted at boot time. Required.", + "image_http_proxy": "URL of a proxy server for HTTP connections. Optional.", + "image_https_proxy": "URL of a proxy server for HTTPS connections. Optional.", + "image_no_proxy": "A comma-separated list of host names, IP addresses and domain names (with optional :port) that will be excluded from proxying. To denote a domain name, use a dot to prefix the domain name. This value will be ignored if ``image_http_proxy`` and ``image_https_proxy`` are not specified. Optional.", + "ipmi_address": "IP address or hostname of the node. Required.", + "ipmi_bridging": "bridging_type; default is \"no\". One of \"single\", \"dual\", \"no\". Optional.", + "ipmi_disable_boot_timeout": "By default ironic will send a raw IPMI command to disable the 60 second timeout for booting. Setting this option to False will NOT send that command; default value is True. Optional.", + "ipmi_force_boot_device": "Whether Ironic should specify the boot device to the BMC each time the server is turned on, eg. because the BMC is not capable of remembering the selected boot device across power cycles; default value is False. Optional.", + "ipmi_local_address": "local IPMB address for bridged requests. Used only if ipmi_bridging is set to \"single\" or \"dual\". Optional.", + "ipmi_password": "password. Optional.", + "ipmi_port": "remote IPMI RMCP port. Optional.", + "ipmi_priv_level": "privilege level; default is ADMINISTRATOR. One of ADMINISTRATOR, CALLBACK, OPERATOR, USER. Optional.", + "ipmi_protocol_version": "the version of the IPMI protocol; default is \"2.0\". One of \"1.5\", \"2.0\". Optional.", + "ipmi_target_address": "destination address for bridged request. Required only if ipmi_bridging is set to \"single\" or \"dual\".", + "ipmi_target_channel": "destination channel for bridged request. Required only if ipmi_bridging is set to \"single\" or \"dual\".", + "ipmi_terminal_port": "node's UDP port to connect to. Only required for console access.", + "ipmi_transit_address": "transit address for bridged request. Required only if ipmi_bridging is set to \"dual\".", + "ipmi_transit_channel": "transit channel for bridged request. Required only if ipmi_bridging is set to \"dual\".", + "ipmi_username": "username; default is NULL user. Optional.", + } + + DriverIpmiToolDisk = drivers.DiskProperties{ + "controller": "Controller to use for this logical disk. If not specified, the driver will choose a suitable RAID controller on the bare metal node. Optional.", + "disk_type": "The type of disk preferred. Valid values are 'hdd' and 'ssd'. If this is not specified, disk type will not be a selection criterion for choosing backing physical disks. Optional.", + "interface_type": "The interface type of disk. Valid values are 'sata', 'scsi' and 'sas'. If this is not specified, interface type will not be a selection criterion for choosing backing physical disks. Optional.", + "is_root_volume": "Specifies whether this disk is a root volume. By default, this is False. Optional.", + "number_of_physical_disks": "Number of physical disks to use for this logical disk. By default, the driver uses the minimum number of disks required for that RAID level. Optional.", + "physical_disks": "The physical disks to use for this logical disk. If not specified, the driver will choose suitable physical disks to use. Optional.", + "raid_level": "RAID level for the logical disk. Valid values are 'JBOD', '0', '1', '2', '5', '6', '1+0', '5+0' and '6+0'. Required.", + "share_physical_disks": "Specifies whether other logical disks can share physical disks with this logical disk. By default, this is False. Optional.", + "size_gb": "Size in GiB (Integer) for the logical disk. Use 'MAX' as size_gb if this logical disk is supposed to use the rest of the space available. Required.", + "volume_name": "Name of the volume to be created. If this is not specified, it will be auto-generated. Optional.", + } +) + +// HandleListDriversSuccessfully sets up the test server to respond to a drivers ListDrivers request. +func HandleListDriversSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/drivers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + fmt.Fprintf(w, ListDriversBody) + }) +} + +// HandleGetDriverDetailsSuccessfully sets up the test server to respond to a drivers GetDriverDetails request. +func HandleGetDriverDetailsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/drivers/ipmi", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleDriverDetails) + }) +} + +// HandleGetDriverPropertiesSuccessfully sets up the test server to respond to a drivers GetDriverProperties request. +func HandleGetDriverPropertiesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/drivers/agent_ipmitool/properties", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleDriverProperties) + }) +} + +// HandleGetDriverDiskPropertiesSuccessfully sets up the test server to respond to a drivers GetDriverDiskProperties request. +func HandleGetDriverDiskPropertiesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/drivers/agent_ipmitool/raid/logical_disk_properties", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleDriverDiskProperties) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/requests_test.go new file mode 100644 index 000000000000..28e53651835f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/testing/requests_test.go @@ -0,0 +1,84 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListDrivers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListDriversSuccessfully(t) + + pages := 0 + err := drivers.ListDrivers(client.ServiceClient(), drivers.ListDriversOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := drivers.ExtractDrivers(page) + if err != nil { + return false, err + } + + if len(actual) != 3 { + t.Fatalf("Expected 3 drivers, got %d", len(actual)) + } + + th.CheckDeepEquals(t, DriverAgentIpmitool, actual[0]) + th.CheckDeepEquals(t, DriverFake, actual[1]) + th.AssertEquals(t, "ipmi", actual[2].Name) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestGetDriverDetails(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetDriverDetailsSuccessfully(t) + + c := client.ServiceClient() + actual, err := drivers.GetDriverDetails(c, "ipmi").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, DriverIpmi, *actual) +} + +func TestGetDriverProperties(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetDriverPropertiesSuccessfully(t) + + c := client.ServiceClient() + actual, err := drivers.GetDriverProperties(c, "agent_ipmitool").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, DriverIpmiToolProperties, *actual) +} + +func TestGetDriverDiskProperties(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetDriverDiskPropertiesSuccessfully(t) + + c := client.ServiceClient() + actual, err := drivers.GetDriverDiskProperties(c, "agent_ipmitool").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, DriverIpmiToolDisk, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/urls.go new file mode 100644 index 000000000000..d5ddba7d89e4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers/urls.go @@ -0,0 +1,19 @@ +package drivers + +import "github.com/gophercloud/gophercloud" + +func driversURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("drivers") +} + +func driverDetailsURL(client *gophercloud.ServiceClient, driverName string) string { + return client.ServiceURL("drivers", driverName) +} + +func driverPropertiesURL(client *gophercloud.ServiceClient, driverName string) string { + return client.ServiceURL("drivers", driverName, "properties") +} + +func driverDiskPropertiesURL(client *gophercloud.ServiceClient, driverName string) string { + return client.ServiceURL("drivers", driverName, "raid", "logical_disk_properties") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go new file mode 100644 index 000000000000..37f60b5f3ef6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go @@ -0,0 +1,130 @@ +/* +Package nodes provides information and interaction with the nodes API +resource in the OpenStack Bare Metal service. + +Example to List Nodes with Detail + + nodes.ListDetail(client, nodes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + nodeList, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + for _, n := range nodeList { + // Do something + } + + return true, nil + }) + +Example to List Nodes + + listOpts := nodes.ListOpts{ + ProvisionState: nodes.Deploying, + Fields: []string{"name"}, + } + + nodes.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + nodeList, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + for _, n := range nodeList { + // Do something + } + + return true, nil + }) + +Example to Create Node + + createOpts := nodes.CreateOpts + Driver: "ipmi", + BootInterface: "pxe", + Name: "coconuts", + DriverInfo: map[string]interface{}{ + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin", + }, + } + + createNode, err := nodes.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get Node + + showNode, err := nodes.Get(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").Extract() + if err != nil { + panic(err) + } + +Example to Update Node + + updateOpts := nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: ReplaceOp, + Path: "/maintenance", + Value: "true", + }, + } + + updateNode, err := nodes.Update(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474", updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete Node + + err = nodes.Delete(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").ExtractErr() + if err != nil { + panic(err) + } + +Example to Validate Node + + validation, err := nodes.Validate(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").Extract() + if err != nil { + panic(err) + } + +Example to inject non-masking interrupts + + err := nodes.InjectNMI(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").ExtractErr() + if err != nil { + panic(err) + } + +Example to get array of supported boot devices for a node + + bootDevices, err := nodes.GetSupportedBootDevices(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").Extract() + if err != nil { + panic(err) + } + +Example to set boot device for a node + + bootOpts := nodes.BootDeviceOpts{ + BootDevice: "pxe", + Persistent: false, + } + + err := nodes.SetBootDevice(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8", bootOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to get boot device for a node + + bootDevice, err := nodes.GetBootDevice(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").Extract() + if err != nil { + panic(err) + } +*/ +package nodes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go new file mode 100644 index 000000000000..4707f7b250d2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go @@ -0,0 +1,593 @@ +package nodes + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToNodeListQuery() (string, error) + ToNodeListDetailQuery() (string, error) +} + +// Provision state reports the current provision state of the node, these are only used in filtering +type ProvisionState string + +const ( + Enroll ProvisionState = "enroll" + Verifying ProvisionState = "verifying" + Manageable ProvisionState = "manageable" + Available ProvisionState = "available" + Active ProvisionState = "active" + DeployWait ProvisionState = "wait call-back" + Deploying ProvisionState = "deploying" + DeployFail ProvisionState = "deploy failed" + DeployDone ProvisionState = "deploy complete" + Deleting ProvisionState = "deleting" + Deleted ProvisionState = "deleted" + Cleaning ProvisionState = "cleaning" + CleanWait ProvisionState = "clean wait" + CleanFail ProvisionState = "clean failed" + Error ProvisionState = "error" + Rebuild ProvisionState = "rebuild" + Inspecting ProvisionState = "inspecting" + InspectFail ProvisionState = "inspect failed" + InspectWait ProvisionState = "inspect wait" + Adopting ProvisionState = "adopting" + AdoptFail ProvisionState = "adopt failed" + Rescue ProvisionState = "rescue" + RescueFail ProvisionState = "rescue failed" + Rescuing ProvisionState = "rescuing" + UnrescueFail ProvisionState = "unrescue failed" +) + +// TargetProvisionState is used when setting the provision state for a node. +type TargetProvisionState string + +const ( + TargetActive TargetProvisionState = "active" + TargetDeleted TargetProvisionState = "deleted" + TargetManage TargetProvisionState = "manage" + TargetProvide TargetProvisionState = "provide" + TargetInspect TargetProvisionState = "inspect" + TargetAbort TargetProvisionState = "abort" + TargetClean TargetProvisionState = "clean" + TargetAdopt TargetProvisionState = "adopt" + TargetRescue TargetProvisionState = "rescue" + TargetUnrescue TargetProvisionState = "unrescue" +) + +// 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 node attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + // Filter the list by specific instance UUID + InstanceUUID string `q:"instance_uuid"` + + // Filter the list by chassis UUID + ChassisUUID string `q:"chassis_uuid"` + + // Filter the list by maintenance set to True or False + Maintenance bool `q:"maintenance"` + + // Nodes which are, or are not, associated with an instance_uuid. + Associated bool `q:"associated"` + + // Only return those with the specified provision_state. + ProvisionState ProvisionState `q:"provision_state"` + + // Filter the list with the specified driver. + Driver string `q:"driver"` + + // Filter the list with the specified resource class. + ResourceClass string `q:"resource_class"` + + // Filter the list with the specified conductor_group. + ConductorGroup string `q:"conductor_group"` + + // Filter the list with the specified fault. + Fault string `q:"fault"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item. + Marker string `q:"marker"` + + // Sorts the response by the requested sort direction. + SortDir string `q:"sort_dir"` + + // Sorts the response by the this attribute value. + SortKey string `q:"sort_key"` + + // A string or UUID of the tenant who owns the baremetal node. + Owner string `q:"owner"` +} + +// ToNodeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToNodeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list nodes accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToNodeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return NodePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ToNodeListDetailQuery formats a ListOpts into a query string for the list details API. +func (opts ListOpts) ToNodeListDetailQuery() (string, error) { + // Detail endpoint can't filter by Fields + if len(opts.Fields) > 0 { + return "", fmt.Errorf("fields is not a valid option when getting a detailed listing of nodes") + } + + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Return a list of bare metal Nodes with complete details. Some filtering is possible by passing in flags in ListOpts, +// but you cannot limit by the fields returned. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + // This URL is deprecated. In the future, we should compare the microversion and if >= 1.43, hit the listURL + // with ListOpts{Detail: true,} + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToNodeListDetailQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return NodePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get requests details on a single node, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToNodeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies node creation parameters. +type CreateOpts struct { + // The boot interface for a Node, e.g. “pxe”. + BootInterface string `json:"boot_interface,omitempty"` + + // The conductor group for a node. Case-insensitive string up to 255 characters, containing a-z, 0-9, _, -, and .. + ConductorGroup string `json:"conductor_group,omitempty"` + + // The console interface for a node, e.g. “no-console”. + ConsoleInterface string `json:"console_interface,omitempty"` + + // The deploy interface for a node, e.g. “iscsi”. + DeployInterface string `json:"deploy_interface,omitempty"` + + // All the metadata required by the driver to manage this Node. List of fields varies between drivers, and can + // be retrieved from the /v1/drivers//properties resource. + DriverInfo map[string]interface{} `json:"driver_info,omitempty"` + + // name of the driver used to manage this Node. + Driver string `json:"driver,omitempty"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra,omitempty"` + + // The interface used for node inspection, e.g. “no-inspect”. + InspectInterface string `json:"inspect_interface,omitempty"` + + // Interface for out-of-band node management, e.g. “ipmitool”. + ManagementInterface string `json:"management_interface,omitempty"` + + // Human-readable identifier for the Node resource. May be undefined. Certain words are reserved. + Name string `json:"name,omitempty"` + + // Which Network Interface provider to use when plumbing the network connections for this Node. + NetworkInterface string `json:"network_interface,omitempty"` + + // Interface used for performing power actions on the node, e.g. “ipmitool”. + PowerInterface string `json:"power_interface,omitempty"` + + // Physical characteristics of this Node. Populated during inspection, if performed. Can be edited via the REST + // API at any time. + Properties map[string]interface{} `json:"properties,omitempty"` + + // Interface used for configuring RAID on this node, e.g. “no-raid”. + RAIDInterface string `json:"raid_interface,omitempty"` + + // The interface used for node rescue, e.g. “no-rescue”. + RescueInterface string `json:"rescue_interface,omitempty"` + + // A string which can be used by external schedulers to identify this Node as a unit of a specific type + // of resource. + ResourceClass string `json:"resource_class,omitempty"` + + // Interface used for attaching and detaching volumes on this node, e.g. “cinder”. + StorageInterface string `json:"storage_interface,omitempty"` + + // The UUID for the resource. + UUID string `json:"uuid,omitempty"` + + // Interface for vendor-specific functionality on this node, e.g. “no-vendor”. + VendorInterface string `json:"vendor_interface,omitempty"` + + // A string or UUID of the tenant who owns the baremetal node. + Owner string `json:"owner,omitempty"` +} + +// ToNodeCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create requests a node to be created +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToNodeCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, nil) + return +} + +type Patch interface { + ToNodeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is a slice of Patches used to update a node +type UpdateOpts []Patch + +type UpdateOp string + +const ( + ReplaceOp UpdateOp = "replace" + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" +) + +type UpdateOperation struct { + Op UpdateOp `json:"op" required:"true"` + Path string `json:"path" required:"true"` + Value interface{} `json:"value,omitempty"` +} + +func (opts UpdateOperation) ToNodeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update requests that a node be updated +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { + body := make([]map[string]interface{}, len(opts)) + for i, patch := range opts { + result, err := patch.ToNodeUpdateMap() + if err != nil { + r.Err = err + return + } + + body[i] = result + } + _, r.Err = client.Patch(updateURL(client, id), body, &r.Body, &gophercloud.RequestOpts{ + JSONBody: &body, + OkCodes: []int{200}, + }) + return +} + +// Delete requests that a node be removed +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Request that Ironic validate whether the Node’s driver has enough information to manage the Node. This polls each +// interface on the driver, and returns the status of that interface. +func Validate(client *gophercloud.ServiceClient, id string) (r ValidateResult) { + _, r.Err = client.Get(validateURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Inject NMI (Non-Masking Interrupts) for the given Node. This feature can be used for hardware diagnostics, and +// actual support depends on a driver. +func InjectNMI(client *gophercloud.ServiceClient, id string) (r InjectNMIResult) { + _, r.Err = client.Put(injectNMIURL(client, id), map[string]string{}, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +type BootDeviceOpts struct { + BootDevice string `json:"boot_device"` // e.g., 'pxe', 'disk', etc. + Persistent bool `json:"persistent"` // Whether this is one-time or not +} + +// BootDeviceOptsBuilder allows extensions to add additional parameters to the +// SetBootDevice request. +type BootDeviceOptsBuilder interface { + ToBootDeviceMap() (map[string]interface{}, error) +} + +// ToBootDeviceSetMap assembles a request body based on the contents of a BootDeviceOpts. +func (opts BootDeviceOpts) ToBootDeviceMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Set the boot device for the given Node, and set it persistently or for one-time boot. The exact behaviour +// of this depends on the hardware driver. +func SetBootDevice(client *gophercloud.ServiceClient, id string, bootDevice BootDeviceOptsBuilder) (r SetBootDeviceResult) { + reqBody, err := bootDevice.ToBootDeviceMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(bootDeviceURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Get the current boot device for the given Node. +func GetBootDevice(client *gophercloud.ServiceClient, id string) (r BootDeviceResult) { + _, r.Err = client.Get(bootDeviceURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Retrieve the acceptable set of supported boot devices for a specific Node. +func GetSupportedBootDevices(client *gophercloud.ServiceClient, id string) (r SupportedBootDeviceResult) { + _, r.Err = client.Get(supportedBootDeviceURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// A cleaning step has required keys ‘interface’ and ‘step’, and optional key ‘args’. If specified, +// the value for ‘args’ is a keyword variable argument dictionary that is passed to the cleaning step +// method. +type CleanStep struct { + Interface string `json:"interface" required:"true"` + Step string `json:"step" required:"true"` + Args map[string]string `json:"args,omitempty"` +} + +// ProvisionStateOptsBuilder allows extensions to add additional parameters to the +// ChangeProvisionState request. +type ProvisionStateOptsBuilder interface { + ToProvisionStateMap() (map[string]interface{}, error) +} + +// Starting with Ironic API version 1.56, a configdrive may be a JSON object with structured data. +// Prior to this version, it must be a base64-encoded, gzipped ISO9660 image. +type ConfigDrive struct { + MetaData map[string]interface{} `json:"meta_data,omitempty"` + NetworkData map[string]interface{} `json:"network_data,omitempty"` + UserData interface{} `json:"user_data,omitempty"` +} + +// ProvisionStateOpts for a request to change a node's provision state. A config drive should be base64-encoded +// gzipped ISO9660 image. +type ProvisionStateOpts struct { + Target TargetProvisionState `json:"target" required:"true"` + ConfigDrive interface{} `json:"configdrive,omitempty"` + CleanSteps []CleanStep `json:"clean_steps,omitempty"` + RescuePassword string `json:"rescue_password,omitempty"` +} + +// ToProvisionStateMap assembles a request body based on the contents of a CreateOpts. +func (opts ProvisionStateOpts) ToProvisionStateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Request a change to the Node’s provision state. Acceptable target states depend on the Node’s current provision +// state. More detailed documentation of the Ironic State Machine is available in the developer docs. +func ChangeProvisionState(client *gophercloud.ServiceClient, id string, opts ProvisionStateOptsBuilder) (r ChangeStateResult) { + reqBody, err := opts.ToProvisionStateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(provisionStateURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +type TargetPowerState string + +// TargetPowerState is used when changing the power state of a node. +const ( + PowerOn TargetPowerState = "power on" + PowerOff TargetPowerState = "power off" + Rebooting TargetPowerState = "rebooting" + SoftPowerOff TargetPowerState = "soft power off" + SoftRebooting TargetPowerState = "soft rebooting" +) + +// PowerStateOptsBuilder allows extensions to add additional parameters to the ChangePowerState request. +type PowerStateOptsBuilder interface { + ToPowerStateMap() (map[string]interface{}, error) +} + +// PowerStateOpts for a request to change a node's power state. +type PowerStateOpts struct { + Target TargetPowerState `json:"target" required:"true"` + Timeout int `json:"timeout,omitempty"` +} + +// ToPowerStateMap assembles a request body based on the contents of a PowerStateOpts. +func (opts PowerStateOpts) ToPowerStateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Request to change a Node's power state. +func ChangePowerState(client *gophercloud.ServiceClient, id string, opts PowerStateOptsBuilder) (r ChangePowerStateResult) { + reqBody, err := opts.ToPowerStateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(powerStateURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// This is the desired RAID configuration on the bare metal node. +type RAIDConfigOpts struct { + LogicalDisks []LogicalDisk `json:"logical_disks"` +} + +// RAIDConfigOptsBuilder allows extensions to modify a set RAID config request. +type RAIDConfigOptsBuilder interface { + ToRAIDConfigMap() (map[string]interface{}, error) +} + +// RAIDLevel type is used to specify the RAID level for a logical disk. +type RAIDLevel string + +const ( + RAID0 RAIDLevel = "0" + RAID1 RAIDLevel = "1" + RAID2 RAIDLevel = "2" + RAID5 RAIDLevel = "5" + RAID6 RAIDLevel = "6" + RAID10 RAIDLevel = "1+0" + RAID50 RAIDLevel = "5+0" + RAID60 RAIDLevel = "6+0" +) + +// DiskType is used to specify the disk type for a logical disk, e.g. hdd or ssd. +type DiskType string + +const ( + HDD DiskType = "hdd" + SSD DiskType = "ssd" +) + +// InterfaceType is used to specify the interface for a logical disk. +type InterfaceType string + +const ( + SATA DiskType = "sata" + SCSI DiskType = "scsi" + SAS DiskType = "sas" +) + +type LogicalDisk struct { + // Size (Integer) of the logical disk to be created in GiB. If unspecified, "MAX" will be used. + SizeGB *int `json:"size_gb"` + + // RAID level for the logical disk. + RAIDLevel RAIDLevel `json:"raid_level" required:"true"` + + // Name of the volume. Should be unique within the Node. If not specified, volume name will be auto-generated. + VolumeName string `json:"volume_name,omitempty"` + + // Set to true if this is the root volume. At most one logical disk can have this set to true. + IsRootVolume *bool `json:"is_root_volume,omitempty"` + + // Set to true if this logical disk can share physical disks with other logical disks. + SharePhysicalDisks *bool `json:"share_physical_disks,omitempty"` + + // If this is not specified, disk type will not be a criterion to find backing physical disks + DiskType DiskType `json:"disk_type,omitempty"` + + // If this is not specified, interface type will not be a criterion to find backing physical disks. + InterfaceType InterfaceType `json:"interface_type,omitempty"` + + // Integer, number of disks to use for the logical disk. Defaults to minimum number of disks required + // for the particular RAID level. + NumberOfPhysicalDisks int `json:"number_of_physical_disks,omitempty"` + + // The name of the controller as read by the RAID interface. + Controller string `json:"controller,omitempty"` + + // A list of physical disks to use as read by the RAID interface. + PhysicalDisks []string `json:"physical_disks,omitempty"` +} + +func (opts RAIDConfigOpts) ToRAIDConfigMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + for _, v := range body["logical_disks"].([]interface{}) { + if logicalDisk, ok := v.(map[string]interface{}); ok { + if logicalDisk["size_gb"] == nil { + logicalDisk["size_gb"] = "MAX" + } + } + } + + return body, nil +} + +// Request to change a Node's RAID config. +func SetRAIDConfig(client *gophercloud.ServiceClient, id string, raidConfigOptsBuilder RAIDConfigOptsBuilder) (r ChangeStateResult) { + reqBody, err := raidConfigOptsBuilder.ToRAIDConfigMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(raidConfigURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go new file mode 100644 index 000000000000..8d61b8d3bd4d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go @@ -0,0 +1,313 @@ +package nodes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type nodeResult struct { + gophercloud.Result +} + +// Extract interprets any nodeResult as a Node, if possible. +func (r nodeResult) Extract() (*Node, error) { + var s Node + err := r.ExtractInto(&s) + return &s, err +} + +// Extract interprets a BootDeviceResult as BootDeviceOpts, if possible. +func (r BootDeviceResult) Extract() (*BootDeviceOpts, error) { + var s BootDeviceOpts + err := r.ExtractInto(&s) + return &s, err +} + +// Extract interprets a SupportedBootDeviceResult as an array of supported boot devices, if possible. +func (r SupportedBootDeviceResult) Extract() ([]string, error) { + var s struct { + Devices []string `json:"supported_boot_devices"` + } + + err := r.ExtractInto(&s) + return s.Devices, err +} + +// Extract interprets a ValidateResult as NodeValidation, if possible. +func (r ValidateResult) Extract() (*NodeValidation, error) { + var s NodeValidation + err := r.ExtractInto(&s) + return &s, err +} + +func (r nodeResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractNodesInto(r pagination.Page, v interface{}) error { + return r.(NodePage).Result.ExtractIntoSlicePtr(v, "nodes") +} + +// Node represents a node in the OpenStack Bare Metal API. +type Node struct { + // UUID for the resource. + UUID string `json:"uuid"` + + // Identifier for the Node resource. May be undefined. Certain words are reserved. + Name string `json:"name"` + + // Current power state of this Node. Usually, “power on” or “power off”, but may be “None” + // if Ironic is unable to determine the power state (eg, due to hardware failure). + PowerState string `json:"power_state"` + + // A power state transition has been requested, this field represents the requested (ie, “target”) + // state either “power on”, “power off”, “rebooting”, “soft power off” or “soft rebooting”. + TargetPowerState string `json:"target_power_state"` + + // Current provisioning state of this Node. + ProvisionState string `json:"provision_state"` + + // A provisioning action has been requested, this field represents the requested (ie, “target”) state. Note + // that a Node may go through several states during its transition to this target state. For instance, when + // requesting an instance be deployed to an AVAILABLE Node, the Node may go through the following state + // change progression: AVAILABLE -> DEPLOYING -> DEPLOYWAIT -> DEPLOYING -> ACTIVE + TargetProvisionState string `json:"target_provision_state"` + + // Whether or not this Node is currently in “maintenance mode”. Setting a Node into maintenance mode removes it + // from the available resource pool and halts some internal automation. This can happen manually (eg, via an API + // request) or automatically when Ironic detects a hardware fault that prevents communication with the machine. + Maintenance bool `json:"maintenance"` + + // Description of the reason why this Node was placed into maintenance mode + MaintenanceReason string `json:"maintenance_reason"` + + // Fault indicates the active fault detected by ironic, typically the Node is in “maintenance mode”. None means no + // fault has been detected by ironic. “power failure” indicates ironic failed to retrieve power state from this + // node. There are other possible types, e.g., “clean failure” and “rescue abort failure”. + Fault string `json:"fault"` + + // Error from the most recent (last) transaction that started but failed to finish. + LastError string `json:"last_error"` + + // Name of an Ironic Conductor host which is holding a lock on this node, if a lock is held. Usually “null”, + // but this field can be useful for debugging. + Reservation string `json:"reservation"` + + // Name of the driver. + Driver string `json:"driver"` + + // The metadata required by the driver to manage this Node. List of fields varies between drivers, and can be + // retrieved from the /v1/drivers//properties resource. + DriverInfo map[string]interface{} `json:"driver_info"` + + // Metadata set and stored by the Node’s driver. This field is read-only. + DriverInternalInfo map[string]interface{} `json:"driver_internal_info"` + + // Characteristics of this Node. Populated by ironic-inspector during inspection. May be edited via the REST + // API at any time. + Properties map[string]interface{} `json:"properties"` + + // Used to customize the deployed image. May include root partition size, a base 64 encoded config drive, and other + // metadata. Note that this field is erased automatically when the instance is deleted (this is done by requesting + // the Node provision state be changed to DELETED). + InstanceInfo map[string]interface{} `json:"instance_info"` + + // ID of the Nova instance associated with this Node. + InstanceUUID string `json:"instance_uuid"` + + // ID of the chassis associated with this Node. May be empty or None. + ChassisUUID string `json:"chassis_uuid"` + + // Set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra"` + + // Whether console access is enabled or disabled on this node. + ConsoleEnabled bool `json:"console_enabled"` + + // The current RAID configuration of the node. Introduced with the cleaning feature. + RAIDConfig map[string]interface{} `json:"raid_config"` + + // The requested RAID configuration of the node, which will be applied when the Node next transitions + // through the CLEANING state. Introduced with the cleaning feature. + TargetRAIDConfig map[string]interface{} `json:"target_raid_config"` + + // Current clean step. Introduced with the cleaning feature. + CleanStep map[string]interface{} `json:"clean_step"` + + // Current deploy step. + DeployStep map[string]interface{} `json:"deploy_step"` + + // 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 + ResourceClass string `json:"resource_class"` + + // Boot interface for a Node, e.g. “pxe”. + BootInterface string `json:"boot_interface"` + + // Console interface for a node, e.g. “no-console”. + ConsoleInterface string `json:"console_interface"` + + // Deploy interface for a node, e.g. “iscsi”. + DeployInterface string `json:"deploy_interface"` + + // Interface used for node inspection, e.g. “no-inspect”. + InspectInterface string `json:"inspect_interface"` + + // For out-of-band node management, e.g. “ipmitool”. + ManagementInterface string `json:"management_interface"` + + // Network Interface provider to use when plumbing the network connections for this Node. + NetworkInterface string `json:"network_interface"` + + // used for performing power actions on the node, e.g. “ipmitool”. + PowerInterface string `json:"power_interface"` + + // Used for configuring RAID on this node, e.g. “no-raid”. + RAIDInterface string `json:"raid_interface"` + + // Interface used for node rescue, e.g. “no-rescue”. + RescueInterface string `json:"rescue_interface"` + + // Used for attaching and detaching volumes on this node, e.g. “cinder”. + StorageInterface string `json:"storage_interface"` + + // Array of traits for this node. + Traits []string `json:"traits"` + + // For vendor-specific functionality on this node, e.g. “no-vendor”. + VendorInterface string `json:"vendor_interface"` + + // Conductor group for a node. Case-insensitive string up to 255 characters, containing a-z, 0-9, _, -, and .. + ConductorGroup string `json:"conductor_group"` + + // The node is protected from undeploying, rebuilding and deletion. + Protected bool `json:"protected"` + + // Reason the node is marked as protected. + ProtectedReason string `json:"protected_reason"` + + // A string or UUID of the tenant who owns the baremetal node. + Owner string `json:"owner"` +} + +// NodePage abstracts the raw results of making a List() request against +// the API. As OpenStack extensions may freely alter the response bodies of +// structures returned to the client, you may only safely access the data +// provided through the ExtractNodes call. +type NodePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Node results. +func (r NodePage) IsEmpty() (bool, error) { + s, err := ExtractNodes(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r NodePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"nodes_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractNodes interprets the results of a single page from a List() call, +// producing a slice of Node entities. +func ExtractNodes(r pagination.Page) ([]Node, error) { + var s []Node + err := ExtractNodesInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Node. +type GetResult struct { + nodeResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + nodeResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Node. +type UpdateResult struct { + nodeResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ValidateResult is the response from a Validate operation. Call its Extract +// method to interpret it as a NodeValidation struct. +type ValidateResult struct { + gophercloud.Result +} + +// InjectNMIResult is the response from an InjectNMI operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type InjectNMIResult struct { + gophercloud.ErrResult +} + +// BootDeviceResult is the response from a GetBootDevice operation. Call its Extract +// method to interpret it as a BootDeviceOpts struct. +type BootDeviceResult struct { + gophercloud.Result +} + +// BootDeviceResult is the response from a GetBootDevice operation. Call its Extract +// method to interpret it as a BootDeviceOpts struct. +type SetBootDeviceResult struct { + gophercloud.ErrResult +} + +// SupportedBootDeviceResult is the response from a GetSupportedBootDevices operation. Call its Extract +// method to interpret it as an array of supported boot device values. +type SupportedBootDeviceResult struct { + gophercloud.Result +} + +// ChangePowerStateResult is the response from a ChangePowerState operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type ChangePowerStateResult struct { + gophercloud.ErrResult +} + +// Each element in the response will contain a “result” variable, which will have a value of “true” or “false”, and +// also potentially a reason. A value of nil indicates that the Node’s driver does not support that interface. +type DriverValidation struct { + Result bool `json:"result"` + Reason string `json:"reason"` +} + +// Ironic validates whether the Node’s driver has enough information to manage the Node. This polls each interface on +// the driver, and returns the status of that interface as an DriverValidation struct. +type NodeValidation struct { + Boot DriverValidation `json:"boot"` + Console DriverValidation `json:"console"` + Deploy DriverValidation `json:"deploy"` + Inspect DriverValidation `json:"inspect"` + Management DriverValidation `json:"management"` + Network DriverValidation `json:"network"` + Power DriverValidation `json:"power"` + RAID DriverValidation `json:"raid"` + Rescue DriverValidation `json:"rescue"` + Storage DriverValidation `json:"storage"` +} + +// ChangeStateResult is the response from any state change operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type ChangeStateResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/doc.go new file mode 100644 index 000000000000..df837788f24b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/doc.go @@ -0,0 +1,2 @@ +// nodes unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/fixtures.go new file mode 100644 index 000000000000..1717f7a58654 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/fixtures.go @@ -0,0 +1,1033 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// NodeListBody contains the canned body of a nodes.List response, without detail. +const NodeListBody = ` + { + "nodes": [ + { + "instance_uuid": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "name": "foo", + "power_state": null, + "provision_state": "enroll" + }, + { + "instance_uuid": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "name": "bar", + "power_state": null, + "provision_state": "enroll" + }, + { + "instance_uuid": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "name": "baz", + "power_state": null, + "provision_state": "enroll" + } + ] +} +` + +// NodeListDetailBody contains the canned body of a nodes.ListDetail response. +const NodeListDetailBody = ` + { + "nodes": [ + { + "bios_interface": "no-bios", + "boot_interface": "pxe", + "chassis_uuid": null, + "clean_step": {}, + "conductor_group": "", + "console_enabled": false, + "console_interface": "no-console", + "created_at": "2019-01-31T19:59:28+00:00", + "deploy_interface": "iscsi", + "deploy_step": {}, + "driver": "ipmi", + "driver_info": { + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin" + + }, + "driver_internal_info": {}, + "extra": {}, + "fault": null, + "inspect_interface": "no-inspect", + "inspection_finished_at": null, + "inspection_started_at": null, + "instance_info": {}, + "instance_uuid": null, + "last_error": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "maintenance_reason": null, + "management_interface": "ipmitool", + "name": "foo", + "network_interface": "flat", + "portgroups": [ + { + "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" + } + ], + "ports": [ + { + "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" + } + ], + "power_interface": "ipmitool", + "power_state": null, + "properties": {}, + "provision_state": "enroll", + "provision_updated_at": null, + "raid_config": {}, + "raid_interface": "no-raid", + "rescue_interface": "no-rescue", + "reservation": null, + "resource_class": null, + "states": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/states", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/states", + "rel": "bookmark" + } + ], + "storage_interface": "noop", + "target_power_state": null, + "target_provision_state": null, + "target_raid_config": {}, + "traits": [], + "updated_at": null, + "uuid": "d2630783-6ec8-4836-b556-ab427c4b581e", + "vendor_interface": "ipmitool", + "volume": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/volume", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/volume", + "rel": "bookmark" + } + ] + }, + { + "bios_interface": "no-bios", + "boot_interface": "pxe", + "chassis_uuid": null, + "clean_step": {}, + "conductor_group": "", + "console_enabled": false, + "console_interface": "no-console", + "created_at": "2019-01-31T19:59:29+00:00", + "deploy_interface": "iscsi", + "deploy_step": {}, + "driver": "ipmi", + "driver_info": {}, + "driver_internal_info": {}, + "extra": {}, + "fault": null, + "inspect_interface": "no-inspect", + "inspection_finished_at": null, + "inspection_started_at": null, + "instance_info": {}, + "instance_uuid": null, + "last_error": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "maintenance_reason": null, + "management_interface": "ipmitool", + "name": "bar", + "network_interface": "flat", + "portgroups": [ + { + "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" + } + ], + "ports": [ + { + "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" + } + ], + "power_interface": "ipmitool", + "power_state": null, + "properties": {}, + "provision_state": "enroll", + "provision_updated_at": null, + "raid_config": {}, + "raid_interface": "no-raid", + "rescue_interface": "no-rescue", + "reservation": null, + "resource_class": null, + "states": [ + { + "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" + } + ], + "storage_interface": "noop", + "target_power_state": null, + "target_provision_state": null, + "target_raid_config": {}, + "traits": [], + "updated_at": null, + "uuid": "08c84581-58f5-4ea2-a0c6-dd2e5d2b3662", + "vendor_interface": "ipmitool", + "volume": [ + { + "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" + } + ] + }, + { + "bios_interface": "no-bios", + "boot_interface": "pxe", + "chassis_uuid": null, + "clean_step": {}, + "conductor_group": "", + "console_enabled": false, + "console_interface": "no-console", + "created_at": "2019-01-31T19:59:30+00:00", + "deploy_interface": "iscsi", + "deploy_step": {}, + "driver": "ipmi", + "driver_info": {}, + "driver_internal_info": {}, + "extra": {}, + "fault": null, + "inspect_interface": "no-inspect", + "inspection_finished_at": null, + "inspection_started_at": null, + "instance_info": {}, + "instance_uuid": null, + "last_error": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "maintenance_reason": null, + "management_interface": "ipmitool", + "name": "baz", + "network_interface": "flat", + "portgroups": [ + { + "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" + } + ], + "ports": [ + { + "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" + } + ], + "power_interface": "ipmitool", + "power_state": null, + "properties": {}, + "provision_state": "enroll", + "provision_updated_at": null, + "raid_config": {}, + "raid_interface": "no-raid", + "rescue_interface": "no-rescue", + "reservation": null, + "resource_class": null, + "states": [ + { + "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" + } + ], + "storage_interface": "noop", + "target_power_state": null, + "target_provision_state": null, + "target_raid_config": {}, + "traits": [], + "updated_at": null, + "uuid": "c9afd385-5d89-4ecb-9e1c-68194da6b474", + "vendor_interface": "ipmitool", + "volume": [ + { + "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" + } + ] + } + ] +} +` + +// SingleNodeBody is the canned body of a Get request on an existing node. +const SingleNodeBody = ` +{ + "bios_interface": "no-bios", + "boot_interface": "pxe", + "chassis_uuid": null, + "clean_step": {}, + "conductor_group": "", + "console_enabled": false, + "console_interface": "no-console", + "created_at": "2019-01-31T19:59:28+00:00", + "deploy_interface": "iscsi", + "deploy_step": {}, + "driver": "ipmi", + "driver_info": { + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin" + }, + "driver_internal_info": {}, + "extra": {}, + "fault": null, + "inspect_interface": "no-inspect", + "inspection_finished_at": null, + "inspection_started_at": null, + "instance_info": {}, + "instance_uuid": null, + "last_error": null, + "links": [ + { + "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" + } + ], + "maintenance": false, + "maintenance_reason": null, + "management_interface": "ipmitool", + "name": "foo", + "network_interface": "flat", + "portgroups": [ + { + "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" + } + ], + "ports": [ + { + "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" + } + ], + "power_interface": "ipmitool", + "power_state": null, + "properties": {}, + "provision_state": "enroll", + "provision_updated_at": null, + "raid_config": {}, + "raid_interface": "no-raid", + "rescue_interface": "no-rescue", + "reservation": null, + "resource_class": null, + "states": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/states", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/states", + "rel": "bookmark" + } + ], + "storage_interface": "noop", + "target_power_state": null, + "target_provision_state": null, + "target_raid_config": {}, + "traits": [], + "updated_at": null, + "uuid": "d2630783-6ec8-4836-b556-ab427c4b581e", + "vendor_interface": "ipmitool", + "volume": [ + { + "href": "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/volume", + "rel": "self" + }, + { + "href": "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/volume", + "rel": "bookmark" + } + ] +} +` + +const NodeValidationBody = ` +{ + "bios": { + "reason": "Driver ipmi does not support bios (disabled or not implemented).", + "result": false + }, + "boot": { + "reason": "Cannot validate image information for node a62b8495-52e2-407b-b3cb-62775d04c2b8 because one or more parameters are missing from its instance_info and insufficent information is present to boot from a remote volume. Missing are: ['ramdisk', 'kernel', 'image_source']", + "result": false + }, + "console": { + "reason": "Driver ipmi does not support console (disabled or not implemented).", + "result": false + }, + "deploy": { + "reason": "Cannot validate image information for node a62b8495-52e2-407b-b3cb-62775d04c2b8 because one or more parameters are missing from its instance_info and insufficent information is present to boot from a remote volume. Missing are: ['ramdisk', 'kernel', 'image_source']", + "result": false + }, + "inspect": { + "reason": "Driver ipmi does not support inspect (disabled or not implemented).", + "result": false + }, + "management": { + "result": true + }, + "network": { + "result": true + }, + "power": { + "result": true + }, + "raid": { + "reason": "Driver ipmi does not support raid (disabled or not implemented).", + "result": false + }, + "rescue": { + "reason": "Driver ipmi does not support rescue (disabled or not implemented).", + "result": false + }, + "storage": { + "result": true + } +} +` + +const NodeBootDeviceBody = ` +{ + "boot_device":"pxe", + "persistent":false +} +` + +const NodeSupportedBootDeviceBody = ` +{ + "supported_boot_devices": [ + "pxe", + "disk" + ] +} +` + +const NodeProvisionStateActiveBody = ` +{ + "target": "active", + "configdrive": "http://127.0.0.1/images/test-node-config-drive.iso.gz" +} +` +const NodeProvisionStateCleanBody = ` +{ + "target": "clean", + "clean_steps": [ + { + "interface": "deploy", + "step": "upgrade_firmware", + "args": { + "force": "True" + } + } + ] +} +` + +const NodeProvisionStateConfigDriveBody = ` +{ + "target": "active", + "configdrive": { + "user_data": { + "ignition": { + "version": "2.2.0" + }, + "systemd": { + "units": [ + { + "enabled": true, + "name": "example.service" + } + ] + } + } + } +} +` + +var ( + NodeFoo = nodes.Node{ + UUID: "d2630783-6ec8-4836-b556-ab427c4b581e", + Name: "foo", + PowerState: "", + TargetPowerState: "", + ProvisionState: "enroll", + TargetProvisionState: "", + Maintenance: false, + MaintenanceReason: "", + Fault: "", + LastError: "", + Reservation: "", + Driver: "ipmi", + DriverInfo: map[string]interface{}{ + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin", + }, + DriverInternalInfo: map[string]interface{}{}, + Properties: map[string]interface{}{}, + InstanceInfo: map[string]interface{}{}, + InstanceUUID: "", + ChassisUUID: "", + Extra: map[string]interface{}{}, + ConsoleEnabled: false, + RAIDConfig: map[string]interface{}{}, + TargetRAIDConfig: map[string]interface{}{}, + CleanStep: map[string]interface{}{}, + DeployStep: map[string]interface{}{}, + ResourceClass: "", + BootInterface: "pxe", + ConsoleInterface: "no-console", + DeployInterface: "iscsi", + InspectInterface: "no-inspect", + ManagementInterface: "ipmitool", + NetworkInterface: "flat", + PowerInterface: "ipmitool", + RAIDInterface: "no-raid", + RescueInterface: "no-rescue", + StorageInterface: "noop", + Traits: []string{}, + VendorInterface: "ipmitool", + ConductorGroup: "", + Protected: false, + ProtectedReason: "", + } + + NodeFooValidation = nodes.NodeValidation{ + Boot: nodes.DriverValidation{ + Result: false, + Reason: "Cannot validate image information for node a62b8495-52e2-407b-b3cb-62775d04c2b8 because one or more parameters are missing from its instance_info and insufficent information is present to boot from a remote volume. Missing are: ['ramdisk', 'kernel', 'image_source']", + }, + Console: nodes.DriverValidation{ + Result: false, + Reason: "Driver ipmi does not support console (disabled or not implemented).", + }, + Deploy: nodes.DriverValidation{ + Result: false, + Reason: "Cannot validate image information for node a62b8495-52e2-407b-b3cb-62775d04c2b8 because one or more parameters are missing from its instance_info and insufficent information is present to boot from a remote volume. Missing are: ['ramdisk', 'kernel', 'image_source']", + }, + Inspect: nodes.DriverValidation{ + Result: false, + Reason: "Driver ipmi does not support inspect (disabled or not implemented).", + }, + Management: nodes.DriverValidation{ + Result: true, + }, + Network: nodes.DriverValidation{ + Result: true, + }, + Power: nodes.DriverValidation{ + Result: true, + }, + RAID: nodes.DriverValidation{ + Result: false, + Reason: "Driver ipmi does not support raid (disabled or not implemented).", + }, + Rescue: nodes.DriverValidation{ + Result: false, + Reason: "Driver ipmi does not support rescue (disabled or not implemented).", + }, + Storage: nodes.DriverValidation{ + Result: true, + }, + } + + NodeBootDevice = nodes.BootDeviceOpts{ + BootDevice: "pxe", + Persistent: false, + } + + NodeSupportedBootDevice = []string{ + "pxe", + "disk", + } + + NodeBar = nodes.Node{ + UUID: "08c84581-58f5-4ea2-a0c6-dd2e5d2b3662", + Name: "bar", + PowerState: "", + TargetPowerState: "", + ProvisionState: "enroll", + TargetProvisionState: "", + Maintenance: false, + MaintenanceReason: "", + Fault: "", + LastError: "", + Reservation: "", + Driver: "ipmi", + DriverInfo: map[string]interface{}{}, + DriverInternalInfo: map[string]interface{}{}, + Properties: map[string]interface{}{}, + InstanceInfo: map[string]interface{}{}, + InstanceUUID: "", + ChassisUUID: "", + Extra: map[string]interface{}{}, + ConsoleEnabled: false, + RAIDConfig: map[string]interface{}{}, + TargetRAIDConfig: map[string]interface{}{}, + CleanStep: map[string]interface{}{}, + DeployStep: map[string]interface{}{}, + ResourceClass: "", + BootInterface: "pxe", + ConsoleInterface: "no-console", + DeployInterface: "iscsi", + InspectInterface: "no-inspect", + ManagementInterface: "ipmitool", + NetworkInterface: "flat", + PowerInterface: "ipmitool", + RAIDInterface: "no-raid", + RescueInterface: "no-rescue", + StorageInterface: "noop", + Traits: []string{}, + VendorInterface: "ipmitool", + ConductorGroup: "", + Protected: false, + ProtectedReason: "", + } + + NodeBaz = nodes.Node{ + UUID: "c9afd385-5d89-4ecb-9e1c-68194da6b474", + Name: "baz", + PowerState: "", + TargetPowerState: "", + ProvisionState: "enroll", + TargetProvisionState: "", + Maintenance: false, + MaintenanceReason: "", + Fault: "", + LastError: "", + Reservation: "", + Driver: "ipmi", + DriverInfo: map[string]interface{}{}, + DriverInternalInfo: map[string]interface{}{}, + Properties: map[string]interface{}{}, + InstanceInfo: map[string]interface{}{}, + InstanceUUID: "", + ChassisUUID: "", + Extra: map[string]interface{}{}, + ConsoleEnabled: false, + RAIDConfig: map[string]interface{}{}, + TargetRAIDConfig: map[string]interface{}{}, + CleanStep: map[string]interface{}{}, + DeployStep: map[string]interface{}{}, + ResourceClass: "", + BootInterface: "pxe", + ConsoleInterface: "no-console", + DeployInterface: "iscsi", + InspectInterface: "no-inspect", + ManagementInterface: "ipmitool", + NetworkInterface: "flat", + PowerInterface: "ipmitool", + RAIDInterface: "no-raid", + RescueInterface: "no-rescue", + StorageInterface: "noop", + Traits: []string{}, + VendorInterface: "ipmitool", + ConductorGroup: "", + Protected: false, + ProtectedReason: "", + } + + ConfigDriveMap = nodes.ConfigDrive{ + UserData: map[string]interface{}{ + "ignition": map[string]string{ + "version": "2.2.0", + }, + "systemd": map[string]interface{}{ + "units": []map[string]interface{}{{ + "name": "example.service", + "enabled": true, + }, + }, + }, + }, + } +) + +// HandleNodeListSuccessfully sets up the test server to respond to a server List request. +func HandleNodeListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, NodeListBody) + + case "9e5476bd-a4ec-4653-93d6-72c93aa682ba": + fmt.Fprintf(w, `{ "servers": [] }`) + default: + t.Fatalf("/nodes invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleNodeListSuccessfully sets up the test server to respond to a server List request. +func HandleNodeListDetailSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + fmt.Fprintf(w, NodeListDetailBody) + }) +} + +// HandleServerCreationSuccessfully sets up the test server to respond to a server creation request +// with a given response. +func HandleNodeCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "boot_interface": "pxe", + "driver": "ipmi", + "driver_info": { + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_address": "192.168.122.1", + "ipmi_password": "admin", + "ipmi_port": "6230", + "ipmi_username": "admin" + }, + "name": "foo" + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleNodeDeletionSuccessfully sets up the test server to respond to a server deletion request. +func HandleNodeDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleNodeGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleNodeBody) + }) +} + +func HandleNodeUpdateSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/nodes/1234asdf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `[{"op": "replace", "path": "/properties", "value": {"root_gb": 25}}]`) + + fmt.Fprintf(w, response) + }) +} + +func HandleNodeValidateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/validate", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, NodeValidationBody) + }) +} + +// HandleInjectNMISuccessfully sets up the test server to respond to a node InjectNMI request +func HandleInjectNMISuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/management/inject_nmi", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, "{}") + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleSetBootDeviceSuccessfully sets up the test server to respond to a set boot device request for a node +func HandleSetBootDeviceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/management/boot_device", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, NodeBootDeviceBody) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleGetBootDeviceSuccessfully sets up the test server to respond to a get boot device request for a node +func HandleGetBootDeviceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/management/boot_device", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, NodeBootDeviceBody) + }) +} + +// HandleGetBootDeviceSuccessfully sets up the test server to respond to a get boot device request for a node +func HandleGetSupportedBootDeviceSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/management/boot_device/supported", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, NodeSupportedBootDeviceBody) + }) +} + +func HandleNodeChangeProvisionStateActive(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, NodeProvisionStateActiveBody) + w.WriteHeader(http.StatusAccepted) + }) +} + +func HandleNodeChangeProvisionStateClean(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, NodeProvisionStateCleanBody) + w.WriteHeader(http.StatusAccepted) + }) +} + +func HandleNodeChangeProvisionStateCleanWithConflict(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, NodeProvisionStateCleanBody) + w.WriteHeader(http.StatusConflict) + }) +} + +func HandleNodeChangeProvisionStateConfigDrive(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, NodeProvisionStateConfigDriveBody) + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleChangePowerStateSuccessfully sets up the test server to respond to a change power state request for a node +func HandleChangePowerStateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "target": "power on", + "timeout": 100 + }`) + + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleChangePowerStateWithConflict sets up the test server to respond to a change power state request for a node with a 409 error +func HandleChangePowerStateWithConflict(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "target": "power on", + "timeout": 100 + }`) + + w.WriteHeader(http.StatusConflict) + }) +} + +func HandleSetRAIDConfig(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/raid", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` + { + "logical_disks" : [ + { + "size_gb" : 100, + "is_root_volume" : true, + "raid_level" : "1" + } + ] + } + `) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleSetRAIDConfigMaxSize(t *testing.T) { + th.Mux.HandleFunc("/nodes/1234asdf/states/raid", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` + { + "logical_disks" : [ + { + "size_gb" : "MAX", + "is_root_volume" : true, + "raid_level" : "1" + } + ] + } + `) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/requests_test.go new file mode 100644 index 000000000000..20c578895de6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/testing/requests_test.go @@ -0,0 +1,432 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListDetailNodes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeListDetailSuccessfully(t) + + pages := 0 + err := nodes.ListDetail(client.ServiceClient(), nodes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + if len(actual) != 3 { + t.Fatalf("Expected 3 nodes, got %d", len(actual)) + } + th.CheckDeepEquals(t, NodeFoo, actual[0]) + th.CheckDeepEquals(t, NodeBar, actual[1]) + th.CheckDeepEquals(t, NodeBaz, actual[2]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListNodes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeListSuccessfully(t) + + pages := 0 + err := nodes.List(client.ServiceClient(), nodes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + if len(actual) != 3 { + t.Fatalf("Expected 3 nodes, got %d", len(actual)) + } + th.AssertEquals(t, "foo", actual[0].Name) + th.AssertEquals(t, "bar", actual[1].Name) + th.AssertEquals(t, "baz", actual[2].Name) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListOpts(t *testing.T) { + // Detail cannot take Fields + opts := nodes.ListOpts{ + Fields: []string{"name", "uuid"}, + } + + _, err := opts.ToNodeListDetailQuery() + th.AssertEquals(t, err.Error(), "fields is not a valid option when getting a detailed listing of nodes") + + // Regular ListOpts can + query, err := opts.ToNodeListQuery() + th.AssertEquals(t, query, "?fields=name&fields=uuid") + th.AssertNoErr(t, err) +} + +func TestCreateNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeCreationSuccessfully(t, SingleNodeBody) + + actual, err := nodes.Create(client.ServiceClient(), nodes.CreateOpts{ + Name: "foo", + Driver: "ipmi", + BootInterface: "pxe", + DriverInfo: map[string]interface{}{ + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin", + }, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, NodeFoo, *actual) +} + +func TestDeleteNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeDeletionSuccessfully(t) + + res := nodes.Delete(client.ServiceClient(), "asdfasdfasdf") + th.AssertNoErr(t, res.Err) +} + +func TestGetNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeGetSuccessfully(t) + + c := client.ServiceClient() + actual, err := nodes.Get(c, "1234asdf").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, NodeFoo, *actual) +} + +func TestUpdateNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeUpdateSuccessfully(t, SingleNodeBody) + + c := client.ServiceClient() + actual, err := nodes.Update(c, "1234asdf", nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.ReplaceOp, + Path: "/properties", + Value: map[string]interface{}{ + "root_gb": 25, + }, + }, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, NodeFoo, *actual) +} + +func TestUpdateRequiredOp(t *testing.T) { + c := client.ServiceClient() + _, err := nodes.Update(c, "1234asdf", nodes.UpdateOpts{ + nodes.UpdateOperation{ + Path: "/driver", + Value: "new-driver", + }, + }).Extract() + + if _, ok := err.(gophercloud.ErrMissingInput); !ok { + t.Fatal("ErrMissingInput was expected to occur") + } + +} + +func TestUpdateRequiredPath(t *testing.T) { + c := client.ServiceClient() + _, err := nodes.Update(c, "1234asdf", nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.ReplaceOp, + Value: "new-driver", + }, + }).Extract() + + if _, ok := err.(gophercloud.ErrMissingInput); !ok { + t.Fatal("ErrMissingInput was expected to occur") + } +} + +func TestValidateNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeValidateSuccessfully(t) + + c := client.ServiceClient() + actual, err := nodes.Validate(c, "1234asdf").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, NodeFooValidation, *actual) +} + +func TestInjectNMI(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleInjectNMISuccessfully(t) + + c := client.ServiceClient() + err := nodes.InjectNMI(c, "1234asdf").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestSetBootDevice(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleSetBootDeviceSuccessfully(t) + + c := client.ServiceClient() + err := nodes.SetBootDevice(c, "1234asdf", nodes.BootDeviceOpts{ + BootDevice: "pxe", + Persistent: false, + }).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetBootDevice(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetBootDeviceSuccessfully(t) + + c := client.ServiceClient() + bootDevice, err := nodes.GetBootDevice(c, "1234asdf").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, NodeBootDevice, *bootDevice) +} + +func TestGetSupportedBootDevices(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSupportedBootDeviceSuccessfully(t) + + c := client.ServiceClient() + bootDevices, err := nodes.GetSupportedBootDevices(c, "1234asdf").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, NodeSupportedBootDevice, bootDevices) +} + +func TestNodeChangeProvisionStateActive(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeChangeProvisionStateActive(t) + + c := client.ServiceClient() + err := nodes.ChangeProvisionState(c, "1234asdf", nodes.ProvisionStateOpts{ + Target: nodes.TargetActive, + ConfigDrive: "http://127.0.0.1/images/test-node-config-drive.iso.gz", + }).ExtractErr() + + th.AssertNoErr(t, err) +} + +func TestHandleNodeChangeProvisionStateConfigDrive(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleNodeChangeProvisionStateConfigDrive(t) + + c := client.ServiceClient() + + err := nodes.ChangeProvisionState(c, "1234asdf", nodes.ProvisionStateOpts{ + Target: nodes.TargetActive, + ConfigDrive: ConfigDriveMap, + }).ExtractErr() + + th.AssertNoErr(t, err) +} + +func TestNodeChangeProvisionStateClean(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeChangeProvisionStateClean(t) + + c := client.ServiceClient() + err := nodes.ChangeProvisionState(c, "1234asdf", nodes.ProvisionStateOpts{ + Target: nodes.TargetClean, + CleanSteps: []nodes.CleanStep{ + { + Interface: "deploy", + Step: "upgrade_firmware", + Args: map[string]string{ + "force": "True", + }, + }, + }, + }).ExtractErr() + + th.AssertNoErr(t, err) +} + +func TestNodeChangeProvisionStateCleanWithConflict(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleNodeChangeProvisionStateCleanWithConflict(t) + + c := client.ServiceClient() + err := nodes.ChangeProvisionState(c, "1234asdf", nodes.ProvisionStateOpts{ + Target: nodes.TargetClean, + CleanSteps: []nodes.CleanStep{ + { + Interface: "deploy", + Step: "upgrade_firmware", + Args: map[string]string{ + "force": "True", + }, + }, + }, + }).ExtractErr() + + if _, ok := err.(gophercloud.ErrDefault409); !ok { + t.Fatal("ErrDefault409 was expected to occur") + } +} + +func TestCleanStepRequiresInterface(t *testing.T) { + c := client.ServiceClient() + err := nodes.ChangeProvisionState(c, "1234asdf", nodes.ProvisionStateOpts{ + Target: nodes.TargetClean, + CleanSteps: []nodes.CleanStep{ + { + Step: "upgrade_firmware", + Args: map[string]string{ + "force": "True", + }, + }, + }, + }).ExtractErr() + + if _, ok := err.(gophercloud.ErrMissingInput); !ok { + t.Fatal("ErrMissingInput was expected to occur") + } +} + +func TestCleanStepRequiresStep(t *testing.T) { + c := client.ServiceClient() + err := nodes.ChangeProvisionState(c, "1234asdf", nodes.ProvisionStateOpts{ + Target: nodes.TargetClean, + CleanSteps: []nodes.CleanStep{ + { + Interface: "deploy", + Args: map[string]string{ + "force": "True", + }, + }, + }, + }).ExtractErr() + + if _, ok := err.(gophercloud.ErrMissingInput); !ok { + t.Fatal("ErrMissingInput was expected to occur") + } +} + +func TestChangePowerState(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleChangePowerStateSuccessfully(t) + + opts := nodes.PowerStateOpts{ + Target: nodes.PowerOn, + Timeout: 100, + } + + c := client.ServiceClient() + err := nodes.ChangePowerState(c, "1234asdf", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestChangePowerStateWithConflict(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleChangePowerStateWithConflict(t) + + opts := nodes.PowerStateOpts{ + Target: nodes.PowerOn, + Timeout: 100, + } + + c := client.ServiceClient() + err := nodes.ChangePowerState(c, "1234asdf", opts).ExtractErr() + if _, ok := err.(gophercloud.ErrDefault409); !ok { + t.Fatal("ErrDefault409 was expected to occur") + } +} + +func TestSetRAIDConfig(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleSetRAIDConfig(t) + + sizeGB := 100 + isRootVolume := true + + config := nodes.RAIDConfigOpts{ + LogicalDisks: []nodes.LogicalDisk{ + { + SizeGB: &sizeGB, + IsRootVolume: &isRootVolume, + RAIDLevel: nodes.RAID1, + }, + }, + } + + c := client.ServiceClient() + err := nodes.SetRAIDConfig(c, "1234asdf", config).ExtractErr() + th.AssertNoErr(t, err) +} + +// Without specifying a size, we need to send a string: "MAX" +func TestSetRAIDConfigMaxSize(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleSetRAIDConfigMaxSize(t) + + isRootVolume := true + + config := nodes.RAIDConfigOpts{ + LogicalDisks: []nodes.LogicalDisk{ + { + IsRootVolume: &isRootVolume, + RAIDLevel: nodes.RAID1, + }, + }, + } + + c := client.ServiceClient() + err := nodes.SetRAIDConfig(c, "1234asdf", config).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go new file mode 100644 index 000000000000..c7ef5503655b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go @@ -0,0 +1,59 @@ +package nodes + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("nodes") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("nodes", "detail") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return deleteURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return deleteURL(client, id) +} + +func validateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "validate") +} + +func injectNMIURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "management", "inject_nmi") +} + +func bootDeviceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "management", "boot_device") +} + +func supportedBootDeviceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "management", "boot_device", "supported") +} + +func statesResourceURL(client *gophercloud.ServiceClient, id string, state string) string { + return client.ServiceURL("nodes", id, "states", state) +} + +func powerStateURL(client *gophercloud.ServiceClient, id string) string { + return statesResourceURL(client, id, "power") +} + +func provisionStateURL(client *gophercloud.ServiceClient, id string) string { + return statesResourceURL(client, id, "provision") +} + +func raidConfigURL(client *gophercloud.ServiceClient, id string) string { + return statesResourceURL(client, id, "raid") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go new file mode 100644 index 000000000000..eb0579bed57c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go @@ -0,0 +1,85 @@ +/* + Package ports contains the functionality to Listing, Searching, Creating, Updating, + and Deleting of bare metal Port resources + + API reference: https://developer.openstack.org/api-ref/baremetal/#ports-ports + + +Example to List Ports with Detail + + ports.ListDetail(client, nil).EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + for _, n := range portList { + // Do something + } + + return true, nil + }) + +Example to List Ports + + listOpts := ports.ListOpts{ + Limit: 10, + } + + ports.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + for _, n := range portList { + // Do something + } + + return true, nil + }) + +Example to Create a Port + + createOpts := ports.CreateOpts{ + NodeUUID: "e8920409-e07e-41bb-8cc1-72acb103e2dd", + Address: "00:1B:63:84:45:E6", + PhysicalNetwork: "my-network", + } + + createPort, err := ports.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Port + + showPort, err := ports.Get(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").Extract() + if err != nil { + panic(err) + } + +Example to Update a Port + + updateOpts := ports.UpdateOpts{ + ports.UpdateOperation{ + Op: ReplaceOp, + Path: "/address", + Value: "22:22:22:22:22:22", + }, + } + + updatePort, err := ports.Update(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474", updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Port + + err = ports.Delete(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").ExtractErr() + if err != nil { + panic(err) + } + +*/ +package ports diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go new file mode 100644 index 000000000000..a5da3f834786 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go @@ -0,0 +1,216 @@ +package ports + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPortListQuery() (string, error) + ToPortListDetailQuery() (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 node attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + // Filter the list by the name or uuid of the Node + Node string `q:"node"` + + // Filter the list by the Node uuid + NodeUUID string `q:"node_uuid"` + + // Filter the list with the specified Portgroup (name or UUID) + PortGroup string `q:"portgroup"` + + // Filter the list with the specified physical hardware address, typically MAC + Address string `q:"address"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item + Marker string `q:"marker"` + + // Sorts the response by the requested sort direction. + // Valid value is asc (ascending) or desc (descending). Default is asc. + SortDir string `q:"sort_dir"` + + // Sorts the response by the this attribute value. Default is id. + SortKey string `q:"sort_key"` +} + +// ToPortListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPortListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list ports accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToPortListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return PortPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ToPortListDetailQuery formats a ListOpts into a query string for the list details API. +func (opts ListOpts) ToPortListDetailQuery() (string, error) { + // Detail endpoint can't filter by Fields + if len(opts.Fields) > 0 { + return "", fmt.Errorf("fields is not a valid option when getting a detailed listing of ports") + } + + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail - Return a list ports with complete details. +// Some filtering is possible by passing in flags in "ListOpts", +// but you cannot limit by the fields returned. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToPortListDetailQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return PortPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get - requests the details off a port, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPortCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies port creation parameters. +type CreateOpts struct { + // UUID of the Node this resource belongs to. + NodeUUID string `json:"node_uuid,omitempty"` + + // Physical hardware address of this network Port, + // typically the hardware MAC address. + Address string `json:"address,omitempty"` + + // UUID of the Portgroup this resource belongs to. + PortGroupUUID string `json:"portgroup_uuid,omitempty"` + + // The Port binding profile. If specified, must contain switch_id (only a MAC + // address or an OpenFlow based datapath_id of the switch are accepted in this + // field) and port_id (identifier of the physical port on the switch to which + // node’s port is connected to) fields. switch_info is an optional string + // field to be used to store any vendor-specific information. + LocalLinkConnection map[string]interface{} `json:"local_link_connection,omitempty"` + + // Indicates whether PXE is enabled or disabled on the Port. + PXEEnabled *bool `json:"pxe_enabled,omitempty"` + + // The name of the physical network to which a port is connected. May be empty. + PhysicalNetwork string `json:"physical_network,omitempty"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra,omitempty"` + + // Indicates whether the Port is a Smart NIC port. + IsSmartNIC *bool `json:"is_smartnic,omitempty"` +} + +// ToPortCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create - requests the creation of a port +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToPortCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, nil) + return +} + +// TODO Update +type Patch interface { + ToPortUpdateMap() map[string]interface{} +} + +// UpdateOpts is a slice of Patches used to update a port +type UpdateOpts []Patch + +type UpdateOp string + +const ( + ReplaceOp UpdateOp = "replace" + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" +) + +type UpdateOperation struct { + Op UpdateOp `json:"op,required"` + Path string `json:"path,required"` + Value interface{} `json:"value,omitempty"` +} + +func (opts UpdateOperation) ToPortUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "op": opts.Op, + "path": opts.Path, + "value": opts.Value, + } +} + +// Update - requests the update of a port +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { + body := make([]map[string]interface{}, len(opts)) + for i, patch := range opts { + body[i] = patch.ToPortUpdateMap() + } + + _, r.Err = client.Patch(updateURL(client, id), body, &r.Body, &gophercloud.RequestOpts{ + JSONBody: &body, + OkCodes: []int{200}, + }) + return +} + +// Delete - requests the deletion of a port +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go new file mode 100644 index 000000000000..506b6c64a7eb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go @@ -0,0 +1,131 @@ +package ports + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type portResult struct { + gophercloud.Result +} + +func (r portResult) Extract() (*Port, error) { + var s Port + err := r.ExtractInto(&s) + return &s, err +} + +func (r portResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractPortsInto(r pagination.Page, v interface{}) error { + return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") +} + +// Port represents a port in the OpenStack Bare Metal API. +type Port struct { + // UUID for the resource. + UUID string `json:"uuid"` + + // Physical hardware address of this network Port, + // typically the hardware MAC address. + Address string `json:"address"` + + // UUID of the Node this resource belongs to. + NodeUUID string `json:"node_uuid"` + + // UUID of the Portgroup this resource belongs to. + PortGroupUUID string `json:"portgroup_uuid"` + + // The Port binding profile. If specified, must contain switch_id (only a MAC + // address or an OpenFlow based datapath_id of the switch are accepted in this + // field) and port_id (identifier of the physical port on the switch to which + // node’s port is connected to) fields. switch_info is an optional string + // field to be used to store any vendor-specific information. + LocalLinkConnection map[string]interface{} `json:"local_link_connection"` + + // Indicates whether PXE is enabled or disabled on the Port. + PXEEnabled bool `json:"pxe_enabled"` + + // The name of the physical network to which a port is connected. + // May be empty. + PhysicalNetwork string `json:"physical_network"` + + // Internal metadata set and stored by the Port. This field is read-only. + InternalInfo map[string]interface{} `json:"internal_info"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. + // May be “null”. + UpdatedAt time.Time `json:"updated_at"` + + // A list of relative links. Includes the self and bookmark links. + Links []interface{} `json:"links"` + + // Indicates whether the Port is a Smart NIC port. + IsSmartNIC bool `json:"is_smartnic"` +} + +// PortPage abstracts the raw results of making a List() request against +// the API. +type PortPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Port results. +func (r PortPage) IsEmpty() (bool, error) { + s, err := ExtractPorts(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r PortPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"ports_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractPorts interprets the results of a single page from a List() call, +// producing a slice of Port entities. +func ExtractPorts(r pagination.Page) ([]Port, error) { + var s []Port + err := ExtractPortsInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Port. +type GetResult struct { + portResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + portResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Port. +type UpdateResult struct { + portResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/doc.go new file mode 100644 index 000000000000..bf82f4eb0d5d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/doc.go @@ -0,0 +1,2 @@ +// ports unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/fixtures.go new file mode 100644 index 000000000000..dafeed845ba6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/fixtures.go @@ -0,0 +1,252 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// PortListBody contains the canned body of a ports.List response, without detail. +const PortListBody = ` +{ + "ports": [ + { + "uuid": "3abe3f36-9708-4e9f-b07e-0f898061d3a7", + "links": [ + { + "href": "http://192.168.0.8/baremetal/v1/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", + "rel": "self" + }, + { + "href": "http://192.168.0.8/baremetal/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", + "rel": "bookmark" + } + ], + "address": "52:54:00:0a:af:d1" + }, + { + "uuid": "f2845e11-dbd4-4728-a8c0-30d19f48924a", + "links": [ + { + "href": "http://192.168.0.8/baremetal/v1/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", + "rel": "self" + }, + { + "href": "http://192.168.0.8/baremetal/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", + "rel": "bookmark" + } + ], + "address": "52:54:00:4d:87:e6" + } + ] +} +` + +// PortListDetailBody contains the canned body of a port.ListDetail response. +const PortListDetailBody = ` +{ + "ports": [ + { + "local_link_connection": {}, + "node_uuid": "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + "uuid": "3abe3f36-9708-4e9f-b07e-0f898061d3a7", + "links": [ + { + "href": "http://192.168.0.8/baremetal/v1/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", + "rel": "self" + }, + { + "href": "http://192.168.0.8/baremetal/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", + "rel": "bookmark" + } + ], + "extra": {}, + "pxe_enabled": true, + "portgroup_uuid": null, + "updated_at": "2019-02-15T09:55:19+00:00", + "physical_network": null, + "address": "52:54:00:0a:af:d1", + "internal_info": { + + }, + "created_at": "2019-02-15T09:52:23+00:00" + }, + { + "local_link_connection": {}, + "node_uuid": "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + "uuid": "f2845e11-dbd4-4728-a8c0-30d19f48924a", + "links": [ + { + "href": "http://192.168.0.8/baremetal/v1/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", + "rel": "self" + }, + { + "href": "http://192.168.0.8/baremetal/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", + "rel": "bookmark" + } + ], + "extra": {}, + "pxe_enabled": true, + "portgroup_uuid": null, + "updated_at": "2019-02-15T09:55:19+00:00", + "physical_network": null, + "address": "52:54:00:4d:87:e6", + "internal_info": {}, + "created_at": "2019-02-15T09:52:24+00:00" + } + ] +} +` + +// SinglePortBody is the canned body of a Get request on an existing port. +const SinglePortBody = ` +{ + "local_link_connection": { + + }, + "node_uuid": "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + "uuid": "f2845e11-dbd4-4728-a8c0-30d19f48924a", + "links": [ + { + "href": "http://192.168.0.8/baremetal/v1/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", + "rel": "self" + }, + { + "href": "http://192.168.0.8/baremetal/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", + "rel": "bookmark" + } + ], + "extra": { + + }, + "pxe_enabled": true, + "portgroup_uuid": null, + "updated_at": "2019-02-15T09:55:19+00:00", + "physical_network": null, + "address": "52:54:00:4d:87:e6", + "internal_info": { + + }, + "created_at": "2019-02-15T09:52:24+00:00" +} +` + +var ( + fooCreated, _ = time.Parse(time.RFC3339, "2019-02-15T09:52:24+00:00") + fooUpdated, _ = time.Parse(time.RFC3339, "2019-02-15T09:55:19+00:00") + BarCreated, _ = time.Parse(time.RFC3339, "2019-02-15T09:52:23+00:00") + BarUpdated, _ = time.Parse(time.RFC3339, "2019-02-15T09:55:19+00:00") + PortFoo = ports.Port{ + UUID: "f2845e11-dbd4-4728-a8c0-30d19f48924a", + NodeUUID: "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + Address: "52:54:00:4d:87:e6", + PXEEnabled: true, + LocalLinkConnection: map[string]interface{}{}, + InternalInfo: map[string]interface{}{}, + Extra: map[string]interface{}{}, + CreatedAt: fooCreated, + UpdatedAt: fooUpdated, + Links: []interface{}{map[string]interface{}{"href": "http://192.168.0.8/baremetal/v1/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", "rel": "self"}, map[string]interface{}{"href": "http://192.168.0.8/baremetal/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", "rel": "bookmark"}}, + } + + PortBar = ports.Port{ + UUID: "3abe3f36-9708-4e9f-b07e-0f898061d3a7", + NodeUUID: "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + Address: "52:54:00:0a:af:d1", + PXEEnabled: true, + LocalLinkConnection: map[string]interface{}{}, + InternalInfo: map[string]interface{}{}, + Extra: map[string]interface{}{}, + CreatedAt: BarCreated, + UpdatedAt: BarUpdated, + Links: []interface{}{map[string]interface{}{"href": "http://192.168.0.8/baremetal/v1/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", "rel": "self"}, map[string]interface{}{"rel": "bookmark", "href": "http://192.168.0.8/baremetal/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7"}}, + } +) + +// HandlePortListSuccessfully sets up the test server to respond to a port List request. +func HandlePortListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, PortListBody) + + case "f2845e11-dbd4-4728-a8c0-30d19f48924a": + fmt.Fprintf(w, `{ "ports": [] }`) + default: + t.Fatalf("/ports invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandlePortListSuccessfully sets up the test server to respond to a port List request. +func HandlePortListDetailSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/ports/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + fmt.Fprintf(w, PortListDetailBody) + }) +} + +// HandleSPortCreationSuccessfully sets up the test server to respond to a port creation request +// with a given response. +func HandlePortCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "node_uuid": "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + "address": "52:54:00:4d:87:e6", + "pxe_enabled": true + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandlePortDeletionSuccessfully sets up the test server to respond to a port deletion request. +func HandlePortDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandlePortGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SinglePortBody) + }) +} + +func HandlePortUpdateSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `[{"op": "replace", "path": "/address", "value": "22:22:22:22:22:22"}]`) + + fmt.Fprintf(w, response) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/requests_test.go new file mode 100644 index 000000000000..cf9519b53d3c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/testing/requests_test.go @@ -0,0 +1,144 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListDetailPorts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePortListDetailSuccessfully(t) + + pages := 0 + err := ports.ListDetail(client.ServiceClient(), ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 ports, got %d", len(actual)) + } + th.CheckDeepEquals(t, PortBar, actual[0]) + th.CheckDeepEquals(t, PortFoo, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListPorts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePortListSuccessfully(t) + + pages := 0 + err := ports.List(client.ServiceClient(), ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 ports, got %d", len(actual)) + } + th.AssertEquals(t, "3abe3f36-9708-4e9f-b07e-0f898061d3a7", actual[0].UUID) + th.AssertEquals(t, "f2845e11-dbd4-4728-a8c0-30d19f48924a", actual[1].UUID) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListOpts(t *testing.T) { + // Detail cannot take Fields + opts := ports.ListOpts{ + Fields: []string{"uuid", "address"}, + } + + _, err := opts.ToPortListDetailQuery() + th.AssertEquals(t, err.Error(), "fields is not a valid option when getting a detailed listing of ports") + + // Regular ListOpts can + query, err := opts.ToPortListQuery() + th.AssertEquals(t, query, "?fields=uuid&fields=address") + th.AssertNoErr(t, err) +} + +func TestCreatePort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePortCreationSuccessfully(t, SinglePortBody) + + iTrue := true + actual, err := ports.Create(client.ServiceClient(), ports.CreateOpts{ + NodeUUID: "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", + Address: "52:54:00:4d:87:e6", + PXEEnabled: &iTrue, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, PortFoo, *actual) +} + +func TestDeletePort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePortDeletionSuccessfully(t) + + res := ports.Delete(client.ServiceClient(), "3abe3f36-9708-4e9f-b07e-0f898061d3a7") + th.AssertNoErr(t, res.Err) +} + +func TestGetPort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePortGetSuccessfully(t) + + c := client.ServiceClient() + actual, err := ports.Get(c, "f2845e11-dbd4-4728-a8c0-30d19f48924a").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, PortFoo, *actual) +} + +func TestUpdatePort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePortUpdateSuccessfully(t, SinglePortBody) + + c := client.ServiceClient() + actual, err := ports.Update(c, "f2845e11-dbd4-4728-a8c0-30d19f48924a", ports.UpdateOpts{ + ports.UpdateOperation{ + Op: ports.ReplaceOp, + Path: "/address", + Value: "22:22:22:22:22:22", + }, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, PortFoo, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go new file mode 100644 index 000000000000..436c95415036 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go @@ -0,0 +1,31 @@ +package ports + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("ports") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("ports", "detail") +} + +func resourceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("ports", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go new file mode 100644 index 000000000000..014180c28b4d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go @@ -0,0 +1,15 @@ +/* +Package noauth provides support for noauth bare metal introspection endpoints. + +Example of obtaining and using a client: + + client, err := noauth.NewBareMetalIntrospectionNoAuth(noauth.EndpointOpts{ + IronicInspectorEndpoint: "http://localhost:5050/v1/", + }) + if err != nil { + panic(err) + } + + introspection.GetIntrospectionStatus(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8") +*/ +package noauth diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go new file mode 100644 index 000000000000..a1c0857c96ce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go @@ -0,0 +1,36 @@ +package noauth + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// EndpointOpts specifies a "noauth" Ironic Inspector Endpoint. +type EndpointOpts struct { + // IronicInspectorEndpoint [required] is currently only used with "noauth" Ironic introspection. + // An Ironic inspector endpoint with "auth_strategy=noauth" is necessary, for example: + // http://ironic.example.com:5050/v1. + IronicInspectorEndpoint string +} + +func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + if eo.IronicInspectorEndpoint == "" { + return nil, fmt.Errorf("IronicInspectorEndpoint is required") + } + + sc.Endpoint = gophercloud.NormalizeURL(eo.IronicInspectorEndpoint) + sc.ProviderClient = client + return sc, nil +} + +// NewBareMetalIntrospectionNoAuth creates a ServiceClient that may be used to access a +// "noauth" bare metal introspection service. +func NewBareMetalIntrospectionNoAuth(eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(&gophercloud.ProviderClient{}, eo) + + sc.Type = "baremetal-inspector" + + return sc, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/testing/requests_test.go new file mode 100644 index 000000000000..33f3d7e4edbb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/testing/requests_test.go @@ -0,0 +1,16 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNoAuth(t *testing.T) { + noauthClient, err := noauth.NewBareMetalIntrospectionNoAuth(noauth.EndpointOpts{ + IronicInspectorEndpoint: "http://ironic:5050/v1", + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, "", noauthClient.TokenID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go new file mode 100644 index 000000000000..423823869455 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go @@ -0,0 +1,59 @@ +/* +Package introspection contains the functionality for Starting introspection, +Get introspection status, List all introspection statuses, Abort an +introspection, Get stored introspection data and reapply introspection on +stored data. + +API reference https://developer.openstack.org/api-ref/baremetal-introspection/#node-introspection + +Example to Start Introspection + + err := introspection.StartIntrospection(client, NodeUUID, introspection.StartOpts{}).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get an Introspection status + + _, err := introspection.GetIntrospectionStatus(client, NodeUUID).Extract() + if err != nil { + panic(err) + } + +Example to List all introspection statuses + + introspection.ListIntrospections(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + introspectionsList, err := introspection.ExtractIntrospections(page) + if err != nil { + return false, err + } + + for _, n := range introspectionsList { + // Do something + } + + return true, nil + }) + +Example to Abort an Introspection + + err := introspection.AbortIntrospection(client, NodeUUID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get stored Introspection Data + + v, err := introspection.GetIntrospectionData(c, NodeUUID).Extract() + if err != nil { + panic(err) + } + +Example to apply Introspection Data + + err := introspection.ApplyIntrospectionData(c, NodeUUID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package introspection diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go new file mode 100644 index 000000000000..a2d02ccde7d1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go @@ -0,0 +1,116 @@ +package introspection + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListIntrospectionsOptsBuilder allows extensions to add additional parameters to the +// ListIntrospections request. +type ListIntrospectionsOptsBuilder interface { + ToIntrospectionsListQuery() (string, error) +} + +// ListIntrospectionsOpts allows the filtering and sorting of paginated collections through +// the Introspection API. Filtering is achieved by passing in struct field values that map to +// the node attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListIntrospectionsOpts struct { + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item. + Marker string `q:"marker"` +} + +// ToIntrospectionsListQuery formats a ListIntrospectionsOpts into a query string. +func (opts ListIntrospectionsOpts) ToIntrospectionsListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListIntrospections makes a request against the Inspector API to list the current introspections. +func ListIntrospections(client *gophercloud.ServiceClient, opts ListIntrospectionsOptsBuilder) pagination.Pager { + url := listIntrospectionsURL(client) + if opts != nil { + query, err := opts.ToIntrospectionsListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + var rpage = IntrospectionPage{pagination.LinkedPageBase{PageResult: r}} + return rpage + }) +} + +// GetIntrospectionStatus makes a request against the Inspector API to get the +// status of a single introspection. +func GetIntrospectionStatus(client *gophercloud.ServiceClient, nodeID string) (r GetIntrospectionStatusResult) { + _, r.Err = client.Get(introspectionURL(client, nodeID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// StartOptsBuilder allows extensions to add additional parameters to the +// Start request. +type StartOptsBuilder interface { + ToStartIntrospectionQuery() (string, error) +} + +// StartOpts represents options to start an introspection. +type StartOpts struct { + // Whether the current installation of ironic-inspector can manage PXE booting of nodes. + ManageBoot *bool `q:"manage_boot"` +} + +// ToStartIntrospectionQuery converts a StartOpts into a request. +func (opts StartOpts) ToStartIntrospectionQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// StartIntrospection initiate hardware introspection for node NodeID . +// All power management configuration for this node needs to be done prior to calling the endpoint. +func StartIntrospection(client *gophercloud.ServiceClient, nodeID string, opts StartOptsBuilder) (r StartResult) { + _, err := opts.ToStartIntrospectionQuery() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(introspectionURL(client, nodeID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// AbortIntrospection abort running introspection. +func AbortIntrospection(client *gophercloud.ServiceClient, nodeID string) (r AbortResult) { + _, r.Err = client.Post(abortIntrospectionURL(client, nodeID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// GetIntrospectionData return stored data from successful introspection. +func GetIntrospectionData(client *gophercloud.ServiceClient, nodeID string) (r DataResult) { + _, r.Err = client.Get(introspectionDataURL(client, nodeID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ReApplyIntrospection triggers introspection on stored unprocessed data. +// No data is allowed to be sent along with the request. +func ReApplyIntrospection(client *gophercloud.ServiceClient, nodeID string) (r ApplyDataResult) { + _, r.Err = client.Post(introspectionUnprocessedDataURL(client, nodeID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go new file mode 100644 index 000000000000..5cb35e999a04 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go @@ -0,0 +1,293 @@ +package introspection + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type introspectionResult struct { + gophercloud.Result +} + +// Extract interprets any introspectionResult as an Introspection, if possible. +func (r introspectionResult) Extract() (*Introspection, error) { + var s Introspection + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractInto will extract a response body into an Introspection struct. +func (r introspectionResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +// ExtractIntrospectionsInto will extract a collection of introspectResult pages into a +// slice of Introspection entities. +func ExtractIntrospectionsInto(r pagination.Page, v interface{}) error { + return r.(IntrospectionPage).Result.ExtractIntoSlicePtr(v, "introspection") +} + +// ExtractIntrospections interprets the results of a single page from a +// ListIntrospections() call, producing a slice of Introspection entities. +func ExtractIntrospections(r pagination.Page) ([]Introspection, error) { + var s []Introspection + err := ExtractIntrospectionsInto(r, &s) + return s, err +} + +// IntrospectionPage abstracts the raw results of making a ListIntrospections() +// request against the Inspector API. As OpenStack extensions may freely alter +// the response bodies of structures returned to the client, you may only safely +// access the data provided through the ExtractIntrospections call. +type IntrospectionPage struct { + pagination.LinkedPageBase +} + +// Introspection represents an introspection in the OpenStack Bare Metal Introspector API. +type Introspection struct { + // Whether introspection is finished + Finished bool `json:"finished"` + + // State of the introspection + State string `json:"state"` + + // Error message, can be null; "Canceled by operator" in case introspection was aborted + Error string `json:"error"` + + // UUID of the introspection + UUID string `json:"uuid"` + + // UTC ISO8601 timestamp + StartedAt time.Time `json:"-"` + + // UTC ISO8601 timestamp or null + FinishedAt time.Time `json:"-"` + + // Link to the introspection URL + Links []interface{} `json:"links"` +} + +// IsEmpty returns true if a page contains no Introspection results. +func (r IntrospectionPage) IsEmpty() (bool, error) { + s, err := ExtractIntrospections(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r IntrospectionPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"introspection_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// UnmarshalJSON trie to convert values for started_at and finished_at from the +// json response into RFC3339 standard. Since Introspection API can remove the +// Z from the format, if the conversion fails, it falls back to an RFC3339 +// with no Z format supported by gophercloud. +func (r *Introspection) UnmarshalJSON(b []byte) error { + type tmp Introspection + var s struct { + tmp + StartedAt string `json:"started_at"` + FinishedAt string `json:"finished_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Introspection(s.tmp) + + if s.StartedAt != "" { + t, err := time.Parse(time.RFC3339, s.StartedAt) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.StartedAt) + if err != nil { + return err + } + } + r.StartedAt = t + } + + if s.FinishedAt != "" { + t, err := time.Parse(time.RFC3339, s.FinishedAt) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.FinishedAt) + if err != nil { + return err + } + } + r.FinishedAt = t + } + + return nil +} + +// GetIntrospectionStatusResult is the response from a GetIntrospectionStatus operation. +// Call its Extract method to interpret it as an Introspection. +type GetIntrospectionStatusResult struct { + introspectionResult +} + +// StartResult is the response from a StartIntrospection operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type StartResult struct { + gophercloud.ErrResult +} + +// AbortResult is the response from an AbortIntrospection operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type AbortResult struct { + gophercloud.ErrResult +} + +// Data represents the full introspection data collected. +// The format and contents of the stored data depends on the ramdisk used +// and plugins enabled both in the ramdisk and in inspector itself. +// This structure has been provided for basic compatibility but it +// will need extensions +type Data struct { + AllInterfaces map[string]BaseInterfaceType `json:"all_interfaces"` + BootInterface string `json:"boot_interface"` + CPUArch string `json:"cpu_arch"` + CPUs int `json:"cpus"` + Error string `json:"error"` + Interfaces map[string]BaseInterfaceType `json:"interfaces"` + Inventory InventoryType `json:"inventory"` + IPMIAddress string `json:"ipmi_address"` + LocalGB int `json:"local_gb"` + MACs []string `json:"macs"` + MemoryMB int `json:"memory_mb"` + RootDisk RootDiskType `json:"root_disk"` +} + +// Sub Types defined under Data and deeper in the structure + +type BaseInterfaceType struct { + ClientID string `json:"client_id"` + IP string `json:"ip"` + MAC string `json:"mac"` + PXE bool `json:"pxe"` + LLDPProcessed map[string]interface{} `json:"lldp_processed"` +} + +type BootInfoType struct { + CurrentBootMode string `json:"current_boot_mode"` + PXEInterface string `json:"pxe_interface"` +} + +type CPUType struct { + Architecture string `json:"architecture"` + Count int `json:"count"` + Flags []string `json:"flags"` + Frequency string `json:"frequency"` + ModelName string `json:"model_name"` +} + +type LLDPTLVType struct { + Type int + Value string +} + +type InterfaceType struct { + BIOSDevName string `json:"biosdevname"` + ClientID string `json:"client_id"` + HasCarrier bool `json:"has_carrier"` + IPV4Address string `json:"ipv4_address"` + IPV6Address string `json:"ipv6_address"` + LLDP []LLDPTLVType `json:"lldp"` + MACAddress string `json:"mac_address"` + Name string `json:"name"` + Product string `json:"product"` + Vendor string `json:"vendor"` +} + +type InventoryType struct { + BmcAddress string `json:"bmc_address"` + Boot BootInfoType `json:"boot"` + CPU CPUType `json:"cpu"` + Disks []RootDiskType `json:"disks"` + Interfaces []InterfaceType `json:"interfaces"` + Memory MemoryType `json:"memory"` + SystemVendor SystemVendorType `json:"system_vendor"` +} + +type MemoryType struct { + PhysicalMb int `json:"physical_mb"` + Total int `json:"total"` +} + +type RootDiskType struct { + Hctl string `json:"hctl"` + Model string `json:"model"` + Name string `json:"name"` + Rotational bool `json:"rotational"` + Serial string `json:"serial"` + Size int `json:"size"` + Vendor string `json:"vendor"` + Wwn string `json:"wwn"` + WwnVendorExtension string `json:"wwn_vendor_extension"` + WwnWithExtension string `json:"wwn_with_extension"` +} + +type SystemVendorType struct { + Manufacturer string `json:"manufacturer"` + ProductName string `json:"product_name"` + SerialNumber string `json:"serial_number"` +} + +// UnmarshalJSON interprets an LLDP TLV [key, value] pair as an LLDPTLVType structure +func (r *LLDPTLVType) UnmarshalJSON(data []byte) error { + var list []interface{} + if err := json.Unmarshal(data, &list); err != nil { + return err + } + + if len(list) != 2 { + return fmt.Errorf("Invalid LLDP TLV key-value pair") + } + + fieldtype, ok := list[0].(float64) + if !ok { + return fmt.Errorf("LLDP TLV key is not number") + } + + value, ok := list[1].(string) + if !ok { + return fmt.Errorf("LLDP TLV value is not string") + } + + r.Type = int(fieldtype) + r.Value = value + return nil +} + +// Extract interprets any IntrospectionDataResult as IntrospectionData, if possible. +func (r DataResult) Extract() (*Data, error) { + var s Data + err := r.ExtractInto(&s) + return &s, err +} + +// DataResult represents the response from a GetIntrospectionData operation. +// Call its Extract method to interpret it as a Data. +type DataResult struct { + gophercloud.Result +} + +// ApplyDataResult is the response from an ApplyData operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type ApplyDataResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go new file mode 100644 index 000000000000..58cb166781f8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go @@ -0,0 +1,396 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// IntrospectionListBody contains the canned body of a introspection.IntrospectionList response. +const IntrospectionListBody = ` +{ + "introspection": [ + { + "error": null, + "finished": true, + "finished_at": "2017-08-17T11:36:16", + "links": [ + { + "href": "http://127.0.0.1:5050/v1/introspection/05ccda19-581b-49bf-8f5a-6ded99701d87", + "rel": "self" + } + ], + "started_at": "2017-08-17T11:33:43", + "state": "finished", + "uuid": "05ccda19-581b-49bf-8f5a-6ded99701d87" + }, + { + "error": null, + "finished": true, + "finished_at": "2017-08-16T12:24:30", + "links": [ + { + "href": "http://127.0.0.1:5050/v1/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", + "rel": "self" + } + ], + "started_at": "2017-08-16T12:22:01", + "state": "finished", + "uuid": "c244557e-899f-46fa-a1ff-5b2c6718616b" + } + ] +} +` + +// IntrospectionStatus contains the respnse of a single introspection satus. +const IntrospectionStatus = ` +{ + "error": null, + "finished": true, + "finished_at": "2017-08-16T12:24:30", + "links": [ + { + "href": "http://127.0.0.1:5050/v1/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", + "rel": "self" + } + ], + "started_at": "2017-08-16T12:22:01", + "state": "finished", + "uuid": "c244557e-899f-46fa-a1ff-5b2c6718616b" +} +` + +// IntrospectionDataJSONSample contains sample data reported by the introspection process. +const IntrospectionDataJSONSample = ` +{ + "cpu_arch":"x86_64", + "macs":[ + "52:54:00:4e:3d:30" + ], + "root_disk":{ + "rotational":true, + "vendor":"0x1af4", + "name":"/dev/vda", + "hctl":null, + "wwn_vendor_extension":null, + "wwn_with_extension":null, + "model":"", + "wwn":null, + "serial":null, + "size":13958643712 + }, + "interfaces": { + "eth0": { + "ip":"172.24.42.100", + "mac":"52:54:00:4e:3d:30", + "pxe":true, + "client_id":null + } + }, + "cpus":2, + "boot_interface":"52:54:00:4e:3d:30", + "memory_mb":2048, + "ipmi_address":"192.167.2.134", + "inventory":{ + "bmc_address":"192.167.2.134", + "interfaces":[ + { + "lldp":[], + "product":"0x0001", + "vendor":"0x1af4", + "name":"eth1", + "has_carrier":true, + "ipv4_address":"172.24.42.101", + "client_id":null, + "mac_address":"52:54:00:47:20:4d" + }, + { + "lldp": [ + [1, "04112233aabbcc"], + [5, "737730312d646973742d31622d623132"] + ], + "product":"0x0001", + "vendor":"0x1af4", + "name":"eth0", + "has_carrier":true, + "ipv4_address":"172.24.42.100", + "client_id":null, + "mac_address":"52:54:00:4e:3d:30" + } + ], + "disks":[ + { + "rotational":true, + "vendor":"0x1af4", + "name":"/dev/vda", + "hctl":null, + "wwn_vendor_extension":null, + "wwn_with_extension":null, + "model":"", + "wwn":null, + "serial":null, + "size":13958643712 + } + ], + "boot":{ + "current_boot_mode":"bios", + "pxe_interface":"52:54:00:4e:3d:30" + }, + "system_vendor":{ + "serial_number":"Not Specified", + "product_name":"Bochs", + "manufacturer":"Bochs" + }, + "memory":{ + "physical_mb":2048, + "total":2105864192 + }, + "cpu":{ + "count":2, + "frequency":"2100.084", + "flags": [ + "fpu", + "mmx", + "fxsr", + "sse", + "sse2" + ], + "architecture":"x86_64" + } + }, + "error":null, + "local_gb":12, + "all_interfaces":{ + "eth1":{ + "ip":"172.24.42.101", + "mac":"52:54:00:47:20:4d", + "pxe":false, + "client_id":null + }, + "eth0":{ + "ip":"172.24.42.100", + "mac":"52:54:00:4e:3d:30", + "pxe":true, + "client_id":null, + "lldp_processed":{ + "switch_chassis_id":"11:22:33:aa:bb:cc", + "switch_system_name":"sw01-dist-1b-b12" + } + } + } +} +` + +var ( + fooTimeStarted, _ = time.Parse(gophercloud.RFC3339NoZ, "2017-08-17T11:33:43") + fooTimeFinished, _ = time.Parse(gophercloud.RFC3339NoZ, "2017-08-17T11:36:16") + IntrospectionFoo = introspection.Introspection{ + Finished: true, + State: "finished", + Error: "", + UUID: "05ccda19-581b-49bf-8f5a-6ded99701d87", + StartedAt: fooTimeStarted, + FinishedAt: fooTimeFinished, + Links: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:5050/v1/introspection/05ccda19-581b-49bf-8f5a-6ded99701d87", + "rel": "self", + }, + }, + } + + barTimeStarted, _ = time.Parse(gophercloud.RFC3339NoZ, "2017-08-16T12:22:01") + barTimeFinished, _ = time.Parse(gophercloud.RFC3339NoZ, "2017-08-16T12:24:30") + IntrospectionBar = introspection.Introspection{ + Finished: true, + State: "finished", + Error: "", + UUID: "c244557e-899f-46fa-a1ff-5b2c6718616b", + StartedAt: barTimeStarted, + FinishedAt: barTimeFinished, + Links: []interface{}{ + map[string]interface{}{ + "href": "http://127.0.0.1:5050/v1/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", + "rel": "self", + }, + }, + } + + IntrospectionDataRes = introspection.Data{ + CPUArch: "x86_64", + MACs: []string{"52:54:00:4e:3d:30"}, + RootDisk: introspection.RootDiskType{ + Rotational: true, + Model: "", + Name: "/dev/vda", + Size: 13958643712, + Vendor: "0x1af4", + }, + Interfaces: map[string]introspection.BaseInterfaceType{ + "eth0": { + IP: "172.24.42.100", + MAC: "52:54:00:4e:3d:30", + PXE: true, + }, + }, + CPUs: 2, + BootInterface: "52:54:00:4e:3d:30", + MemoryMB: 2048, + IPMIAddress: "192.167.2.134", + Inventory: introspection.InventoryType{ + SystemVendor: introspection.SystemVendorType{ + Manufacturer: "Bochs", + ProductName: "Bochs", + SerialNumber: "Not Specified", + }, + BmcAddress: "192.167.2.134", + Boot: introspection.BootInfoType{ + CurrentBootMode: "bios", + PXEInterface: "52:54:00:4e:3d:30", + }, + CPU: introspection.CPUType{ + Count: 2, + Flags: []string{"fpu", "mmx", "fxsr", "sse", "sse2"}, + Frequency: "2100.084", + Architecture: "x86_64", + }, + Disks: []introspection.RootDiskType{ + introspection.RootDiskType{ + Rotational: true, + Model: "", + Name: "/dev/vda", + Size: 13958643712, + Vendor: "0x1af4", + }, + }, + Interfaces: []introspection.InterfaceType{ + introspection.InterfaceType{ + Vendor: "0x1af4", + HasCarrier: true, + MACAddress: "52:54:00:47:20:4d", + Name: "eth1", + Product: "0x0001", + IPV4Address: "172.24.42.101", + LLDP: []introspection.LLDPTLVType{}, + }, + introspection.InterfaceType{ + IPV4Address: "172.24.42.100", + MACAddress: "52:54:00:4e:3d:30", + Name: "eth0", + Product: "0x0001", + HasCarrier: true, + Vendor: "0x1af4", + LLDP: []introspection.LLDPTLVType{ + introspection.LLDPTLVType{ + Type: 1, + Value: "04112233aabbcc", + }, + introspection.LLDPTLVType{ + Type: 5, + Value: "737730312d646973742d31622d623132", + }, + }, + }, + }, + Memory: introspection.MemoryType{ + PhysicalMb: 2048.0, + Total: 2.105864192e+09, + }, + }, + Error: "", + LocalGB: 12, + AllInterfaces: map[string]introspection.BaseInterfaceType{ + "eth1": { + IP: "172.24.42.101", + MAC: "52:54:00:47:20:4d", + PXE: false, + }, + "eth0": { + IP: "172.24.42.100", + MAC: "52:54:00:4e:3d:30", + PXE: true, + LLDPProcessed: map[string]interface{}{ + "switch_chassis_id": "11:22:33:aa:bb:cc", + "switch_system_name": "sw01-dist-1b-b12", + }, + }, + }, + } +) + +// HandleListIntrospectionsSuccessfully sets up the test server to respond to a server ListIntrospections request. +func HandleListIntrospectionsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/introspection", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + + marker := r.Form.Get("marker") + + switch marker { + case "": + fmt.Fprintf(w, IntrospectionListBody) + + case "c244557e-899f-46fa-a1ff-5b2c6718616b": + fmt.Fprintf(w, `{ "introspection": [] }`) + + default: + t.Fatalf("/introspection invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleGetIntrospectionStatusSuccessfully sets up the test server to respond to a GetIntrospectionStatus request. +func HandleGetIntrospectionStatusSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleStartIntrospectionSuccessfully sets up the test server to respond to a StartIntrospection request. +func HandleStartIntrospectionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleAbortIntrospectionSuccessfully sets up the test server to respond to an AbortIntrospection request. +func HandleAbortIntrospectionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/abort", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} + +// HandleGetIntrospectionDataSuccessfully sets up the test server to respond to a GetIntrospectionData request. +func HandleGetIntrospectionDataSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/data", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, IntrospectionDataJSONSample) + }) +} + +// HandleReApplyIntrospectionSuccessfully sets up the test server to respond to a ReApplyIntrospection request. +func HandleReApplyIntrospectionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/data/unprocessed", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go new file mode 100644 index 000000000000..d27ee30a51de --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go @@ -0,0 +1,98 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListIntrospections(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListIntrospectionsSuccessfully(t) + + pages := 0 + err := introspection.ListIntrospections(client.ServiceClient(), introspection.ListIntrospectionsOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := introspection.ExtractIntrospections(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 introspections, got %d", len(actual)) + } + th.CheckDeepEquals(t, IntrospectionFoo, actual[0]) + th.CheckDeepEquals(t, IntrospectionBar, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestGetIntrospectionStatus(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetIntrospectionStatusSuccessfully(t) + + c := client.ServiceClient() + actual, err := introspection.GetIntrospectionStatus(c, "c244557e-899f-46fa-a1ff-5b2c6718616b").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, IntrospectionBar, *actual) +} + +func TestStartIntrospection(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleStartIntrospectionSuccessfully(t) + + c := client.ServiceClient() + err := introspection.StartIntrospection(c, "c244557e-899f-46fa-a1ff-5b2c6718616b", introspection.StartOpts{}).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestAbortIntrospection(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAbortIntrospectionSuccessfully(t) + + c := client.ServiceClient() + err := introspection.AbortIntrospection(c, "c244557e-899f-46fa-a1ff-5b2c6718616b").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetIntrospectionData(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetIntrospectionDataSuccessfully(t) + + c := client.ServiceClient() + actual, err := introspection.GetIntrospectionData(c, "c244557e-899f-46fa-a1ff-5b2c6718616b").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, IntrospectionDataRes, *actual) +} + +func TestReApplyIntrospection(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleReApplyIntrospectionSuccessfully(t) + + c := client.ServiceClient() + err := introspection.ReApplyIntrospection(c, "c244557e-899f-46fa-a1ff-5b2c6718616b").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/results_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/results_test.go new file mode 100644 index 000000000000..87383ed6a151 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/testing/results_test.go @@ -0,0 +1,29 @@ +package testing + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" +) + +func TestLLDPTLVErrors(t *testing.T) { + badInputs := []string{ + "[1]", + "[1, 2]", + "[\"foo\", \"bar\"]", + } + + for _, input := range badInputs { + var output introspection.LLDPTLVType + err := json.Unmarshal([]byte(input), &output) + if err == nil { + t.Errorf("No JSON parse error for invalid LLDP TLV %s", input) + } + if !strings.Contains(err.Error(), "LLDP TLV") { + t.Errorf("Unexpected JSON parse error \"%s\" for invalid LLDP TLV %s", + err, input) + } + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go new file mode 100644 index 000000000000..e480613749a0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go @@ -0,0 +1,23 @@ +package introspection + +import "github.com/gophercloud/gophercloud" + +func listIntrospectionsURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("introspection") +} + +func introspectionURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID) +} + +func abortIntrospectionURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID, "abort") +} + +func introspectionDataURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID, "data") +} + +func introspectionUnprocessedDataURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID, "data", "unprocessed") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/doc.go new file mode 100644 index 000000000000..0897f639bd39 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/doc.go @@ -0,0 +1,61 @@ +/* +Package backups provides information and interaction with backups in the +OpenStack Block Storage service. A backup is a point in time copy of the +data contained in an external storage volume, and can be controlled +programmatically. + +Example to List Backups + + listOpts := backups.ListOpts{ + VolumeID: "uuid", + } + + allPages, err := backups.List(client, listOpts).AllPages() + if err != nil { + panic(err) + } + + allBackups, err := backups.ExtractBackups(allPages) + if err != nil { + panic(err) + } + + for _, backup := range allBackups { + fmt.Println(backup) + } + +Example to Create a Backup + + createOpts := backups.CreateOpts{ + VolumeID: "uuid", + Name: "my-backup", + } + + backup, err := backups.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println(backup) + +Example to Update a Backup + + updateOpts := backups.UpdateOpts{ + Name: "new-name", + } + + backup, err := backups.Update(client, "uuid", updateOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println(backup) + +Example to Delete a Backup + + err := backups.Delete(client, "uuid").ExtractErr() + if err != nil { + panic(err) + } +*/ +package backups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/microversions.go new file mode 100644 index 000000000000..4c12690c581b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/microversions.go @@ -0,0 +1,21 @@ +package backups + +// ExtractMetadata will extract the metadata of a backup. +// This requires the client to be set to microversion 3.43 or later. +func (r commonResult) ExtractMetadata() (map[string]string, error) { + var s struct { + Metadata map[string]string `json:"metadata"` + } + err := r.ExtractInto(&s) + return s.Metadata, err +} + +// ExtractAvailaiblityZone will extract the availability zone of a backup. +// This requires the client to be set to microversion 3.51 or later. +func (r commonResult) ExtractAvailabilityZone() (string, error) { + var s struct { + AvailabilityZone string `json:"availability_zone"` + } + err := r.ExtractInto(&s) + return s.AvailabilityZone, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/requests.go new file mode 100644 index 000000000000..a312f1daa01d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/requests.go @@ -0,0 +1,184 @@ +package backups + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToBackupCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a Backup. This object is passed to +// the backups.Create function. For more information about these parameters, +// see the Backup object. +type CreateOpts struct { + // VolumeID is the ID of the volume to create the backup from. + VolumeID string `json:"volume_id" required:"true"` + + // Force will force the creation of a backup regardless of the + //volume's status. + Force bool `json:"force,omitempty"` + + // Name is the name of the backup. + Name string `json:"name,omitempty"` + + // Description is the description of the backup. + Description string `json:"description,omitempty"` + + // Metadata is metadata for the backup. + // Requires microversion 3.43 or later. + Metadata map[string]string `json:"metadata,omitempty"` + + // Container is a container to store the backup. + Container string `json:"container,omitempty"` + + // Incremental is whether the backup should be incremental or not. + Incremental bool `json:"incremental,omitempty"` + + // SnapshotID is the ID of a snapshot to backup. + SnapshotID string `json:"snapshot_id,omitempty"` + + // AvailabilityZone is an availability zone to locate the volume or snapshot. + // Requires microversion 3.51 or later. + AvailabilityZone string `json:"availability_zone,omitempty"` +} + +// ToBackupCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToBackupCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "backup") +} + +// Create will create a new Backup based on the values in CreateOpts. To +// extract the Backup object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToBackupCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// Delete will delete the existing Backup with the provided ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get retrieves the Backup with the provided ID. To extract the Backup +// object from the response, call the Extract method on the GetResult. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToBackupListQuery() (string, error) +} + +type ListOpts struct { + // AllTenants will retrieve backups of all tenants/projects. + AllTenants bool `q:"all_tenants"` + + // Name will filter by the specified backup name. + // This does not work in later microversions. + Name string `q:"name"` + + // Status will filter by the specified status. + // This does not work in later microversions. + Status string `q:"status"` + + // TenantID will filter by a specific tenant/project ID. + // Setting AllTenants is required to use this. + TenantID string `q:"project_id"` + + // VolumeID will filter by a specified volume ID. + // This does not work in later microversions. + VolumeID string `q:"volume_id"` + + // Comma-separated list of sort keys and optional sort directions in the + // form of [:]. + Sort string `q:"sort"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // Used in conjunction with limit to return a slice of items. + Offset int `q:"offset"` + + // The ID of the last-seen item. + Marker string `q:"marker"` +} + +// ToBackupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToBackupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Backups optionally limited by the conditions provided in +// ListOpts. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToBackupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return BackupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToBackupUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Backup. +type UpdateOpts struct { + // Name is the name of the backup. + Name *string `json:"name,omitempty"` + + // Description is the description of the backup. + Description *string `json:"description,omitempty"` + + // Metadata is metadata for the backup. + // Requires microversion 3.43 or later. + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToBackupUpdateMap assembles a request body based on the contents of +// an UpdateOpts. +func (opts UpdateOpts) ToBackupUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update will update the Backup with provided information. To extract +// the updated Backup from the response, call the Extract method on the +// UpdateResult. +// Requires microversion 3.9 or later. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToBackupUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/results.go new file mode 100644 index 000000000000..6293f979f14e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/results.go @@ -0,0 +1,155 @@ +package backups + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Backup contains all the information associated with a Cinder Backup. +type Backup struct { + // ID is the Unique identifier of the backup. + ID string `json:"id"` + + // CreatedAt is the date the backup was created. + CreatedAt time.Time `json:"-"` + + // UpdatedAt is the date the backup was updated. + UpdatedAt time.Time `json:"-"` + + // Name is the display name of the backup. + Name string `json:"name"` + + // Description is the description of the backup. + Description string `json:"description"` + + // VolumeID is the ID of the Volume from which this backup was created. + VolumeID string `json:"volume_id"` + + // SnapshotID is the ID of the snapshot from which this backup was created. + SnapshotID string `json:"snapshot_id"` + + // Status is the status of the backup. + Status string `json:"status"` + + // Size is the size of the backup, in GB. + Size int `json:"size"` + + // Object Count is the number of objects in the backup. + ObjectCount int `json:"object_count"` + + // Container is the container where the backup is stored. + Container string `json:"container"` + + // AvailabilityZone is the availability zone of the backup. + AvailabilityZone string `json:"availability_zone"` + + // HasDependentBackups is whether there are other backups + // depending on this backup. + HasDependentBackups bool `json:"has_dependent_backups"` + + // FailReason has the reason for the backup failure. + FailReason string `json:"fail_reason"` + + // IsIncremental is whether this is an incremental backup. + IsIncremental bool `json:"is_incremental"` + + // DataTimestamp is the time when the data on the volume was first saved. + DataTimestamp time.Time `json:"-"` + + // ProjectID is the ID of the project that owns the backup. This is + // an admin-only field. + ProjectID string `json:"os-backup-project-attr:project_id"` +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// GetResult contains the response body and error from a Get request. +type GetResult struct { + commonResult +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// BackupPage is a pagination.Pager that is returned from a call to the List function. +type BackupPage struct { + pagination.LinkedPageBase +} + +// UnmarshalJSON converts our JSON API response into our backup struct +func (r *Backup) UnmarshalJSON(b []byte) error { + type tmp Backup + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + DataTimestamp gophercloud.JSONRFC3339MilliNoZ `json:"data_timestamp"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Backup(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + r.DataTimestamp = time.Time(s.DataTimestamp) + + return err +} + +// IsEmpty returns true if a BackupPage contains no Backups. +func (r BackupPage) IsEmpty() (bool, error) { + volumes, err := ExtractBackups(r) + return len(volumes) == 0, err +} + +func (page BackupPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"backups_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractBackups extracts and returns Backups. It is used while iterating over a backups.List call. +func ExtractBackups(r pagination.Page) ([]Backup, error) { + var s []Backup + err := ExtractBackupsInto(r, &s) + return s, err +} + +// UpdateResult contains the response body and error from an Update request. +type UpdateResult struct { + commonResult +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Backup object out of the commonResult object. +func (r commonResult) Extract() (*Backup, error) { + var s Backup + err := r.ExtractInto(&s) + return &s, err +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "backup") +} + +func ExtractBackupsInto(r pagination.Page, v interface{}) error { + return r.(BackupPage).Result.ExtractIntoSlicePtr(v, "backups") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/fixtures.go new file mode 100644 index 000000000000..088a88ae8d0e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/fixtures.go @@ -0,0 +1,133 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ListResponse = ` +{ + "backups": [ + { + "id": "289da7f8-6440-407c-9fb4-7db01ec49164", + "name": "backup-001", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "description": "Daily Backup", + "status": "available", + "size": 30, + "created_at": "2017-05-30T03:35:03.000000" + }, + { + "id": "96c3bda7-c82a-4f50-be73-ca7621794835", + "name": "backup-002", + "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", + "description": "Weekly Backup", + "status": "available", + "size": 25, + "created_at": "2017-05-30T03:35:03.000000" + } + ], + "backups_links": [ + { + "href": "%s/backups?marker=1", + "rel": "next" + } + ] +} +` + +const GetResponse = ` +{ + "backup": { + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "name": "backup-001", + "description": "Daily backup", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "status": "available", + "size": 30, + "created_at": "2017-05-30T03:35:03.000000" + } +} +` +const CreateRequest = ` +{ + "backup": { + "volume_id": "1234", + "name": "backup-001" + } +} +` + +const CreateResponse = ` +{ + "backup": { + "volume_id": "1234", + "name": "backup-001", + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "description": "Daily backup", + "volume_id": "1234", + "status": "available", + "size": 30, + "created_at": "2017-05-30T03:35:03.000000" + } +} +` + +func MockListResponse(t *testing.T) { + th.Mux.HandleFunc("/backups", 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) + + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, ListResponse, th.Server.URL) + case "1": + fmt.Fprintf(w, `{"backups": []}`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +func MockGetResponse(t *testing.T) { + th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetResponse) + }) +} + +func MockCreateResponse(t *testing.T) { + th.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + + fmt.Fprintf(w, CreateResponse) + }) +} + +func MockDeleteResponse(t *testing.T) { + th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/requests_test.go new file mode 100644 index 000000000000..d9d4953734f1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/testing/requests_test.go @@ -0,0 +1,96 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListResponse(t) + + count := 0 + + backups.List(client.ServiceClient(), &backups.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := backups.ExtractBackups(page) + if err != nil { + t.Errorf("Failed to extract backups: %v", err) + return false, err + } + + expected := []backups.Backup{ + { + ID: "289da7f8-6440-407c-9fb4-7db01ec49164", + Name: "backup-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", + }, + { + ID: "96c3bda7-c82a-4f50-be73-ca7621794835", + Name: "backup-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", + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockGetResponse(t) + + v, err := backups.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, v.Name, "backup-001") + th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockCreateResponse(t) + + options := backups.CreateOpts{VolumeID: "1234", Name: "backup-001"} + n, err := backups.Create(client.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.VolumeID, "1234") + th.AssertEquals(t, n.Name, "backup-001") + th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockDeleteResponse(t) + + res := backups.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/urls.go new file mode 100644 index 000000000000..ed09773519c7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups/urls.go @@ -0,0 +1,23 @@ +package backups + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("backups") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("backups", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("backups", id) +} + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("backups") +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("backups", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/doc.go new file mode 100644 index 000000000000..109f78f50672 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/doc.go @@ -0,0 +1,42 @@ +/* +Package quotasets enables retrieving and managing Block Storage quotas. + +Example to Get a Quota Set + + quotaset, err := quotasets.Get(blockStorageClient, "project-id").Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", quotaset) + +Example to Get Quota Set Usage + + quotaset, err := quotasets.GetUsage(blockStorageClient, "project-id").Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", quotaset) + +Example to Update a Quota Set + + updateOpts := quotasets.UpdateOpts{ + Volumes: gophercloud.IntToPointer(100), + } + + quotaset, err := quotasets.Update(blockStorageClient, "project-id", updateOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", quotaset) + +Example to Delete a Quota Set + + err := quotasets.Delete(blockStorageClient, "project-id").ExtractErr() + if err != nil { + panic(err) + } +*/ +package quotasets diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/requests.go new file mode 100644 index 000000000000..c89b4e3514ca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/requests.go @@ -0,0 +1,94 @@ +package quotasets + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// Get returns public data about a previously created QuotaSet. +func Get(client *gophercloud.ServiceClient, projectID string) (r GetResult) { + _, r.Err = client.Get(getURL(client, projectID), &r.Body, nil) + return +} + +// GetDefaults returns public data about the project's default block storage quotas. +func GetDefaults(client *gophercloud.ServiceClient, projectID string) (r GetResult) { + _, r.Err = client.Get(getDefaultsURL(client, projectID), &r.Body, nil) + return +} + +// GetUsage returns detailed public data about a previously created QuotaSet. +func GetUsage(client *gophercloud.ServiceClient, projectID string) (r GetUsageResult) { + u := fmt.Sprintf("%s?usage=true", getURL(client, projectID)) + _, r.Err = client.Get(u, &r.Body, nil) + return +} + +// Updates the quotas for the given projectID and returns the new QuotaSet. +func Update(client *gophercloud.ServiceClient, projectID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToBlockStorageQuotaUpdateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(updateURL(client, projectID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return r +} + +// UpdateOptsBuilder enables extensins to add parameters to the update request. +type UpdateOptsBuilder interface { + // Extra specific name to prevent collisions with interfaces for other quotas + // (e.g. neutron) + ToBlockStorageQuotaUpdateMap() (map[string]interface{}, error) +} + +// ToBlockStorageQuotaUpdateMap builds the update options into a serializable +// format. +func (opts UpdateOpts) ToBlockStorageQuotaUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "quota_set") +} + +// Options for Updating the quotas of a Tenant. +// All int-values are pointers so they can be nil if they are not needed. +// You can use gopercloud.IntToPointer() for convenience +type UpdateOpts struct { + // Volumes is the number of volumes that are allowed for each project. + Volumes *int `json:"volumes,omitempty"` + + // Snapshots is the number of snapshots that are allowed for each project. + Snapshots *int `json:"snapshots,omitempty"` + + // Gigabytes is the size (GB) of volumes and snapshots that are allowed for + // each project. + Gigabytes *int `json:"gigabytes,omitempty"` + + // PerVolumeGigabytes is the size (GB) of volumes and snapshots that are + // allowed for each project and the specifed volume type. + PerVolumeGigabytes *int `json:"per_volume_gigabytes,omitempty"` + + // Backups is the number of backups that are allowed for each project. + Backups *int `json:"backups,omitempty"` + + // BackupGigabytes is the size (GB) of backups that are allowed for each + // project. + BackupGigabytes *int `json:"backup_gigabytes,omitempty"` + + // Groups is the number of groups that are allowed for each project. + Groups *int `json:"groups,omitempty"` + + // Force will update the quotaset even if the quota has already been used + // and the reserved quota exceeds the new quota. + Force bool `json:"force,omitempty"` +} + +// Resets the quotas for the given tenant to their default values. +func Delete(client *gophercloud.ServiceClient, projectID string) (r DeleteResult) { + _, r.Err = client.Delete(updateURL(client, projectID), &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/results.go new file mode 100644 index 000000000000..8ccfb5bfebca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/results.go @@ -0,0 +1,165 @@ +package quotasets + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// QuotaSet is a set of operational limits that allow for control of block +// storage usage. +type QuotaSet struct { + // ID is project associated with this QuotaSet. + ID string `json:"id"` + + // Volumes is the number of volumes that are allowed for each project. + Volumes int `json:"volumes"` + + // Snapshots is the number of snapshots that are allowed for each project. + Snapshots int `json:"snapshots"` + + // Gigabytes is the size (GB) of volumes and snapshots that are allowed for + // each project. + Gigabytes int `json:"gigabytes"` + + // PerVolumeGigabytes is the size (GB) of volumes and snapshots that are + // allowed for each project and the specifed volume type. + PerVolumeGigabytes int `json:"per_volume_gigabytes"` + + // Backups is the number of backups that are allowed for each project. + Backups int `json:"backups"` + + // BackupGigabytes is the size (GB) of backups that are allowed for each + // project. + BackupGigabytes int `json:"backup_gigabytes"` +} + +// QuotaUsageSet represents details of both operational limits of block +// storage resources and the current usage of those resources. +type QuotaUsageSet struct { + // ID is the project ID associated with this QuotaUsageSet. + ID string `json:"id"` + + // Volumes is the volume usage information for this project, including + // in_use, limit, reserved and allocated attributes. Note: allocated + // attribute is available only when nested quota is enabled. + Volumes QuotaUsage `json:"volumes"` + + // Snapshots is the snapshot usage information for this project, including + // in_use, limit, reserved and allocated attributes. Note: allocated + // attribute is available only when nested quota is enabled. + Snapshots QuotaUsage `json:"snapshots"` + + // Gigabytes is the size (GB) usage information of volumes and snapshots + // for this project, including in_use, limit, reserved and allocated + // attributes. Note: allocated attribute is available only when nested + // quota is enabled. + Gigabytes QuotaUsage `json:"gigabytes"` + + // PerVolumeGigabytes is the size (GB) usage information for each volume, + // including in_use, limit, reserved and allocated attributes. Note: + // allocated attribute is available only when nested quota is enabled and + // only limit is meaningful here. + PerVolumeGigabytes QuotaUsage `json:"per_volume_gigabytes"` + + // Backups is the backup usage information for this project, including + // in_use, limit, reserved and allocated attributes. Note: allocated + // attribute is available only when nested quota is enabled. + Backups QuotaUsage `json:"backups"` + + // BackupGigabytes is the size (GB) usage information of backup for this + // project, including in_use, limit, reserved and allocated attributes. + // Note: allocated attribute is available only when nested quota is + // enabled. + BackupGigabytes QuotaUsage `json:"backup_gigabytes"` +} + +// QuotaUsage is a set of details about a single operational limit that allows +// for control of block storage usage. +type QuotaUsage struct { + // InUse is the current number of provisioned resources of the given type. + InUse int `json:"in_use"` + + // Allocated is the current number of resources of a given type allocated + // for use. It is only available when nested quota is enabled. + Allocated int `json:"allocated"` + + // Reserved is a transitional state when a claim against quota has been made + // but the resource is not yet fully online. + Reserved int `json:"reserved"` + + // Limit is the maximum number of a given resource that can be + // allocated/provisioned. This is what "quota" usually refers to. + Limit int `json:"limit"` +} + +// QuotaSetPage stores a single page of all QuotaSet results from a List call. +type QuotaSetPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines whether or not a QuotaSetsetPage is empty. +func (r QuotaSetPage) IsEmpty() (bool, error) { + ks, err := ExtractQuotaSets(r) + return len(ks) == 0, err +} + +// ExtractQuotaSets interprets a page of results as a slice of QuotaSets. +func ExtractQuotaSets(r pagination.Page) ([]QuotaSet, error) { + var s struct { + QuotaSets []QuotaSet `json:"quotas"` + } + err := (r.(QuotaSetPage)).ExtractInto(&s) + return s.QuotaSets, err +} + +type quotaResult struct { + gophercloud.Result +} + +// Extract is a method that attempts to interpret any QuotaSet resource response +// as a QuotaSet struct. +func (r quotaResult) Extract() (*QuotaSet, error) { + var s struct { + QuotaSet *QuotaSet `json:"quota_set"` + } + err := r.ExtractInto(&s) + return s.QuotaSet, err +} + +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a QuotaSet. +type GetResult struct { + quotaResult +} + +// UpdateResult is the response from a Update operation. Call its Extract method +// to interpret it as a QuotaSet. +type UpdateResult struct { + quotaResult +} + +type quotaUsageResult struct { + gophercloud.Result +} + +// GetUsageResult is the response from a Get operation. Call its Extract +// method to interpret it as a QuotaSet. +type GetUsageResult struct { + quotaUsageResult +} + +// Extract is a method that attempts to interpret any QuotaUsageSet resource +// response as a set of QuotaUsageSet structs. +func (r quotaUsageResult) Extract() (QuotaUsageSet, error) { + var s struct { + QuotaUsageSet QuotaUsageSet `json:"quota_set"` + } + err := r.ExtractInto(&s) + return s.QuotaUsageSet, err +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/doc.go new file mode 100644 index 000000000000..30d864eb95f8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/doc.go @@ -0,0 +1,2 @@ +// quotasets unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/fixtures.go new file mode 100644 index 000000000000..fe1a5d885dfa --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/fixtures.go @@ -0,0 +1,162 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const FirstTenantID = "555544443333222211110000ffffeeee" + +var getExpectedJSONBody = ` +{ + "quota_set" : { + "volumes" : 8, + "snapshots" : 9, + "gigabytes" : 10, + "per_volume_gigabytes" : 11, + "backups" : 12, + "backup_gigabytes" : 13 + } +}` + +var getExpectedQuotaSet = quotasets.QuotaSet{ + Volumes: 8, + Snapshots: 9, + Gigabytes: 10, + PerVolumeGigabytes: 11, + Backups: 12, + BackupGigabytes: 13, +} + +var getUsageExpectedJSONBody = ` +{ + "quota_set" : { + "id": "555544443333222211110000ffffeeee", + "volumes" : { + "in_use": 15, + "limit": 16, + "reserved": 17 + }, + "snapshots" : { + "in_use": 18, + "limit": 19, + "reserved": 20 + }, + "gigabytes" : { + "in_use": 21, + "limit": 22, + "reserved": 23 + }, + "per_volume_gigabytes" : { + "in_use": 24, + "limit": 25, + "reserved": 26 + }, + "backups" : { + "in_use": 27, + "limit": 28, + "reserved": 29 + }, + "backup_gigabytes" : { + "in_use": 30, + "limit": 31, + "reserved": 32 + } + } + } +}` + +var getUsageExpectedQuotaSet = quotasets.QuotaUsageSet{ + ID: FirstTenantID, + Volumes: quotasets.QuotaUsage{InUse: 15, Limit: 16, Reserved: 17}, + Snapshots: quotasets.QuotaUsage{InUse: 18, Limit: 19, Reserved: 20}, + Gigabytes: quotasets.QuotaUsage{InUse: 21, Limit: 22, Reserved: 23}, + PerVolumeGigabytes: quotasets.QuotaUsage{InUse: 24, Limit: 25, Reserved: 26}, + Backups: quotasets.QuotaUsage{InUse: 27, Limit: 28, Reserved: 29}, + BackupGigabytes: quotasets.QuotaUsage{InUse: 30, Limit: 31, Reserved: 32}, +} + +var fullUpdateExpectedJSONBody = ` +{ + "quota_set": { + "volumes": 8, + "snapshots": 9, + "gigabytes": 10, + "per_volume_gigabytes": 11, + "backups": 12, + "backup_gigabytes": 13 + } +}` + +var fullUpdateOpts = quotasets.UpdateOpts{ + Volumes: gophercloud.IntToPointer(8), + Snapshots: gophercloud.IntToPointer(9), + Gigabytes: gophercloud.IntToPointer(10), + PerVolumeGigabytes: gophercloud.IntToPointer(11), + Backups: gophercloud.IntToPointer(12), + BackupGigabytes: gophercloud.IntToPointer(13), +} + +var fullUpdateExpectedQuotaSet = quotasets.QuotaSet{ + Volumes: 8, + Snapshots: 9, + Gigabytes: 10, + PerVolumeGigabytes: 11, + Backups: 12, + BackupGigabytes: 13, +} + +var partialUpdateExpectedJSONBody = ` +{ + "quota_set": { + "volumes": 200, + "snapshots": 0, + "gigabytes": 0, + "per_volume_gigabytes": 0, + "backups": 0, + "backup_gigabytes": 0 + } +}` + +var partialUpdateOpts = quotasets.UpdateOpts{ + Volumes: gophercloud.IntToPointer(200), + Snapshots: gophercloud.IntToPointer(0), + Gigabytes: gophercloud.IntToPointer(0), + PerVolumeGigabytes: gophercloud.IntToPointer(0), + Backups: gophercloud.IntToPointer(0), + BackupGigabytes: gophercloud.IntToPointer(0), +} + +var partiualUpdateExpectedQuotaSet = quotasets.QuotaSet{Volumes: 200} + +// HandleSuccessfulRequest configures the test server to respond to an HTTP request. +func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput string, uriQueryParams map[string]string) { + + th.Mux.HandleFunc(uriPath, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, httpMethod) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + + if uriQueryParams != nil { + th.TestFormValues(t, r, uriQueryParams) + } + + fmt.Fprintf(w, jsonOutput) + }) +} + +// HandleDeleteSuccessfully tests quotaset deletion. +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusOK) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/requests_test.go new file mode 100644 index 000000000000..922b8d336e27 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/testing/requests_test.go @@ -0,0 +1,80 @@ +package testing + +import ( + "errors" + "testing" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + uriQueryParms := map[string]string{} + HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Get(client.ServiceClient(), FirstTenantID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &getExpectedQuotaSet, actual) +} + +func TestGetUsage(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + uriQueryParms := map[string]string{"usage": "true"} + HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getUsageExpectedJSONBody, uriQueryParms) + actual, err := quotasets.GetUsage(client.ServiceClient(), FirstTenantID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, getUsageExpectedQuotaSet, actual) +} + +func TestFullUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + uriQueryParms := map[string]string{} + HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, fullUpdateExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, fullUpdateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &fullUpdateExpectedQuotaSet, actual) +} + +func TestPartialUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + uriQueryParms := map[string]string{} + HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, partialUpdateExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, partialUpdateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &partiualUpdateExpectedQuotaSet, actual) +} + +type ErrorUpdateOpts quotasets.UpdateOpts + +func (opts ErrorUpdateOpts) ToBlockStorageQuotaUpdateMap() (map[string]interface{}, error) { + return nil, errors.New("This is an error") +} + +func TestErrorInToBlockStorageQuotaUpdateMap(t *testing.T) { + opts := &ErrorUpdateOpts{} + th.SetupHTTP() + defer th.TeardownHTTP() + HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, "", nil) + _, err := quotasets.Update(client.ServiceClient(), FirstTenantID, opts).Extract() + if err == nil { + t.Fatal("Error handling failed") + } +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + + err := quotasets.Delete(client.ServiceClient(), FirstTenantID).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/urls.go new file mode 100644 index 000000000000..7d8e5ceb7a30 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/quotasets/urls.go @@ -0,0 +1,21 @@ +package quotasets + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "os-quota-sets" + +func getURL(c *gophercloud.ServiceClient, projectID string) string { + return c.ServiceURL(resourcePath, projectID) +} + +func getDefaultsURL(c *gophercloud.ServiceClient, projectID string) string { + return c.ServiceURL(resourcePath, projectID, "defaults") +} + +func updateURL(c *gophercloud.ServiceClient, projectID string) string { + return getURL(c, projectID) +} + +func deleteURL(c *gophercloud.ServiceClient, projectID string) string { + return getURL(c, projectID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/doc.go new file mode 100644 index 000000000000..3a0d451b659a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/doc.go @@ -0,0 +1,52 @@ +/* +Package schedulerhints extends the volume create request with the ability to +specify additional parameters which determine where the volume will be +created in the OpenStack cloud. + +Example to Place Volume B on a Different Host than Volume A + + schedulerHints := schedulerhints.SchedulerHints{ + DifferentHost: []string{ + "volume-a-uuid", + } + } + + volumeCreateOpts := volumes.CreateOpts{ + Name: "volume_b", + Size: 10, + } + + createOpts := schedulerhints.CreateOptsExt{ + VolumeCreateOptsBuilder: volumeCreateOpts, + SchedulerHints: schedulerHints, + } + + volume, err := volumes.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Place Volume B on the Same Host as Volume A + + schedulerHints := schedulerhints.SchedulerHints{ + SameHost: []string{ + "volume-a-uuid", + } + } + + volumeCreateOpts := volumes.CreateOpts{ + Name: "volume_b", + Size: 10 + } + + createOpts := schedulerhints.CreateOptsExt{ + VolumeCreateOptsBuilder: volumeCreateOpts, + SchedulerHints: schedulerHints, + } + + volume, err := volumes.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } +*/ +package schedulerhints diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/requests.go new file mode 100644 index 000000000000..05b722da4112 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/requests.go @@ -0,0 +1,124 @@ +package schedulerhints + +import ( + "regexp" + + "github.com/gophercloud/gophercloud" +) + +// SchedulerHints represents a set of scheduling hints that are passed to the +// OpenStack scheduler. +type SchedulerHints struct { + // DifferentHost will place the volume on a different back-end that does not + // host the given volumes. + DifferentHost []string + + // SameHost will place the volume on a back-end that hosts the given volumes. + SameHost []string + + // LocalToInstance will place volume on same host on a given instance + LocalToInstance string + + // Query is a conditional statement that results in back-ends able to + // host the volume. + Query string + + // AdditionalProperies are arbitrary key/values that are not validated by nova. + AdditionalProperties map[string]interface{} +} + +// VolumeCreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type VolumeCreateOptsBuilder interface { + ToVolumeCreateMap() (map[string]interface{}, error) +} + +// CreateOptsBuilder builds the scheduler hints into a serializable format. +type CreateOptsBuilder interface { + ToVolumeSchedulerHintsCreateMap() (map[string]interface{}, error) +} + +// ToVolumeSchedulerHintsMap builds the scheduler hints into a serializable format. +func (opts SchedulerHints) ToVolumeSchedulerHintsCreateMap() (map[string]interface{}, error) { + sh := make(map[string]interface{}) + + uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$") + + if len(opts.DifferentHost) > 0 { + for _, diffHost := range opts.DifferentHost { + if !uuidRegex.MatchString(diffHost) { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.DifferentHost" + err.Value = opts.DifferentHost + err.Info = "The hosts must be in UUID format." + return nil, err + } + } + sh["different_host"] = opts.DifferentHost + } + + if len(opts.SameHost) > 0 { + for _, sameHost := range opts.SameHost { + if !uuidRegex.MatchString(sameHost) { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.SameHost" + err.Value = opts.SameHost + err.Info = "The hosts must be in UUID format." + return nil, err + } + } + sh["same_host"] = opts.SameHost + } + + if opts.LocalToInstance != "" { + if !uuidRegex.MatchString(opts.LocalToInstance) { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.LocalToInstance" + err.Value = opts.LocalToInstance + err.Info = "The instance must be in UUID format." + return nil, err + } + sh["local_to_instance"] = opts.LocalToInstance + } + + if opts.Query != "" { + sh["query"] = opts.Query + } + + if opts.AdditionalProperties != nil { + for k, v := range opts.AdditionalProperties { + sh[k] = v + } + } + + return sh, nil +} + +// CreateOptsExt adds a SchedulerHints option to the base CreateOpts. +type CreateOptsExt struct { + VolumeCreateOptsBuilder + + // SchedulerHints provides a set of hints to the scheduler. + SchedulerHints CreateOptsBuilder +} + +// ToVolumeCreateMap adds the SchedulerHints option to the base volume creation options. +func (opts CreateOptsExt) ToVolumeCreateMap() (map[string]interface{}, error) { + base, err := opts.VolumeCreateOptsBuilder.ToVolumeCreateMap() + if err != nil { + return nil, err + } + + schedulerHints, err := opts.SchedulerHints.ToVolumeSchedulerHintsCreateMap() + if err != nil { + return nil, err + } + + if len(schedulerHints) == 0 { + return base, nil + } + + base["OS-SCH-HNT:scheduler_hints"] = schedulerHints + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/doc.go new file mode 100644 index 000000000000..1915aef2fed5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/doc.go @@ -0,0 +1,2 @@ +// schedulerhints unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/requests_test.go new file mode 100644 index 000000000000..2ba27c7ef752 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints/testing/requests_test.go @@ -0,0 +1,58 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerhints" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCreateOpts(t *testing.T) { + + base := volumes.CreateOpts{ + Size: 10, + Name: "testvolume", + } + schedulerHints := schedulerhints.SchedulerHints{ + DifferentHost: []string{ + "a0cf03a5-d921-4877-bb5c-86d26cf818e1", + "8c19174f-4220-44f0-824a-cd1eeef10287", + }, + SameHost: []string{ + "a0cf03a5-d921-4877-bb5c-86d26cf818e1", + "8c19174f-4220-44f0-824a-cd1eeef10287", + }, + LocalToInstance: "0ffb2c1b-d621-4fc1-9ae4-88d99c088ff6", + AdditionalProperties: map[string]interface{}{"mark": "a0cf03a5-d921-4877-bb5c-86d26cf818e1"}, + } + + ext := schedulerhints.CreateOptsExt{ + VolumeCreateOptsBuilder: base, + SchedulerHints: schedulerHints, + } + + expected := ` + { + "volume": { + "size": 10, + "name": "testvolume" + }, + "OS-SCH-HNT:scheduler_hints": { + "different_host": [ + "a0cf03a5-d921-4877-bb5c-86d26cf818e1", + "8c19174f-4220-44f0-824a-cd1eeef10287" + ], + "same_host": [ + "a0cf03a5-d921-4877-bb5c-86d26cf818e1", + "8c19174f-4220-44f0-824a-cd1eeef10287" + ], + "local_to_instance": "0ffb2c1b-d621-4fc1-9ae4-88d99c088ff6", + "mark": "a0cf03a5-d921-4877-bb5c-86d26cf818e1" + } + } + ` + actual, err := ext.ToVolumeCreateMap() + th.AssertNoErr(t, err) + th.CheckJSONEquals(t, expected, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats/results.go index 3da8f80c3002..11109673c8ec 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats/results.go @@ -23,13 +23,13 @@ type Capabilities struct { LocationInfo string `json:"location_info"` QoSSupport bool `json:"QoS_support"` ProvisionedCapacityGB float64 `json:"provisioned_capacity_gb"` - MaxOverSubscriptionRatio float64 `json:"max_over_subscription_ratio"` + MaxOverSubscriptionRatio string `json:"max_over_subscription_ratio"` ThinProvisioningSupport bool `json:"thin_provisioning_support"` ThickProvisioningSupport bool `json:"thick_provisioning_support"` TotalVolumes int64 `json:"total_volumes"` FilterFunction string `json:"filter_function"` GoodnessFuction string `json:"goodness_function"` - Mutliattach bool `json:"multiattach"` + Multiattach bool `json:"multiattach"` SparseCopyVolume bool `json:"sparse_copy_volume"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go index d1861ac196d8..41aebdc5f2bd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go @@ -1,18 +1,20 @@ package apiversions import ( - "net/url" "strings" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" ) func getURL(c *gophercloud.ServiceClient, version string) string { - return c.ServiceURL(strings.TrimRight(version, "/") + "/") + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + strings.TrimRight(version, "/") + "/" + return endpoint } func listURL(c *gophercloud.ServiceClient) string { - u, _ := url.Parse(c.ServiceURL("")) - u.Path = "/" - return u.String() + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + return endpoint } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots/requests.go index cb9d0d0e068c..fed1252ac991 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots/requests.go @@ -130,7 +130,12 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go index 566def518111..1da94238b9cc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go @@ -110,8 +110,8 @@ type UpdateOptsBuilder interface { // to the volumes.Update function. For more information about the parameters, see // the Volume object. type UpdateOpts struct { - Name string `json:"display_name,omitempty"` - Description string `json:"display_description,omitempty"` + Name *string `json:"display_name,omitempty"` + Description *string `json:"display_description,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } @@ -139,7 +139,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/testing/requests_test.go index c4ce23a75a8a..0ba830e03802 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/testing/requests_test.go @@ -145,7 +145,8 @@ func TestUpdate(t *testing.T) { MockUpdateResponse(t) - options := volumes.UpdateOpts{Name: "vol-002"} + var name = "vol-002" + options := volumes.UpdateOpts{Name: &name} v, err := volumes.Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "vol-002", v.Name) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go index dc707fd93d07..939e502048fa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go @@ -142,7 +142,12 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go index 2ec10ad55ed4..c27ddbf67c3c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go @@ -61,9 +61,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } +// DeleteOptsBuilder allows extensions to add additional parameters to the +// Delete request. +type DeleteOptsBuilder interface { + ToVolumeDeleteQuery() (string, error) +} + +// DeleteOpts contains options for deleting a Volume. This object is passed to +// the volumes.Delete function. +type DeleteOpts struct { + // Delete all snapshots of this volume as well. + Cascade bool `q:"cascade"` +} + +// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string. +func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + // Delete will delete the existing Volume with the provided ID. -func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) +func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) { + url := deleteURL(client, id) + if opts != nil { + query, err := opts.ToVolumeDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = client.Delete(url, nil) return } @@ -98,6 +126,19 @@ type ListOpts struct { // TenantID will filter by a specific tenant/project ID. // Setting AllTenants is required for this. TenantID string `q:"project_id"` + + // Comma-separated list of sort keys and optional sort directions in the + // form of [:]. + Sort string `q:"sort"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // Used in conjunction with limit to return a slice of items. + Offset int `q:"offset"` + + // The ID of the last-seen item. + Marker string `q:"marker"` } // ToVolumeListQuery formats a ListOpts into a query string. @@ -118,7 +159,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa } return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return VolumePage{pagination.SinglePageBase(r)} + return VolumePage{pagination.LinkedPageBase{PageResult: r}} }) } @@ -132,8 +173,8 @@ type UpdateOptsBuilder interface { // to the volumes.Update function. For more information about the parameters, see // the Volume object. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } @@ -161,7 +202,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go index 674ec3468658..96572b01b458 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go @@ -98,7 +98,7 @@ func (r *Volume) UnmarshalJSON(b []byte) error { // VolumePage is a pagination.pager that is returned from a call to the List function. type VolumePage struct { - pagination.SinglePageBase + pagination.LinkedPageBase } // IsEmpty returns true if a ListResult contains no Volumes. @@ -107,6 +107,19 @@ func (r VolumePage) IsEmpty() (bool, error) { return len(volumes) == 0, err } +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r VolumePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"volumes_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + // ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. func ExtractVolumes(r pagination.Page) ([]Volume, error) { var s []Volume diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go index cd7dcf5ecb76..349f746a33a5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go @@ -220,7 +220,7 @@ func TestDelete(t *testing.T) { MockDeleteResponse(t) - res := volumes.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := volumes.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", volumes.DeleteOpts{}) th.AssertNoErr(t, res.Err) } @@ -230,7 +230,8 @@ func TestUpdate(t *testing.T) { MockUpdateResponse(t) - options := volumes.UpdateOpts{Name: "vol-002"} + var name = "vol-002" + options := volumes.UpdateOpts{Name: &name} v, err := volumes.Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "vol-002", v.Name) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go index 7df688ede827..22531c9fdfcf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go @@ -153,7 +153,12 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go index 43727409dd91..25f70b27c1a9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go @@ -38,6 +38,8 @@ type CreateOpts struct { ImageID string `json:"imageRef,omitempty"` // The associated volume type VolumeType string `json:"volume_type,omitempty"` + // Multiattach denotes if the volume is multi-attach capable. + Multiattach bool `json:"multiattach,omitempty"` } // ToVolumeCreateMap assembles a request body based on the contents of a @@ -61,9 +63,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } +// DeleteOptsBuilder allows extensions to add additional parameters to the +// Delete request. +type DeleteOptsBuilder interface { + ToVolumeDeleteQuery() (string, error) +} + +// DeleteOpts contains options for deleting a Volume. This object is passed to +// the volumes.Delete function. +type DeleteOpts struct { + // Delete all snapshots of this volume as well. + Cascade bool `q:"cascade"` +} + +// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string. +func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + // Delete will delete the existing Volume with the provided ID. -func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) +func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) { + url := deleteURL(client, id) + if opts != nil { + query, err := opts.ToVolumeDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = client.Delete(url, nil) return } @@ -145,8 +175,8 @@ type UpdateOptsBuilder interface { // to the volumes.Update function. For more information about the parameters, see // the Volume object. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } @@ -174,7 +204,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go index 87f71262c1db..3a33b5864bb6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go @@ -77,6 +77,8 @@ type Volume struct { ConsistencyGroupID string `json:"consistencygroup_id"` // Multiattach denotes if the volume is multi-attach capable. Multiattach bool `json:"multiattach"` + // Image metadata entries, only included for volumes that were created from an image, or from a snapshot of a volume originally created from an image. + VolumeImageMetadata map[string]string `json:"volume_image_metadata"` } // UnmarshalJSON another unmarshalling function diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/fixtures.go index 74efb1a2564a..dca8634c0320 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/fixtures.go @@ -141,6 +141,10 @@ func MockGetResponse(t *testing.T) { "os-vol-mig-status-attr:migstat": null, "metadata": {}, "status": "available", + "volume_image_metadata": { + "container_format": "bare", + "image_name": "centos" + }, "description": null } } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/requests_test.go index 5a1e46b653af..4745198dd8b1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/testing/requests_test.go @@ -220,7 +220,7 @@ func TestDelete(t *testing.T) { MockDeleteResponse(t) - res := volumes.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := volumes.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", volumes.DeleteOpts{}) th.AssertNoErr(t, res.Err) } @@ -230,7 +230,8 @@ func TestUpdate(t *testing.T) { MockUpdateResponse(t) - options := volumes.UpdateOpts{Name: "vol-002"} + var name = "vol-002" + options := volumes.UpdateOpts{Name: &name} v, err := volumes.Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "vol-002", v.Name) @@ -249,6 +250,7 @@ func TestGetWithExtensions(t *testing.T) { err := volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "304dc00909ac4d0da6c62d816bcb3459", s.TenantID) + th.AssertEquals(t, "centos", s.Volume.VolumeImageMetadata["image_name"]) err = volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(s) if err == nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/requests.go index 82a79bcf6a06..a477c97d9f22 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/requests.go @@ -112,9 +112,9 @@ type UpdateOptsBuilder interface { // to the volumetypes.Update function. For more information about the parameters, see // the Volume Type object. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - IsPublic *bool `json:"is_public,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + IsPublic *bool `json:"is_public,omitempty"` } // ToVolumeUpdateMap assembles a request body based on the contents of an diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/testing/requests_test.go index 00c3e6ea9e6d..b1435805d99e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes/testing/requests_test.go @@ -106,8 +106,9 @@ func TestUpdate(t *testing.T) { MockUpdateResponse(t) var isPublic = true + var name = "vol-type-002" options := volumetypes.UpdateOpts{ - Name: "vol-type-002", + Name: &name, IsPublic: &isPublic, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go index 85705d2126a9..50f239711e72 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -2,10 +2,7 @@ package openstack import ( "fmt" - "net/url" "reflect" - "regexp" - "strings" "github.com/gophercloud/gophercloud" tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" @@ -38,21 +35,11 @@ A basic example of using this would be: client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) */ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { - u, err := url.Parse(endpoint) + base, err := utils.BaseEndpoint(endpoint) if err != nil { return nil, err } - u.RawQuery, u.Fragment = "", "" - - var base string - versionRe := regexp.MustCompile("v[0-9.]+/?") - if version := versionRe.FindString(u.Path); version != "" { - base = strings.Replace(u.String(), version, "", -1) - } else { - base = u.String() - } - endpoint = gophercloud.NormalizeURL(endpoint) base = gophercloud.NormalizeURL(base) @@ -148,7 +135,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc result := tokens2.Create(v2Client, v2Opts) - token, err := result.ExtractToken() + err = client.SetTokenAndAuthResult(result) if err != nil { return err } @@ -163,8 +150,9 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`, // this should retry authentication only once tac := *client + tac.SetThrowaway(true) tac.ReauthFunc = nil - tac.TokenID = "" + tac.SetTokenAndAuthResult(nil) tao := options tao.AllowReauth = false client.ReauthFunc = func() error { @@ -172,11 +160,10 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc if err != nil { return err } - client.TokenID = tac.TokenID + client.CopyTokenFrom(&tac) return nil } } - client.TokenID = token.ID client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V2EndpointURL(catalog, opts) } @@ -202,7 +189,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au result := tokens3.Create(v3Client, opts) - token, err := result.ExtractToken() + err = client.SetTokenAndAuthResult(result) if err != nil { return err } @@ -212,15 +199,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au return err } - client.TokenID = token.ID - if opts.CanReauth() { // here we're creating a throw-away client (tac). it's a copy of the user's provider client, but // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`, // this should retry authentication only once tac := *client + tac.SetThrowaway(true) tac.ReauthFunc = nil - tac.TokenID = "" + tac.SetTokenAndAuthResult(nil) var tao tokens3.AuthOptionsBuilder switch ot := opts.(type) { case *gophercloud.AuthOptions: @@ -239,7 +225,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au if err != nil { return err } - client.TokenID = tac.TokenID + client.CopyTokenFrom(&tac) return nil } } @@ -287,11 +273,17 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp // Ensure endpoint still has a suffix of v3. // This is because EndpointLocator might have found a versionless - // endpoint and requests will fail unless targeted at /v3. - if !strings.HasSuffix(endpoint, "v3/") { - endpoint = endpoint + "v3/" + // endpoint or the published endpoint is still /v2.0. In both + // cases, we need to fix the endpoint to point to /v3. + base, err := utils.BaseEndpoint(endpoint) + if err != nil { + return nil, err } + base = gophercloud.NormalizeURL(base) + + endpoint = base + "v3/" + return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, @@ -312,6 +304,18 @@ func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointO return sc, nil } +// NewBareMetalV1 creates a ServiceClient that may be used with the v1 +// bare metal package. +func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "baremetal") +} + +// NewBareMetalIntrospectionV1 creates a ServiceClient that may be used with the v1 +// bare metal introspection package. +func NewBareMetalIntrospectionV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "baremetal-inspector") +} + // NewObjectStorageV1 creates a ServiceClient that may be used with the v1 // object storage package. func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { @@ -400,3 +404,35 @@ func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { return initClientOpts(client, eo, "clustering") } + +// 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.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") +} + +// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key +// manager service. +func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "key-manager") + sc.ResourceBase = sc.Endpoint + "v1/" + return sc, err +} + +// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management +// package. +func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container-infra") +} + +// 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") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/doc.go new file mode 100644 index 000000000000..2312a512f356 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/doc.go @@ -0,0 +1,33 @@ +/* +Package actions provides listing and retrieving of senlin actions for the +OpenStack Clustering Service. + +Example to List Actions + + opts := actions.ListOpts{ + Limit: 5, + } + + err = actions.List(serviceClient, opts).EachPage(func(page pagination.Page) (bool, error) { + actionInfos, err := actions.ExtractActions(page) + if err != nil { + return false, err + } + + for _, actionInfo := range actionInfos { + fmt.Println("%+v\n", actionInfo) + } + return true, nil + }) + +Example to Get an Action + + actionID := "edce3528-864f-41fb-8759-f4707925cc09" + action, err := actions.Get(serviceClient, actionID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("Action %+v: ", action) +*/ +package actions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/requests.go new file mode 100644 index 000000000000..f1a4f43f3ec2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/requests.go @@ -0,0 +1,51 @@ +package actions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToActionListQuery() (string, error) +} + +// ListOpts represents options used to list actions. +type ListOpts struct { + Limit int `q:"limit"` + Marker string `q:"marker"` + Sort string `q:"sort"` + GlobalProject *bool `q:"global_project"` + Name string `q:"name"` + Target string `q:"target"` + Action string `q:"action"` + Status string `q:"status"` +} + +// ToClusterListQuery builds a query string from ListOpts. +func (opts ListOpts) ToActionListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of actions. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToActionListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ActionPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details of a single action. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/results.go new file mode 100644 index 000000000000..ca03a4b71c89 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/results.go @@ -0,0 +1,106 @@ +package actions + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Action represents a detailed Action. +type Action struct { + Action string `json:"action"` + Cause string `json:"cause"` + CreatedAt time.Time `json:"-"` + Data map[string]interface{} `json:"data"` + DependedBy []string `json:"depended_by"` + DependsOn []string `json:"depends_on"` + StartTime float64 `json:"start_time"` + EndTime float64 `json:"end_time"` + ID string `json:"id"` + Inputs map[string]interface{} `json:"inputs"` + Interval int `json:"interval"` + Name string `json:"name"` + Outputs map[string]interface{} `json:"outputs"` + Owner string `json:"owner"` + Project string `json:"project"` + Status string `json:"status"` + StatusReason string `json:"status_reason"` + Target string `json:"target"` + Timeout int `json:"timeout"` + UpdatedAt time.Time `json:"-"` + User string `json:"user"` +} + +func (r *Action) UnmarshalJSON(b []byte) error { + type tmp Action + var s struct { + tmp + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Action(s.tmp) + + if s.CreatedAt != "" { + r.CreatedAt, err = time.Parse(time.RFC3339, s.CreatedAt) + if err != nil { + return err + } + } + + if s.UpdatedAt != "" { + r.UpdatedAt, err = time.Parse(time.RFC3339, s.UpdatedAt) + if err != nil { + return err + } + } + + return nil +} + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult-based result as an Action. +func (r commonResult) Extract() (*Action, error) { + var s struct { + Action *Action `json:"action"` + } + err := r.ExtractInto(&s) + return s.Action, err +} + +// GetResult is the response of a Get operations. Call its Extract method to +// interpret it as an Action. +type GetResult struct { + commonResult +} + +// ActionPage contains a single page of all actions from a List call. +type ActionPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a ActionPage contains any results. +func (r ActionPage) IsEmpty() (bool, error) { + actions, err := ExtractActions(r) + return len(actions) == 0, err +} + +// ExtractActions returns a slice of Actions from the List operation. +func ExtractActions(r pagination.Page) ([]Action, error) { + var s struct { + Actions []Action `json:"actions"` + } + err := (r.(ActionPage)).ExtractInto(&s) + return s.Actions, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/doc.go new file mode 100644 index 000000000000..ffecf4d863e8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_actions_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/fixtures.go new file mode 100644 index 000000000000..4709b7c51630 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/fixtures.go @@ -0,0 +1,166 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/actions" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ListResponse = ` +{ + "actions": [ + { + "action": "NODE_DELETE", + "cause": "RPC Request", + "created_at": "2015-11-04T05:21:41Z", + "data": {}, + "depended_by": ["ef67fe80-6547-40f2-ba1b-83e950aa38df"], + "depends_on": ["ef67fe80-6547-40f2-ba1b-83e950aa38df"], + "end_time": 1425550000.0, + "id": "edce3528-864f-41fb-8759-f4707925cc09", + "inputs": {}, + "interval": -1, + "name": "node_delete_f0de9b9c", + "outputs": {}, + "owner": null, + "project": "f1fe61dcda2f4618a14c10dc7abc214d", + "start_time": 1425550000.0, + "status": "SUCCEEDED", + "status_reason": "Action completed successfully.", + "target": "f0de9b9c-6d48-4a46-af21-2ca8607777fe", + "timeout": 3600, + "updated_at": "2016-11-04T05:21:41Z", + "user": "8bcd2cdca7684c02afc9e4f2fc0f0c79" + }, + { + "action": "NODE_DELETE", + "cause": "RPC Request", + "created_at": null, + "data": {}, + "depended_by": ["ef67fe80-6547-40f2-ba1b-83e950aa38df"], + "depends_on": ["ef67fe80-6547-40f2-ba1b-83e950aa38df"], + "end_time": 1425550000.0, + "id": "edce3528-864f-41fb-8759-f4707925cc09", + "inputs": {}, + "interval": -1, + "name": "node_delete_f0de9b9c", + "outputs": {}, + "owner": null, + "project": "f1fe61dcda2f4618a14c10dc7abc214d", + "start_time": 1425550000.0, + "status": "SUCCEEDED", + "status_reason": "Action completed successfully.", + "target": "f0de9b9c-6d48-4a46-af21-2ca8607777fe", + "timeout": 3600, + "updated_at": "", + "user": "8bcd2cdca7684c02afc9e4f2fc0f0c79" + } + ] +} +` + +const GetResponse = ` +{ + "action": { + "action": "NODE_DELETE", + "cause": "RPC Request", + "created_at": "2015-11-04T05:21:41Z", + "data": {}, + "depended_by": ["ef67fe80-6547-40f2-ba1b-83e950aa38df"], + "depends_on": ["ef67fe80-6547-40f2-ba1b-83e950aa38df"], + "end_time": 1425550000.0, + "id": "edce3528-864f-41fb-8759-f4707925cc09", + "inputs": {}, + "interval": -1, + "name": "node_delete_f0de9b9c", + "outputs": {}, + "owner": null, + "project": "f1fe61dcda2f4618a14c10dc7abc214d", + "start_time": 1425550000.0, + "status": "SUCCEEDED", + "status_reason": "Action completed successfully.", + "target": "f0de9b9c-6d48-4a46-af21-2ca8607777fe", + "timeout": 3600, + "updated_at": "2016-11-04T05:21:41Z", + "user": "8bcd2cdca7684c02afc9e4f2fc0f0c79" + } +} +` + +var ExpectedAction1 = actions.Action{ + Action: "NODE_DELETE", + Cause: "RPC Request", + CreatedAt: time.Date(2015, 11, 4, 5, 21, 41, 0, time.UTC), + Data: map[string]interface{}{}, + DependedBy: []string{"ef67fe80-6547-40f2-ba1b-83e950aa38df"}, + DependsOn: []string{"ef67fe80-6547-40f2-ba1b-83e950aa38df"}, + EndTime: 1425550000.0, + ID: "edce3528-864f-41fb-8759-f4707925cc09", + Inputs: make(map[string]interface{}), + Interval: -1, + Name: "node_delete_f0de9b9c", + Outputs: make(map[string]interface{}), + Owner: "", + Project: "f1fe61dcda2f4618a14c10dc7abc214d", + StartTime: 1425550000.0, + Status: "SUCCEEDED", + StatusReason: "Action completed successfully.", + Target: "f0de9b9c-6d48-4a46-af21-2ca8607777fe", + Timeout: 3600, + UpdatedAt: time.Date(2016, 11, 4, 5, 21, 41, 0, time.UTC), + User: "8bcd2cdca7684c02afc9e4f2fc0f0c79", +} + +var ExpectedAction2 = actions.Action{ + Action: "NODE_DELETE", + Cause: "RPC Request", + CreatedAt: time.Time{}, + Data: map[string]interface{}{}, + DependedBy: []string{"ef67fe80-6547-40f2-ba1b-83e950aa38df"}, + DependsOn: []string{"ef67fe80-6547-40f2-ba1b-83e950aa38df"}, + EndTime: 1425550000.0, + ID: "edce3528-864f-41fb-8759-f4707925cc09", + Inputs: make(map[string]interface{}), + Interval: -1, + Name: "node_delete_f0de9b9c", + Outputs: make(map[string]interface{}), + Owner: "", + Project: "f1fe61dcda2f4618a14c10dc7abc214d", + StartTime: 1425550000.0, + Status: "SUCCEEDED", + StatusReason: "Action completed successfully.", + Target: "f0de9b9c-6d48-4a46-af21-2ca8607777fe", + Timeout: 3600, + UpdatedAt: time.Time{}, + User: "8bcd2cdca7684c02afc9e4f2fc0f0c79", +} + +var ExpectedActions = []actions.Action{ExpectedAction1, ExpectedAction2} + +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListResponse) + }) +} + +func HandleGetSuccessfully(t *testing.T, id string) { + th.Mux.HandleFunc("/v1/actions/"+id, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, GetResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/requests_test.go new file mode 100644 index 000000000000..6a4e5f1592a5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/testing/requests_test.go @@ -0,0 +1,44 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/actions" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListActions(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListSuccessfully(t) + + pageCount := 0 + err := actions.List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + pageCount++ + actual, err := actions.ExtractActions(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedActions, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if pageCount != 1 { + t.Errorf("Expected 1 page, got %d", pageCount) + } +} + +func TestGetAction(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetSuccessfully(t, ExpectedAction1.ID) + + actual, err := actions.Get(fake.ServiceClient(), ExpectedAction1.ID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAction1, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/urls.go new file mode 100644 index 000000000000..3288cc805e90 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/actions/urls.go @@ -0,0 +1,22 @@ +package actions + +import "github.com/gophercloud/gophercloud" + +var apiVersion = "v1" +var apiName = "actions" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/doc.go new file mode 100644 index 000000000000..7d28ac594521 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/doc.go @@ -0,0 +1,287 @@ +/* +Package clusters provides information and interaction with the clusters through +the OpenStack Clustering service. + +Example to Create a Cluster + + createOpts := clusters.CreateOpts{ + Name: "test-cluster", + DesiredCapacity: 1, + ProfileID: "b7b870ee-d3c5-4a93-b9d7-846c53b2c2da", + } + + cluster, err := clusters.Create(serviceClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Cluster + + clusterName := "cluster123" + cluster, err := clusters.Get(serviceClient, clusterName).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", cluster) + +Example to List Clusters + + listOpts := clusters.ListOpts{ + Name: "testcluster", + } + + allPages, err := clusters.List(serviceClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allClusters, err := clusters.ExtractClusters(allPages) + if err != nil { + panic(err) + } + + for _, cluster := range allClusters { + fmt.Printf("%+v\n", cluster) + } + +Example to Update a Cluster + + updateOpts := clusters.UpdateOpts{ + Name: "testcluster", + ProfileID: "b7b870ee-d3c5-4a93-b9d7-846c53b2c2da", + } + + clusterID := "7d85f602-a948-4a30-afd4-e84f47471c15" + cluster, err := clusters.Update(serviceClient, clusterName, opts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", cluster) + +Example to Delete a Cluster + + clusterID := "dc6d336e3fc4c0a951b5698cd1236ee" + err := clusters.Delete(serviceClient, clusterID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Resize a Cluster + + number := 1 + maxSize := 5 + minSize := 1 + minStep := 1 + strict := true + + resizeOpts := clusters.ResizeOpts{ + AdjustmentType: clusters.ChangeInCapacityAdjustment, + Number: number, + MaxSize: &maxSize, + MinSize: &minSize, + MinStep: &minStep, + Strict: &strict, + } + + actionID, err := clusters.Resize(client, clusterName, resizeOpts).Extract() + if err != nil { + t.Fatalf("Unable to resize cluster: %v", err) + } + fmt.Println("Resize actionID", actionID) + +Example to ScaleIn a Cluster + + count := 2 + scaleInOpts := clusters.ScaleInOpts{ + Count: &count, + } + clusterID: "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + + action, err := clusters.ScaleIn(computeClient, clusterID, scaleInOpts).Extract() + if err != nil { + panic(err) + } + +Example to ScaleOut a cluster + + scaleOutOpts := clusters.ScaleOutOpts{ + Count: 2, + } + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + + actionID, err := clusters.ScaleOut(computeClient, clusterID, scaleOutOpts).Extract() + if err != nil { + panic(err) + } + +Example to List Policies for a Cluster + + clusterID := "7d85f602-a948-4a30-afd4-e84f47471c15" + allPages, err := clusters.ListPolicies(serviceClient, clusterID, nil).AllPages() + if err != nil { + panic(err) + } + + allClusterPolicies, err := clusters.ExtractClusterPolicies(allPages) + if err != nil { + panic(err) + } + + for _, clusterPolicy := range allClusterPolicies { + fmt.Printf("%+v\n", clusterPolicy) + } + +Example to Get a Cluster Policy + + clusterID := "7d85f602-a948-4a30-afd4-e84f47471c15" + profileID := "714fe676-a08f-4196-b7af-61d52eeded15" + clusterPolicy, err := clusterpolicies.Get(serviceCLient, clusterID, profileID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", clusterPolicy) + +Example to Attach a Policy to a Cluster + + enabled := true + attachPolicyOpts := clusters.AttachPolicyOpts{ + PolicyID: "policy-123", + Enabled: &enabled, + } + + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := clusters.AttachPolicy(serviceClient, clusterID, attachPolicyOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println("Attach Policy actionID", actionID) + +Example to Detach a Policy to Cluster + + detachpolicyOpts := clusters.DetachPolicyOpts{ + PolicyID: "policy-123", + } + + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := clusters.DetachPolicy(serviceClient, clusterID, detachpolicyOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println("Update Policy actionID", actionID) + +Example to Update a Policy to a Cluster + + enabled := true + updatePolicyOpts := clusters.UpdatePolicyOpts{ + PolicyID: "policy-123", + Enabled: &enabled, + } + + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := clusters.UpdatePolicy(serviceClient, clusterID, updatePolicyOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println("Attach Policy actionID", actionID) + +Example to Recover a Cluster + + check := true + checkCapacity := true + recoverOpts := clusters.RecoverOpts{ + Operation: clusters.RebuildRecovery, + Check: &check, + CheckCapacity: &checkCapacity, + } + + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := clusters.Recover(computeClient, clusterID, recoverOpts).Extract() + if err != nil { + panic(err) + } + fmt.Println("action=", actionID) + +Example to Check a Cluster + + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + action, err := clusters.Check(computeClient, clusterID).Extract() + if err != nil { + panic(err) + } + +Example to Complete Life Cycle + + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + lifecycleOpts := clusters.CompleteLifecycleOpts{LifecycleActionTokenID: "2b827124-69e1-496e-9484-33ca769fe4df"} + + action, err := clusters.CompleteLifecycle(computeClient, clusterID, lifecycleOpts).Extract() + if err != nil { + panic(err) + } + +Example to add nodes to a cluster + + addNodesOpts := clusters.AddNodesOpts{ + Nodes: []string{"node-123"}, + } + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := clusters.AddNodes(serviceClient, clusterID, addNodesOpts).Extract() + if err != nil { + panic(err) + } + fmt.Println("action=", actionID) + +Example to remove nodes from a cluster + + removeNodesOpts := clusters.RemoveNodesOpts{ + Nodes: []string{"node-123"}, + } + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + err := clusters.RemoveNodes(serviceClient, clusterID, removeNodesOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to replace nodes for a cluster + + replaceNodesOpts := clusters.ReplaceNodesOpts{ + Nodes: map[string]string{"node-1234": "node-5678"}, + } + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := clusters.ReplaceNodes(serviceClient, clusterID, replaceNodesOpts).Extract() + if err != nil { + panic(err) + } + +Example to collect node attributes across a cluster + + serviceClient.Microversion = "1.2" + clusterID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + opts := clusters.CollectOpts{ + Path: "status", + } + attrs, err := clusters.Collect(serviceClient, clusterID, opts).Extract() + if err != nil { + panic(err) + } + +Example to perform an operation on a cluster + + serviceClient.Microversion = "1.4" + clusterID := "cluster123" + operationOpts := clusters.OperationOpts{ + Operation: clusters.RebootOperation, + Filters: clusters.OperationFilters{"role": "slave"}, + Params: clusters.OperationParams{"type": "SOFT"}, + } + actionID, err := clusters.Ops(serviceClient, clusterID, operationOpts).Extract() + if err != nil { + panic(err) + } + +*/ +package clusters diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/requests.go new file mode 100644 index 000000000000..43312ed4d390 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/requests.go @@ -0,0 +1,694 @@ +package clusters + +import ( + "fmt" + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// AdjustmentType represents valid values for resizing a cluster. +type AdjustmentType string + +const ( + ExactCapacityAdjustment AdjustmentType = "EXACT_CAPACITY" + ChangeInCapacityAdjustment AdjustmentType = "CHANGE_IN_CAPACITY" + ChangeInPercentageAdjustment AdjustmentType = "CHANGE_IN_PERCENTAGE" +) + +// RecoveryAction represents valid values for recovering a cluster. +type RecoveryAction string + +const ( + RebootRecovery RecoveryAction = "REBOOT" + RebuildRecovery RecoveryAction = "REBUILD" + RecreateRecovery RecoveryAction = "RECREATE" +) + +// CreateOptsBuilder allows extensions to add additional parameters +// to the Create request. +type CreateOptsBuilder interface { + ToClusterCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used to create a cluster. +type CreateOpts struct { + Name string `json:"name" required:"true"` + DesiredCapacity int `json:"desired_capacity"` + ProfileID string `json:"profile_id" required:"true"` + MinSize *int `json:"min_size,omitempty"` + Timeout int `json:"timeout,omitempty"` + MaxSize int `json:"max_size,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + Config map[string]interface{} `json:"config,omitempty"` +} + +// ToClusterCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "cluster") +} + +// Create requests the creation of a new cluster. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToClusterCreateMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// Get retrieves details of a single cluster. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + var result *http.Response + result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request. +type ListOptsBuilder interface { + ToClusterListQuery() (string, error) +} + +// ListOpts represents options to list clusters. +type ListOpts struct { + Limit int `q:"limit"` + Marker string `q:"marker"` + Sort string `q:"sort"` + GlobalProject *bool `q:"global_project"` + Name string `q:"name,omitempty"` + Status string `q:"status,omitempty"` +} + +// ToClusterListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToClusterListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of clusters. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToClusterListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ClusterPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToClusterUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options to update a cluster. +type UpdateOpts struct { + Config string `json:"config,omitempty"` + Name string `json:"name,omitempty"` + ProfileID string `json:"profile_id,omitempty"` + Timeout *int `json:"timeout,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + ProfileOnly *bool `json:"profile_only,omitempty"` +} + +// ToClusterUpdateMap assembles a request body based on the contents of +// UpdateOpts. +func (opts UpdateOpts) ToClusterUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "cluster") + if err != nil { + return nil, err + } + return b, nil +} + +// Update will update an existing cluster. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToClusterUpdateMap() + if err != nil { + r.Err = err + return r + } + + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Delete deletes the specified cluster ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(deleteURL(client, id), nil) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// ResizeOptsBuilder allows extensions to add additional parameters to the +// resize request. +type ResizeOptsBuilder interface { + ToClusterResizeMap() (map[string]interface{}, error) +} + +// ResizeOpts represents options for resizing a cluster. +type ResizeOpts struct { + AdjustmentType AdjustmentType `json:"adjustment_type,omitempty"` + Number interface{} `json:"number,omitempty"` + MinSize *int `json:"min_size,omitempty"` + MaxSize *int `json:"max_size,omitempty"` + MinStep *int `json:"min_step,omitempty"` + Strict *bool `json:"strict,omitempty"` +} + +// ToClusterResizeMap constructs a request body from ResizeOpts. +func (opts ResizeOpts) ToClusterResizeMap() (map[string]interface{}, error) { + if opts.AdjustmentType != "" && opts.Number == nil { + return nil, fmt.Errorf("Number field MUST NOT be empty when AdjustmentType field used") + } + + switch opts.Number.(type) { + case nil, int, int32, int64: + // Valid type. Always allow + case float32, float64: + if opts.AdjustmentType != ChangeInPercentageAdjustment { + return nil, fmt.Errorf("Only ChangeInPercentageAdjustment allows float value for Number field") + } + default: + return nil, fmt.Errorf("Number field must be either int, float, or omitted") + } + + return gophercloud.BuildRequestBody(opts, "resize") +} + +func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterResizeMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ScaleInOptsBuilder allows extensions to add additional parameters to the +// ScaleIn request. +type ScaleInOptsBuilder interface { + ToClusterScaleInMap() (map[string]interface{}, error) +} + +// ScaleInOpts represents options used to scale-in a cluster. +type ScaleInOpts struct { + Count *int `json:"count,omitempty"` +} + +// ToClusterScaleInMap constructs a request body from ScaleInOpts. +func (opts ScaleInOpts) ToClusterScaleInMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "scale_in") +} + +// ScaleIn will reduce the capacity of a cluster. +func ScaleIn(client *gophercloud.ServiceClient, id string, opts ScaleInOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterScaleInMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ScaleOutOptsBuilder allows extensions to add additional parameters to the +// ScaleOut request. +type ScaleOutOptsBuilder interface { + ToClusterScaleOutMap() (map[string]interface{}, error) +} + +// ScaleOutOpts represents options used to scale-out a cluster. +type ScaleOutOpts struct { + Count int `json:"count,omitempty"` +} + +// ToClusterScaleOutMap constructs a request body from ScaleOutOpts. +func (opts ScaleOutOpts) ToClusterScaleOutMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "scale_out") +} + +// ScaleOut will increase the capacity of a cluster. +func ScaleOut(client *gophercloud.ServiceClient, id string, opts ScaleOutOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterScaleOutMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// AttachPolicyOptsBuilder allows extensions to add additional parameters to the +// AttachPolicy request. +type AttachPolicyOptsBuilder interface { + ToClusterAttachPolicyMap() (map[string]interface{}, error) +} + +// PolicyOpts params +type AttachPolicyOpts struct { + PolicyID string `json:"policy_id" required:"true"` + Enabled *bool `json:"enabled,omitempty"` +} + +// ToClusterAttachPolicyMap constructs a request body from AttachPolicyOpts. +func (opts AttachPolicyOpts) ToClusterAttachPolicyMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "policy_attach") +} + +// Attach Policy will attach a policy to a cluster. +func AttachPolicy(client *gophercloud.ServiceClient, id string, opts AttachPolicyOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterAttachPolicyMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// UpdatePolicyOptsBuilder allows extensions to add additional parameters to the +// UpdatePolicy request. +type UpdatePolicyOptsBuilder interface { + ToClusterUpdatePolicyMap() (map[string]interface{}, error) +} + +// UpdatePolicyOpts represents options used to update a cluster policy. +type UpdatePolicyOpts struct { + PolicyID string `json:"policy_id" required:"true"` + Enabled *bool `json:"enabled,omitempty" required:"true"` +} + +// ToClusterUpdatePolicyMap constructs a request body from UpdatePolicyOpts. +func (opts UpdatePolicyOpts) ToClusterUpdatePolicyMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "policy_update") +} + +// UpdatePolicy will update a cluster's policy. +func UpdatePolicy(client *gophercloud.ServiceClient, id string, opts UpdatePolicyOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterUpdatePolicyMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// DetachPolicyOptsBuilder allows extensions to add additional parameters to the +// DetachPolicy request. +type DetachPolicyOptsBuilder interface { + ToClusterDetachPolicyMap() (map[string]interface{}, error) +} + +// DetachPolicyOpts represents options used to detach a policy from a cluster. +type DetachPolicyOpts struct { + PolicyID string `json:"policy_id" required:"true"` +} + +// ToClusterDetachPolicyMap constructs a request body from DetachPolicyOpts. +func (opts DetachPolicyOpts) ToClusterDetachPolicyMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "policy_detach") +} + +// DetachPolicy will detach a policy from a cluster. +func DetachPolicy(client *gophercloud.ServiceClient, id string, opts DetachPolicyOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterDetachPolicyMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ListPolicyOptsBuilder allows extensions to add additional parameters to the +// ListPolicies request. +type ListPoliciesOptsBuilder interface { + ToClusterPoliciesListQuery() (string, error) +} + +// ListPoliciesOpts represents options to list a cluster's policies. +type ListPoliciesOpts struct { + Enabled *bool `q:"enabled"` + Name string `q:"policy_name"` + Type string `q:"policy_type"` + Sort string `q:"sort"` +} + +// ToClusterPoliciesListQuery formats a ListOpts into a query string. +func (opts ListPoliciesOpts) ToClusterPoliciesListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListPolicies instructs OpenStack to provide a list of policies for a cluster. +func ListPolicies(client *gophercloud.ServiceClient, clusterID string, opts ListPoliciesOptsBuilder) pagination.Pager { + url := listPoliciesURL(client, clusterID) + if opts != nil { + query, err := opts.ToClusterPoliciesListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ClusterPolicyPage{pagination.SinglePageBase(r)} + }) +} + +// GetPolicy retrieves details of a cluster policy. +func GetPolicy(client *gophercloud.ServiceClient, clusterID string, policyID string) (r GetPolicyResult) { + _, r.Err = client.Get(getPolicyURL(client, clusterID, policyID), &r.Body, nil) + return +} + +// RecoverOptsBuilder allows extensions to add additional parameters to the +// Recover request. +type RecoverOptsBuilder interface { + ToClusterRecoverMap() (map[string]interface{}, error) +} + +// RecoverOpts represents options used to recover a cluster. +type RecoverOpts struct { + Operation RecoveryAction `json:"operation,omitempty"` + Check *bool `json:"check,omitempty"` + CheckCapacity *bool `json:"check_capacity,omitempty"` +} + +// ToClusterRecovermap constructs a request body from RecoverOpts. +func (opts RecoverOpts) ToClusterRecoverMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "recover") +} + +// Recover implements cluster recover request. +func Recover(client *gophercloud.ServiceClient, id string, opts RecoverOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterRecoverMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// Check will perform a health check on a cluster. +func Check(client *gophercloud.ServiceClient, id string) (r ActionResult) { + b := map[string]interface{}{ + "check": map[string]interface{}{}, + } + + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ToClusterCompleteLifecycleMap constructs a request body from CompleteLifecycleOpts. +func (opts CompleteLifecycleOpts) ToClusterCompleteLifecycleMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "complete_lifecycle") +} + +type CompleteLifecycleOpts struct { + LifecycleActionTokenID string `json:"lifecycle_action_token" required:"true"` +} + +func CompleteLifecycle(client *gophercloud.ServiceClient, id string, opts CompleteLifecycleOpts) (r ActionResult) { + b, err := opts.ToClusterCompleteLifecycleMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +func (opts AddNodesOpts) ToClusterAddNodeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "add_nodes") +} + +type AddNodesOpts struct { + Nodes []string `json:"nodes" required:"true"` +} + +func AddNodes(client *gophercloud.ServiceClient, id string, opts AddNodesOpts) (r ActionResult) { + b, err := opts.ToClusterAddNodeMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(nodeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + r.Header = result.Header + return +} + +func (opts RemoveNodesOpts) ToClusterRemoveNodeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "del_nodes") +} + +type RemoveNodesOpts struct { + Nodes []string `json:"nodes" required:"true"` +} + +func RemoveNodes(client *gophercloud.ServiceClient, clusterID string, opts RemoveNodesOpts) (r DeleteResult) { + b, err := opts.ToClusterRemoveNodeMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(nodeURL(client, clusterID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + r.Header = result.Header + return +} + +func (opts ReplaceNodesOpts) ToClusterReplaceNodeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "replace_nodes") +} + +type ReplaceNodesOpts struct { + Nodes map[string]string `json:"nodes" required:"true"` +} + +func ReplaceNodes(client *gophercloud.ServiceClient, id string, opts ReplaceNodesOpts) (r ActionResult) { + b, err := opts.ToClusterReplaceNodeMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(nodeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + r.Header = result.Header + return +} + +type CollectOptsBuilder interface { + ToClusterCollectMap() (string, error) +} + +// CollectOpts represents options to collect attribute values across a cluster +type CollectOpts struct { + Path string `q:"path" required:"true"` +} + +func (opts CollectOpts) ToClusterCollectMap() (string, error) { + return opts.Path, nil +} + +// Collect instructs OpenStack to aggregate attribute values across a cluster +func Collect(client *gophercloud.ServiceClient, id string, opts CollectOptsBuilder) (r CollectResult) { + query, err := opts.ToClusterCollectMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Get(collectURL(client, id, query), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// OperationName represents valid values for cluster operation +type OperationName string + +const ( + // Nova Profile Op Names + RebootOperation OperationName = "reboot" + RebuildOperation OperationName = "rebuild" + ChangePasswordOperation OperationName = "change_password" + PauseOperation OperationName = "pause" + UnpauseOperation OperationName = "unpause" + SuspendOperation OperationName = "suspend" + ResumeOperation OperationName = "resume" + LockOperation OperationName = "lock" + UnlockOperation OperationName = "unlock" + StartOperation OperationName = "start" + StopOperation OperationName = "stop" + RescueOperation OperationName = "rescue" + UnrescueOperation OperationName = "unrescue" + EvacuateOperation OperationName = "evacuate" + + // Heat Pofile Op Names + AbandonOperation OperationName = "abandon" +) + +// ToClusterOperationMap constructs a request body from OperationOpts. +func (opts OperationOpts) ToClusterOperationMap() (map[string]interface{}, error) { + operationArg := struct { + Filters OperationFilters `json:"filters,omitempty"` + Params OperationParams `json:"params,omitempty"` + }{ + Filters: opts.Filters, + Params: opts.Params, + } + + return gophercloud.BuildRequestBody(operationArg, string(opts.Operation)) +} + +// OperationOptsBuilder allows extensions to add additional parameters to the +// Op request. +type OperationOptsBuilder interface { + ToClusterOperationMap() (map[string]interface{}, error) +} +type OperationFilters map[string]interface{} +type OperationParams map[string]interface{} + +// OperationOpts represents options used to perform an operation on a cluster +type OperationOpts struct { + Operation OperationName `json:"operation" required:"true"` + Filters OperationFilters `json:"filters,omitempty"` + Params OperationParams `json:"params,omitempty"` +} + +func Ops(client *gophercloud.ServiceClient, id string, opts OperationOptsBuilder) (r ActionResult) { + b, err := opts.ToClusterOperationMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(opsURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/results.go new file mode 100644 index 000000000000..8548d4bb4c98 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/results.go @@ -0,0 +1,219 @@ +package clusters + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Cluster represents an OpenStack Clustering cluster. +type Cluster struct { + Config map[string]interface{} `json:"config"` + CreatedAt time.Time `json:"-"` + Data map[string]interface{} `json:"data"` + Dependents map[string]interface{} `json:"dependents"` + DesiredCapacity int `json:"desired_capacity"` + Domain string `json:"domain"` + ID string `json:"id"` + InitAt time.Time `json:"-"` + MaxSize int `json:"max_size"` + Metadata map[string]interface{} `json:"metadata"` + MinSize int `json:"min_size"` + Name string `json:"name"` + Nodes []string `json:"nodes"` + Policies []string `json:"policies"` + ProfileID string `json:"profile_id"` + ProfileName string `json:"profile_name"` + Project string `json:"project"` + Status string `json:"status"` + StatusReason string `json:"status_reason"` + Timeout int `json:"timeout"` + UpdatedAt time.Time `json:"-"` + User string `json:"user"` +} + +func (r *Cluster) UnmarshalJSON(b []byte) error { + type tmp Cluster + var s struct { + tmp + CreatedAt string `json:"created_at"` + InitAt string `json:"init_at"` + UpdatedAt string `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Cluster(s.tmp) + + if s.CreatedAt != "" { + r.CreatedAt, err = time.Parse(gophercloud.RFC3339Milli, s.CreatedAt) + if err != nil { + return err + } + } + + if s.InitAt != "" { + r.InitAt, err = time.Parse(gophercloud.RFC3339Milli, s.InitAt) + if err != nil { + return err + } + } + + if s.UpdatedAt != "" { + r.UpdatedAt, err = time.Parse(gophercloud.RFC3339Milli, s.UpdatedAt) + if err != nil { + return err + } + } + + return nil +} + +// ClusterPolicy represents and OpenStack Clustering cluster policy. +type ClusterPolicy struct { + ClusterID string `json:"cluster_id"` + ClusterName string `json:"cluster_name"` + Enabled bool `json:"enabled"` + ID string `json:"id"` + PolicyID string `json:"policy_id"` + PolicyName string `json:"policy_name"` + PolicyType string `json:"policy_type"` +} + +type ClusterAttributes struct { + ID string `json:"id"` + Value interface{} `json:"value"` +} + +// Action represents an OpenStack Clustering action. +type Action struct { + Action string `json:"action"` +} + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult-based result as a Cluster. +func (r commonResult) Extract() (*Cluster, error) { + var s struct { + Cluster *Cluster `json:"cluster"` + } + + err := r.ExtractInto(&s) + return s.Cluster, err +} + +// CreateResult is the response of a Create operations. Call its Extract method +// to interpret it as a Cluster. +type CreateResult struct { + commonResult +} + +// GetResult is the response of a Get operations. Call its Extract method to +// interpret it as a Cluster. +type GetResult struct { + commonResult +} + +// UpdateResult is the response of a Update operations. Call its Extract method +// to interpret it as a Cluster. +type UpdateResult struct { + commonResult +} + +// GetPolicyResult is the response of a Get operations. Call its Extract method +// to interpret it as a ClusterPolicy. +type GetPolicyResult struct { + gophercloud.Result +} + +// Extract interprets a GetPolicyResult as a ClusterPolicy. +func (r GetPolicyResult) Extract() (*ClusterPolicy, error) { + var s struct { + ClusterPolicy *ClusterPolicy `json:"cluster_policy"` + } + err := r.ExtractInto(&s) + return s.ClusterPolicy, err +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ClusterPage contains a single page of all clusters from a List call. +type ClusterPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Clusters contains any results. +func (page ClusterPage) IsEmpty() (bool, error) { + clusters, err := ExtractClusters(page) + return len(clusters) == 0, err +} + +// ClusterPolicyPage contains a single page of all policies from a List call +type ClusterPolicyPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines whether or not a page of ClusterPolicies contains any +// results. +func (page ClusterPolicyPage) IsEmpty() (bool, error) { + clusterPolicies, err := ExtractClusterPolicies(page) + return len(clusterPolicies) == 0, err +} + +// ActionResult is the response of Senlin actions. Call its Extract method to +// obtain the Action ID of the action. +type ActionResult struct { + gophercloud.Result +} + +// Extract interprets any Action result as an Action. +func (r ActionResult) Extract() (string, error) { + var s struct { + Action string `json:"action"` + } + err := r.ExtractInto(&s) + return s.Action, err +} + +type CollectResult struct { + gophercloud.Result +} + +// ExtractClusters returns a slice of Clusters from the List operation. +func ExtractClusters(r pagination.Page) ([]Cluster, error) { + var s struct { + Clusters []Cluster `json:"clusters"` + } + err := (r.(ClusterPage)).ExtractInto(&s) + return s.Clusters, err +} + +// ExtractClusterPolicies returns a slice of ClusterPolicies from the +// ListClusterPolicies operation. +func ExtractClusterPolicies(r pagination.Page) ([]ClusterPolicy, error) { + var s struct { + ClusterPolicies []ClusterPolicy `json:"cluster_policies"` + } + err := (r.(ClusterPolicyPage)).ExtractInto(&s) + return s.ClusterPolicies, err +} + +// Extract returns collected attributes across a cluster +func (r CollectResult) Extract() ([]ClusterAttributes, error) { + var s struct { + Attributes []ClusterAttributes `json:"cluster_attributes"` + } + err := r.ExtractInto(&s) + return s.Attributes, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/doc.go new file mode 100644 index 000000000000..022157789316 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_clusters_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/fixtures.go new file mode 100644 index 000000000000..3d47671ef166 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/fixtures.go @@ -0,0 +1,705 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ClusterResponse = ` +{ + "cluster": { + "config": {}, + "created_at": "2015-02-10T14:26:14Z", + "data": {}, + "dependents": {}, + "desired_capacity": 3, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": "2015-02-10T15:26:14Z", + "max_size": 20, + "metadata": {}, + "min_size": 1, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": "2015-02-10T16:26:14Z", + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedCluster = clusters.Cluster{ + Config: map[string]interface{}{}, + CreatedAt: time.Date(2015, 2, 10, 14, 26, 14, 0, time.UTC), + Data: map[string]interface{}{}, + Dependents: map[string]interface{}{}, + DesiredCapacity: 3, + Domain: "", + ID: "7d85f602-a948-4a30-afd4-e84f47471c15", + InitAt: time.Date(2015, 2, 10, 15, 26, 14, 0, time.UTC), + MaxSize: 20, + Metadata: map[string]interface{}{}, + MinSize: 1, + Name: "cluster1", + Nodes: []string{ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac", + }, + Policies: []string{}, + ProfileID: "edc63d0a-2ca4-48fa-9854-27926da76a4a", + ProfileName: "mystack", + Project: "6e18cc2bdbeb48a5b3cad2dc499f6804", + Status: "ACTIVE", + StatusReason: "Cluster scale-in succeeded", + Timeout: 3600, + UpdatedAt: time.Date(2015, 2, 10, 16, 26, 14, 0, time.UTC), + User: "5e5bf8027826429c96af157f68dc9072", +} + +const ClusterResponse_EmptyTime = ` +{ + "cluster": { + "config": {}, + "created_at": null, + "data": {}, + "dependents": {}, + "desired_capacity": 3, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": null, + "max_size": 20, + "metadata": {}, + "min_size": 1, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": null, + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedCluster_EmptyTime = clusters.Cluster{ + Config: map[string]interface{}{}, + Data: map[string]interface{}{}, + Dependents: map[string]interface{}{}, + DesiredCapacity: 3, + Domain: "", + ID: "7d85f602-a948-4a30-afd4-e84f47471c15", + MaxSize: 20, + Metadata: map[string]interface{}{}, + MinSize: 1, + Name: "cluster1", + Nodes: []string{ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac", + }, + Policies: []string{}, + ProfileID: "edc63d0a-2ca4-48fa-9854-27926da76a4a", + ProfileName: "mystack", + Project: "6e18cc2bdbeb48a5b3cad2dc499f6804", + Status: "ACTIVE", + StatusReason: "Cluster scale-in succeeded", + Timeout: 3600, + User: "5e5bf8027826429c96af157f68dc9072", +} + +const ClusterResponse_Metadata = ` +{ + "cluster": { + "config": {}, + "created_at": "2015-02-10T14:26:14Z", + "data": {}, + "dependents": {}, + "desired_capacity": 3, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": "2015-02-10T15:26:14Z", + "max_size": 20, + "metadata": { + "test": { + "nil_interface": null, + "bool_value": false, + "string_value": "test_string", + "float_value": 123.3 + }, + "foo": "bar" + }, + "min_size": 1, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": "2015-02-10T16:26:14Z", + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedCluster_Metadata = clusters.Cluster{ + Config: map[string]interface{}{}, + CreatedAt: time.Date(2015, 2, 10, 14, 26, 14, 0, time.UTC), + Data: map[string]interface{}{}, + Dependents: map[string]interface{}{}, + DesiredCapacity: 3, + Domain: "", + ID: "7d85f602-a948-4a30-afd4-e84f47471c15", + InitAt: time.Date(2015, 2, 10, 15, 26, 14, 0, time.UTC), + MaxSize: 20, + MinSize: 1, + Metadata: map[string]interface{}{ + "foo": "bar", + "test": map[string]interface{}{ + "nil_interface": interface{}(nil), + "float_value": float64(123.3), + "string_value": "test_string", + "bool_value": false, + }, + }, + Name: "cluster1", + Nodes: []string{ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac", + }, + Policies: []string{}, + ProfileID: "edc63d0a-2ca4-48fa-9854-27926da76a4a", + ProfileName: "mystack", + Project: "6e18cc2bdbeb48a5b3cad2dc499f6804", + Status: "ACTIVE", + StatusReason: "Cluster scale-in succeeded", + Timeout: 3600, + UpdatedAt: time.Date(2015, 2, 10, 16, 26, 14, 0, time.UTC), + User: "5e5bf8027826429c96af157f68dc9072", +} + +const ListResponse = ` +{ + "clusters": [ + { + "config": {}, + "created_at": "2015-02-10T14:26:14Z", + "data": {}, + "dependents": {}, + "desired_capacity": 3, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": "2015-02-10T15:26:14Z", + "max_size": 20, + "min_size": 1, + "metadata": {}, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": "2015-02-10T16:26:14Z", + "user": "5e5bf8027826429c96af157f68dc9072" + }, + { + "config": {}, + "created_at": null, + "data": {}, + "dependents": {}, + "desired_capacity": 3, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": null, + "max_size": 20, + "metadata": {}, + "min_size": 1, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": null, + "user": "5e5bf8027826429c96af157f68dc9072" + } + ] +}` + +var ExpectedClusters = []clusters.Cluster{ExpectedCluster, ExpectedCluster_EmptyTime} + +const UpdateResponse = ` +{ + "cluster": { + "config": {}, + "created_at": "2015-02-10T14:26:14Z", + "data": {}, + "dependents": {}, + "desired_capacity": 4, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": "2015-02-10T15:26:14Z", + "max_size": -1, + "metadata": {}, + "min_size": 0, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "profile1", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": "2015-02-10T16:26:14Z", + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +const UpdateResponse_EmptyTime = ` +{ + "cluster": { + "config": {}, + "created_at": null, + "data": {}, + "dependents": {}, + "desired_capacity": 3, + "domain": null, + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_at": null, + "max_size": 20, + "metadata": {}, + "min_size": 1, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": null, + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +const ActionResponse = ` +{ + "action": "2a0ff107-e789-4660-a122-3816c43af703" +}` + +const ExpectedActionID = "2a0ff107-e789-4660-a122-3816c43af703" + +const OperationActionResponse = ` +{ + "action": "2a0ff107-e789-4660-a122-3816c43af703" +}` + +const OperationExpectedActionID = "2a0ff107-e789-4660-a122-3816c43af703" + +const ListPoliciesResult = `{ + "cluster_policies": [ + { + "cluster_id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "cluster_name": "cluster4", + "enabled": true, + "id": "06be3a1f-b238-4a96-a737-ceec5714087e", + "policy_id": "714fe676-a08f-4196-b7af-61d52eeded15", + "policy_name": "dp01", + "policy_type": "senlin.policy.deletion-1.0" + } + ] +}` + +var ExpectedClusterPolicy = clusters.ClusterPolicy{ + ClusterID: "7d85f602-a948-4a30-afd4-e84f47471c15", + ClusterName: "cluster4", + Enabled: true, + ID: "06be3a1f-b238-4a96-a737-ceec5714087e", + PolicyID: "714fe676-a08f-4196-b7af-61d52eeded15", + PolicyName: "dp01", + PolicyType: "senlin.policy.deletion-1.0", +} + +var ExpectedListPolicies = []clusters.ClusterPolicy{ExpectedClusterPolicy} + +const GetPolicyResponse = ` +{ + "cluster_policy": { + "cluster_id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "cluster_name": "cluster4", + "enabled": true, + "id": "06be3a1f-b238-4a96-a737-ceec5714087e", + "policy_id": "714fe676-a08f-4196-b7af-61d52eeded15", + "policy_name": "dp01", + "policy_type": "senlin.policy.deletion-1.0" + } +}` + +const CollectResponse = ` +{ + "cluster_attributes": [{ + "id": "foo", + "value": "bar" + } + ] +}` + +var ExpectedCollectAttributes = []clusters.ClusterAttributes{ + { + ID: "foo", + Value: string("bar"), + }, +} + +func HandleCreateClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.Header().Add("Location", "http://senlin.cloud.blizzard.net:8778/v1/actions/625628cd-f877-44be-bde0-fec79f84e13d") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterResponse) + }) +} + +func HandleCreateClusterEmptyTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterResponse_EmptyTime) + }) +} + +func HandleCreateClusterMetadataSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterResponse_Metadata) + }) +} + +func HandleGetClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterResponse) + }) +} + +func HandleGetClusterEmptyTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterResponse_EmptyTime) + }) +} + +func HandleListClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ListResponse) + }) +} + +func HandleUpdateClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/"+ExpectedCluster.ID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterResponse) + }) +} + +func HandleUpdateClusterEmptyTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/"+ExpectedCluster_EmptyTime.ID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse_EmptyTime) + }) +} + +func HandleDeleteClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleResizeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleScaleInSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleScaleOutSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleListPoliciesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ListPoliciesResult) + }) +} + +func HandleGetPolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/policies/714fe676-a08f-4196-b7af-61d52eeded15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetPolicyResponse) + }) +} + +func HandleRecoverSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleAttachPolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleDetachPolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleUpdatePolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleCheckSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleLifecycleSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.Header().Add("Location", "http://senlin.cloud.blizzard.net:8778/v1/actions/2a0ff107-e789-4660-a122-3816c43af703") + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleAddNodesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleRemoveNodesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleReplaceNodeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleClusterCollectSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/attrs/foo.bar", 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.Header().Add("X-OpenStack-Request-ID", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, CollectResponse) + }) +} + +func HandleOpsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/7d85f602-a948-4a30-afd4-e84f47471c15/ops", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, OperationActionResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/requests_test.go new file mode 100644 index 000000000000..1de7161a4b00 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/testing/requests_test.go @@ -0,0 +1,493 @@ +package testing + +import ( + "strings" + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateClusterSuccessfully(t) + + minSize := 1 + opts := clusters.CreateOpts{ + Name: "cluster1", + DesiredCapacity: 3, + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + MinSize: &minSize, + MaxSize: 20, + Timeout: 3600, + Metadata: map[string]interface{}{}, + Config: map[string]interface{}{}, + } + + res := clusters.Create(fake.ServiceClient(), opts) + th.AssertNoErr(t, res.Err) + + location := res.Header.Get("Location") + th.AssertEquals(t, "http://senlin.cloud.blizzard.net:8778/v1/actions/625628cd-f877-44be-bde0-fec79f84e13d", location) + + locationFields := strings.Split(location, "actions/") + actionID := locationFields[1] + th.AssertEquals(t, "625628cd-f877-44be-bde0-fec79f84e13d", actionID) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster, *actual) +} + +func TestCreateClusterEmptyTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateClusterEmptyTimeSuccessfully(t) + + minSize := 1 + opts := clusters.CreateOpts{ + Name: "cluster1", + DesiredCapacity: 3, + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + MinSize: &minSize, + MaxSize: 20, + Timeout: 3600, + Metadata: map[string]interface{}{}, + Config: map[string]interface{}{}, + } + + actual, err := clusters.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster_EmptyTime, *actual) +} + +func TestCreateClusterMetadata(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateClusterMetadataSuccessfully(t) + + minSize := 1 + opts := clusters.CreateOpts{ + Name: "cluster1", + DesiredCapacity: 3, + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + MinSize: &minSize, + MaxSize: 20, + Timeout: 3600, + Metadata: map[string]interface{}{ + "foo": "bar", + "test": map[string]interface{}{ + "nil_interface": interface{}(nil), + "float_value": float64(123.3), + "string_value": "test_string", + "bool_value": false, + }, + }, + Config: map[string]interface{}{}, + } + + actual, err := clusters.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster_Metadata, *actual) +} + +func TestGetCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetClusterSuccessfully(t) + + actual, err := clusters.Get(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster, *actual) +} + +func TestGetClusterEmptyTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetClusterEmptyTimeSuccessfully(t) + + actual, err := clusters.Get(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster_EmptyTime, *actual) +} + +func TestListClusters(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListClusterSuccessfully(t) + + count := 0 + + clusters.List(fake.ServiceClient(), clusters.ListOpts{GlobalProject: new(bool)}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := clusters.ExtractClusters(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedClusters, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestUpdateCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateClusterSuccessfully(t) + + updateOpts := clusters.UpdateOpts{ + Name: "cluster1", + ProfileID: "edc63d0a-2ca4-48fa-9854-27926da76a4a", + } + + actual, err := clusters.Update(fake.ServiceClient(), ExpectedCluster.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster, *actual) +} + +func TestUpdateClusterEmptyTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateClusterEmptyTimeSuccessfully(t) + + updateOpts := clusters.UpdateOpts{ + Name: "cluster1", + ProfileID: "edc63d0a-2ca4-48fa-9854-27926da76a4a", + } + + actual, err := clusters.Update(fake.ServiceClient(), ExpectedCluster_EmptyTime.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCluster_EmptyTime, *actual) +} + +func TestDeleteCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteClusterSuccessfully(t) + + err := clusters.Delete(fake.ServiceClient(), "6dc6d336e3fc4c0a951b5698cd1236ee").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestResizeCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleResizeSuccessfully(t) + + maxSize := 5 + minSize := 1 + number := -2 + strict := true + opts := clusters.ResizeOpts{ + AdjustmentType: "CHANGE_IN_CAPACITY", + MaxSize: &maxSize, + MinSize: &minSize, + Number: number, + Strict: &strict, + } + + actionID, err := clusters.Resize(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +// Test case for Number field having a float value +func TestResizeClusterNumberFloat(t *testing.T) { + maxSize := 5 + minSize := 1 + number := 100.0 + strict := true + opts := clusters.ResizeOpts{ + AdjustmentType: "CHANGE_IN_PERCENTAGE", + MaxSize: &maxSize, + MinSize: &minSize, + Number: number, + Strict: &strict, + } + + _, err := opts.ToClusterResizeMap() + th.AssertNoErr(t, err) +} + +// Test case for missing Number field. +func TestResizeClusterMissingNumber(t *testing.T) { + maxSize := 5 + minSize := 1 + strict := true + opts := clusters.ResizeOpts{ + MaxSize: &maxSize, + MinSize: &minSize, + Strict: &strict, + } + + _, err := opts.ToClusterResizeMap() + th.AssertNoErr(t, err) +} + +// Test case for missing Number field which is required when AdjustmentType is specified +func TestResizeClusterInvalidParamsMissingNumber(t *testing.T) { + maxSize := 5 + minSize := 1 + strict := true + opts := clusters.ResizeOpts{ + AdjustmentType: "CHANGE_IN_CAPACITY", + MaxSize: &maxSize, + MinSize: &minSize, + Strict: &strict, + } + + _, err := opts.ToClusterResizeMap() + isValid := err == nil + th.AssertEquals(t, false, isValid) +} + +// Test case for float Number field which is only valid for CHANGE_IN_PERCENTAGE. +func TestResizeClusterInvalidParamsNumberFloat(t *testing.T) { + maxSize := 5 + minSize := 1 + number := 100.0 + strict := true + opts := clusters.ResizeOpts{ + AdjustmentType: "CHANGE_IN_CAPACITY", + MaxSize: &maxSize, + MinSize: &minSize, + Number: number, + Strict: &strict, + } + + _, err := opts.ToClusterResizeMap() + isValid := err == nil + th.AssertEquals(t, false, isValid) +} + +func TestClusterScaleIn(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleScaleInSuccessfully(t) + + count := 5 + scaleOpts := clusters.ScaleInOpts{ + Count: &count, + } + actionID, err := clusters.ScaleIn(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09", scaleOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestListClusterPolicies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListPoliciesSuccessfully(t) + + pageCount := 0 + err := clusters.ListPolicies(fake.ServiceClient(), ExpectedClusterPolicy.ClusterID, clusters.ListPoliciesOpts{Name: "Test"}).EachPage(func(page pagination.Page) (bool, error) { + pageCount++ + actual, err := clusters.ExtractClusterPolicies(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedListPolicies, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) + th.AssertEquals(t, pageCount, 1) +} + +func TestGetClusterPolicies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetPolicySuccessfully(t) + + actual, err := clusters.GetPolicy(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", "714fe676-a08f-4196-b7af-61d52eeded15").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedClusterPolicy, *actual) +} + +func TestClusterRecover(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleRecoverSuccessfully(t) + + recoverOpts := clusters.RecoverOpts{ + Operation: clusters.RebuildRecovery, + Check: new(bool), + CheckCapacity: new(bool), + } + actionID, err := clusters.Recover(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09", recoverOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestAttachPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleAttachPolicySuccessfully(t) + + enabled := true + opts := clusters.AttachPolicyOpts{ + PolicyID: "policy1", + Enabled: &enabled, + } + actionID, err := clusters.AttachPolicy(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestDetachPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDetachPolicySuccessfully(t) + + opts := clusters.DetachPolicyOpts{ + PolicyID: "policy1", + } + actionID, err := clusters.DetachPolicy(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestUpdatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdatePolicySuccessfully(t) + + enabled := true + opts := clusters.UpdatePolicyOpts{ + PolicyID: "policy1", + Enabled: &enabled, + } + actionID, err := clusters.UpdatePolicy(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestClusterScaleOut(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleScaleOutSuccessfully(t) + + scaleOutOpts := clusters.ScaleOutOpts{ + Count: 5, + } + actionID, err := clusters.ScaleOut(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09", scaleOutOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestClusterCheck(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCheckSuccessfully(t) + + actionID, err := clusters.Check(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestLifecycle(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleLifecycleSuccessfully(t) + + opts := clusters.CompleteLifecycleOpts{ + LifecycleActionTokenID: "976528c6-dcf6-4d8d-9f4c-588f4e675f29", + } + + res := clusters.CompleteLifecycle(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09", opts) + location := res.Header.Get("Location") + th.AssertEquals(t, "http://senlin.cloud.blizzard.net:8778/v1/actions/2a0ff107-e789-4660-a122-3816c43af703", location) + + actionID, err := res.Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "2a0ff107-e789-4660-a122-3816c43af703", actionID) +} + +func TestAddNodes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleAddNodesSuccessfully(t) + + opts := clusters.AddNodesOpts{ + Nodes: []string{"node1", "node2", "node3"}, + } + result, err := clusters.AddNodes(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, result, "2a0ff107-e789-4660-a122-3816c43af703") +} + +func TestRemoveNodes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRemoveNodesSuccessfully(t) + opts := clusters.RemoveNodesOpts{ + Nodes: []string{"node1", "node2", "node3"}, + } + err := clusters.RemoveNodes(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestReplaceNodes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleReplaceNodeSuccessfully(t) + opts := clusters.ReplaceNodesOpts{ + Nodes: map[string]string{"node-1234": "node-5678"}, + } + actionID, err := clusters.ReplaceNodes(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, actionID, "2a0ff107-e789-4660-a122-3816c43af703") +} + +func TestClusterCollect(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleClusterCollectSuccessfully(t) + opts := clusters.CollectOpts{ + Path: "foo.bar", + } + attributes, err := clusters.Collect(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCollectAttributes, attributes) +} + +func TestOperation(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleOpsSuccessfully(t) + + clusterOpts := clusters.OperationOpts{ + Operation: clusters.PauseOperation, + Filters: clusters.OperationFilters{"role": "slave"}, + Params: clusters.OperationParams{"type": "soft"}, + } + actual, err := clusters.Ops(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", clusterOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, OperationExpectedActionID, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/urls.go new file mode 100644 index 000000000000..ea1fa0d52cfa --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters/urls.go @@ -0,0 +1,58 @@ +package clusters + +import "github.com/gophercloud/gophercloud" + +var apiVersion = "v1" +var apiName = "clusters" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id, "actions") +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func listPoliciesURL(client *gophercloud.ServiceClient, clusterID string) string { + return client.ServiceURL(apiVersion, apiName, clusterID, "policies") +} + +func getPolicyURL(client *gophercloud.ServiceClient, clusterID string, policyID string) string { + return client.ServiceURL(apiVersion, apiName, clusterID, "policies", policyID) +} + +func nodeURL(client *gophercloud.ServiceClient, id string) string { + return actionURL(client, id) +} + +func collectURL(client *gophercloud.ServiceClient, clusterID string, path string) string { + return client.ServiceURL(apiVersion, apiName, clusterID, "attrs", path) +} + +func opsURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id, "ops") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/doc.go new file mode 100644 index 000000000000..05db5590016f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/doc.go @@ -0,0 +1,33 @@ +/* +Package events provides listing and retrieving of senlin events for the +OpenStack Clustering Service. + +Example to List Events + + opts := events.ListOpts{ + Limit: 5, + } + + err = events.List(serviceClient, opts).EachPage(func(page pagination.Page) (bool, error) { + eventInfos, err := events.ExtractEvents(page) + if err != nil { + return false, err + } + + for _, eventInfo := range eventInfos { + fmt.Println("%+v\n", eventInfo) + } + return true, nil + }) + +Example to Get an Event + + eventID := "edce3528-864f-41fb-8759-f4707925cc09" + event, err := events.Get(serviceClient, eventID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("Event %+v: ", event) +*/ +package events diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/requests.go new file mode 100644 index 000000000000..c962122ef8fc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/requests.go @@ -0,0 +1,53 @@ +package events + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToEventListQuery() (string, error) +} + +// ListOpts represents options used to list events. +type ListOpts struct { + Limit int `q:"limit,omitempty"` + Level int `q:"level,omitempty"` + Marker string `q:"marker,omitempty"` + Sort string `q:"sort,omitempty"` + GlobalProject *bool `q:"global_project,omitempty"` + OID string `q:"oid,omitempty"` + OType string `q:"otype,omitempty"` + OName string `q:"oname,omitempty"` + ClusterID string `q:"cluster_id,omitempty"` + Action string `q:"action"` +} + +// ToEventListQuery builds a query string from ListOpts. +func (opts ListOpts) ToEventListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of events. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToEventListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return EventPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details of a single event. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/results.go new file mode 100644 index 000000000000..b8542d268561 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/results.go @@ -0,0 +1,66 @@ +package events + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Event represents a detailed Event. +type Event struct { + Action string `json:"action"` + Cluster string `json:"cluster"` + ClusterID string `json:"cluster_id"` + ID string `json:"id"` + Level string `json:"level"` + Metadata map[string]interface{} `json:"meta_data"` + OID string `json:"oid"` + OName string `json:"oname"` + OType string `json:"otype"` + Project string `json:"project"` + Status string `json:"status"` + StatusReason string `json:"status_reason"` + Timestamp time.Time `json:"timestamp"` + User string `json:"user"` +} + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult-based result as an Event. +func (r commonResult) Extract() (*Event, error) { + var s struct { + Event *Event `json:"event"` + } + err := r.ExtractInto(&s) + return s.Event, err +} + +// GetResult is the response of a Get operations. Call its Extract method to +// interpret it as an Event. +type GetResult struct { + commonResult +} + +// EventPage contains a single page of all events from a List call. +type EventPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a EventPage contains any results. +func (r EventPage) IsEmpty() (bool, error) { + events, err := ExtractEvents(r) + return len(events) == 0, err +} + +// ExtractEvents returns a slice of Events from the List operation. +func ExtractEvents(r pagination.Page) ([]Event, error) { + var s struct { + Events []Event `json:"events"` + } + err := (r.(EventPage)).ExtractInto(&s) + return s.Events, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/doc.go new file mode 100644 index 000000000000..93a668d499b3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_events_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/fixtures.go new file mode 100644 index 000000000000..6e6b8fd33f04 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/fixtures.go @@ -0,0 +1,131 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/events" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ListResponse = ` +{ + "events": [ + { + "action": "CLUSTER_CREATE", + "cluster": null, + "cluster_id": null, + "id": "edce3528-864f-41fb-8759-f4707925cc09", + "level": "INFO", + "meta_data": {}, + "oid": "0df0931b-e251-4f2e-8719-4ebfda3627ba", + "oname": "cluster001", + "otype": "CLUSTER", + "project": "f1fe61dcda2f4618a14c10dc7abc214d", + "status": "start", + "status_reason": "Initializing", + "timestamp": "2015-03-05T08:53:15Z", + "user": "8bcd2cdca7684c02afc9e4f2fc0f0c79" + }, + { + "action": "NODE_DELETE", + "cluster": null, + "cluster_id": null, + "id": "abcd1234-864f-41fb-8759-f4707925dd10", + "level": "INFO", + "meta_data": {}, + "oid": "0df0931b-e251-4f2e-8719-4ebfda3627ba", + "oname": "node119", + "otype": "node", + "project": "f1fe61dcda2f4618a14c10dc7abc214d", + "status": "start", + "status_reason": "84492c96", + "timestamp": "2015-03-06T18:53:15Z", + "user": "8bcd2cdca7684c02afc9e4f2fc0f0c79" + } + ] +} +` + +const GetResponse = ` +{ + "event": { + "action": "CLUSTER_CREATE", + "cluster_id": null, + "id": "edce3528-864f-41fb-8759-f4707925cc09", + "level": "INFO", + "meta_data": {}, + "oid": "0df0931b-e251-4f2e-8719-4ebfda3627ba", + "oname": "cluster001", + "otype": "CLUSTER", + "project": "f1fe61dcda2f4618a14c10dc7abc214d", + "status": "start", + "status_reason": "Initializing", + "timestamp": "2015-03-05T08:53:15Z", + "user": "8bcd2cdca7684c02afc9e4f2fc0f0c79" + } +} +` + +var ExpectedEvent1 = events.Event{ + Action: "CLUSTER_CREATE", + Cluster: "", + ClusterID: "", + ID: "edce3528-864f-41fb-8759-f4707925cc09", + Level: "INFO", + Metadata: map[string]interface{}{}, + OID: "0df0931b-e251-4f2e-8719-4ebfda3627ba", + OName: "cluster001", + OType: "CLUSTER", + Project: "f1fe61dcda2f4618a14c10dc7abc214d", + Status: "start", + StatusReason: "Initializing", + Timestamp: time.Date(2015, 3, 5, 8, 53, 15, 0, time.UTC), + User: "8bcd2cdca7684c02afc9e4f2fc0f0c79", +} + +var ExpectedEvent2 = events.Event{ + Action: "NODE_DELETE", + Cluster: "", + ClusterID: "", + ID: "abcd1234-864f-41fb-8759-f4707925dd10", + Level: "INFO", + Metadata: map[string]interface{}{}, + OID: "0df0931b-e251-4f2e-8719-4ebfda3627ba", + OName: "node119", + OType: "node", + Project: "f1fe61dcda2f4618a14c10dc7abc214d", + Status: "start", + StatusReason: "84492c96", + Timestamp: time.Date(2015, 3, 6, 18, 53, 15, 0, time.UTC), + User: "8bcd2cdca7684c02afc9e4f2fc0f0c79", +} + +var ExpectedEvents = []events.Event{ExpectedEvent1, ExpectedEvent2} + +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/events", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListResponse) + }) +} + +func HandleGetSuccessfully(t *testing.T, id string) { + th.Mux.HandleFunc("/v1/events/"+id, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, GetResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/requests_test.go new file mode 100644 index 000000000000..e06d28f205e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/testing/requests_test.go @@ -0,0 +1,45 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/events" + + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListEvents(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListSuccessfully(t) + + pageCount := 0 + err := events.List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + pageCount++ + actual, err := events.ExtractEvents(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedEvents, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if pageCount != 1 { + t.Errorf("Expected 1 page, got %d", pageCount) + } +} + +func TestGetEvent(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetSuccessfully(t, ExpectedEvent1.ID) + + actual, err := events.Get(fake.ServiceClient(), ExpectedEvent1.ID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedEvent1, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/urls.go new file mode 100644 index 000000000000..65d9c1a8f48c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/events/urls.go @@ -0,0 +1,22 @@ +package events + +import "github.com/gophercloud/gophercloud" + +var apiVersion = "v1" +var apiName = "events" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/doc.go new file mode 100644 index 000000000000..2bd644604bfb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/doc.go @@ -0,0 +1,111 @@ +/* +Package nodes provides information and interaction with the nodes through +the OpenStack Clustering service. + +Example to Create a Node + + opts := nodes.CreateOpts{ + ClusterID: "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + Metadata: map[string]interface{}{}, + Name: "node-e395be1e-002", + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + Role: "", + } + + node, err := nodes.Create(serviceClient, opts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("node", node) + +Example to List Nodes + + listOpts := nodes.ListOpts{ + Name: "testnode", + } + + allPages, err := nodes.List(serviceClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allNodes, err := nodes.ExtractNodes(allPages) + if err != nil { + panic(err) + } + + for _, node := range allNodes { + fmt.Printf("%+v\n", node) + } + +Example to Update a Node + + opts := nodes.UpdateOpts{ + Name: "new-node-name", + } + + nodeID := "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1" + node, err := nodes.Update(serviceClient, nodeID, opts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", node) + +Example to Delete a Node + + nodeID := "6dc6d336e3fc4c0a951b5698cd1236ee" + err := nodes.Delete(serviceClient, nodeID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get a Node + + nodeID := "node123" + node, err := nodes.Get(serviceClient, nodeID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", node) + +Example to Perform an Operation on a Node + + serviceClient.Microversion = "1.4" + nodeID := "node123" + operationOpts := nodes.OperationOpts{ + Operation: nodes.RebootOperation, + Params: nodes.OperationParams{"type": "SOFT"}, + } + actionID, err := nodes.Ops(serviceClient, nodeID, operationOpts).Extract() + if err != nil { + panic(err) + } + +Example to Recover a Node + + nodeID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + check := true + recoverOpts := nodes.RecoverOpts{ + Operation: nodes.RebuildRecovery, + Check: &check, + } + actionID, err := nodes.Recover(computeClient, nodeID, recoverOpts).Extract() + if err != nil { + panic(err) + } + fmt.Println("action=", actionID) + +Example to Check a Node + + nodeID := "b7b870e3-d3c5-4a93-b9d7-846c53b2c2da" + actionID, err := nodes.Check(serviceClient, nodeID).Extract() + if err != nil { + panic(err) + } + fmt.Println("action=", actionID) + +*/ +package nodes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/requests.go new file mode 100644 index 000000000000..a9ac890b96a6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/requests.go @@ -0,0 +1,255 @@ +package nodes + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToNodeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used to create a Node. +type CreateOpts struct { + Role string `json:"role,omitempty"` + ProfileID string `json:"profile_id" required:"true"` + ClusterID string `json:"cluster_id,omitempty"` + Name string `json:"name" required:"true"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// ToNodeCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "node") +} + +// Create requests the creation of a new node. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToNodeCreateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToNodeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update a Node. +type UpdateOpts struct { + Name string `json:"name,omitempty"` + ProfileID string `json:"profile_id,omitempty"` + Role string `json:"role,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// ToNodeUpdateMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "node") +} + +// Update requests the update of a node. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToNodeUpdateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ListOptsBuilder allows extensions to add additional parmeters to the +// List request. +type ListOptsBuilder interface { + ToNodeListQuery() (string, error) +} + +// ListOpts represents options used to list nodes. +type ListOpts struct { + Limit int `q:"limit"` + Marker string `q:"marker"` + Sort string `q:"sort"` + GlobalProject *bool `q:"global_project"` + ClusterID string `q:"cluster_id"` + Name string `q:"name"` + Status string `q:"status"` +} + +// ToNodeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToNodeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of nodes. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToNodeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return NodePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete deletes the specified node. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(deleteURL(client, id), nil) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Get makes a request against senlin to get a details of a node type +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + var result *http.Response + result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// OperationName represents valid values for node operation +type OperationName string + +const ( + // Nova Profile Op Names + RebootOperation OperationName = "reboot" + RebuildOperation OperationName = "rebuild" + ChangePasswordOperation OperationName = "change_password" + PauseOperation OperationName = "pause" + UnpauseOperation OperationName = "unpause" + SuspendOperation OperationName = "suspend" + ResumeOperation OperationName = "resume" + LockOperation OperationName = "lock" + UnlockOperation OperationName = "unlock" + StartOperation OperationName = "start" + StopOperation OperationName = "stop" + RescueOperation OperationName = "rescue" + UnrescueOperation OperationName = "unrescue" + EvacuateOperation OperationName = "evacuate" + + // Heat Pofile Op Names + AbandonOperation OperationName = "abandon" +) + +// ToNodeOperationMap constructs a request body from OperationOpts. +func (opts OperationOpts) ToNodeOperationMap() (map[string]interface{}, error) { + optsMap := map[string]interface{}{string(opts.Operation): opts.Params} + return optsMap, nil +} + +// OperationOptsBuilder allows extensions to add additional parameters to the +// Op request. +type OperationOptsBuilder interface { + ToNodeOperationMap() (map[string]interface{}, error) +} +type OperationParams map[string]interface{} + +// OperationOpts represents options used to perform an operation on a node +type OperationOpts struct { + Operation OperationName `json:"operation" required:"true"` + Params OperationParams `json:"params,omitempty"` +} + +func Ops(client *gophercloud.ServiceClient, id string, opts OperationOptsBuilder) (r ActionResult) { + b, err := opts.ToNodeOperationMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(opsURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +func (opts RecoverOpts) ToNodeRecoverMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "recover") +} + +// RecoverAction represents valid values for recovering a node. +type RecoverAction string + +const ( + RebootRecovery RecoverAction = "REBOOT" + RebuildRecovery RecoverAction = "REBUILD" + // RECREATE currently is NOT supported. See https://github.com/openstack/senlin/blob/b30b2b8496b2b8af243ccd5292f38aec7a95664f/senlin/profiles/base.py#L533 + RecreateRecovery RecoverAction = "RECREATE" +) + +type RecoverOpts struct { + Operation RecoverAction `json:"operation,omitempty"` + Check *bool `json:"check,omitempty"` +} + +func Recover(client *gophercloud.ServiceClient, id string, opts RecoverOpts) (r ActionResult) { + b, err := opts.ToNodeRecoverMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + r.Header = result.Header + return +} + +func Check(client *gophercloud.ServiceClient, id string) (r ActionResult) { + b := map[string]interface{}{ + "check": map[string]interface{}{}, + } + + var result *http.Response + result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/results.go new file mode 100644 index 000000000000..8921a7649e72 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/results.go @@ -0,0 +1,144 @@ +package nodes + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Node represents an OpenStack clustering node. +type Node struct { + ClusterID string `json:"cluster_id"` + CreatedAt time.Time `json:"-"` + Data map[string]interface{} `json:"data"` + Dependents map[string]interface{} `json:"dependents"` + Domain string `json:"domain"` + ID string `json:"id"` + Index int `json:"index"` + InitAt time.Time `json:"-"` + Metadata map[string]interface{} `json:"metadata"` + Name string `json:"name"` + PhysicalID string `json:"physical_id"` + ProfileID string `json:"profile_id"` + ProfileName string `json:"profile_name"` + Project string `json:"project"` + Role string `json:"role"` + Status string `json:"status"` + StatusReason string `json:"status_reason"` + UpdatedAt time.Time `json:"-"` + User string `json:"user"` +} + +func (r *Node) UnmarshalJSON(b []byte) error { + type tmp Node + var s struct { + tmp + CreatedAt string `json:"created_at"` + InitAt string `json:"init_at"` + UpdatedAt string `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Node(s.tmp) + + if s.CreatedAt != "" { + r.CreatedAt, err = time.Parse(time.RFC3339, s.CreatedAt) + if err != nil { + return err + } + } + + if s.InitAt != "" { + r.InitAt, err = time.Parse(time.RFC3339, s.InitAt) + if err != nil { + return err + } + } + + if s.UpdatedAt != "" { + r.UpdatedAt, err = time.Parse(time.RFC3339, s.UpdatedAt) + if err != nil { + return err + } + } + + return nil +} + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult-based result as a Node. +func (r commonResult) Extract() (*Node, error) { + var s struct { + Node *Node `json:"node"` + } + err := r.ExtractInto(&s) + return s.Node, err +} + +// CreateResult is the result of a Create operation. Call its Extract +// method to intepret it as a Node. +type CreateResult struct { + commonResult +} + +// GetResult is the result of a Get operation. Call its Extract method to +// interpret it as a Node. +type GetResult struct { + commonResult +} + +// DeleteResult is the result from a Delete operation. Call ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the result of an Update operation. Call its Extract method +// to interpet it as a Node. +type UpdateResult struct { + commonResult +} + +// NodePage contains a single page of all nodes from a List call. +type NodePage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a NodePage contains any results. +func (page NodePage) IsEmpty() (bool, error) { + nodes, err := ExtractNodes(page) + return len(nodes) == 0, err +} + +// ExtractNodes returns a slice of Nodes from the List operation. +func ExtractNodes(r pagination.Page) ([]Node, error) { + var s struct { + Nodes []Node `json:"nodes"` + } + err := (r.(NodePage)).ExtractInto(&s) + return s.Nodes, err +} + +// ActionResult is the response of Senlin actions. Call its Extract method to +// obtain the Action ID of the action. +type ActionResult struct { + gophercloud.Result +} + +// Extract interprets any Action result as an Action. +func (r ActionResult) Extract() (string, error) { + var s struct { + Action string `json:"action"` + } + err := r.ExtractInto(&s) + return s.Action, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/doc.go new file mode 100644 index 000000000000..cb76666d2dd2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_nodes_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/fixtures.go new file mode 100644 index 000000000000..1f972276f63a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/fixtures.go @@ -0,0 +1,364 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const CreateResponse = `{ + "node": { + "cluster_id": "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + "created_at": "2016-05-13T07:02:20Z", + "data": { + "internal_ports": [ + { + "network_id": "847e4f65-1ff1-42b1-9e74-74e6a109ad11", + "security_group_ids": ["8db277ab-1d98-4148-ba72-724721789427"], + "fixed_ips": [ + { + "subnet_id": "863b20c0-c011-4650-85c2-ad531f4570a4", + "ip_address": "10.63.177.162" + } + ], + "id": "43aa53d7-a70b-4f40-812f-4feecb687018", + "remove": true + } + ], + "placement": { + "zone": "nova" + } + }, + "dependents": {}, + "domain": "1235be1e-8d8e-43bb-bd6c-943eccf76a6d", + "id": "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + "index": 2, + "init_at": "2016-05-13T08:02:04Z", + "metadata": { + "test": { + "nil_interface": null, + "bool_value": false, + "string_value": "test_string", + "float_value": 123.3 + }, + "foo": "bar" + }, + "name": "node-e395be1e-002", + "physical_id": "66a81d68-bf48-4af5-897b-a3bfef7279a8", + "profile_id": "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + "profile_name": "pcirros", + "project": "eee0b7c083e84501bdd50fb269d2a10e", + "role": "", + "status": "ACTIVE", + "status_reason": "Creation succeeded", + "updated_at": null, + "user": "ab79b9647d074e46ac223a8fa297b846" + } +}` + +var ExpectedCreate = nodes.Node{ + ClusterID: "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + CreatedAt: time.Date(2016, 5, 13, 7, 2, 20, 0, time.UTC), + Data: map[string]interface{}{ + "internal_ports": []map[string]interface{}{ + { + "network_id": "847e4f65-1ff1-42b1-9e74-74e6a109ad11", + "security_group_ids": []interface{}{ + "8db277ab-1d98-4148-ba72-724721789427", + }, + "fixed_ips": []interface{}{ + map[string]interface{}{ + "subnet_id": "863b20c0-c011-4650-85c2-ad531f4570a4", + "ip_address": "10.63.177.162", + }, + }, + "id": "43aa53d7-a70b-4f40-812f-4feecb687018", + "remove": true, + }, + }, + "placement": map[string]interface{}{ + "zone": "nova", + }, + }, + Dependents: map[string]interface{}{}, + Domain: "1235be1e-8d8e-43bb-bd6c-943eccf76a6d", + ID: "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + Index: 2, + InitAt: time.Date(2016, 5, 13, 8, 2, 4, 0, time.UTC), + Metadata: map[string]interface{}{ + "foo": "bar", + "test": map[string]interface{}{ + "nil_interface": interface{}(nil), + "float_value": float64(123.3), + "string_value": "test_string", + "bool_value": false, + }, + }, + Name: "node-e395be1e-002", + PhysicalID: "66a81d68-bf48-4af5-897b-a3bfef7279a8", + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + ProfileName: "pcirros", + Project: "eee0b7c083e84501bdd50fb269d2a10e", + Role: "", + Status: "ACTIVE", + StatusReason: "Creation succeeded", + User: "ab79b9647d074e46ac223a8fa297b846", +} + +const ListResponse = ` +{ + "nodes": [ + { + "cluster_id": "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + "created_at": "2016-05-13T07:02:20Z", + "data": {}, + "dependents": {}, + "domain": null, + "id": "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + "index": 2, + "init_at": "2016-05-13T08:02:04Z", + "metadata": {}, + "name": "node-e395be1e-002", + "physical_id": "66a81d68-bf48-4af5-897b-a3bfef7279a8", + "profile_id": "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + "profile_name": "pcirros", + "project": "eee0b7c083e84501bdd50fb269d2a10e", + "role": "", + "status": "ACTIVE", + "status_reason": "Creation succeeded", + "updated_at": "2016-05-13T09:02:04Z", + "user": "ab79b9647d074e46ac223a8fa297b846" } + ] +}` + +var ExpectedList1 = nodes.Node{ + ClusterID: "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + CreatedAt: time.Date(2016, 5, 13, 7, 2, 20, 0, time.UTC), + Data: map[string]interface{}{}, + Dependents: map[string]interface{}{}, + Domain: "", + ID: "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + Index: 2, + InitAt: time.Date(2016, 5, 13, 8, 2, 4, 0, time.UTC), + Metadata: map[string]interface{}{}, + Name: "node-e395be1e-002", + PhysicalID: "66a81d68-bf48-4af5-897b-a3bfef7279a8", + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + ProfileName: "pcirros", + Project: "eee0b7c083e84501bdd50fb269d2a10e", + Role: "", + Status: "ACTIVE", + StatusReason: "Creation succeeded", + UpdatedAt: time.Date(2016, 5, 13, 9, 2, 4, 0, time.UTC), + User: "ab79b9647d074e46ac223a8fa297b846", +} + +var ExpectedList = []nodes.Node{ExpectedList1} + +const GetResponse = ` +{ + "node": { + "cluster_id": "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + "created_at": "2016-05-13T07:02:20Z", + "data": {}, + "dependents": {}, + "domain": null, + "id": "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + "index": 2, + "init_at": "2016-05-13T07:02:04Z", + "metadata": {"foo": "bar"}, + "name": "node-e395be1e-002", + "physical_id": "66a81d68-bf48-4af5-897b-a3bfef7279a8", + "profile_id": "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + "profile_name": "pcirros", + "project": "eee0b7c083e84501bdd50fb269d2a10e", + "role": "", + "status": "ACTIVE", + "status_reason": "Creation succeeded", + "updated_at": "2016-05-13T07:02:20Z", + "user": "ab79b9647d074e46ac223a8fa297b846" + } +}` + +var ExpectedGet = nodes.Node{ + ClusterID: "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + CreatedAt: time.Date(2016, 5, 13, 7, 2, 20, 0, time.UTC), + Data: map[string]interface{}{}, + Dependents: map[string]interface{}{}, + Domain: "", + ID: "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + Index: 2, + InitAt: time.Date(2016, 5, 13, 7, 2, 4, 0, time.UTC), + Metadata: map[string]interface{}{"foo": "bar"}, + Name: "node-e395be1e-002", + PhysicalID: "66a81d68-bf48-4af5-897b-a3bfef7279a8", + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + ProfileName: "pcirros", + Project: "eee0b7c083e84501bdd50fb269d2a10e", + Role: "", + Status: "ACTIVE", + StatusReason: "Creation succeeded", + UpdatedAt: time.Date(2016, 5, 13, 7, 2, 20, 0, time.UTC), + User: "ab79b9647d074e46ac223a8fa297b846", +} + +const UpdateResponse = ` +{ + "node": { + "cluster_id": "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + "created_at": "2016-05-13T07:02:20Z", + "data": {}, + "dependents": {}, + "domain": null, + "id": "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + "index": 2, + "init_at": "2016-05-13T08:02:04Z", + "metadata": {"foo":"bar"}, + "name": "node-e395be1e-002", + "physical_id": "66a81d68-bf48-4af5-897b-a3bfef7279a8", + "profile_id": "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + "profile_name": "pcirros", + "project": "eee0b7c083e84501bdd50fb269d2a10e", + "role": "", + "status": "ACTIVE", + "status_reason": "Creation succeeded", + "updated_at": "2016-05-13T09:02:04Z", + "user": "ab79b9647d074e46ac223a8fa297b846" + } +}` + +var ExpectedUpdate = nodes.Node{ + ClusterID: "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + CreatedAt: time.Date(2016, 5, 13, 7, 2, 20, 0, time.UTC), + Data: map[string]interface{}{}, + Dependents: map[string]interface{}{}, + Domain: "", + ID: "82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", + Index: 2, + InitAt: time.Date(2016, 5, 13, 8, 2, 4, 0, time.UTC), + Metadata: map[string]interface{}{"foo": "bar"}, + Name: "node-e395be1e-002", + PhysicalID: "66a81d68-bf48-4af5-897b-a3bfef7279a8", + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + ProfileName: "pcirros", + Project: "eee0b7c083e84501bdd50fb269d2a10e", + Role: "", + Status: "ACTIVE", + StatusReason: "Creation succeeded", + UpdatedAt: time.Date(2016, 5, 13, 9, 2, 4, 0, time.UTC), + User: "ab79b9647d074e46ac223a8fa297b846", +} + +const OperationActionResponse = ` +{ + "action": "2a0ff107-e789-4660-a122-3816c43af703" +}` + +const OperationExpectedActionID = "2a0ff107-e789-4660-a122-3816c43af703" + +const ActionResponse = ` +{ + "action": "2a0ff107-e789-4660-a122-3816c43af703" +}` + +const ExpectedActionID = "2a0ff107-e789-4660-a122-3816c43af703" + +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-3791a089-9d46-4671-a3f9-55e95e55d2b4") + w.Header().Add("Location", "http://senlin.cloud.blizzard.net:8778/v1/actions/ffd94dd8-6266-4887-9a8c-5b78b72136da") + + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, CreateResponse) + }) +} + +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ListResponse) + }) +} + +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes/573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetResponse) + }) +} + +func HandleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes/82fe28e0-9fcb-42ca-a2fa-6eb7dddd75a1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse) + }) +} + +func HandleOpsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes/7d85f602-a948-4a30-afd4-e84f47471c15/ops", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, OperationActionResponse) + }) +} + +func HandleRecoverSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-edce3528-864f-41fb-8759-f4707925cc09") + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, ActionResponse) + }) +} + +func HandleCheckSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/nodes/edce3528-864f-41fb-8759-f4707925cc09/actions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-ID", "req-edce3528-864f-41fb-8759-f4707925cc09") + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, ActionResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/requests_test.go new file mode 100644 index 000000000000..154bfb4729cb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/testing/requests_test.go @@ -0,0 +1,144 @@ +package testing + +import ( + "strings" + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateSuccessfully(t) + + createOpts := nodes.CreateOpts{ + ClusterID: "e395be1e-8d8e-43bb-bd6c-943eccf76a6d", + Metadata: map[string]interface{}{ + "foo": "bar", + "test": map[string]interface{}{ + "nil_interface": interface{}(nil), + "float_value": float64(123.3), + "string_value": "test_string", + "bool_value": false, + }, + }, + Name: "node-e395be1e-002", + ProfileID: "d8a48377-f6a3-4af4-bbbb-6e8bcaa0cbc0", + Role: "", + } + + res := nodes.Create(fake.ServiceClient(), createOpts) + th.AssertNoErr(t, res.Err) + + requestID := res.Header.Get("X-Openstack-Request-Id") + th.AssertEquals(t, "req-3791a089-9d46-4671-a3f9-55e95e55d2b4", requestID) + + location := res.Header.Get("Location") + th.AssertEquals(t, "http://senlin.cloud.blizzard.net:8778/v1/actions/ffd94dd8-6266-4887-9a8c-5b78b72136da", location) + + locationFields := strings.Split(location, "actions/") + actionID := locationFields[1] + th.AssertEquals(t, "ffd94dd8-6266-4887-9a8c-5b78b72136da", actionID) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCreate, *actual) +} + +func TestListNodes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListSuccessfully(t) + + count := 0 + err := nodes.List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + actual, err := nodes.ExtractNodes(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedList, actual) + count++ + return true, nil + }) + + th.AssertNoErr(t, err) + th.AssertEquals(t, count, 1) +} + +func TestDeleteNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteSuccessfully(t) + + deleteResult := nodes.Delete(fake.ServiceClient(), "6dc6d336e3fc4c0a951b5698cd1236ee") + th.AssertNoErr(t, deleteResult.ExtractErr()) +} + +func TestGetNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetSuccessfully(t) + + actual, err := nodes.Get(fake.ServiceClient(), "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedGet, *actual) +} + +func TestUpdateNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateSuccessfully(t) + + nodeOpts := nodes.UpdateOpts{ + Name: "node-e395be1e-002", + } + actual, err := nodes.Update(fake.ServiceClient(), ExpectedUpdate.ID, nodeOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdate, *actual) +} + +func TestOpsNode(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleOpsSuccessfully(t) + + nodeOpts := nodes.OperationOpts{ + Operation: nodes.PauseOperation, + } + actual, err := nodes.Ops(fake.ServiceClient(), "7d85f602-a948-4a30-afd4-e84f47471c15", nodeOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, OperationExpectedActionID, actual) +} + +func TestNodeRecover(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleRecoverSuccessfully(t) + recoverOpts := nodes.RecoverOpts{ + Operation: nodes.RebuildRecovery, + Check: new(bool), + } + actionID, err := nodes.Recover(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09", recoverOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} + +func TestNodeCheck(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCheckSuccessfully(t) + + actionID, err := nodes.Check(fake.ServiceClient(), "edce3528-864f-41fb-8759-f4707925cc09").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedActionID, actionID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/urls.go new file mode 100644 index 000000000000..1224dcb06644 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes/urls.go @@ -0,0 +1,42 @@ +package nodes + +import "github.com/gophercloud/gophercloud" + +var apiVersion = "v1" +var apiName = "nodes" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id, "actions") +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func opsURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id, "ops") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/doc.go new file mode 100644 index 000000000000..ffe75f6e1394 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/doc.go @@ -0,0 +1,96 @@ +/* +Package policies provides information and interaction with the policies through +the OpenStack Clustering service. + +Example to List Policies + + listOpts := policies.ListOpts{ + Limit: 2, + } + + allPages, err := policies.List(clusteringClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPolicies, err := policies.ExtractPolicies(allPages) + if err != nil { + panic(err) + } + + for _, policy := range allPolicies { + fmt.Printf("%+v\n", policy) + } + + +Example to Create a Policy + + opts := policies.CreateOpts{ + Name: "new_policy", + Spec: policies.Spec{ + Description: "new policy description", + Properties: map[string]interface{}{ + "hooks": map[string]interface{}{ + "type": "zaqar", + "params": map[string]interface{}{ + "queue": "my_zaqar_queue", + }, + "timeout": 10, + }, + }, + Type: "senlin.policy.deletion", + Version: "1.1", + }, + } + + createdPolicy, err := policies.Create(client, opts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Policy + + policyName := "get_policy" + policyDetail, err := policies.Get(clusteringClient, policyName).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", policyDetail) + +Example to Update a Policy + + opts := policies.UpdateOpts{ + Name: "update_policy", + } + + updatePolicy, err := policies.Update(client, opts).Extract() + if err != nil { + panic(err) + } + +Example to Validate a Policy + + opts := policies.ValidateOpts{ + Spec: policies.Spec{ + Description: "new policy description", + Properties: map[string]interface{}{ + "hooks": map[string]interface{}{ + "type": "zaqar", + "params": map[string]interface{}{ + "queue": "my_zaqar_queue", + }, + "timeout": 10, + }, + }, + Type: "senlin.policy.deletion", + Version: "1.1", + }, + } + + validatePolicy, err := policies.Validate(client, opts).Extract() + if err != nil { + panic(err) + } +*/ +package policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/requests.go new file mode 100644 index 000000000000..f3e2ad249b90 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/requests.go @@ -0,0 +1,193 @@ +package policies + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPolicyListQuery() (string, error) +} + +// ListOpts represents options used to list policies. +type ListOpts struct { + // Limit limits the number of Policies to return. + Limit int `q:"limit"` + + // Marker and Limit control paging. Marker instructs List where to start + // listing from. + Marker string `q:"marker"` + + // Sorts the response by one or more attribute and optional sort direction + // combinations. + Sort string `q:"sort"` + + // GlobalProject indicates whether to include resources for all projects or + // resources for the current project. + GlobalProject *bool `q:"global_project"` + + // Name to filter the response by the specified name property of the object. + Name string `q:"name"` + + // Filter the response by the specified type property of the object. + Type string `q:"type"` +} + +// ToPolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to retrieve a list of policies. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := policyListURL(client) + if opts != nil { + query, err := opts.ToPolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + p := PolicyPage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPolicyCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used to create a policy. +type CreateOpts struct { + Name string `json:"name"` + Spec Spec `json:"spec"` +} + +// ToPolicyCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToPolicyCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return map[string]interface{}{"policy": b}, nil +} + +// Create makes a request against the API to create a policy +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPolicyCreateMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(policyCreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Delete makes a request against the API to delete a policy. +func Delete(client *gophercloud.ServiceClient, policyID string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(policyDeleteURL(client, policyID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPolicyUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options to update a policy. +type UpdateOpts struct { + Name string `json:"name,omitempty"` +} + +// ToPolicyUpdateMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToPolicyUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "policy") +} + +// Update updates a specified policy. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPolicyUpdateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// ValidateOptsBuilder allows extensions to add additional parameters to the +// Validate request. +type ValidateOptsBuilder interface { + ToPolicyValidateMap() (map[string]interface{}, error) +} + +// ValidateOpts represents options used to validate a policy. +type ValidateOpts struct { + Spec Spec `json:"spec"` +} + +// ToPolicyValidateMap formats a CreateOpts into a body map. +func (opts ValidateOpts) ToPolicyValidateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "policy") +} + +// Validate policy will validate a specified policy. +func Validate(client *gophercloud.ServiceClient, opts ValidateOptsBuilder) (r ValidateResult) { + b, err := opts.ToPolicyValidateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(validateURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Get makes a request against the API to get details for a policy. +func Get(client *gophercloud.ServiceClient, policyTypeName string) (r GetResult) { + url := policyGetURL(client, policyTypeName) + + _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/results.go new file mode 100644 index 000000000000..89b565aaf9c7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/results.go @@ -0,0 +1,185 @@ +package policies + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Policy represents a clustering policy in the Openstack cloud. +type Policy struct { + CreatedAt time.Time `json:"-"` + Data map[string]interface{} `json:"data"` + Domain string `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` + Project string `json:"project"` + Spec Spec `json:"spec"` + Type string `json:"type"` + UpdatedAt time.Time `json:"-"` + User string `json:"user"` +} + +func (r *Policy) UnmarshalJSON(b []byte) error { + type tmp Policy + var s struct { + tmp + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Policy(s.tmp) + + if s.CreatedAt != "" { + r.CreatedAt, err = time.Parse(gophercloud.RFC3339MilliNoZ, s.CreatedAt) + if err != nil { + r.CreatedAt, err = time.Parse(time.RFC3339, s.CreatedAt) + if err != nil { + return err + } + } + } + + if s.UpdatedAt != "" { + r.UpdatedAt, err = time.Parse(gophercloud.RFC3339MilliNoZ, s.UpdatedAt) + if err != nil { + r.UpdatedAt, err = time.Parse(time.RFC3339, s.UpdatedAt) + if err != nil { + return err + } + } + } + + return nil +} + +// Spec represents an OpenStack clustering policy spec. +type Spec struct { + Description string `json:"description"` + Properties map[string]interface{} `json:"properties"` + Type string `json:"type"` + Version string `json:"-"` +} + +func (r *Spec) UnmarshalJSON(b []byte) error { + type tmp Spec + var s struct { + tmp + Version interface{} `json:"version"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Spec(s.tmp) + + switch t := s.Version.(type) { + case float64: + if t == 1 { + r.Version = fmt.Sprintf("%.1f", t) + } else { + r.Version = strconv.FormatFloat(t, 'f', -1, 64) + } + case string: + r.Version = t + } + + return nil +} + +func (r Spec) MarshalJSON() ([]byte, error) { + spec := struct { + Type string `json:"type"` + Version string `json:"version"` + Properties map[string]interface{} `json:"properties"` + }{ + Type: r.Type, + Version: r.Version, + Properties: r.Properties, + } + return json.Marshal(spec) +} + +// policyResult is the resposne of a base Policy result. +type policyResult struct { + gophercloud.Result +} + +// Extract interpets any policyResult-base result as a Policy. +func (r policyResult) Extract() (*Policy, error) { + var s struct { + Policy *Policy `json:"policy"` + } + err := r.ExtractInto(&s) + + return s.Policy, err +} + +// CreateResult is the result of an Update operation. Call its Extract +// method to interpret it as a Policy. +type CreateResult struct { + policyResult +} + +// GetResult is the result of a Get operation. Call its Extract method to +// interpret it as a Policy. +type GetResult struct { + policyResult +} + +// UpdateResult is the result of an Update operation. Call its Extract +// method to interpret it as a Policy. +type UpdateResult struct { + policyResult +} + +// ValidateResult is the result of a Validate operation. Call its Extract +// method to interpret it as a Policy. +type ValidateResult struct { + policyResult +} + +// DeleteResult is the result of a Delete operation. Call its Extract +// method to interpret it as a DeleteHeader. +type DeleteResult struct { + gophercloud.ErrResult +} + +// PolicyPage contains a list page of all policies from a List call. +type PolicyPage struct { + pagination.MarkerPageBase +} + +// IsEmpty determines if a PolicyPage contains any results. +func (page PolicyPage) IsEmpty() (bool, error) { + policies, err := ExtractPolicies(page) + return len(policies) == 0, err +} + +// LastMarker returns the last policy ID in a ListResult. +func (r PolicyPage) LastMarker() (string, error) { + policies, err := ExtractPolicies(r) + if err != nil { + return "", err + } + if len(policies) == 0 { + return "", nil + } + return policies[len(policies)-1].ID, nil +} + +// ExtractPolicies returns a slice of Policies from the List operation. +func ExtractPolicies(r pagination.Page) ([]Policy, error) { + var s struct { + Policies []Policy `json:"policies"` + } + err := (r.(PolicyPage)).ExtractInto(&s) + return s.Policies, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/doc.go new file mode 100644 index 000000000000..61bc1c3b6d4e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_policies_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/fixtures.go new file mode 100644 index 000000000000..cf03210184cb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/fixtures.go @@ -0,0 +1,513 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/policies" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const PolicyListBody1 = ` +{ + "policies": [ + { + "created_at": "2018-04-02T21:43:30.000000", + "data": {}, + "domain": null, + "id": "PolicyListBodyID1", + "name": "delpol", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": true, + "grace_period": 60, + "reduce_desired_capacity": false + }, + "type": "senlin.policy.deletion", + "version": 1 + }, + "type": "senlin.policy.deletion-1.0", + "updated_at": "2018-04-02T00:19:12Z", + "user": "fe43e41739154b72818565e0d2580819" + } + ] +} +` + +const PolicyListBody2 = ` +{ + "policies": [ + { + "created_at": "2018-04-02T22:29:36.000000", + "data": {}, + "domain": null, + "id": "PolicyListBodyID2", + "name": "delpol2", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": true, + "grace_period": 60, + "reduce_desired_capacity": false + }, + "type": "senlin.policy.deletion", + "version": "1.0" + }, + "type": "senlin.policy.deletion-1.0", + "updated_at": "2018-04-02T23:15:11.000000", + "user": "fe43e41739154b72818565e0d2580819" + } + ] +} +` + +const PolicyCreateBody = ` +{ + "policy": { + "created_at": "2018-04-04T00:18:36Z", + "data": {}, + "domain": null, + "id": "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + "name": "delpol4", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "hooks": { + "params": { + "queue": "zaqar_queue_name" + }, + "timeout": 180, + "type": "zaqar" + } + }, + "type": "senlin.policy.deletion", + "version": 1.1 + }, + "type": "senlin.policy.deletion-1.1", + "updated_at": null, + "user": "fe43e41739154b72818565e0d2580819" + } +} +` + +const PolicyGetBody = ` +{ + "policy": { + "created_at": "2018-04-02T21:43:30.000000", + "data": {}, + "domain": null, + "id": "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + "name": "delpol", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": true, + "grace_period": 60, + "reduce_desired_capacity": false + }, + "type": "senlin.policy.deletion", + "version": 1 + }, + "type": "senlin.policy.deletion-1.0", + "updated_at": "2018-04-02T00:19:12Z", + "user": "fe43e41739154b72818565e0d2580819" + } +} +` + +const PolicyUpdateBody = ` +{ + "policy": { + "created_at": "2018-04-02T21:43:30.000000", + "data": {}, + "domain": null, + "id": "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + "name": "delpol4", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "hooks": { + "params": { + "queue": "zaqar_queue_name" + }, + "timeout": 180, + "type": "zaqar" + } + }, + "type": "senlin.policy.deletion", + "version": 1.1 + }, + "type": "senlin.policy.deletion-1.1", + "updated_at": null, + "user": "fe43e41739154b72818565e0d2580819" + } +} +` + +const PolicyBadUpdateBody = ` +{ + "policy": { + "created_at": "invalid", + "data": {}, + "domain": null, + "id": "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + "name": "delpol4", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "hooks": { + "params": { + "queue": "zaqar_queue_name" + }, + "timeout": 180, + "type": "zaqar" + } + }, + "type": "senlin.policy.deletion", + "version": 1.1 + }, + "type": "invalid", + "updated_at": null, + "user": "fe43e41739154b72818565e0d2580819" + } +} +` + +const PolicyValidateBody = ` +{ + "policy": { + "created_at": "2018-04-02T21:43:30.000000", + "data": {}, + "domain": null, + "id": "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + "name": "delpol4", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "hooks": { + "params": { + "queue": "zaqar_queue_name" + }, + "timeout": 180, + "type": "zaqar" + } + }, + "type": "senlin.policy.deletion", + "version": 1.1 + }, + "type": "senlin.policy.deletion-1.1", + "updated_at": null, + "user": "fe43e41739154b72818565e0d2580819" + } +} +` + +const PolicyBadValidateBody = ` +{ + "policy": { + "created_at": "invalid", + "data": {}, + "domain": null, + "id": "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + "name": "delpol4", + "project": "018cd0909fb44cd5bc9b7a3cd664920e", + "spec": { + "description": "A policy for choosing victim node(s) from a cluster for deletion.", + "properties": { + "hooks": { + "params": { + "queue": "zaqar_queue_name" + }, + "timeout": 180, + "type": "zaqar" + } + }, + "type": "senlin.policy.deletion", + "version": 1.1 + }, + "type": "invalid", + "updated_at": null, + "user": "fe43e41739154b72818565e0d2580819" + } +} +` + +const PolicyDeleteRequestID = "req-7328d1b0-9945-456f-b2cd-5166b77d14a8" +const PolicyIDtoUpdate = "b99b3ab4-3aa6-4fba-b827-69b88b9c544a" +const PolicyIDtoGet = "b99b3ab4-3aa6-4fba-b827-69b88b9c544a" +const PolicyIDtoDelete = "1" + +var ExpectedPolicy1 = policies.Policy{ + CreatedAt: time.Date(2018, 4, 2, 21, 43, 30, 0, time.UTC), + Data: map[string]interface{}{}, + Domain: "", + ID: "PolicyListBodyID1", + Name: "delpol", + Project: "018cd0909fb44cd5bc9b7a3cd664920e", + + Spec: policies.Spec{ + Description: "A policy for choosing victim node(s) from a cluster for deletion.", + Properties: map[string]interface{}{ + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": true, + "grace_period": float64(60), + "reduce_desired_capacity": false, + }, + Type: "senlin.policy.deletion", + Version: "1.0", + }, + Type: "senlin.policy.deletion-1.0", + User: "fe43e41739154b72818565e0d2580819", + UpdatedAt: time.Date(2018, 4, 2, 0, 19, 12, 0, time.UTC), +} + +var ExpectedPolicy2 = policies.Policy{ + CreatedAt: time.Date(2018, 4, 2, 22, 29, 36, 0, time.UTC), + Data: map[string]interface{}{}, + Domain: "", + ID: "PolicyListBodyID2", + Name: "delpol2", + Project: "018cd0909fb44cd5bc9b7a3cd664920e", + + Spec: policies.Spec{ + Description: "A policy for choosing victim node(s) from a cluster for deletion.", + Properties: map[string]interface{}{ + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": true, + "grace_period": float64(60), + "reduce_desired_capacity": false, + }, + Type: "senlin.policy.deletion", + Version: "1.0", + }, + Type: "senlin.policy.deletion-1.0", + User: "fe43e41739154b72818565e0d2580819", + UpdatedAt: time.Date(2018, 4, 2, 23, 15, 11, 0, time.UTC), +} + +var ExpectedPolicies = [][]policies.Policy{ + []policies.Policy{ExpectedPolicy1}, + []policies.Policy{ExpectedPolicy2}, +} + +var ExpectedCreatePolicy = policies.Policy{ + CreatedAt: time.Date(2018, 4, 4, 0, 18, 36, 0, time.UTC), + Data: map[string]interface{}{}, + Domain: "", + ID: "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + Name: "delpol4", + Project: "018cd0909fb44cd5bc9b7a3cd664920e", + + Spec: policies.Spec{ + Description: "A policy for choosing victim node(s) from a cluster for deletion.", + Properties: map[string]interface{}{ + "hooks": map[string]interface{}{ + "params": map[string]interface{}{ + "queue": "zaqar_queue_name", + }, + "timeout": float64(180), + "type": "zaqar", + }, + }, + Type: "senlin.policy.deletion", + Version: "1.1", + }, + Type: "senlin.policy.deletion-1.1", + User: "fe43e41739154b72818565e0d2580819", +} + +var ExpectedGetPolicy = policies.Policy{ + CreatedAt: time.Date(2018, 4, 2, 21, 43, 30, 0, time.UTC), + Data: map[string]interface{}{}, + Domain: "", + ID: "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + Name: "delpol", + Project: "018cd0909fb44cd5bc9b7a3cd664920e", + + Spec: policies.Spec{ + Description: "A policy for choosing victim node(s) from a cluster for deletion.", + Properties: map[string]interface{}{ + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": true, + "grace_period": float64(60), + "reduce_desired_capacity": false, + }, + Type: "senlin.policy.deletion", + Version: "1.0", + }, + Type: "senlin.policy.deletion-1.0", + User: "fe43e41739154b72818565e0d2580819", + UpdatedAt: time.Date(2018, 4, 2, 0, 19, 12, 0, time.UTC), +} + +var ExpectedUpdatePolicy = policies.Policy{ + CreatedAt: time.Date(2018, 4, 2, 21, 43, 30, 0, time.UTC), + Data: map[string]interface{}{}, + Domain: "", + ID: "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + Name: "delpol4", + Project: "018cd0909fb44cd5bc9b7a3cd664920e", + + Spec: policies.Spec{ + Description: "A policy for choosing victim node(s) from a cluster for deletion.", + Properties: map[string]interface{}{ + "hooks": map[string]interface{}{ + "params": map[string]interface{}{ + "queue": "zaqar_queue_name", + }, + "timeout": float64(180), + "type": "zaqar", + }, + }, + Type: "senlin.policy.deletion", + Version: "1.1", + }, + Type: "senlin.policy.deletion-1.1", + User: "fe43e41739154b72818565e0d2580819", +} + +var ExpectedValidatePolicy = policies.Policy{ + CreatedAt: time.Date(2018, 4, 2, 21, 43, 30, 0, time.UTC), + Data: map[string]interface{}{}, + Domain: "", + ID: "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + Name: "delpol4", + Project: "018cd0909fb44cd5bc9b7a3cd664920e", + + Spec: policies.Spec{ + Description: "A policy for choosing victim node(s) from a cluster for deletion.", + Properties: map[string]interface{}{ + "hooks": map[string]interface{}{ + "params": map[string]interface{}{ + "queue": "zaqar_queue_name", + }, + "timeout": float64(180), + "type": "zaqar", + }, + }, + Type: "senlin.policy.deletion", + Version: "1.1", + }, + Type: "senlin.policy.deletion-1.1", + User: "fe43e41739154b72818565e0d2580819", +} + +func HandlePolicyList(t *testing.T) { + th.Mux.HandleFunc("/v1/policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, PolicyListBody1) + case "PolicyListBodyID1": + fmt.Fprintf(w, PolicyListBody2) + case "PolicyListBodyID2": + fmt.Fprintf(w, `{"policies":[]}`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +func HandlePolicyCreate(t *testing.T) { + th.Mux.HandleFunc("/v1/policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, PolicyCreateBody) + }) +} + +func HandlePolicyDelete(t *testing.T) { + th.Mux.HandleFunc("/v1/policies/"+PolicyIDtoDelete, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("X-OpenStack-Request-Id", PolicyDeleteRequestID) + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandlePolicyGet(t *testing.T) { + th.Mux.HandleFunc("/v1/policies/"+PolicyIDtoGet, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, PolicyGetBody) + }) +} + +func HandlePolicyUpdate(t *testing.T) { + th.Mux.HandleFunc("/v1/policies/"+PolicyIDtoUpdate, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, PolicyUpdateBody) + }) +} + +func HandleBadPolicyUpdate(t *testing.T) { + th.Mux.HandleFunc("/v1/policies/"+PolicyIDtoUpdate, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, PolicyBadUpdateBody) + }) +} + +func HandlePolicyValidate(t *testing.T) { + th.Mux.HandleFunc("/v1/policies/validate", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, PolicyValidateBody) + }) +} + +func HandleBadPolicyValidate(t *testing.T) { + th.Mux.HandleFunc("/v1/policies/validate", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, PolicyBadValidateBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/requests_test.go new file mode 100644 index 000000000000..7bf3a527ee51 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/testing/requests_test.go @@ -0,0 +1,148 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/policies" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListPolicies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyList(t) + + listOpts := policies.ListOpts{ + Limit: 1, + } + + count := 0 + err := policies.List(fake.ServiceClient(), listOpts).EachPage(func(page pagination.Page) (bool, error) { + actual, err := policies.ExtractPolicies(page) + if err != nil { + t.Errorf("Failed to extract policies: %v", err) + return false, err + } + + th.AssertDeepEquals(t, ExpectedPolicies[count], actual) + count++ + + return true, nil + }) + + th.AssertNoErr(t, err) + + if count != 2 { + t.Errorf("Expected 2 pages, got %d", count) + } +} + +func TestCreatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyCreate(t) + + expected := ExpectedCreatePolicy + + opts := policies.CreateOpts{ + Name: ExpectedCreatePolicy.Name, + Spec: ExpectedCreatePolicy.Spec, + } + + actual, err := policies.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, &expected, actual) +} + +func TestDeletePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyDelete(t) + + res := policies.Delete(fake.ServiceClient(), PolicyIDtoDelete) + th.AssertNoErr(t, res.ExtractErr()) + + requestID := res.Header["X-Openstack-Request-Id"][0] + th.AssertEquals(t, PolicyDeleteRequestID, requestID) +} + +func TestUpdatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyUpdate(t) + + expected := ExpectedUpdatePolicy + + opts := policies.UpdateOpts{ + Name: ExpectedUpdatePolicy.Name, + } + + actual, err := policies.Update(fake.ServiceClient(), PolicyIDtoUpdate, opts).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, &expected, actual) +} + +func TestBadUpdatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleBadPolicyUpdate(t) + + opts := policies.UpdateOpts{ + Name: ExpectedUpdatePolicy.Name, + } + + _, err := policies.Update(fake.ServiceClient(), PolicyIDtoUpdate, opts).Extract() + th.AssertEquals(t, false, err == nil) +} + +func TestValidatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyValidate(t) + + expected := ExpectedValidatePolicy + + opts := policies.ValidateOpts{ + Spec: ExpectedValidatePolicy.Spec, + } + + actual, err := policies.Validate(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, &expected, actual) +} + +func TestBadValidatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleBadPolicyValidate(t) + + opts := policies.ValidateOpts{ + Spec: ExpectedValidatePolicy.Spec, + } + + _, err := policies.Validate(fake.ServiceClient(), opts).Extract() + th.AssertEquals(t, false, err == nil) +} + +func TestGetPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyGet(t) + + actual, err := policies.Get(fake.ServiceClient(), PolicyIDtoGet).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedGetPolicy, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/urls.go new file mode 100644 index 000000000000..d558ab0dcc07 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policies/urls.go @@ -0,0 +1,32 @@ +package policies + +import "github.com/gophercloud/gophercloud" + +const ( + apiVersion = "v1" + apiName = "policies" +) + +func policyListURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func policyCreateURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func policyDeleteURL(client *gophercloud.ServiceClient, policyID string) string { + return client.ServiceURL(apiVersion, apiName, policyID) +} + +func policyGetURL(client *gophercloud.ServiceClient, policyID string) string { + return client.ServiceURL(apiVersion, apiName, policyID) +} + +func updateURL(client *gophercloud.ServiceClient, policyID string) string { + return client.ServiceURL(apiVersion, apiName, policyID) +} + +func validateURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName, "validate") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/doc.go index d89641c014fb..2b1b6d686021 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/doc.go @@ -1,8 +1,8 @@ /* -Package policytypes lists all policy types and shows details for a policy type from the OpenStack -Clustering Service. +Package policytypes lists all policy types and shows details for a policy type +from the OpenStack Clustering Service. -Example to list policy types +Example to List Policy Types allPages, err := policytypes.List(clusteringClient).AllPages() if err != nil { @@ -17,5 +17,15 @@ Example to list policy types for _, policyType := range allPolicyTypes { fmt.Printf("%+v\n", policyType) } + +Example to Get a Policy Type + + policyTypeName := "senlin.policy.affinity-1.0" + policyTypeDetail, err := policyTypes.Get(clusteringClient, policyTypeName).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", policyTypeDetail) */ package policytypes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/requests.go index 87ef56a04900..ca9a5a3be1f0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/requests.go @@ -8,7 +8,19 @@ import ( // List makes a request against the API to list policy types. func List(client *gophercloud.ServiceClient) pagination.Pager { url := policyTypeListURL(client) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { return PolicyTypePage{pagination.SinglePageBase(r)} }) } + +// Get makes a request against the API to get details for a policy type. +func Get(client *gophercloud.ServiceClient, policyTypeName string) (r GetResult) { + url := policyTypeGetURL(client, policyTypeName) + + _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/results.go index 2be8b3df092f..98c1e53cb7ab 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/results.go @@ -1,29 +1,50 @@ package policytypes import ( + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) -// PolicyType represents a clustering policy type in the Openstack cloud +// PolicyType represents a clustering policy type in the Openstack cloud. type PolicyType struct { Name string `json:"name"` Version string `json:"version"` SupportStatus map[string][]SupportStatusType `json:"support_status"` } -// SupportStatusType represents the support status information for a clustering policy type +// SupportStatusType represents the support status information for a +// clustering policy type. type SupportStatusType struct { Status string `json:"status"` Since string `json:"since"` } -// ExtractPolicyTypes interprets a page of results as a slice of PolicyTypes. -func ExtractPolicyTypes(r pagination.Page) ([]PolicyType, error) { +// PolicyTypeDetail represents the detailed policy type information for a +// clustering policy type. +type PolicyTypeDetail struct { + Name string `json:"name"` + Schema map[string]interface{} `json:"schema"` + SupportStatus map[string][]SupportStatusType `json:"support_status,omitempty"` +} + +// policyTypeResult is the base result of a Policy Type operation. +type policyTypeResult struct { + gophercloud.Result +} + +// Extract interprets any policyTypeResult result as a PolicyTypeDetail. +func (r policyTypeResult) Extract() (*PolicyTypeDetail, error) { var s struct { - PolicyTypes []PolicyType `json:"policy_types"` + PolicyType *PolicyTypeDetail `json:"policy_type"` } - err := (r.(PolicyTypePage)).ExtractInto(&s) - return s.PolicyTypes, err + err := r.ExtractInto(&s) + return s.PolicyType, err +} + +// GetResult is the result of a Get operation. Call its Extract method to +// interpret it as a PolicyTypeDetail. +type GetResult struct { + policyTypeResult } // PolicyTypePage contains a single page of all policy types from a List call. @@ -36,3 +57,12 @@ func (page PolicyTypePage) IsEmpty() (bool, error) { policyTypes, err := ExtractPolicyTypes(page) return len(policyTypes) == 0, err } + +// ExtractPolicyTypes returns a slice of PolicyTypes from a List operation. +func ExtractPolicyTypes(r pagination.Page) ([]PolicyType, error) { + var s struct { + PolicyTypes []PolicyType `json:"policy_types"` + } + err := (r.(PolicyTypePage)).ExtractInto(&s) + return s.PolicyTypes, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/fixtures.go index 4854d7032160..2732dd9ebfe8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/fixtures.go @@ -10,6 +10,8 @@ import ( fake "github.com/gophercloud/gophercloud/testhelper/client" ) +const FakePolicyTypetoGet = "fake-policytype" + const PolicyTypeBody = ` { "policy_types": [ @@ -69,71 +71,165 @@ const PolicyTypeBody = ` } ` -var ( - ExpectedPolicyTypes = []policytypes.PolicyType{ - { - Name: "senlin.policy.affinity", - Version: "1.0", - SupportStatus: map[string][]policytypes.SupportStatusType{ - "1.0": { - { - Status: "SUPPORTED", - Since: "2016.10", - }, - }, +const PolicyTypeDetailBody = ` +{ + "policy_type": { + "name": "senlin.policy.batch-1.0", + "schema": { + "max_batch_size": { + "default": -1, + "description": "Maximum number of nodes that will be updated in parallel.", + "required": false, + "type": "Integer", + "updatable": false + }, + "min_in_service": { + "default": 1, + "description": "Minimum number of nodes in service when performing updates.", + "required": false, + "type": "Integer", + "updatable": false + }, + "pause_time": { + "default": 60, + "description": "Interval in seconds between update batches if any.", + "required": false, + "type": "Integer", + "updatable": false + } + }, + "support_status": { + "1.0": [ + { + "status": "EXPERIMENTAL", + "since": "2017.02" + } + ] + } + } +} +` + +var ExpectedPolicyType1 = policytypes.PolicyType{ + Name: "senlin.policy.affinity", + Version: "1.0", + SupportStatus: map[string][]policytypes.SupportStatusType{ + "1.0": { + { + Status: "SUPPORTED", + Since: "2016.10", }, }, - { - Name: "senlin.policy.health", - Version: "1.0", - SupportStatus: map[string][]policytypes.SupportStatusType{ - "1.0": { - { - Status: "EXPERIMENTAL", - Since: "2016.10", - }, - }, + }, +} + +var ExpectedPolicyType2 = policytypes.PolicyType{ + Name: "senlin.policy.health", + Version: "1.0", + SupportStatus: map[string][]policytypes.SupportStatusType{ + "1.0": { + { + Status: "EXPERIMENTAL", + Since: "2016.10", }, }, - { - Name: "senlin.policy.scaling", - Version: "1.0", - SupportStatus: map[string][]policytypes.SupportStatusType{ - "1.0": { - { - Status: "SUPPORTED", - Since: "2016.04", - }, - }, + }, +} + +var ExpectedPolicyType3 = policytypes.PolicyType{ + Name: "senlin.policy.scaling", + Version: "1.0", + SupportStatus: map[string][]policytypes.SupportStatusType{ + "1.0": { + { + Status: "SUPPORTED", + Since: "2016.04", }, }, - { - Name: "senlin.policy.region_placement", - Version: "1.0", - SupportStatus: map[string][]policytypes.SupportStatusType{ - "1.0": { - { - Status: "EXPERIMENTAL", - Since: "2016.04", - }, - { - Status: "SUPPORTED", - Since: "2016.10", - }, - }, + }, +} + +var ExpectedPolicyType4 = policytypes.PolicyType{ + Name: "senlin.policy.region_placement", + Version: "1.0", + SupportStatus: map[string][]policytypes.SupportStatusType{ + "1.0": { + { + Status: "EXPERIMENTAL", + Since: "2016.04", + }, + { + Status: "SUPPORTED", + Since: "2016.10", }, }, - } -) + }, +} + +var ExpectedPolicyTypes = []policytypes.PolicyType{ + ExpectedPolicyType1, + ExpectedPolicyType2, + ExpectedPolicyType3, + ExpectedPolicyType4, +} + +var ExpectedPolicyTypeDetail = &policytypes.PolicyTypeDetail{ + Name: "senlin.policy.batch-1.0", + Schema: map[string]interface{}{ + "max_batch_size": map[string]interface{}{ + "default": float64(-1), + "description": "Maximum number of nodes that will be updated in parallel.", + "required": false, + "type": "Integer", + "updatable": false, + }, + "min_in_service": map[string]interface{}{ + "default": float64(1), + "description": "Minimum number of nodes in service when performing updates.", + "required": false, + "type": "Integer", + "updatable": false, + }, + "pause_time": map[string]interface{}{ + "default": float64(60), + "description": "Interval in seconds between update batches if any.", + "required": false, + "type": "Integer", + "updatable": false, + }, + }, + SupportStatus: map[string][]policytypes.SupportStatusType{ + "1.0": []policytypes.SupportStatusType{ + { + Status: "EXPERIMENTAL", + Since: "2017.02", + }, + }, + }, +} func HandlePolicyTypeList(t *testing.T) { - th.Mux.HandleFunc("/v1/policy-types", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.Mux.HandleFunc("/v1/policy-types", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, PolicyTypeBody) + }) +} + +func HandlePolicyTypeGet(t *testing.T) { + th.Mux.HandleFunc("/v1/policy-types/"+FakePolicyTypetoGet, + 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) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, PolicyTypeBody) - }) + fmt.Fprintf(w, PolicyTypeDetailBody) + }) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/requests_test.go index 2714c9876f5e..87e42fab1ac8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/testing/requests_test.go @@ -35,3 +35,15 @@ func TestListPolicyTypes(t *testing.T) { t.Errorf("Expected 1 page, got %d", count) } } + +func TestGetPolicyType(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandlePolicyTypeGet(t) + + actual, err := policytypes.Get(fake.ServiceClient(), FakePolicyTypetoGet).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedPolicyTypeDetail, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/urls.go index 3cc0fec8fbae..b291a95c701b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/policytypes/urls.go @@ -10,3 +10,7 @@ const ( func policyTypeListURL(client *gophercloud.ServiceClient) string { return client.ServiceURL(apiVersion, apiName) } + +func policyTypeGetURL(client *gophercloud.ServiceClient, policyTypeName string) string { + return client.ServiceURL(apiVersion, apiName, policyTypeName) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/doc.go new file mode 100644 index 000000000000..4e27eb88618b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/doc.go @@ -0,0 +1,110 @@ +/* +Package profiles provides information and interaction with profiles through +the OpenStack Clustering service. + +Example to Create a Profile + + networks := []map[string]interface{} { + {"network": "test-network"}, + } + + props := map[string]interface{}{ + "name": "test_gophercloud_profile", + "flavor": "t2.micro", + "image": "centos7.3-latest", + "networks": networks, + "security_groups": "", + } + + createOpts := profiles.CreateOpts { + Name: "test_profile", + Spec: profiles.Spec{ + Type: "os.nova.server", + Version: "1.0", + Properties: props, + }, + } + + profile, err := profiles.Create(serviceClient, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println("Profile", profile) + +Example to Get a Profile + + profile, err := profiles.Get(serviceClient, "profile-name").Extract() + if err != nil { + panic(err) + } + + fmt.Print("profile", profile) + + +Example to List Profiles + + listOpts := profiles.ListOpts{ + Limit: 2, + } + + profiles.List(serviceClient, listOpts).EachPage(func(page pagination.Page) (bool, error) { + allProfiles, err := profiles.ExtractProfiles(page) + if err != nil { + panic(err) + } + + for _, profile := range allProfiles { + fmt.Printf("%+v\n", profile) + } + return true, nil + }) + +Example to Update a Profile + + updateOpts := profiles.UpdateOpts{ + Name: "new-name", + } + + profile, err := profiles.Update(serviceClient, profileName, updateOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Print("profile", profile) + +Example to Delete a Profile + + profileID := "6dc6d336e3fc4c0a951b5698cd1236ee" + err := profiles.Delete(serviceClient, profileID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Validate a profile + + serviceClient.Microversion = "1.2" + + validateOpts := profiles.ValidateOpts{ + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.micro", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + } + + profile, err := profiles.Validate(serviceClient, validateOpts).Extract() + if err != nil { + panic(err) + } + +*/ +package profiles diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/requests.go new file mode 100644 index 000000000000..7d48ddafd7d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/requests.go @@ -0,0 +1,173 @@ +package profiles + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToProfileCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used for creating a profile. +type CreateOpts struct { + Name string `json:"name" required:"true"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + Spec Spec `json:"spec" required:"true"` +} + +// ToProfileCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToProfileCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "profile") +} + +// Create requests the creation of a new profile on the server. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToProfileCreateMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Get retrieves detail of a single profile. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + var result *http.Response + result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToProfileListQuery() (string, error) +} + +// ListOpts represents options used to list profiles. +type ListOpts struct { + GlobalProject *bool `q:"global_project"` + Limit int `q:"limit"` + Marker string `q:"marker"` + Name string `q:"name"` + Sort string `q:"sort"` + Type string `q:"type"` +} + +// ToProfileListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToProfileListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of profiles. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToProfileListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ProfilePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToProfileUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update a profile. +type UpdateOpts struct { + Metadata map[string]interface{} `json:"metadata,omitempty"` + Name string `json:"name,omitempty"` +} + +// ToProfileUpdateMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToProfileUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "profile") +} + +// Update updates a profile. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToProfileUpdateMap() + if err != nil { + r.Err = err + return r + } + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Delete deletes the specified profile via profile id. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(deleteURL(client, id), nil) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// ValidateOptsBuilder allows extensions to add additional parameters to the +// Validate request. +type ValidateOptsBuilder interface { + ToProfileValidateMap() (map[string]interface{}, error) +} + +// ValidateOpts params +type ValidateOpts struct { + Spec Spec `json:"spec" required:"true"` +} + +// ToProfileValidateMap formats a CreateOpts into a body map. +func (opts ValidateOpts) ToProfileValidateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "profile") +} + +// Validate profile. +func Validate(client *gophercloud.ServiceClient, opts ValidateOpts) (r ValidateResult) { + b, err := opts.ToProfileValidateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(validateURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/results.go new file mode 100644 index 000000000000..8ce15bad82dc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/results.go @@ -0,0 +1,166 @@ +package profiles + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Profile represent a detailed profile. +type Profile struct { + CreatedAt time.Time `json:"-"` + Domain string `json:"domain"` + ID string `json:"id"` + Metadata map[string]interface{} `json:"metadata"` + Name string `json:"name"` + Project string `json:"project"` + Spec Spec `json:"spec"` + Type string `json:"type"` + UpdatedAt time.Time `json:"-"` + User string `json:"user"` +} + +func (r *Profile) UnmarshalJSON(b []byte) error { + type tmp Profile + var s struct { + tmp + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Profile(s.tmp) + + if s.CreatedAt != "" { + r.CreatedAt, err = time.Parse(time.RFC3339, s.CreatedAt) + if err != nil { + return err + } + } + + if s.UpdatedAt != "" { + r.UpdatedAt, err = time.Parse(time.RFC3339, s.UpdatedAt) + if err != nil { + return err + } + } + + return nil +} + +// Spec represents a profile spec. +type Spec struct { + Type string `json:"type"` + Version string `json:"-"` + Properties map[string]interface{} `json:"properties"` +} + +func (r *Spec) UnmarshalJSON(b []byte) error { + type tmp Spec + var s struct { + tmp + Version interface{} `json:"version"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Spec(s.tmp) + + switch t := s.Version.(type) { + case float64: + if t == 1 { + r.Version = fmt.Sprintf("%.1f", t) + } else { + r.Version = strconv.FormatFloat(t, 'f', -1, 64) + } + case string: + r.Version = t + } + + return nil +} + +func (r Spec) MarshalJSON() ([]byte, error) { + spec := struct { + Type string `json:"type"` + Version string `json:"version"` + Properties map[string]interface{} `json:"properties"` + }{ + Type: r.Type, + Version: r.Version, + Properties: r.Properties, + } + return json.Marshal(spec) +} + +// commonResult is the base result of a Profile operation. +type commonResult struct { + gophercloud.Result +} + +// Extract provides access to Profile returned by the Get and Create functions. +func (r commonResult) Extract() (*Profile, error) { + var s struct { + Profile *Profile `json:"profile"` + } + err := r.ExtractInto(&s) + return s.Profile, err +} + +// CreateResult is the result of a Create operation. Call its Extract +// method to interpret it as a Profile. +type CreateResult struct { + commonResult +} + +// GetResult is the result of a Get operations. Call its Extract +// method to interpret it as a Profile. +type GetResult struct { + commonResult +} + +// UpdateResult is the result of a Update operations. Call its Extract +// method to interpret it as a Profile. +type UpdateResult struct { + commonResult +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ValidateResult is the response of a Validate operations. +type ValidateResult struct { + commonResult +} + +// ProfilePage contains a single page of all profiles from a List operation. +type ProfilePage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a ProfilePage contains any results. +func (page ProfilePage) IsEmpty() (bool, error) { + profiles, err := ExtractProfiles(page) + return len(profiles) == 0, err +} + +// ExtractProfiles returns a slice of Profiles from the List operation. +func ExtractProfiles(r pagination.Page) ([]Profile, error) { + var s struct { + Profiles []Profile `json:"profiles"` + } + err := (r.(ProfilePage)).ExtractInto(&s) + return s.Profiles, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/doc.go new file mode 100644 index 000000000000..2a99a8a64b3e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_profiles_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/fixtures.go new file mode 100644 index 000000000000..590f69ed46b3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/fixtures.go @@ -0,0 +1,463 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const CreateResponse = ` +{ + "profile": { + "created_at": "2016-01-03T16:22:23Z", + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": {}, + "name": "test-profile", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": "t2.small", + "image": "centos7.3-latest", + "name": "centos_server", + "networks": [ + { + "network": "private-network" + } + ] + }, + "type": "os.nova.server", + "version": "1.0" + }, + "type": "os.nova.server-1.0", + "updated_at": "2016-01-03T17:22:23Z", + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedCreate = profiles.Profile{ + CreatedAt: time.Date(2016, 1, 3, 16, 22, 23, 0, time.UTC), + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{}, + Name: "test-profile", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.small", + "image": "centos7.3-latest", + "name": "centos_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private-network"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + UpdatedAt: time.Date(2016, 1, 3, 17, 22, 23, 0, time.UTC), + User: "5e5bf8027826429c96af157f68dc9072", +} + +const GetResponse = ` +{ + "profile": { + "created_at": "2016-01-03T16:22:23Z", + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": {}, + "name": "pserver", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": 1, + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": [ + { + "network": "private" + } + ] + }, + "type": "os.nova.server", + "version": "1.0" + }, + "type": "os.nova.server-1.0", + "updated_at": null, + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedGet = profiles.Profile{ + CreatedAt: time.Date(2016, 1, 3, 16, 22, 23, 0, time.UTC), + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{}, + Name: "pserver", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": float64(1), + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + User: "5e5bf8027826429c96af157f68dc9072", +} + +const ListResponse = ` +{ + "profiles": [ + { + "created_at": "2016-01-03T16:22:23Z", + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": {}, + "name": "pserver", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": "t2.small", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": [ + { + "network": "private" + } + ] + }, + "type": "os.nova.server", + "version": 1.0 + }, + "type": "os.nova.server-1.0", + "updated_at": "2016-01-03T17:22:23Z", + "user": "5e5bf8027826429c96af157f68dc9072" + }, + { + "created_at": null, + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": {}, + "name": "pserver", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": "t2.small", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": [ + { + "network": "private" + } + ] + }, + "type": "os.nova.server", + "version": 1.0 + }, + "type": "os.nova.server-1.0", + "updated_at": null, + "user": "5e5bf8027826429c96af157f68dc9072" + }, + { + "created_at": "", + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": {}, + "name": "pserver", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": "t2.small", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": [ + { + "network": "private" + } + ] + }, + "type": "os.nova.server", + "version": "1.0" + }, + "type": "os.nova.server-1.0", + "updated_at": "", + "user": "5e5bf8027826429c96af157f68dc9072" + } + ] +}` + +var ExpectedListProfile1 = profiles.Profile{ + CreatedAt: time.Date(2016, 1, 3, 16, 22, 23, 0, time.UTC), + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{}, + Name: "pserver", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.small", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + UpdatedAt: time.Date(2016, 1, 3, 17, 22, 23, 0, time.UTC), + User: "5e5bf8027826429c96af157f68dc9072", +} + +var ExpectedListProfile2 = profiles.Profile{ + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{}, + Name: "pserver", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.small", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + User: "5e5bf8027826429c96af157f68dc9072", +} + +var ExpectedListProfile3 = profiles.Profile{ + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{}, + Name: "pserver", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.small", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + User: "5e5bf8027826429c96af157f68dc9072", +} + +var ExpectedList = []profiles.Profile{ + ExpectedListProfile1, + ExpectedListProfile2, + ExpectedListProfile3, +} + +const UpdateResponse = ` +{ + "profile": { + "created_at": "2016-01-03T16:22:23Z", + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": { + "foo": "bar" + }, + "name": "pserver", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": 1, + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": [ + { + "network": "private" + } + ] + }, + "type": "os.nova.server", + "version": "1.0" + }, + "type": "os.nova.server-1.0", + "updated_at": "2016-01-03T17:22:23Z", + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedUpdate = profiles.Profile{ + CreatedAt: time.Date(2016, 1, 3, 16, 22, 23, 0, time.UTC), + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{"foo": "bar"}, + Name: "pserver", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": float64(1), + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + UpdatedAt: time.Date(2016, 1, 3, 17, 22, 23, 0, time.UTC), + User: "5e5bf8027826429c96af157f68dc9072", +} + +const ValidateResponse = ` +{ + "profile": { + "created_at": "2016-01-03T16:22:23Z", + "domain": null, + "id": "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + "metadata": {}, + "name": "pserver", + "project": "42d9e9663331431f97b75e25136307ff", + "spec": { + "properties": { + "flavor": "t2.micro", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": [ + { + "network": "private" + } + ] + }, + "type": "os.nova.server", + "version": "1.0" + }, + "type": "os.nova.server-1.0", + "updated_at": "2016-01-03T17:22:23Z", + "user": "5e5bf8027826429c96af157f68dc9072" + } +}` + +var ExpectedValidate = profiles.Profile{ + CreatedAt: time.Date(2016, 1, 3, 16, 22, 23, 0, time.UTC), + Domain: "", + ID: "9e1c6f42-acf5-4688-be2c-8ce954ef0f23", + Metadata: map[string]interface{}{}, + Name: "pserver", + Project: "42d9e9663331431f97b75e25136307ff", + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.micro", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + Type: "os.nova.server-1.0", + UpdatedAt: time.Date(2016, 1, 3, 17, 22, 23, 0, time.UTC), + User: "5e5bf8027826429c96af157f68dc9072", +} + +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profiles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, CreateResponse) + }) +} + +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profiles/9e1c6f42-acf5-4688-be2c-8ce954ef0f23", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetResponse) + }) +} + +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profiles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ListResponse) + }) +} + +func HandleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profiles/9e1c6f42-acf5-4688-be2c-8ce954ef0f23", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse) + }) +} + +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profiles/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleValidateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profiles/validate", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "OpenStack-API-Version", "clustering 1.2") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ValidateResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/requests_test.go new file mode 100644 index 000000000000..8ddde9233cbd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/testing/requests_test.go @@ -0,0 +1,140 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateProfile(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateSuccessfully(t) + + networks := []map[string]interface{}{ + {"network": "private-network"}, + } + + props := map[string]interface{}{ + "name": "test_gopher_cloud_profile", + "flavor": "t2.small", + "image": "centos7.3-latest", + "networks": networks, + "security_groups": "", + } + + createOpts := &profiles.CreateOpts{ + Name: "TestProfile", + Spec: profiles.Spec{ + Type: "os.nova.server", + Version: "1.0", + Properties: props, + }, + } + + profile, err := profiles.Create(fake.ServiceClient(), createOpts).Extract() + if err != nil { + t.Errorf("Failed to extract profile: %v", err) + } + + th.AssertDeepEquals(t, ExpectedCreate, *profile) +} + +func TestGetProfile(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetSuccessfully(t) + + actual, err := profiles.Get(fake.ServiceClient(), ExpectedGet.ID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedGet, *actual) +} + +func TestListProfiles(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListSuccessfully(t) + + var iFalse bool + listOpts := profiles.ListOpts{ + GlobalProject: &iFalse, + } + + count := 0 + err := profiles.List(fake.ServiceClient(), listOpts).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := profiles.ExtractProfiles(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedList, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page of profiles, got %d pages instead", count) + } +} + +func TestUpdateProfile(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateSuccessfully(t) + + updateOpts := profiles.UpdateOpts{ + Name: "pserver", + } + + actual, err := profiles.Update(fake.ServiceClient(), ExpectedUpdate.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdate, *actual) +} + +func TestDeleteProfile(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteSuccessfully(t) + + deleteResult := profiles.Delete(fake.ServiceClient(), "6dc6d336e3fc4c0a951b5698cd1236ee") + th.AssertNoErr(t, deleteResult.ExtractErr()) +} + +func TestValidateProfile(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleValidateSuccessfully(t) + + validateOpts := profiles.ValidateOpts{ + Spec: profiles.Spec{ + Properties: map[string]interface{}{ + "flavor": "t2.micro", + "image": "cirros-0.3.4-x86_64-uec", + "key_name": "oskey", + "name": "cirros_server", + "networks": []interface{}{ + map[string]interface{}{"network": "private"}, + }, + }, + Type: "os.nova.server", + Version: "1.0", + }, + } + + client := fake.ServiceClient() + client.Microversion = "1.2" + client.Type = "clustering" + + profile, err := profiles.Validate(client, validateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedValidate, *profile) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/urls.go new file mode 100644 index 000000000000..5a3d56aa522f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles/urls.go @@ -0,0 +1,38 @@ +package profiles + +import "github.com/gophercloud/gophercloud" + +var apiVersion = "v1" +var apiName = "profiles" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func validateURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName, "validate") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/doc.go new file mode 100644 index 000000000000..39877ace88e1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/doc.go @@ -0,0 +1,47 @@ +/* +Package profiletypes lists all profile types and shows details for a profile type from the OpenStack +Clustering Service. + +Example to List ProfileType + + err = profiletypes.List(serviceClient).EachPage(func(page pagination.Page) (bool, error) { + profileTypes, err := profiletypes.ExtractProfileTypes(page) + if err != nil { + return false, err + } + + for _, profileType := range profileTypes { + fmt.Println("%+v\n", profileType) + } + return true, nil + }) + +Example to Get a ProfileType + + profileTypeName := "os.nova.server" + profileType, err := profiletypes.Get(clusteringClient, profileTypeName).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", profileType) + +Example of list operations supported by a profile type + serviceClient.Microversion = "1.5" + + profileTypeName := "os.nova.server-1.0" + allPages, err := profiletypes.ListOps(serviceClient, profileTypeName).AllPages() + if err != nil { + panic(err) + } + + ops, err := profiletypes.ExtractOps(allPages) + if err != nil { + panic(err) + } + + for _, op := range ops { + fmt.Printf("%+v\n", op) + } + +*/ +package profiletypes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/requests.go new file mode 100644 index 000000000000..37fe0313390f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/requests.go @@ -0,0 +1,28 @@ +package profiletypes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, + &gophercloud.RequestOpts{OkCodes: []int{200}}) + + return +} + +// List makes a request against the API to list profile types. +func List(client *gophercloud.ServiceClient) pagination.Pager { + url := listURL(client) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ProfileTypePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +func ListOps(client *gophercloud.ServiceClient, id string) pagination.Pager { + url := listOpsURL(client, id) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return OperationPage{pagination.SinglePageBase(r)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/results.go new file mode 100644 index 000000000000..5364727f67f4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/results.go @@ -0,0 +1,67 @@ +package profiletypes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// GetResult is the response of a Get operations. +type GetResult struct { + commonResult +} + +type Schema map[string]interface{} +type SupportStatus map[string]interface{} + +type ProfileType struct { + Name string `json:"name"` + Schema map[string]Schema `json:"schema"` + SupportStatus map[string][]SupportStatus `json:"support_status"` +} + +func (r commonResult) Extract() (*ProfileType, error) { + var s struct { + ProfileType *ProfileType `json:"profile_type"` + } + err := r.ExtractInto(&s) + return s.ProfileType, err +} + +// ExtractProfileTypes provides access to the list of profiles in a page acquired from the List operation. +func ExtractProfileTypes(r pagination.Page) ([]ProfileType, error) { + var s struct { + ProfileTypes []ProfileType `json:"profile_types"` + } + err := (r.(ProfileTypePage)).ExtractInto(&s) + return s.ProfileTypes, err +} + +// ProfileTypePage contains a single page of all profiles from a List call. +type ProfileTypePage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if ExtractProfileTypes contains any results. +func (page ProfileTypePage) IsEmpty() (bool, error) { + profileTypes, err := ExtractProfileTypes(page) + return len(profileTypes) == 0, err +} + +// OperationPage contains a single page of all profile type operations from a ListOps call. +type OperationPage struct { + pagination.SinglePageBase +} + +// ExtractOps provides access to the list of operations in a page acquired from the ListOps operation. +func ExtractOps(r pagination.Page) (map[string]interface{}, error) { + var s struct { + Operations map[string]interface{} `json:"operations"` + } + err := (r.(OperationPage)).ExtractInto(&s) + return s.Operations, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/doc.go new file mode 100644 index 000000000000..7890e625432b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_profiletypes_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/fixtures.go new file mode 100644 index 000000000000..64a112fff6b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/fixtures.go @@ -0,0 +1,296 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ProfileTypeRequestID = "req-7328d1b0-9945-456f-b2cd-5166b77d14a8" +const ListResponse = ` +{ + "profile_types": [ + { + "name": "os.nova.server-1.0", + "schema": { + "context": { + "description": "Customized security context for operating containers.", + "required": false, + "type": "Map", + "updatable": false + }, + "name": { + "description": "The name of the container.", + "required": false, + "type": "Map", + "updatable": false + } + } + }, + { + "name": "os.heat.stack-1.0", + "schema": { + "context": { + "default": {}, + "description": "A dictionary for specifying the customized context for stack operations", + "required": false, + "type": "Map", + "updatable": false + }, + "disable_rollback": { + "default": true, + "description": "A boolean specifying whether a stack operation can be rolled back.", + "required": false, + "type": "Boolean", + "updatable": true + }, + "environment": { + "default": {}, + "description": "A map that specifies the environment used for stack operations.", + "required": false, + "type": "Map", + "updatable": true + }, + "files": { + "default": {}, + "description": "Contents of files referenced by the template, if any.", + "required": false, + "type": "Map", + "updatable": true + } + }, + "support_status": { + "1.0": [ + { + "status": "SUPPORTED", + "since": "2016.04" + } + ] + } + } + ] +} +` + +const GetResponse1 = ` +{ + "profile_type": { + "name": "os.nova.server-1.0", + "schema": { + "context": { + "description": "Customized security context for operating containers.", + "required": false, + "type": "Map", + "updatable": false + }, + "name": { + "description": "The name of the container.", + "required": false, + "type": "Map", + "updatable": false + } + } + } +} +` + +const GetResponse15 = ` +{ + "profile_type": { + "name": "os.heat.stack-1.0", + "schema": { + "context": { + "default": {}, + "description": "A dictionary for specifying the customized context for stack operations", + "required": false, + "type": "Map", + "updatable": false + }, + "disable_rollback": { + "default": true, + "description": "A boolean specifying whether a stack operation can be rolled back.", + "required": false, + "type": "Boolean", + "updatable": true + }, + "environment": { + "default": {}, + "description": "A map that specifies the environment used for stack operations.", + "required": false, + "type": "Map", + "updatable": true + }, + "files": { + "default": {}, + "description": "Contents of files referenced by the template, if any.", + "required": false, + "type": "Map", + "updatable": true + } + }, + "support_status": { + "1.0": [ + { + "status": "SUPPORTED", + "since": "2016.04" + } + ] + } + } +} +` + +var ExpectedProfileType1 = profiletypes.ProfileType{ + Name: "os.nova.server-1.0", + Schema: map[string]profiletypes.Schema{ + "context": { + "description": "Customized security context for operating containers.", + "required": false, + "type": "Map", + "updatable": false, + }, + "name": { + "description": "The name of the container.", + "required": false, + "type": "Map", + "updatable": false, + }, + }, +} + +var ExpectedProfileType15 = profiletypes.ProfileType{ + Name: "os.heat.stack-1.0", + Schema: map[string]profiletypes.Schema{ + "context": { + "default": map[string]interface{}{}, + "description": "A dictionary for specifying the customized context for stack operations", + "required": false, + "type": "Map", + "updatable": false, + }, + "disable_rollback": { + "default": true, + "description": "A boolean specifying whether a stack operation can be rolled back.", + "required": false, + "type": "Boolean", + "updatable": true, + }, + "environment": { + "default": map[string]interface{}{}, + "description": "A map that specifies the environment used for stack operations.", + "required": false, + "type": "Map", + "updatable": true, + }, + "files": { + "default": map[string]interface{}{}, + "description": "Contents of files referenced by the template, if any.", + "required": false, + "type": "Map", + "updatable": true, + }, + }, + SupportStatus: map[string][]profiletypes.SupportStatus{ + "1.0": { + { + "status": "SUPPORTED", + "since": "2016.04", + }, + }, + }, +} + +var ExpectedProfileTypes = []profiletypes.ProfileType{ExpectedProfileType1, ExpectedProfileType15} + +func HandleList1Successfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profile-types", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListResponse) + }) +} + +func HandleGet1Successfully(t *testing.T, id string) { + th.Mux.HandleFunc("/v1/profile-types/"+id, 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.Header().Add("X-OpenStack-Request-ID", ProfileTypeRequestID) + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, GetResponse1) + }) +} + +func HandleGet15Successfully(t *testing.T, id string) { + th.Mux.HandleFunc("/v1/profile-types/"+id, 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.Header().Add("X-OpenStack-Request-ID", ProfileTypeRequestID) + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, GetResponse15) + }) +} + +const ProfileTypeName = "os.nova.server-1.0" + +const ListOpsResponse = ` +{ + "operations": { + "pause": { + "description": "Pause the server from running.", + "parameter": null + }, + "change_password": { + "description": "Change the administrator password.", + "parameters": { + "admin_pass": { + "description": "New password for the administrator.", + "required": false, + "type": "String" + } + } + } + } +} +` + +var ExpectedOps = map[string]interface{}{ + "change_password": map[string]interface{}{ + "description": "Change the administrator password.", + "parameters": map[string]interface{}{ + "admin_pass": map[string]interface{}{ + "description": "New password for the administrator.", + "required": false, + "type": "String", + }, + }, + }, + "pause": map[string]interface{}{ + "description": "Pause the server from running.", + "parameter": nil, + }, +} + +func HandleListOpsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/profile-types/"+ProfileTypeName+"/ops", 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.Header().Add("X-OpenStack-Request-ID", ProfileTypeRequestID) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOpsResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/requests_test.go new file mode 100644 index 000000000000..c4f379b64544 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/testing/requests_test.go @@ -0,0 +1,74 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListProfileTypes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleList1Successfully(t) + + pageCount := 0 + err := profiletypes.List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + pageCount++ + actual, err := profiletypes.ExtractProfileTypes(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedProfileTypes, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if pageCount != 1 { + t.Errorf("Expected 1 page, got %d", pageCount) + } +} + +func TestGetProfileType10(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGet1Successfully(t, ExpectedProfileType1.Name) + + actual, err := profiletypes.Get(fake.ServiceClient(), ExpectedProfileType1.Name).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedProfileType1, *actual) +} + +func TestGetProfileType15(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGet15Successfully(t, ExpectedProfileType15.Name) + + actual, err := profiletypes.Get(fake.ServiceClient(), ExpectedProfileType15.Name).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedProfileType15, *actual) +} + +func TestListProfileTypesOps(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListOpsSuccessfully(t) + + allPages, err := profiletypes.ListOps(fake.ServiceClient(), ProfileTypeName).AllPages() + th.AssertNoErr(t, err) + + allPolicyTypes, err := profiletypes.ExtractOps(allPages) + th.AssertNoErr(t, err) + + for k, v := range allPolicyTypes { + tools.PrintResource(t, k) + tools.PrintResource(t, v) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/urls.go new file mode 100644 index 000000000000..cec8bfe46a3a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/profiletypes/urls.go @@ -0,0 +1,28 @@ +package profiletypes + +import "github.com/gophercloud/gophercloud" + +const ( + apiVersion = "v1" + apiName = "profile-types" +) + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func profileTypeURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return profileTypeURL(client, id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func listOpsURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id, "ops") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/doc.go new file mode 100644 index 000000000000..92506c856b70 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/doc.go @@ -0,0 +1,81 @@ +/* +Package receivers provides information and interaction with the receivers through +the OpenStack Clustering service. + +Example to Create a Receiver + + createOpts := receivers.CreateOpts{ + Action: "CLUSTER_DEL_NODES", + ClusterID: "b7b870ee-d3c5-4a93-b9d7-846c53b2c2dc", + Name: "test_receiver", + Type: receivers.WebhookReceiver, + } + + receiver, err := receivers.Create(serviceClient, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", receiver) + +Example to Get a Receiver + + receiver, err := receivers.Get(serviceClient, "receiver-name").Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", receiver) + +Example to Delete receiver + + receiverID := "6dc6d336e3fc4c0a951b5698cd1236ee" + err := receivers.Delete(serviceClient, receiverID).ExtractErr() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", receiver) + +Example to Update Receiver + + updateOpts := receivers.UpdateOpts{ + Name: "new-name", + } + + receiverID := "6dc6d336e3fc4c0a951b5698cd1236ee" + receiver, err := receivers.Update(serviceClient, receiverID, updateOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", receiver) + +Example to List Receivers + + listOpts := receivers.ListOpts{ + Limit: 2, + } + + receivers.List(serviceClient, listOpts).EachPage(func(page pagination.Page) (bool, error) { + allReceivers, err := receivers.ExtractReceivers(page) + if err != nil { + panic(err) + } + + for _, receiver := range allReceivers { + fmt.Printf("%+v\n", receiver) + } + return true, nil + }) + +Example to Notify a Receiver + + receiverID := "6dc6d336e3fc4c0a951b5698cd1236ee" + requestID, err := receivers.Notify(serviceClient, receiverID).Extract() + if err != nil { + panic(err) + } + +*/ +package receivers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/requests.go new file mode 100644 index 000000000000..27d45136cbe3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/requests.go @@ -0,0 +1,157 @@ +package receivers + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ReceiverType represents a valid type of receiver +type ReceiverType string + +const ( + WebhookReceiver ReceiverType = "webhook" + MessageReceiver ReceiverType = "message" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToReceiverCreateMap() (map[string]interface{}, error) +} + +// CreatOpts represents options used to create a receiver. +type CreateOpts struct { + Name string `json:"name" required:"true"` + ClusterID string `json:"cluster_id,omitempty"` + Type ReceiverType `json:"type" required:"true"` + Action string `json:"action,omitempty"` + Actor map[string]interface{} `json:"actor,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// ToReceiverCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToReceiverCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "receiver") +} + +// Create requests the creation of a new receiver. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToReceiverCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToReceiverUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update a receiver. +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Action string `json:"action,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// ToReceiverUpdateMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToReceiverUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "receiver") +} + +// Update requests the update of a receiver. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToReceiverUpdateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Get retrieves details of a single receiver. Use Extract to convert its result into a Receiver. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + var result *http.Response + result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToReceiverListQuery() (string, error) +} + +// ListOpts represents options used to list recievers. +type ListOpts struct { + Limit int `q:"limit"` + Marker string `q:"marker"` + Sort string `q:"sort"` + GlobalProject *bool `q:"global_project"` + Name string `q:"name"` + Type string `q:"type"` + ClusterID string `q:"cluster_id"` + Action string `q:"action"` + User string `q:"user"` +} + +// ToReceiverListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToReceiverListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of cluster. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToReceiverListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ReceiverPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete deletes the specified receiver ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Notify Notifies message type receiver +func Notify(client *gophercloud.ServiceClient, id string) (r NotifyResult) { + result, err := client.Post(notifyURL(client, id), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + + if err == nil { + r.Header = result.Header + } + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/results.go new file mode 100644 index 000000000000..a25192b6a9ef --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/results.go @@ -0,0 +1,126 @@ +package receivers + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Receiver represent a detailed receiver +type Receiver struct { + Action string `json:"action"` + Actor map[string]interface{} `json:"actor"` + Channel map[string]interface{} `json:"channel"` + ClusterID string `json:"cluster_id"` + CreatedAt time.Time `json:"-"` + Domain string `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` + Params map[string]interface{} `json:"params"` + Project string `json:"project"` + Type string `json:"type"` + UpdatedAt time.Time `json:"-"` + User string `json:"user"` +} + +func (r *Receiver) UnmarshalJSON(b []byte) error { + type tmp Receiver + var s struct { + tmp + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Receiver(s.tmp) + + if s.CreatedAt != "" { + r.CreatedAt, err = time.Parse(time.RFC3339, s.CreatedAt) + if err != nil { + return err + } + } + + if s.UpdatedAt != "" { + r.UpdatedAt, err = time.Parse(time.RFC3339, s.UpdatedAt) + if err != nil { + return err + } + } + + return nil +} + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult-based result as a Receiver. +func (r commonResult) Extract() (*Receiver, error) { + var s struct { + Receiver *Receiver `json:"receiver"` + } + err := r.ExtractInto(&s) + return s.Receiver, err +} + +// CreateResult is the result of a Create operation. Call its Extract method +// to interpret it as a Receiver. +type CreateResult struct { + commonResult +} + +// GetResult is the result for of a Get operation. Call its Extract method +// to interpret it as a Receiver. +type GetResult struct { + commonResult +} + +// UpdateResult is the result of a Update operation. Call its Extract method +// to interpret it as a Receiver. +type UpdateResult struct { + commonResult +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// NotifyResult is the result from a Notify operation. Call its Extract +// method to determine if the call succeeded or failed. +type NotifyResult struct { + commonResult +} + +// ReceiverPage contains a single page of all nodes from a List operation. +type ReceiverPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a ReceiverPage contains any results. +func (page ReceiverPage) IsEmpty() (bool, error) { + receivers, err := ExtractReceivers(page) + return len(receivers) == 0, err +} + +// ExtractReceivers returns a slice of Receivers from the List operation. +func ExtractReceivers(r pagination.Page) ([]Receiver, error) { + var s struct { + Receivers []Receiver `json:"receivers"` + } + err := (r.(ReceiverPage)).ExtractInto(&s) + return s.Receivers, err +} + +// Extract returns action for notify receivers +func (r NotifyResult) Extract() (string, error) { + requestID := r.Header.Get("X-Openstack-Request-Id") + return requestID, r.Err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/doc.go new file mode 100644 index 000000000000..c692a35a01a4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_receivers_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/fixtures.go new file mode 100644 index 000000000000..8be67870bbb7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/fixtures.go @@ -0,0 +1,241 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const CreateResponse = ` +{ + "receiver": { + "action": "CLUSTER_SCALE_OUT", + "actor": { + "trust_id": [ + "6dc6d336e3fc4c0a951b5698cd1236d9" + ] + }, + "channel": { + "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1-8c6a-74ba891e5422/trigger?V=1&count=1" + }, + "cluster_id": "ae63a10b-4a90-452c-aef1-113a0b255ee3", + "created_at": "2015-11-04T05:21:41Z", + "domain": "Default", + "id": "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", + "name": "cluster_inflate", + "params": { + "count": "1" + }, + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "type": "webhook", + "updated_at": "2016-11-04T05:21:41Z", + "user": "b4ad2d6e18cc2b9c48049f6dbe8a5b3c" + } +}` + +var ExpectedReceiver = receivers.Receiver{ + Action: "CLUSTER_SCALE_OUT", + Actor: map[string]interface{}{ + "trust_id": []string{ + "6dc6d336e3fc4c0a951b5698cd1236d9", + }, + }, + Channel: map[string]interface{}{ + "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1-8c6a-74ba891e5422/trigger?V=1&count=1", + }, + ClusterID: "ae63a10b-4a90-452c-aef1-113a0b255ee3", + CreatedAt: time.Date(2015, 11, 4, 5, 21, 41, 0, time.UTC), + Domain: "Default", + ID: "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", + Name: "cluster_inflate", + Params: map[string]interface{}{ + "count": "1", + }, + Project: "6e18cc2bdbeb48a5b3cad2dc499f6804", + Type: "webhook", + UpdatedAt: time.Date(2016, 11, 4, 5, 21, 41, 0, time.UTC), + User: "b4ad2d6e18cc2b9c48049f6dbe8a5b3c", +} + +const GetResponse = ` +{ + "receiver": { + "action": "CLUSTER_SCALE_OUT", + "actor": { + "trust_id": [ + "6dc6d336e3fc4c0a951b5698cd1236d9" + ] + }, + "channel": { + "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1-8c6a-74ba891e5422/trigger?V=1&count=1" + }, + "cluster_id": "ae63a10b-4a90-452c-aef1-113a0b255ee3", + "created_at": "2015-11-04T05:21:41Z", + "domain": "Default", + "id": "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", + "name": "cluster_inflate", + "params": { + "count": "1" + }, + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "type": "webhook", + "updated_at": "2016-11-04T05:21:41Z", + "user": "b4ad2d6e18cc2b9c48049f6dbe8a5b3c" + } +}` + +const UpdateResponse = ` +{ + "receiver": { + "action": "CLUSTER_SCALE_OUT", + "actor": { + "trust_id": [ + "6dc6d336e3fc4c0a951b5698cd1236d9" + ] + }, + "channel": { + "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1-8c6a-74ba891e5422/trigger?V=1&count=1" + }, + "cluster_id": "ae63a10b-4a90-452c-aef1-113a0b255ee3", + "created_at": "2015-06-27T05:09:43Z", + "domain": "Default", + "id": "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", + "name": "cluster_inflate", + "params": { + "count": "1" + }, + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "type": "webhook", + "updated_at": null, + "user": "b4ad2d6e18cc2b9c48049f6dbe8a5b3c" + } +}` + +var ExpectedUpdateReceiver = receivers.Receiver{ + Action: "CLUSTER_SCALE_OUT", + Actor: map[string]interface{}{ + "trust_id": []string{ + "6dc6d336e3fc4c0a951b5698cd1236d9", + }, + }, + Channel: map[string]interface{}{ + "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1-8c6a-74ba891e5422/trigger?V=1&count=1", + }, + ClusterID: "ae63a10b-4a90-452c-aef1-113a0b255ee3", + CreatedAt: time.Date(2015, 6, 27, 5, 9, 43, 0, time.UTC), + Domain: "Default", + ID: "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", + Name: "cluster_inflate", + Params: map[string]interface{}{ + "count": "1", + }, + Project: "6e18cc2bdbeb48a5b3cad2dc499f6804", + Type: "webhook", + User: "b4ad2d6e18cc2b9c48049f6dbe8a5b3c", +} + +const ListResponse = ` +{ + "receivers": [ + { + "action": "CLUSTER_SCALE_OUT", + "actor": { + "trust_id": [ + "6dc6d336e3fc4c0a951b5698cd1236d9" + ] + }, + "channel": { + "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1-8c6a-74ba891e5422/trigger?V=1&count=1" + }, + "cluster_id": "ae63a10b-4a90-452c-aef1-113a0b255ee3", + "created_at": "2015-06-27T05:09:43Z", + "domain": "Default", + "id": "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", + "name": "cluster_inflate", + "params": { + "count": "1" + }, + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "type": "webhook", + "updated_at": null, + "user": "b4ad2d6e18cc2b9c48049f6dbe8a5b3c" + } + ] +}` + +var ExpectedReceiversList = []receivers.Receiver{ExpectedUpdateReceiver} +var ExpectedNotifyRequestID = "66a81d68-bf48-4af5-897b-a3bfef7279a8" + +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/receivers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, CreateResponse) + }) +} + +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/receivers/573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetResponse) + }) +} + +func HandleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/receivers/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse) + }) +} + +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/receivers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestFormValues(t, r, map[string]string{"limit": "2", "sort": "name:asc,status:desc"}) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ListResponse) + }) +} + +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/receivers/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleNotifySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/receivers/6dc6d336e3fc4c0a951b5698cd1236ee/notify", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", ExpectedNotifyRequestID) + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/requests_test.go new file mode 100644 index 000000000000..d04ac24abf9b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/testing/requests_test.go @@ -0,0 +1,105 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateReceiver(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateSuccessfully(t) + + opts := receivers.CreateOpts{ + Name: "cluster_inflate", + ClusterID: "ae63a10b-4a90-452c-aef1-113a0b255ee3", + Type: receivers.WebhookReceiver, + Action: "CLUSTER_SCALE_OUT", + Actor: map[string]interface{}{}, + Params: map[string]interface{}{}, + } + + actual, err := receivers.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedReceiver, *actual) +} + +func TestGetReceivers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetSuccessfully(t) + + actual, err := receivers.Get(fake.ServiceClient(), "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedReceiver, *actual) +} + +func TestUpdateReceiver(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateSuccessfully(t) + + opts := receivers.UpdateOpts{ + Name: "cluster_inflate", + Action: "CLUSTER_SCALE_OUT", + Params: map[string]interface{}{ + "count": "2", + }, + } + actual, err := receivers.Update(fake.ServiceClient(), "6dc6d336e3fc4c0a951b5698cd1236ee", opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdateReceiver, *actual) +} + +func TestListReceivers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListSuccessfully(t) + + opts := receivers.ListOpts{ + Limit: 2, + Sort: "name:asc,status:desc", + } + + count := 0 + receivers.List(fake.ServiceClient(), opts).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := receivers.ExtractReceivers(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedReceiversList, actual) + + return true, nil + }) + + th.AssertEquals(t, count, 1) +} + +func TestDeleteReceiver(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteSuccessfully(t) + + deleteResult := receivers.Delete(fake.ServiceClient(), "6dc6d336e3fc4c0a951b5698cd1236ee") + th.AssertNoErr(t, deleteResult.ExtractErr()) +} + +func TestNotifyReceivers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleNotifySuccessfully(t) + + requestID, err := receivers.Notify(fake.ServiceClient(), "6dc6d336e3fc4c0a951b5698cd1236ee").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedNotifyRequestID, requestID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/urls.go new file mode 100644 index 000000000000..62ad621b26bf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers/urls.go @@ -0,0 +1,38 @@ +package receivers + +import "github.com/gophercloud/gophercloud" + +var apiVersion = "v1" +var apiName = "receivers" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func notifyURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiVersion, apiName, id, "notify") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/doc.go new file mode 100644 index 000000000000..c76dc11ffba5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/doc.go @@ -0,0 +1,15 @@ +/* +Package webhooks provides the ability to trigger an action represented by a webhook from the OpenStack Clustering +Service. + +Example to Trigger webhook action + + result, err := webhooks.Trigger(serviceClient(), "f93f83f6-762b-41b6-b757-80507834d394", webhooks.TriggerOpts{V: "1"}).Extract() + if err != nil { + panic(err) + } + + fmt.Println("result", result) + +*/ +package webhooks diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/requests.go new file mode 100644 index 000000000000..1b211b4447c2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/requests.go @@ -0,0 +1,53 @@ +package webhooks + +import ( + "fmt" + "net/url" + + "github.com/gophercloud/gophercloud" +) + +// TriggerOpts represents options used for triggering an action +type TriggerOpts struct { + V string `q:"V" required:"true"` + Params map[string]string +} + +// TriggerOptsBuilder Query string builder interface for webhooks +type TriggerOptsBuilder interface { + ToWebhookTriggerQuery() (string, error) +} + +// Query string builder for webhooks +func (opts TriggerOpts) ToWebhookTriggerQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + params := q.Query() + + for k, v := range opts.Params { + params.Add(k, v) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// Trigger an action represented by a webhook. +func Trigger(client *gophercloud.ServiceClient, id string, opts TriggerOptsBuilder) (r TriggerResult) { + url := triggerURL(client, id) + if opts != nil { + query, err := opts.ToWebhookTriggerQuery() + if err != nil { + r.Err = err + return + } + url += query + } else { + r.Err = fmt.Errorf("Must contain V for TriggerOpt") + return + } + + _, r.Err = client.Post(url, nil, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/results.go new file mode 100644 index 000000000000..ccb06086a2f8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/results.go @@ -0,0 +1,22 @@ +package webhooks + +import ( + "github.com/gophercloud/gophercloud" +) + +type commonResult struct { + gophercloud.Result +} + +type TriggerResult struct { + commonResult +} + +// Extract retrieves the response action +func (r commonResult) Extract() (string, error) { + var s struct { + Action string `json:"action"` + } + err := r.ExtractInto(&s) + return s.Action, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/doc.go new file mode 100644 index 000000000000..9a759ee29a5e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/doc.go @@ -0,0 +1,2 @@ +// clustering_webhooks_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/requests_test.go new file mode 100644 index 000000000000..4a519fdf6afd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/testing/requests_test.go @@ -0,0 +1,135 @@ +package testing + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestWebhookTrigger(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` + { + "action": "290c44fa-c60f-4d75-a0eb-87433ba982a3" + }`) + }) + + triggerOpts := webhooks.TriggerOpts{ + V: "1", + Params: map[string]string{ + "foo": "bar", + "bar": "baz", + }, + } + result, err := webhooks.Trigger(fake.ServiceClient(), "f93f83f6-762b-41b6-b757-80507834d394", triggerOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, result, "290c44fa-c60f-4d75-a0eb-87433ba982a3") +} + +// Test webhook with params that generates query strings +func TestWebhookParams(t *testing.T) { + triggerOpts := webhooks.TriggerOpts{ + V: "1", + Params: map[string]string{ + "foo": "bar", + "bar": "baz", + }, + } + expected := "?V=1&bar=baz&foo=bar" + actual, err := triggerOpts.ToWebhookTriggerQuery() + th.AssertNoErr(t, err) + th.AssertEquals(t, actual, expected) +} + +// Nagative test case for returning invalid type (integer) for action id +func TestWebhooksInvalidAction(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` + { + "action": 123 + }`) + }) + + triggerOpts := webhooks.TriggerOpts{ + V: "1", + Params: map[string]string{ + "foo": "bar", + "bar": "baz", + }, + } + _, err := webhooks.Trigger(fake.ServiceClient(), "f93f83f6-762b-41b6-b757-80507834d394", triggerOpts).Extract() + isValid := err.(*json.UnmarshalTypeError) == nil + th.AssertEquals(t, false, isValid) +} + +// Negative test case for passing empty TriggerOpt +func TestWebhookTriggerInvalidEmptyOpt(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` + { + "action": "290c44fa-c60f-4d75-a0eb-87433ba982a3" + }`) + }) + + _, err := webhooks.Trigger(fake.ServiceClient(), "f93f83f6-762b-41b6-b757-80507834d394", webhooks.TriggerOpts{}).Extract() + if err == nil { + t.Errorf("Expected error without V param") + } +} + +// Negative test case for passing in nil for TriggerOpt +func TestWebhookTriggerInvalidNilOpt(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` + { + "action": "290c44fa-c60f-4d75-a0eb-87433ba982a3" + }`) + }) + + _, err := webhooks.Trigger(fake.ServiceClient(), "f93f83f6-762b-41b6-b757-80507834d394", nil).Extract() + + if err == nil { + t.Errorf("Expected error with nil param") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/urls.go new file mode 100644 index 000000000000..563cf81122d6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/clustering/v1/webhooks/urls.go @@ -0,0 +1,7 @@ +package webhooks + +import "github.com/gophercloud/gophercloud" + +func triggerURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("v1", "webhooks", id, "trigger") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go index 18dade837c17..874f7a61ec4c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go @@ -28,7 +28,6 @@ type CreateOptsBuilder interface { // CreateOpts specifies parameters of a new interface attachment. type CreateOpts struct { - // PortID is the ID of the port for which you want to create an interface. // The NetworkID and PortID parameters are mutually exclusive. // If you do not specify the PortID parameter, the OpenStack Networking API @@ -43,6 +42,7 @@ type CreateOpts struct { // Slice of FixedIPs. If you request a specific FixedIP address without a // NetworkID, the request returns a Bad Request (400) response code. + // Note: this uses the FixedIP struct, but only the IPAddress field can be used. FixedIPs []FixedIP `json:"fixed_ips,omitempty"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go index a16fa14f7598..7d15e1ecb4bc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go @@ -37,8 +37,10 @@ type DeleteResult struct { } // FixedIP represents a Fixed IP Address. +// This struct is also used when creating an attachment, +// but it is not possible to specify a SubnetID. type FixedIP struct { - SubnetID string `json:"subnet_id"` + SubnetID string `json:"subnet_id,omitempty"` IPAddress string `json:"ip_address"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go index 9dae14c7a93e..30c6170117d8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go @@ -67,6 +67,14 @@ type BlockDevice struct { // VolumeSize is the size of the volume to create (in gigabytes). This can be // omitted for existing volumes. VolumeSize int `json:"volume_size,omitempty"` + + // DeviceType specifies the device type of the block devices. + // Examples of this are disk, cdrom, floppy, lun, etc. + DeviceType string `json:"device_type,omitempty"` + + // DiskBus is the bus type of the block devices. + // Examples of this are ide, usb, virtio, scsi, etc. + DiskBus string `json:"disk_bus,omitempty"` } // CreateOptsExt is a structure that extends the server `CreateOpts` structure diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go new file mode 100644 index 000000000000..484e40a09a24 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go @@ -0,0 +1,275 @@ +package testing + +import ( + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" +) + +var BaseCreateOpts = servers.CreateOpts{ + Name: "createdserver", + FlavorRef: "performance1-1", +} + +var BaseCreateOptsWithImageRef = servers.CreateOpts{ + Name: "createdserver", + FlavorRef: "performance1-1", + ImageRef: "asdfasdfasdf", +} + +const ExpectedNewVolumeRequest = ` +{ + "server": { + "name":"createdserver", + "flavorRef":"performance1-1", + "imageRef":"", + "block_device_mapping_v2":[ + { + "uuid":"123456", + "source_type":"image", + "destination_type":"volume", + "boot_index": 0, + "delete_on_termination": true, + "volume_size": 10 + } + ] + } +} +` + +var NewVolumeRequest = bootfromvolume.CreateOptsExt{ + CreateOptsBuilder: BaseCreateOpts, + BlockDevice: []bootfromvolume.BlockDevice{ + { + UUID: "123456", + SourceType: bootfromvolume.SourceImage, + DestinationType: bootfromvolume.DestinationVolume, + VolumeSize: 10, + DeleteOnTermination: true, + }, + }, +} + +const ExpectedExistingVolumeRequest = ` +{ + "server": { + "name":"createdserver", + "flavorRef":"performance1-1", + "imageRef":"", + "block_device_mapping_v2":[ + { + "uuid":"123456", + "source_type":"volume", + "destination_type":"volume", + "boot_index": 0, + "delete_on_termination": true + } + ] + } +} +` + +var ExistingVolumeRequest = bootfromvolume.CreateOptsExt{ + CreateOptsBuilder: BaseCreateOpts, + BlockDevice: []bootfromvolume.BlockDevice{ + { + UUID: "123456", + SourceType: bootfromvolume.SourceVolume, + DestinationType: bootfromvolume.DestinationVolume, + DeleteOnTermination: true, + }, + }, +} + +const ExpectedImageRequest = ` +{ + "server": { + "name": "createdserver", + "imageRef": "asdfasdfasdf", + "flavorRef": "performance1-1", + "block_device_mapping_v2":[ + { + "boot_index": 0, + "delete_on_termination": true, + "destination_type":"local", + "source_type":"image", + "uuid":"asdfasdfasdf" + } + ] + } +} +` + +var ImageRequest = bootfromvolume.CreateOptsExt{ + CreateOptsBuilder: BaseCreateOptsWithImageRef, + BlockDevice: []bootfromvolume.BlockDevice{ + { + BootIndex: 0, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationLocal, + SourceType: bootfromvolume.SourceImage, + UUID: "asdfasdfasdf", + }, + }, +} + +const ExpectedMultiEphemeralRequest = ` +{ + "server": { + "name": "createdserver", + "imageRef": "asdfasdfasdf", + "flavorRef": "performance1-1", + "block_device_mapping_v2":[ + { + "boot_index": 0, + "delete_on_termination": true, + "destination_type":"local", + "source_type":"image", + "uuid":"asdfasdfasdf" + }, + { + "boot_index": -1, + "delete_on_termination": true, + "destination_type":"local", + "guest_format":"ext4", + "source_type":"blank", + "volume_size": 1 + }, + { + "boot_index": -1, + "delete_on_termination": true, + "destination_type":"local", + "guest_format":"ext4", + "source_type":"blank", + "volume_size": 1 + } + ] + } +} +` + +var MultiEphemeralRequest = bootfromvolume.CreateOptsExt{ + CreateOptsBuilder: BaseCreateOptsWithImageRef, + BlockDevice: []bootfromvolume.BlockDevice{ + { + BootIndex: 0, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationLocal, + SourceType: bootfromvolume.SourceImage, + UUID: "asdfasdfasdf", + }, + { + BootIndex: -1, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationLocal, + GuestFormat: "ext4", + SourceType: bootfromvolume.SourceBlank, + VolumeSize: 1, + }, + { + BootIndex: -1, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationLocal, + GuestFormat: "ext4", + SourceType: bootfromvolume.SourceBlank, + VolumeSize: 1, + }, + }, +} + +const ExpectedImageAndNewVolumeRequest = ` +{ + "server": { + "name": "createdserver", + "imageRef": "asdfasdfasdf", + "flavorRef": "performance1-1", + "block_device_mapping_v2":[ + { + "boot_index": 0, + "delete_on_termination": true, + "destination_type":"local", + "source_type":"image", + "uuid":"asdfasdfasdf" + }, + { + "boot_index": 1, + "delete_on_termination": true, + "destination_type":"volume", + "source_type":"blank", + "volume_size": 1, + "device_type": "disk", + "disk_bus": "scsi" + } + ] + } +} +` + +var ImageAndNewVolumeRequest = bootfromvolume.CreateOptsExt{ + CreateOptsBuilder: BaseCreateOptsWithImageRef, + BlockDevice: []bootfromvolume.BlockDevice{ + { + BootIndex: 0, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationLocal, + SourceType: bootfromvolume.SourceImage, + UUID: "asdfasdfasdf", + }, + { + BootIndex: 1, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationVolume, + SourceType: bootfromvolume.SourceBlank, + VolumeSize: 1, + DeviceType: "disk", + DiskBus: "scsi", + }, + }, +} + +const ExpectedImageAndExistingVolumeRequest = ` +{ + "server": { + "name": "createdserver", + "imageRef": "asdfasdfasdf", + "flavorRef": "performance1-1", + "block_device_mapping_v2":[ + { + "boot_index": 0, + "delete_on_termination": true, + "destination_type":"local", + "source_type":"image", + "uuid":"asdfasdfasdf" + }, + { + "boot_index": 1, + "delete_on_termination": true, + "destination_type":"volume", + "source_type":"volume", + "uuid":"123456", + "volume_size": 1 + } + ] + } +} +` + +var ImageAndExistingVolumeRequest = bootfromvolume.CreateOptsExt{ + CreateOptsBuilder: BaseCreateOptsWithImageRef, + BlockDevice: []bootfromvolume.BlockDevice{ + { + BootIndex: 0, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationLocal, + SourceType: bootfromvolume.SourceImage, + UUID: "asdfasdfasdf", + }, + { + BootIndex: 1, + DeleteOnTermination: true, + DestinationType: bootfromvolume.DestinationVolume, + SourceType: bootfromvolume.SourceVolume, + UUID: "123456", + VolumeSize: 1, + }, + }, +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/requests_test.go index 7fd3e7d84a00..6b59ff722e23 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/testing/requests_test.go @@ -3,325 +3,42 @@ package testing import ( "testing" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" th "github.com/gophercloud/gophercloud/testhelper" ) func TestBootFromNewVolume(t *testing.T) { - base := servers.CreateOpts{ - Name: "createdserver", - FlavorRef: "performance1-1", - } - ext := bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: base, - BlockDevice: []bootfromvolume.BlockDevice{ - { - UUID: "123456", - SourceType: bootfromvolume.SourceImage, - DestinationType: bootfromvolume.DestinationVolume, - VolumeSize: 10, - DeleteOnTermination: true, - }, - }, - } - - expected := ` - { - "server": { - "name":"createdserver", - "flavorRef":"performance1-1", - "imageRef":"", - "block_device_mapping_v2":[ - { - "uuid":"123456", - "source_type":"image", - "destination_type":"volume", - "boot_index": 0, - "delete_on_termination": true, - "volume_size": 10 - } - ] - } - } - ` - actual, err := ext.ToServerCreateMap() + actual, err := NewVolumeRequest.ToServerCreateMap() th.AssertNoErr(t, err) - th.CheckJSONEquals(t, expected, actual) + th.CheckJSONEquals(t, ExpectedNewVolumeRequest, actual) } func TestBootFromExistingVolume(t *testing.T) { - base := servers.CreateOpts{ - Name: "createdserver", - FlavorRef: "performance1-1", - } - - ext := bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: base, - BlockDevice: []bootfromvolume.BlockDevice{ - { - UUID: "123456", - SourceType: bootfromvolume.SourceVolume, - DestinationType: bootfromvolume.DestinationVolume, - DeleteOnTermination: true, - }, - }, - } - - expected := ` - { - "server": { - "name":"createdserver", - "flavorRef":"performance1-1", - "imageRef":"", - "block_device_mapping_v2":[ - { - "uuid":"123456", - "source_type":"volume", - "destination_type":"volume", - "boot_index": 0, - "delete_on_termination": true - } - ] - } - } - ` - actual, err := ext.ToServerCreateMap() + actual, err := ExistingVolumeRequest.ToServerCreateMap() th.AssertNoErr(t, err) - th.CheckJSONEquals(t, expected, actual) + th.CheckJSONEquals(t, ExpectedExistingVolumeRequest, actual) } func TestBootFromImage(t *testing.T) { - base := servers.CreateOpts{ - Name: "createdserver", - ImageRef: "asdfasdfasdf", - FlavorRef: "performance1-1", - } - - ext := bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: base, - BlockDevice: []bootfromvolume.BlockDevice{ - { - BootIndex: 0, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationLocal, - SourceType: bootfromvolume.SourceImage, - UUID: "asdfasdfasdf", - }, - }, - } - - expected := ` - { - "server": { - "name": "createdserver", - "imageRef": "asdfasdfasdf", - "flavorRef": "performance1-1", - "block_device_mapping_v2":[ - { - "boot_index": 0, - "delete_on_termination": true, - "destination_type":"local", - "source_type":"image", - "uuid":"asdfasdfasdf" - } - ] - } - } - ` - actual, err := ext.ToServerCreateMap() + actual, err := ImageRequest.ToServerCreateMap() th.AssertNoErr(t, err) - th.CheckJSONEquals(t, expected, actual) + th.CheckJSONEquals(t, ExpectedImageRequest, actual) } func TestCreateMultiEphemeralOpts(t *testing.T) { - base := servers.CreateOpts{ - Name: "createdserver", - ImageRef: "asdfasdfasdf", - FlavorRef: "performance1-1", - } - - ext := bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: base, - BlockDevice: []bootfromvolume.BlockDevice{ - { - BootIndex: 0, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationLocal, - SourceType: bootfromvolume.SourceImage, - UUID: "asdfasdfasdf", - }, - { - BootIndex: -1, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationLocal, - GuestFormat: "ext4", - SourceType: bootfromvolume.SourceBlank, - VolumeSize: 1, - }, - { - BootIndex: -1, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationLocal, - GuestFormat: "ext4", - SourceType: bootfromvolume.SourceBlank, - VolumeSize: 1, - }, - }, - } - - expected := ` - { - "server": { - "name": "createdserver", - "imageRef": "asdfasdfasdf", - "flavorRef": "performance1-1", - "block_device_mapping_v2":[ - { - "boot_index": 0, - "delete_on_termination": true, - "destination_type":"local", - "source_type":"image", - "uuid":"asdfasdfasdf" - }, - { - "boot_index": -1, - "delete_on_termination": true, - "destination_type":"local", - "guest_format":"ext4", - "source_type":"blank", - "volume_size": 1 - }, - { - "boot_index": -1, - "delete_on_termination": true, - "destination_type":"local", - "guest_format":"ext4", - "source_type":"blank", - "volume_size": 1 - } - ] - } - } - ` - actual, err := ext.ToServerCreateMap() + actual, err := MultiEphemeralRequest.ToServerCreateMap() th.AssertNoErr(t, err) - th.CheckJSONEquals(t, expected, actual) + th.CheckJSONEquals(t, ExpectedMultiEphemeralRequest, actual) } func TestAttachNewVolume(t *testing.T) { - base := servers.CreateOpts{ - Name: "createdserver", - ImageRef: "asdfasdfasdf", - FlavorRef: "performance1-1", - } - - ext := bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: base, - BlockDevice: []bootfromvolume.BlockDevice{ - { - BootIndex: 0, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationLocal, - SourceType: bootfromvolume.SourceImage, - UUID: "asdfasdfasdf", - }, - { - BootIndex: 1, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationVolume, - SourceType: bootfromvolume.SourceBlank, - VolumeSize: 1, - }, - }, - } - - expected := ` - { - "server": { - "name": "createdserver", - "imageRef": "asdfasdfasdf", - "flavorRef": "performance1-1", - "block_device_mapping_v2":[ - { - "boot_index": 0, - "delete_on_termination": true, - "destination_type":"local", - "source_type":"image", - "uuid":"asdfasdfasdf" - }, - { - "boot_index": 1, - "delete_on_termination": true, - "destination_type":"volume", - "source_type":"blank", - "volume_size": 1 - } - ] - } - } - ` - actual, err := ext.ToServerCreateMap() + actual, err := ImageAndNewVolumeRequest.ToServerCreateMap() th.AssertNoErr(t, err) - th.CheckJSONEquals(t, expected, actual) + th.CheckJSONEquals(t, ExpectedImageAndNewVolumeRequest, actual) } func TestAttachExistingVolume(t *testing.T) { - base := servers.CreateOpts{ - Name: "createdserver", - ImageRef: "asdfasdfasdf", - FlavorRef: "performance1-1", - } - - ext := bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: base, - BlockDevice: []bootfromvolume.BlockDevice{ - { - BootIndex: 0, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationLocal, - SourceType: bootfromvolume.SourceImage, - UUID: "asdfasdfasdf", - }, - { - BootIndex: 1, - DeleteOnTermination: true, - DestinationType: bootfromvolume.DestinationVolume, - SourceType: bootfromvolume.SourceVolume, - UUID: "123456", - VolumeSize: 1, - }, - }, - } - - expected := ` - { - "server": { - "name": "createdserver", - "imageRef": "asdfasdfasdf", - "flavorRef": "performance1-1", - "block_device_mapping_v2":[ - { - "boot_index": 0, - "delete_on_termination": true, - "destination_type":"local", - "source_type":"image", - "uuid":"asdfasdfasdf" - }, - { - "boot_index": 1, - "delete_on_termination": true, - "destination_type":"volume", - "source_type":"volume", - "uuid":"123456", - "volume_size": 1 - } - ] - } - } - ` - actual, err := ext.ToServerCreateMap() + actual, err := ImageAndExistingVolumeRequest.ToServerCreateMap() th.AssertNoErr(t, err) - th.CheckJSONEquals(t, expected, actual) + th.CheckJSONEquals(t, ExpectedImageAndExistingVolumeRequest, actual) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/doc.go new file mode 100644 index 000000000000..53e24dbe1b5d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/doc.go @@ -0,0 +1,67 @@ +/* +Package extendedserverattributes provides the ability to extend a +server result with the extended usage information. + +Example to Get basic extended information: + + type serverAttributesExt struct { + servers.Server + extendedserverattributes.ServerAttributesExt + } + var serverWithAttributesExt serverAttributesExt + + err := servers.Get(computeClient, "d650a0ce-17c3-497d-961a-43c4af80998a").ExtractInto(&serverWithAttributesExt) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", serverWithAttributesExt) + +Example to get additional fields with microversion 2.3 or later + + computeClient.Microversion = "2.3" + result := servers.Get(computeClient, "d650a0ce-17c3-497d-961a-43c4af80998a") + + reservationID, err := extendedserverattributes.ExtractReservationID(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", reservationID) + + launchIndex, err := extendedserverattributes.ExtractLaunchIndex(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%d\n", launchIndex) + + ramdiskID, err := extendedserverattributes.ExtractRamdiskID(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", ramdiskID) + + kernelID, err := extendedserverattributes.ExtractKernelID(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", kernelID) + + hostname, err := extendedserverattributes.ExtractHostname(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", hostname) + + rootDeviceName, err := extendedserverattributes.ExtractRootDeviceName(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", rootDeviceName) + + userData, err := extendedserverattributes.ExtractUserData(result.Result) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", userData) +*/ +package extendedserverattributes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/microversions.go new file mode 100644 index 000000000000..709f84d965da --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/microversions.go @@ -0,0 +1,82 @@ +package extendedserverattributes + +import ( + "github.com/gophercloud/gophercloud" +) + +// ExtractReservationID will extract the reservation_id attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractReservationID(r gophercloud.Result) (string, error) { + var s struct { + ReservationID string `json:"OS-EXT-SRV-ATTR:reservation_id"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.ReservationID, err +} + +// ExtractLaunchIndex will extract the launch_index attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractLaunchIndex(r gophercloud.Result) (int, error) { + var s struct { + LaunchIndex int `json:"OS-EXT-SRV-ATTR:launch_index"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.LaunchIndex, err +} + +// ExtractRamdiskID will extract the ramdisk_id attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractRamdiskID(r gophercloud.Result) (string, error) { + var s struct { + RamdiskID string `json:"OS-EXT-SRV-ATTR:ramdisk_id"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.RamdiskID, err +} + +// ExtractKernelID will extract the kernel_id attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractKernelID(r gophercloud.Result) (string, error) { + var s struct { + KernelID string `json:"OS-EXT-SRV-ATTR:kernel_id"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.KernelID, err +} + +// ExtractHostname will extract the hostname attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractHostname(r gophercloud.Result) (string, error) { + var s struct { + Hostname string `json:"OS-EXT-SRV-ATTR:hostname"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.Hostname, err +} + +// ExtractRootDeviceName will extract the root_device_name attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractRootDeviceName(r gophercloud.Result) (string, error) { + var s struct { + RootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.RootDeviceName, err +} + +// ExtractUserData will extract the userdata attribute. +// This requires the client to be set to microversion 2.3 or later. +func ExtractUserData(r gophercloud.Result) (string, error) { + var s struct { + Userdata string `json:"OS-EXT-SRV-ATTR:userdata"` + } + err := r.ExtractIntoStructPtr(&s, "server") + + return s.Userdata, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/results.go new file mode 100644 index 000000000000..6c2ffc9818d5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/results.go @@ -0,0 +1,10 @@ +package extendedserverattributes + +// ServerAttributesExt represents basic OS-EXT-SRV-ATTR server response fields. +// You should use extract methods from microversions.go to retrieve additional +// fields. +type ServerAttributesExt struct { + Host string `json:"OS-EXT-SRV-ATTR:host"` + InstanceName string `json:"OS-EXT-SRV-ATTR:instance_name"` + HypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/fixtures.go new file mode 100644 index 000000000000..c2f0b83e80cb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/fixtures.go @@ -0,0 +1,28 @@ +package testing + +// ServerWithAttributesExtResult represents a raw server response from the +// Compute API with OS-EXT-SRV-ATTR data. +// Most of the actual fields were deleted from the response. +const ServerWithAttributesExtResult = ` +{ + "server": { + "OS-EXT-SRV-ATTR:user_data": "", + "OS-EXT-SRV-ATTR:instance_name": "instance-00000001", + "OS-EXT-SRV-ATTR:root_device_name": "/dev/sda", + "OS-EXT-SRV-ATTR:hostname": "test00", + "OS-EXT-SRV-ATTR:reservation_id": "r-ky9gim1l", + "OS-EXT-SRV-ATTR:ramdisk_id": "", + "OS-EXT-SRV-ATTR:host": "compute01", + "OS-EXT-SRV-ATTR:kernel_id": "", + "OS-EXT-SRV-ATTR:hypervisor_hostname": "compute01", + "OS-EXT-SRV-ATTR:launch_index": 0, + "created": "2018-07-27T09:15:48Z", + "updated": "2018-07-27T09:15:55Z", + "id": "d650a0ce-17c3-497d-961a-43c4af80998a", + "name": "test_instance", + "status": "ACTIVE", + "user_id": "0f2f3822679e4b3ea073e5d1c6ed5f02", + "tenant_id": "424e7cf0243c468ca61732ba45973b3e" + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go new file mode 100644 index 000000000000..360d295b422c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go @@ -0,0 +1,58 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/extendedserverattributes" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestServerWithUsageExt(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/servers/d650a0ce-17c3-497d-961a-43c4af80998a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, ServerWithAttributesExtResult) + }) + + type serverAttributesExt struct { + servers.Server + extendedserverattributes.ServerAttributesExt + } + var serverWithAttributesExt serverAttributesExt + + result := servers.Get(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a") + + // Extract basic fields. + err := servers.Get(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a").ExtractInto(&serverWithAttributesExt) + th.AssertNoErr(t, err) + + // Extract additional fields. + reservationID, err := extendedserverattributes.ExtractReservationID(result.Result) + th.AssertNoErr(t, err) + + launchIndex, err := extendedserverattributes.ExtractLaunchIndex(result.Result) + th.AssertNoErr(t, err) + + hostname, err := extendedserverattributes.ExtractHostname(result.Result) + th.AssertNoErr(t, err) + + rootDeviceName, err := extendedserverattributes.ExtractRootDeviceName(result.Result) + th.AssertNoErr(t, err) + + th.AssertEquals(t, serverWithAttributesExt.Host, "compute01") + th.AssertEquals(t, serverWithAttributesExt.InstanceName, "instance-00000001") + th.AssertEquals(t, serverWithAttributesExt.HypervisorHostname, "compute01") + th.AssertEquals(t, reservationID, "r-ky9gim1l") + th.AssertEquals(t, launchIndex, 0) + th.AssertEquals(t, hostname, "test00") + th.AssertEquals(t, rootDeviceName, "/dev/sda") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/doc.go index b8eb699edbe3..8f7d773e744f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/doc.go @@ -4,14 +4,24 @@ and shows summary statistics for all hypervisors over all compute nodes in the O Example of Show Hypervisor Details - hypervisorID := 42 - hypervisor, err := hypervisors.Get(computeClient, 42).Extract() + hypervisorID := "42" + hypervisor, err := hypervisors.Get(computeClient, hypervisorID).Extract() if err != nil { panic(err) } fmt.Printf("%+v\n", hypervisor) +Example of Show Hypervisor Details with Compute API microversion greater than 2.53 + + hypervisorID := "c48f6247-abe4-4a24-824e-ea39e108874f" + hypervisor, err := hypervisors.Get(computeClient, hypervisorID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", hypervisor) + Example of Retrieving Details of All Hypervisors allPages, err := hypervisors.List(computeClient).AllPages() @@ -28,7 +38,7 @@ Example of Retrieving Details of All Hypervisors fmt.Printf("%+v\n", hypervisor) } -Example of Show Hypervisor Statistics +Example of Show Hypervisors Statistics hypervisorsStatistics, err := hypervisors.GetStatistics(computeClient).Extract() if err != nil { @@ -39,7 +49,7 @@ Example of Show Hypervisor Statistics Example of Show Hypervisor Uptime - hypervisorID := 42 + hypervisorID := "42" hypervisorUptime, err := hypervisors.GetUptime(computeClient, hypervisorID).Extract() if err != nil { panic(err) @@ -47,5 +57,14 @@ Example of Show Hypervisor Uptime fmt.Printf("%+v\n", hypervisorUptime) +Example of Show Hypervisor Uptime with Compute API microversion greater than 2.53 + + hypervisorID := "c48f6247-abe4-4a24-824e-ea39e108874f" + hypervisorUptime, err := hypervisors.GetUptime(computeClient, hypervisorID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", hypervisorUptime) */ package hypervisors diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/requests.go index b6f1c541cb22..db16543e2237 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/requests.go @@ -1,8 +1,6 @@ package hypervisors import ( - "strconv" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -23,18 +21,16 @@ func GetStatistics(client *gophercloud.ServiceClient) (r StatisticsResult) { } // Get makes a request against the API to get details for specific hypervisor. -func Get(client *gophercloud.ServiceClient, hypervisorID int) (r HypervisorResult) { - v := strconv.Itoa(hypervisorID) - _, r.Err = client.Get(hypervisorsGetURL(client, v), &r.Body, &gophercloud.RequestOpts{ +func Get(client *gophercloud.ServiceClient, hypervisorID string) (r HypervisorResult) { + _, r.Err = client.Get(hypervisorsGetURL(client, hypervisorID), &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) return } // GetUptime makes a request against the API to get uptime for specific hypervisor. -func GetUptime(client *gophercloud.ServiceClient, hypervisorID int) (r UptimeResult) { - v := strconv.Itoa(hypervisorID) - _, r.Err = client.Get(hypervisorsUptimeURL(client, v), &r.Body, &gophercloud.RequestOpts{ +func GetUptime(client *gophercloud.ServiceClient, hypervisorID string) (r UptimeResult) { + _, r.Err = client.Get(hypervisorsUptimeURL(client, hypervisorID), &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) return diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/results.go index 7f3fafe1aa13..5dab0f3d37e3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/results.go @@ -3,6 +3,7 @@ package hypervisors import ( "encoding/json" "fmt" + "strconv" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" @@ -27,10 +28,40 @@ type CPUInfo struct { // Service represents a Compute service running on the hypervisor. type Service struct { Host string `json:"host"` - ID int `json:"id"` + ID string `json:"-"` DisabledReason string `json:"disabled_reason"` } +func (r *Service) UnmarshalJSON(b []byte) error { + type tmp Service + var s struct { + tmp + ID interface{} `json:"id"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Service(s.tmp) + + // OpenStack Compute service returns ID in string representation since + // 2.53 microversion API (Pike release). + switch t := s.ID.(type) { + case int: + r.ID = strconv.Itoa(t) + case float64: + r.ID = strconv.Itoa(int(t)) + case string: + r.ID = t + default: + return fmt.Errorf("ID has unexpected type: %T", t) + } + + return nil +} + // Hypervisor represents a hypervisor in the OpenStack cloud. type Hypervisor struct { // A structure that contains cpu information like arch, model, vendor, @@ -72,7 +103,7 @@ type Hypervisor struct { HypervisorVersion int `json:"-"` // ID is the unique ID of the hypervisor. - ID int `json:"id"` + ID string `json:"-"` // LocalGB is the disk space in the hypervisor, measured in GB. LocalGB int `json:"-"` @@ -103,6 +134,7 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { type tmp Hypervisor var s struct { tmp + ID interface{} `json:"id"` CPUInfo interface{} `json:"cpu_info"` HypervisorVersion interface{} `json:"hypervisor_version"` FreeDiskGB interface{} `json:"free_disk_gb"` @@ -133,9 +165,11 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { return fmt.Errorf("CPUInfo has unexpected type: %T", t) } - 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 + } } // These fields may be returned as a scientific notation, so they need @@ -146,7 +180,7 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { case float64: r.HypervisorVersion = int(t) default: - return fmt.Errorf("Hypervisor version of unexpected type") + return fmt.Errorf("Hypervisor version has unexpected type: %T", t) } switch t := s.FreeDiskGB.(type) { @@ -155,7 +189,7 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { case float64: r.FreeDiskGB = int(t) default: - return fmt.Errorf("Free disk GB of unexpected type") + return fmt.Errorf("Free disk GB has unexpected type: %T", t) } switch t := s.LocalGB.(type) { @@ -164,7 +198,20 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { case float64: r.LocalGB = int(t) default: - return fmt.Errorf("Local GB of unexpected type") + return fmt.Errorf("Local GB has unexpected type: %T", t) + } + + // OpenStack Compute service returns ID in string representation since + // 2.53 microversion API (Pike release). + switch t := s.ID.(type) { + case int: + r.ID = strconv.Itoa(t) + case float64: + r.ID = strconv.Itoa(int(t)) + case string: + r.ID = t + default: + return fmt.Errorf("ID has unexpected type: %T", t) } return nil @@ -264,7 +311,7 @@ type Uptime struct { HypervisorHostname string `json:"hypervisor_hostname"` // The id of the hypervisor. - ID int `json:"id"` + ID string `json:"-"` // The state of the hypervisor. One of up or down. State string `json:"state"` @@ -276,6 +323,36 @@ type Uptime struct { Uptime string `json:"uptime"` } +func (r *Uptime) UnmarshalJSON(b []byte) error { + type tmp Uptime + var s struct { + tmp + ID interface{} `json:"id"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Uptime(s.tmp) + + // OpenStack Compute service returns ID in string representation since + // 2.53 microversion API (Pike release). + switch t := s.ID.(type) { + case int: + r.ID = strconv.Itoa(t) + case float64: + r.ID = strconv.Itoa(int(t)) + case string: + r.ID = t + default: + return fmt.Errorf("ID has unexpected type: %T", t) + } + + return nil +} + type UptimeResult struct { gophercloud.Result } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/fixtures.go index 1dc05fb9b0a2..34e61f5b7859 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/fixtures.go @@ -3,7 +3,6 @@ package testing import ( "fmt" "net/http" - "strconv" "testing" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors" @@ -11,9 +10,11 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +// HypervisorListBodyPre253 represents a raw hypervisor list from the Compute +// API with microversion older than 2.53. // The first hypervisor represents what the specification says (~Newton) // The second is exactly the same, but what you can get off a real system (~Kilo) -const HypervisorListBody = ` +const HypervisorListBodyPre253 = ` { "hypervisors": [ { @@ -84,6 +85,78 @@ const HypervisorListBody = ` ] }` +// HypervisorListBody represents a raw hypervisor list result with Pike+ release. +const HypervisorListBody = ` +{ + "hypervisors": [ + { + "cpu_info": { + "arch": "x86_64", + "model": "Nehalem", + "vendor": "Intel", + "features": [ + "pge", + "clflush" + ], + "topology": { + "cores": 1, + "threads": 1, + "sockets": 4 + } + }, + "current_workload": 0, + "status": "enabled", + "state": "up", + "disk_available_least": 0, + "host_ip": "1.1.1.1", + "free_disk_gb": 1028, + "free_ram_mb": 7680, + "hypervisor_hostname": "fake-mini", + "hypervisor_type": "fake", + "hypervisor_version": 2002000, + "id": "c48f6247-abe4-4a24-824e-ea39e108874f", + "local_gb": 1028, + "local_gb_used": 0, + "memory_mb": 8192, + "memory_mb_used": 512, + "running_vms": 0, + "service": { + "host": "e6a37ee802d74863ab8b91ade8f12a67", + "id": "9c2566e7-7a54-4777-a1ae-c2662f0c407c", + "disabled_reason": null + }, + "vcpus": 1, + "vcpus_used": 0 + }, + { + "cpu_info": "{\"arch\": \"x86_64\", \"model\": \"Nehalem\", \"vendor\": \"Intel\", \"features\": [\"pge\", \"clflush\"], \"topology\": {\"cores\": 1, \"threads\": 1, \"sockets\": 4}}", + "current_workload": 0, + "status": "enabled", + "state": "up", + "disk_available_least": 0, + "host_ip": "1.1.1.1", + "free_disk_gb": 1028, + "free_ram_mb": 7680, + "hypervisor_hostname": "fake-mini", + "hypervisor_type": "fake", + "hypervisor_version": 2.002e+06, + "id": "c48f6247-abe4-4a24-824e-ea39e108874f", + "local_gb": 1028, + "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 + } + ] +}` + const HypervisorsStatisticsBody = ` { "hypervisor_statistics": { @@ -103,6 +176,7 @@ const HypervisorsStatisticsBody = ` } ` +// HypervisorGetBody represents a raw hypervisor GET result with Pike+ release. const HypervisorGetBody = ` { "hypervisor":{ @@ -130,7 +204,40 @@ const HypervisorGetBody = ` "hypervisor_hostname":"fake-mini", "hypervisor_type":"fake", "hypervisor_version":2002000, - "id":1, + "id":"c48f6247-abe4-4a24-824e-ea39e108874f", + "local_gb":1028, + "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 + } +} +` + +// HypervisorGetEmptyCPUInfoBody represents a raw hypervisor GET result with +// no cpu_info +const HypervisorGetEmptyCPUInfoBody = ` +{ + "hypervisor":{ + "cpu_info": "", + "current_workload":0, + "status":"enabled", + "state":"up", + "disk_available_least":0, + "host_ip":"1.1.1.1", + "free_disk_gb":1028, + "free_ram_mb":7680, + "hypervisor_hostname":"fake-mini", + "hypervisor_type":"fake", + "hypervisor_version":2002000, + "id":"c48f6247-abe4-4a24-824e-ea39e108874f", "local_gb":1028, "local_gb_used":0, "memory_mb":8192, @@ -138,7 +245,7 @@ const HypervisorGetBody = ` "running_vms":0, "service":{ "host":"e6a37ee802d74863ab8b91ade8f12a67", - "id":2, + "id":"9c2566e7-7a54-4777-a1ae-c2662f0c407c", "disabled_reason":null }, "vcpus":1, @@ -147,11 +254,13 @@ const HypervisorGetBody = ` } ` +// HypervisorUptimeBody represents a raw hypervisor uptime request result with +// Pike+ release. const HypervisorUptimeBody = ` { "hypervisor": { "hypervisor_hostname": "fake-mini", - "id": 1, + "id": "c48f6247-abe4-4a24-824e-ea39e108874f", "state": "up", "status": "enabled", "uptime": " 08:32:11 up 93 days, 18:25, 12 users, load average: 0.20, 0.12, 0.14" @@ -160,6 +269,46 @@ const HypervisorUptimeBody = ` ` var ( + HypervisorFakePre253 = hypervisors.Hypervisor{ + CPUInfo: hypervisors.CPUInfo{ + Arch: "x86_64", + Model: "Nehalem", + Vendor: "Intel", + Features: []string{ + "pge", + "clflush", + }, + Topology: hypervisors.Topology{ + Cores: 1, + Threads: 1, + Sockets: 4, + }, + }, + CurrentWorkload: 0, + Status: "enabled", + State: "up", + DiskAvailableLeast: 0, + HostIP: "1.1.1.1", + FreeDiskGB: 1028, + FreeRamMB: 7680, + HypervisorHostname: "fake-mini", + HypervisorType: "fake", + HypervisorVersion: 2002000, + ID: "1", + LocalGB: 1028, + LocalGBUsed: 0, + MemoryMB: 8192, + MemoryMBUsed: 512, + RunningVMs: 0, + Service: hypervisors.Service{ + Host: "e6a37ee802d74863ab8b91ade8f12a67", + ID: "2", + DisabledReason: "", + }, + VCPUs: 1, + VCPUsUsed: 0, + } + HypervisorFake = hypervisors.Hypervisor{ CPUInfo: hypervisors.CPUInfo{ Arch: "x86_64", @@ -185,7 +334,7 @@ var ( HypervisorHostname: "fake-mini", HypervisorType: "fake", HypervisorVersion: 2002000, - ID: 1, + ID: "c48f6247-abe4-4a24-824e-ea39e108874f", LocalGB: 1028, LocalGBUsed: 0, MemoryMB: 8192, @@ -193,12 +342,39 @@ var ( RunningVMs: 0, Service: hypervisors.Service{ Host: "e6a37ee802d74863ab8b91ade8f12a67", - ID: 2, + ID: "9c2566e7-7a54-4777-a1ae-c2662f0c407c", DisabledReason: "", }, VCPUs: 1, VCPUsUsed: 0, } + + HypervisorEmptyCPUInfo = hypervisors.Hypervisor{ + CurrentWorkload: 0, + Status: "enabled", + State: "up", + DiskAvailableLeast: 0, + HostIP: "1.1.1.1", + FreeDiskGB: 1028, + FreeRamMB: 7680, + HypervisorHostname: "fake-mini", + HypervisorType: "fake", + HypervisorVersion: 2002000, + ID: "c48f6247-abe4-4a24-824e-ea39e108874f", + LocalGB: 1028, + LocalGBUsed: 0, + MemoryMB: 8192, + MemoryMBUsed: 512, + RunningVMs: 0, + Service: hypervisors.Service{ + Host: "e6a37ee802d74863ab8b91ade8f12a67", + ID: "9c2566e7-7a54-4777-a1ae-c2662f0c407c", + DisabledReason: "", + }, + VCPUs: 1, + VCPUsUsed: 0, + } + HypervisorsStatisticsExpected = hypervisors.Statistics{ Count: 1, CurrentWorkload: 0, @@ -213,9 +389,10 @@ var ( VCPUs: 2, VCPUsUsed: 0, } + HypervisorUptimeExpected = hypervisors.Uptime{ HypervisorHostname: "fake-mini", - ID: 1, + ID: "c48f6247-abe4-4a24-824e-ea39e108874f", State: "up", Status: "enabled", Uptime: " 08:32:11 up 93 days, 18:25, 12 users, load average: 0.20, 0.12, 0.14", @@ -232,6 +409,16 @@ func HandleHypervisorsStatisticsSuccessfully(t *testing.T) { }) } +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) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(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") @@ -243,8 +430,7 @@ func HandleHypervisorListSuccessfully(t *testing.T) { } func HandleHypervisorGetSuccessfully(t *testing.T) { - v := strconv.Itoa(HypervisorFake.ID) - testhelper.Mux.HandleFunc("/os-hypervisors/"+v, func(w http.ResponseWriter, r *http.Request) { + 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) @@ -253,9 +439,18 @@ func HandleHypervisorGetSuccessfully(t *testing.T) { }) } +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) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, HypervisorGetEmptyCPUInfoBody) + }) +} + func HandleHypervisorUptimeSuccessfully(t *testing.T) { - v := strconv.Itoa(HypervisorFake.ID) - testhelper.Mux.HandleFunc("/os-hypervisors/"+v+"/uptime", func(w http.ResponseWriter, r *http.Request) { + 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) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go index 95f9636c0cd1..21428d60a3f1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go @@ -9,6 +9,49 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +func TestListHypervisorsPre253(t *testing.T) { + testhelper.SetupHTTP() + defer testhelper.TeardownHTTP() + HandleHypervisorListPre253Successfully(t) + + pages := 0 + err := hypervisors.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := hypervisors.ExtractHypervisors(page) + if err != nil { + return false, err + } + + 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]) + + return true, nil + }) + + testhelper.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllHypervisorsPre253(t *testing.T) { + testhelper.SetupHTTP() + defer testhelper.TeardownHTTP() + HandleHypervisorListPre253Successfully(t) + + allPages, err := hypervisors.List(client.ServiceClient()).AllPages() + testhelper.AssertNoErr(t, err) + actual, err := hypervisors.ExtractHypervisors(allPages) + testhelper.AssertNoErr(t, err) + testhelper.CheckDeepEquals(t, HypervisorFakePre253, actual[0]) + testhelper.CheckDeepEquals(t, HypervisorFakePre253, actual[1]) +} + func TestListHypervisors(t *testing.T) { testhelper.SetupHTTP() defer testhelper.TeardownHTTP() @@ -76,6 +119,18 @@ func TestGetHypervisor(t *testing.T) { testhelper.CheckDeepEquals(t, &expected, actual) } +func TestGetHypervisorEmptyCPUInfo(t *testing.T) { + testhelper.SetupHTTP() + defer testhelper.TeardownHTTP() + HandleHypervisorGetEmptyCPUInfoSuccessfully(t) + + expected := HypervisorEmptyCPUInfo + + actual, err := hypervisors.Get(client.ServiceClient(), expected.ID).Extract() + testhelper.AssertNoErr(t, err) + testhelper.CheckDeepEquals(t, &expected, actual) +} + func TestHypervisorsUptime(t *testing.T) { testhelper.SetupHTTP() defer testhelper.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go index 4e5e499e3aa4..b72807770ee8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go @@ -68,7 +68,7 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + OkCodes: []int{200, 201}, }) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/requests.go index 34d935893e62..d5daa7e3fb67 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/requests.go @@ -5,34 +5,32 @@ import ( ) // Get returns public data about a previously created QuotaSet. -func Get(client *gophercloud.ServiceClient, tenantID string) GetResult { - var res GetResult - _, res.Err = client.Get(getURL(client, tenantID), &res.Body, nil) - return res +func Get(client *gophercloud.ServiceClient, tenantID string) (r GetResult) { + _, r.Err = client.Get(getURL(client, tenantID), &r.Body, nil) + return } // GetDetail returns detailed public data about a previously created QuotaSet. -func GetDetail(client *gophercloud.ServiceClient, tenantID string) GetDetailResult { - var res GetDetailResult - _, res.Err = client.Get(getDetailURL(client, tenantID), &res.Body, nil) - return res +func GetDetail(client *gophercloud.ServiceClient, tenantID string) (r GetDetailResult) { + _, r.Err = client.Get(getDetailURL(client, tenantID), &r.Body, nil) + return } // Updates the quotas for the given tenantID and returns the new QuotaSet. -func Update(client *gophercloud.ServiceClient, tenantID string, opts UpdateOptsBuilder) (res UpdateResult) { +func Update(client *gophercloud.ServiceClient, tenantID string, opts UpdateOptsBuilder) (r UpdateResult) { reqBody, err := opts.ToComputeQuotaUpdateMap() if err != nil { - res.Err = err + r.Err = err return } - _, res.Err = client.Put(updateURL(client, tenantID), reqBody, &res.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) - return res + _, r.Err = client.Put(updateURL(client, tenantID), reqBody, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + return } // Resets the quotas for the given tenant to their default values. -func Delete(client *gophercloud.ServiceClient, tenantID string) (res DeleteResult) { - _, res.Err = client.Delete(deleteURL(client, tenantID), nil) +func Delete(client *gophercloud.ServiceClient, tenantID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, tenantID), nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/testing/fixtures.go index 53516413dbc2..c0955c5ca91e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets/testing/fixtures.go @@ -22,7 +22,7 @@ const GetOutput = ` "injected_file_content_bytes" : 10240, "injected_files" : 5, "metadata_items" : 128, - "ram" : 200000, + "ram" : 9216000, "key_pairs" : 10, "injected_file_path_bytes" : 255, "server_groups" : 2, @@ -73,7 +73,7 @@ const GetDetailsOutput = ` }, "ram" : { "in_use": 0, - "limit": 200000, + "limit": 9216000, "reserved": 0 }, "key_pairs" : { @@ -110,7 +110,7 @@ var FirstQuotaSet = quotasets.QuotaSet{ InjectedFiles: 5, KeyPairs: 10, MetadataItems: 128, - RAM: 200000, + RAM: 9216000, SecurityGroupRules: 20, SecurityGroups: 10, Cores: 200, @@ -121,13 +121,13 @@ var FirstQuotaSet = quotasets.QuotaSet{ // FirstQuotaDetailsSet is the first result in ListOutput. var FirstQuotaDetailsSet = quotasets.QuotaDetailSet{ - ID: FirstTenantID, + ID: FirstTenantID, InjectedFileContentBytes: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 10240}, InjectedFilePathBytes: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 255}, InjectedFiles: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 5}, KeyPairs: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 10}, MetadataItems: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 128}, - RAM: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 200000}, + RAM: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 9216000}, SecurityGroupRules: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 20}, SecurityGroups: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 10}, Cores: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 200}, @@ -137,7 +137,7 @@ var FirstQuotaDetailsSet = quotasets.QuotaDetailSet{ } //The expected update Body. Is also returned by PUT request -const UpdateOutput = `{"quota_set":{"cores":200,"fixed_ips":0,"floating_ips":0,"injected_file_content_bytes":10240,"injected_file_path_bytes":255,"injected_files":5,"instances":25,"key_pairs":10,"metadata_items":128,"ram":200000,"security_group_rules":20,"security_groups":10,"server_groups":2,"server_group_members":3}}` +const UpdateOutput = `{"quota_set":{"cores":200,"fixed_ips":0,"floating_ips":0,"injected_file_content_bytes":10240,"injected_file_path_bytes":255,"injected_files":5,"instances":25,"key_pairs":10,"metadata_items":128,"ram":9216000,"security_group_rules":20,"security_groups":10,"server_groups":2,"server_group_members":3}}` //The expected partialupdate Body. Is also returned by PUT request const PartialUpdateBody = `{"quota_set":{"cores":200, "force":true}}` @@ -151,7 +151,7 @@ var UpdatedQuotaSet = quotasets.UpdateOpts{ InjectedFiles: gophercloud.IntToPointer(5), KeyPairs: gophercloud.IntToPointer(10), MetadataItems: gophercloud.IntToPointer(128), - RAM: gophercloud.IntToPointer(200000), + RAM: gophercloud.IntToPointer(9216000), SecurityGroupRules: gophercloud.IntToPointer(20), SecurityGroups: gophercloud.IntToPointer(10), Cores: gophercloud.IntToPointer(200), diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/doc.go new file mode 100644 index 000000000000..1ab269b47e46 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/doc.go @@ -0,0 +1,25 @@ +/* +Package remoteconsoles provides the ability to create server remote consoles +through the Compute API. +You need to specify at least "2.6" microversion for the ComputeClient to use +that API. + +Example of Creating a new RemoteConsole + + computeClient, err := openstack.NewComputeV2(providerClient, endpointOptions) + computeClient.Microversion = "2.6" + + createOpts := remoteconsoles.CreateOpts{ + Protocol: remoteconsoles.ConsoleProtocolVNC, + Type: remoteconsoles.ConsoleTypeNoVNC, + } + serverID := "b16ba811-199d-4ffd-8839-ba96c1185a67" + + remtoteConsole, err := remoteconsoles.Create(computeClient, serverID, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("Console URL: %s\n", remtoteConsole.URL) +*/ +package remoteconsoles diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/requests.go new file mode 100644 index 000000000000..5c41035f70bf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/requests.go @@ -0,0 +1,83 @@ +package remoteconsoles + +import ( + "github.com/gophercloud/gophercloud" +) + +// ConsoleProtocol represents valid remote console protocol. +// It can be used to create a remote console with one of the pre-defined protocol. +type ConsoleProtocol string + +const ( + // ConsoleProtocolVNC represents the VNC console protocol. + ConsoleProtocolVNC ConsoleProtocol = "vnc" + + // ConsoleProtocolSPICE represents the SPICE console protocol. + ConsoleProtocolSPICE ConsoleProtocol = "spice" + + // ConsoleProtocolRDP represents the RDP console protocol. + ConsoleProtocolRDP ConsoleProtocol = "rdp" + + // ConsoleProtocolSerial represents the Serial console protocol. + ConsoleProtocolSerial ConsoleProtocol = "serial" + + // ConsoleProtocolMKS represents the MKS console protocol. + ConsoleProtocolMKS ConsoleProtocol = "mks" +) + +// ConsoleType represents valid remote console type. +// It can be used to create a remote console with one of the pre-defined type. +type ConsoleType string + +const ( + // ConsoleTypeNoVNC represents the VNC console type. + ConsoleTypeNoVNC ConsoleType = "novnc" + + // ConsoleTypeXVPVNC represents the XVP VNC console type. + ConsoleTypeXVPVNC ConsoleType = "xvpvnc" + + // ConsoleTypeRDPHTML5 represents the RDP HTML5 console type. + ConsoleTypeRDPHTML5 ConsoleType = "rdp-html5" + + // ConsoleTypeSPICEHTML5 represents the SPICE HTML5 console type. + ConsoleTypeSPICEHTML5 ConsoleType = "spice-html5" + + // ConsoleTypeSerial represents the Serial console type. + ConsoleTypeSerial ConsoleType = "serial" + + // ConsoleTypeWebMKS represents the Web MKS console type. + ConsoleTypeWebMKS ConsoleType = "webmks" +) + +// CreateOptsBuilder allows to add additional parameters to the Create request. +type CreateOptsBuilder interface { + ToRemoteConsoleCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters to the Create request. +type CreateOpts struct { + // Protocol specifies the protocol of a new remote console. + Protocol ConsoleProtocol `json:"protocol" required:"true"` + + // Type specifies the type of a new remote console. + Type ConsoleType `json:"type" required:"true"` +} + +// ToRemoteConsoleCreateMap builds a request body from the CreateOpts. +func (opts CreateOpts) ToRemoteConsoleCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "remote_console") +} + +// Create requests the creation of a new remote console on the specified server. +func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToRemoteConsoleCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client, serverID), reqBody, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/results.go new file mode 100644 index 000000000000..684c637086e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/results.go @@ -0,0 +1,38 @@ +package remoteconsoles + +import "github.com/gophercloud/gophercloud" + +type commonResult struct { + gophercloud.Result +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a RemoteConsole. +type CreateResult struct { + commonResult +} + +// RemoteConsole represents the Compute service remote console object. +type RemoteConsole struct { + // Protocol contains remote console protocol. + // You can use the RemoteConsoleProtocol custom type to unmarshal raw JSON + // response into the pre-defined valid console protocol. + Protocol string `json:"protocol"` + + // Type contains remote console type. + // You can use the RemoteConsoleType custom type to unmarshal raw JSON + // response into the pre-defined valid console type. + Type string `json:"type"` + + // URL can be used to connect to the remote console. + URL string `json:"url"` +} + +// Extract interprets any commonResult as a RemoteConsole. +func (r commonResult) Extract() (*RemoteConsole, error) { + var s struct { + RemoteConsole *RemoteConsole `json:"remote_console"` + } + err := r.ExtractInto(&s) + return s.RemoteConsole, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/fixtures.go new file mode 100644 index 000000000000..9644a4895eb5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/fixtures.go @@ -0,0 +1,22 @@ +package testing + +// RemoteConsoleCreateRequest represents a request to create a remote console. +const RemoteConsoleCreateRequest = ` +{ + "remote_console": { + "protocol": "vnc", + "type": "novnc" + } +} +` + +// RemoteConsoleCreateResult represents a raw server responce to the RemoteConsoleCreateRequest. +const RemoteConsoleCreateResult = ` +{ + "remote_console": { + "protocol": "vnc", + "type": "novnc", + "url": "http://192.168.0.4:6080/vnc_auto.html?token=9a2372b9-6a0e-4f71-aca1-56020e6bb677" + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/requests_test.go new file mode 100644 index 000000000000..f0b2c8c4c9d1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/testing/requests_test.go @@ -0,0 +1,40 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/servers/b16ba811-199d-4ffd-8839-ba96c1185a67/remote-consoles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, RemoteConsoleCreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, RemoteConsoleCreateResult) + }) + + opts := remoteconsoles.CreateOpts{ + Protocol: remoteconsoles.ConsoleProtocolVNC, + Type: remoteconsoles.ConsoleTypeNoVNC, + } + s, err := remoteconsoles.Create(fake.ServiceClient(), "b16ba811-199d-4ffd-8839-ba96c1185a67", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Protocol, string(remoteconsoles.ConsoleProtocolVNC)) + th.AssertEquals(t, s.Type, string(remoteconsoles.ConsoleTypeNoVNC)) + th.AssertEquals(t, s.URL, "http://192.168.0.4:6080/vnc_auto.html?token=9a2372b9-6a0e-4f71-aca1-56020e6bb677") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/urls.go new file mode 100644 index 000000000000..b4d7ec2bbf87 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles/urls.go @@ -0,0 +1,17 @@ +package remoteconsoles + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "servers" + + resourcePath = "remote-consoles" +) + +func rootURL(c *gophercloud.ServiceClient, serverID string) string { + return c.ServiceURL(rootPath, serverID, resourcePath) +} + +func createURL(c *gophercloud.ServiceClient, serverID string) string { + return rootURL(c, serverID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/doc.go new file mode 100644 index 000000000000..2081018cdb03 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/doc.go @@ -0,0 +1,28 @@ +/* +Package rescueunrescue provides the ability to place a server into rescue mode +and to return it back. + +Example to Rescue a server + + rescueOpts := rescueunrescue.RescueOpts{ + AdminPass: "aUPtawPzE9NU", + RescueImageRef: "115e5c5b-72f0-4a0a-9067-60706545248c", + } + serverID := "3f54d05f-3430-4d80-aa07-63e6af9e2488" + + adminPass, err := rescueunrescue.Rescue(computeClient, serverID, rescueOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("adminPass of the rescued server %s: %s\n", serverID, adminPass) + +Example to Unrescue a server + + serverID := "3f54d05f-3430-4d80-aa07-63e6af9e2488" + + if err := rescueunrescue.Unrescue(computeClient, serverID).ExtractErr(); err != nil { + panic(err) + } +*/ +package rescueunrescue diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/requests.go new file mode 100644 index 000000000000..61a23aad668b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/requests.go @@ -0,0 +1,48 @@ +package rescueunrescue + +import "github.com/gophercloud/gophercloud" + +// RescueOptsBuilder is an interface that allows extensions to override the +// default structure of a Rescue request. +type RescueOptsBuilder interface { + ToServerRescueMap() (map[string]interface{}, error) +} + +// RescueOpts represents the configuration options used to control a Rescue +// option. +type RescueOpts struct { + // AdminPass is the desired administrative password for the instance in + // RESCUE mode. + // If it's left blank, the server will generate a password. + AdminPass string `json:"adminPass,omitempty"` + + // RescueImageRef contains reference on an image that needs to be used as + // rescue image. + // If it's left blank, the server will be rescued with the default image. + RescueImageRef string `json:"rescue_image_ref,omitempty"` +} + +// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON +// request body for the Rescue request. +func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "rescue") +} + +// Rescue instructs the provider to place the server into RESCUE mode. +func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) { + b, err := opts.ToServerRescueMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Unrescue instructs the provider to return the server from RESCUE mode. +func Unrescue(client *gophercloud.ServiceClient, id string) (r UnrescueResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unrescue": nil}, nil, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/results.go new file mode 100644 index 000000000000..1966b15c46ca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/results.go @@ -0,0 +1,28 @@ +package rescueunrescue + +import "github.com/gophercloud/gophercloud" + +type commonResult struct { + gophercloud.Result +} + +// RescueResult is the response from a Rescue operation. Call its Extract +// method to retrieve adminPass for a rescued server. +type RescueResult struct { + commonResult +} + +// UnrescueResult is the response from an UnRescue operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type UnrescueResult struct { + gophercloud.ErrResult +} + +// Extract interprets any RescueResult as an AdminPass, if possible. +func (r RescueResult) Extract() (string, error) { + var s struct { + AdminPass string `json:"adminPass"` + } + err := r.ExtractInto(&s) + return s.AdminPass, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/fixtures.go new file mode 100644 index 000000000000..c822193d6a2d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/fixtures.go @@ -0,0 +1,25 @@ +package testing + +// RescueRequest represents request to rescue a server. +const RescueRequest = ` +{ + "rescue": { + "adminPass": "aUPtawPzE9NU", + "rescue_image_ref": "115e5c5b-72f0-4a0a-9067-60706545248c" + } +} +` + +// RescueResult represents a raw server response to a RescueRequest. +const RescueResult = ` +{ + "adminPass": "aUPtawPzE9NU" +} +` + +// UnrescueRequest represents request to unrescue a server. +const UnrescueRequest = ` +{ + "unrescue": null +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go new file mode 100644 index 000000000000..fda9023e2fa9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go @@ -0,0 +1,49 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestRescue(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/servers/3f54d05f-3430-4d80-aa07-63e6af9e2488/action", 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, RescueRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, RescueResult) + }) + + s, err := rescueunrescue.Rescue(fake.ServiceClient(), "3f54d05f-3430-4d80-aa07-63e6af9e2488", rescueunrescue.RescueOpts{ + AdminPass: "aUPtawPzE9NU", + RescueImageRef: "115e5c5b-72f0-4a0a-9067-60706545248c", + }).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "aUPtawPzE9NU", s) +} + +func TestUnrescue(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/servers/3f54d05f-3430-4d80-aa07-63e6af9e2488/action", 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, UnrescueRequest) + + w.WriteHeader(http.StatusAccepted) + }) + + err := rescueunrescue.Unrescue(fake.ServiceClient(), "3f54d05f-3430-4d80-aa07-63e6af9e2488").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/urls.go new file mode 100644 index 000000000000..fa5cd3735ff5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue/urls.go @@ -0,0 +1,7 @@ +package rescueunrescue + +import "github.com/gophercloud/gophercloud" + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go index 8b93f08b07e4..92a0331e18d1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go @@ -23,19 +23,14 @@ func ListByServer(client *gophercloud.ServiceClient, serverID string) pagination return commonList(client, listByServerURL(client, serverID)) } -// GroupOpts is the underlying struct responsible for creating or updating -// security groups. It therefore represents the mutable attributes of a -// security group. -type GroupOpts struct { +// CreateOpts is the struct responsible for creating a security group. +type CreateOpts struct { // the name of your security group. Name string `json:"name" required:"true"` // the description of your security group. - Description string `json:"description" required:"true"` + Description string `json:"description,omitempty"` } -// CreateOpts is the struct responsible for creating a security group. -type CreateOpts GroupOpts - // CreateOptsBuilder allows extensions to add additional parameters to the // Create request. type CreateOptsBuilder interface { @@ -61,7 +56,12 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create } // UpdateOpts is the struct responsible for updating an existing security group. -type UpdateOpts GroupOpts +type UpdateOpts struct { + // the name of your security group. + Name string `json:"name,omitempty"` + // the description of your security group. + Description *string `json:"description,omitempty"` +} // UpdateOptsBuilder allows extensions to add additional parameters to the // Update request. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go index cf08547e9012..046889220654 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go @@ -72,7 +72,7 @@ type Rule struct { IPRange IPRange `json:"ip_range"` // The security group ID to which this rule belongs. - ParentGroupID string `json:"parent_group_id"` + ParentGroupID string `json:"-"` // Not documented. Group Group diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/testing/requests_test.go index b5207646be3e..01ab9f7356e5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/testing/requests_test.go @@ -115,9 +115,10 @@ func TestUpdate(t *testing.T) { mockUpdateGroupResponse(t, groupID) + description := "new_desc" opts := secgroups.UpdateOpts{ Name: "new_name", - Description: "new_desc", + Description: &description, } group, err := secgroups.Update(client.ServiceClient(), groupID, opts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/doc.go new file mode 100644 index 000000000000..0f3127f04254 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/doc.go @@ -0,0 +1,20 @@ +/* +Package serverusage provides the ability the ability to extend a server result +with the extended usage information. + +Example to Get an extended information: + + type serverUsageExt struct { + servers.Server + serverusage.UsageExt + } + var serverWithUsageExt serverUsageExt + + err := servers.Get(computeClient, "d650a0ce-17c3-497d-961a-43c4af80998a").ExtractInto(&serverWithUsageExt) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", serverWithUsageExt) +*/ +package serverusage diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/results.go new file mode 100644 index 000000000000..a80abb6bc95b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/results.go @@ -0,0 +1,34 @@ +package serverusage + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" +) + +// UsageExt represents OS-SRV-USG server response fields. +type UsageExt struct { + LaunchedAt time.Time `json:"-"` + TerminatedAt time.Time `json:"-"` +} + +// UnmarshalJSON helps to unmarshal UsageExt fields into needed values. +func (r *UsageExt) UnmarshalJSON(b []byte) error { + type tmp UsageExt + var s struct { + tmp + LaunchedAt gophercloud.JSONRFC3339MilliNoZ `json:"OS-SRV-USG:launched_at"` + TerminatedAt gophercloud.JSONRFC3339MilliNoZ `json:"OS-SRV-USG:terminated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = UsageExt(s.tmp) + + r.LaunchedAt = time.Time(s.LaunchedAt) + r.TerminatedAt = time.Time(s.TerminatedAt) + + return nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/fixtures.go new file mode 100644 index 000000000000..2d2cf2ef538b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/fixtures.go @@ -0,0 +1,20 @@ +package testing + +// ServerWithUsageExtResult represents a raw server response from the Compute API +// with OS-SRV-USG data. +// Most of the actual fields were deleted from the response. +const ServerWithUsageExtResult = ` +{ + "server": { + "OS-SRV-USG:launched_at": "2018-07-27T09:15:55.000000", + "OS-SRV-USG:terminated_at": null, + "created": "2018-07-27T09:15:48Z", + "updated": "2018-07-27T09:15:55Z", + "id": "d650a0ce-17c3-497d-961a-43c4af80998a", + "name": "test_instance", + "status": "ACTIVE", + "user_id": "0f2f3822679e4b3ea073e5d1c6ed5f02", + "tenant_id": "424e7cf0243c468ca61732ba45973b3e" + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/requests_test.go new file mode 100644 index 000000000000..b05dee5786db --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage/testing/requests_test.go @@ -0,0 +1,44 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/serverusage" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestServerWithUsageExt(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/servers/d650a0ce-17c3-497d-961a-43c4af80998a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, ServerWithUsageExtResult) + }) + + type serverUsageExt struct { + servers.Server + serverusage.UsageExt + } + var serverWithUsageExt serverUsageExt + err := servers.Get(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a").ExtractInto(&serverWithUsageExt) + th.AssertNoErr(t, err) + + th.AssertEquals(t, serverWithUsageExt.LaunchedAt, time.Date(2018, 07, 27, 9, 15, 55, 0, time.UTC)) + th.AssertEquals(t, serverWithUsageExt.TerminatedAt, time.Time{}) + th.AssertEquals(t, serverWithUsageExt.Created, time.Date(2018, 07, 27, 9, 15, 48, 0, time.UTC)) + th.AssertEquals(t, serverWithUsageExt.Updated, time.Date(2018, 07, 27, 9, 15, 55, 0, time.UTC)) + th.AssertEquals(t, serverWithUsageExt.ID, "d650a0ce-17c3-497d-961a-43c4af80998a") + th.AssertEquals(t, serverWithUsageExt.Name, "test_instance") + th.AssertEquals(t, serverWithUsageExt.Status, "ACTIVE") + th.AssertEquals(t, serverWithUsageExt.UserID, "0f2f3822679e4b3ea073e5d1c6ed5f02") + th.AssertEquals(t, serverWithUsageExt.TenantID, "424e7cf0243c468ca61732ba45973b3e") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/results.go index 1ffc99cf9d23..df80fc2a3bf5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/results.go @@ -2,6 +2,8 @@ package services import ( "encoding/json" + "fmt" + "strconv" "time" "github.com/gophercloud/gophercloud" @@ -20,7 +22,7 @@ type Service struct { Host string `json:"host"` // The id of the service. - ID int `json:"id"` + ID string `json:"-"` // The state of the service. One of up or down. State string `json:"state"` @@ -40,6 +42,7 @@ func (r *Service) UnmarshalJSON(b []byte) error { type tmp Service var s struct { tmp + ID interface{} `json:"id"` UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` } err := json.Unmarshal(b, &s) @@ -50,6 +53,19 @@ func (r *Service) UnmarshalJSON(b []byte) error { r.UpdatedAt = time.Time(s.UpdatedAt) + // OpenStack Compute service returns ID in string representation since + // 2.53 microversion API (Pike release). + switch t := s.ID.(type) { + case int: + r.ID = strconv.Itoa(t) + case float64: + r.ID = strconv.Itoa(int(t)) + case string: + r.ID = t + default: + return fmt.Errorf("ID has unexpected type: %T", t) + } + return nil } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/fixtures.go index 79e704a7a734..9f149dace55a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/fixtures.go @@ -11,8 +11,9 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) -// ServiceListBody is sample response to the List call -const ServiceListBody = ` +// ServiceListBodyPre253 represents a raw service list from the Compute API +// with microversion older than 2.53. +const ServiceListBodyPre253 = ` { "services": [ { @@ -63,55 +64,176 @@ const ServiceListBody = ` } ` -// First service from the ServiceListBody -var FirstFakeService = services.Service{ - Binary: "nova-scheduler", - DisabledReason: "test1", - Host: "host1", - ID: 1, - State: "up", - Status: "disabled", - UpdatedAt: time.Date(2012, 10, 29, 13, 42, 2, 0, time.UTC), - Zone: "internal", -} +var ( + // FirstFakeServicePre253 represents the first service from the + // ServiceListBodyPre253. + FirstFakeServicePre253 = services.Service{ + Binary: "nova-scheduler", + DisabledReason: "test1", + Host: "host1", + ID: "1", + State: "up", + Status: "disabled", + UpdatedAt: time.Date(2012, 10, 29, 13, 42, 2, 0, time.UTC), + Zone: "internal", + } -// Second service from the ServiceListBody -var SecondFakeService = services.Service{ - Binary: "nova-compute", - DisabledReason: "test2", - Host: "host1", - ID: 2, - State: "up", - Status: "disabled", - UpdatedAt: time.Date(2012, 10, 29, 13, 42, 5, 0, time.UTC), - Zone: "nova", -} + // SecondFakeServicePre253 represents the second service from the + // ServiceListBodyPre253. + SecondFakeServicePre253 = services.Service{ + Binary: "nova-compute", + DisabledReason: "test2", + Host: "host1", + ID: "2", + State: "up", + Status: "disabled", + UpdatedAt: time.Date(2012, 10, 29, 13, 42, 5, 0, time.UTC), + Zone: "nova", + } + + // ThirdFakeServicePre253 represents the third service from the + // ServiceListBodyPre253. + ThirdFakeServicePre253 = services.Service{ + Binary: "nova-scheduler", + DisabledReason: "", + Host: "host2", + ID: "3", + State: "down", + Status: "enabled", + UpdatedAt: time.Date(2012, 9, 19, 6, 55, 34, 0, time.UTC), + Zone: "internal", + } -// Third service from the ServiceListBody -var ThirdFakeService = services.Service{ - Binary: "nova-scheduler", - DisabledReason: "", - Host: "host2", - ID: 3, - State: "down", - Status: "enabled", - UpdatedAt: time.Date(2012, 9, 19, 6, 55, 34, 0, time.UTC), - Zone: "internal", + // FourthFakeServicePre253 represents the fourth service from the + // ServiceListBodyPre253. + FourthFakeServicePre253 = services.Service{ + Binary: "nova-compute", + DisabledReason: "test4", + Host: "host2", + ID: "4", + State: "down", + Status: "disabled", + UpdatedAt: time.Date(2012, 9, 18, 8, 3, 38, 0, time.UTC), + Zone: "nova", + } +) + +// ServiceListBody represents a raw service list result with Pike+ release. +const ServiceListBody = ` +{ + "services": [ + { + "id": "4c720fa0-02c3-4834-8279-9eecf9edb6cb", + "binary": "nova-scheduler", + "disabled_reason": "test1", + "host": "host1", + "state": "up", + "status": "disabled", + "updated_at": "2012-10-29T13:42:02.000000", + "forced_down": false, + "zone": "internal" + }, + { + "id": "1fdfec3e-ee03-4e36-b99b-71cf2967b70c", + "binary": "nova-compute", + "disabled_reason": "test2", + "host": "host1", + "state": "up", + "status": "disabled", + "updated_at": "2012-10-29T13:42:05.000000", + "forced_down": false, + "zone": "nova" + }, + { + "id": "bd0b2e30-809e-4160-bd3d-f23ca30e9b68", + "binary": "nova-scheduler", + "disabled_reason": null, + "host": "host2", + "state": "down", + "status": "enabled", + "updated_at": "2012-09-19T06:55:34.000000", + "forced_down": false, + "zone": "internal" + }, + { + "id": "fe41c476-33e2-4ac3-ad21-3ffaf1b9c644", + "binary": "nova-compute", + "disabled_reason": "test4", + "host": "host2", + "state": "down", + "status": "disabled", + "updated_at": "2012-09-18T08:03:38.000000", + "forced_down": false, + "zone": "nova" + } + ] } +` -// Fourth service from the ServiceListBody -var FourthFakeService = services.Service{ - Binary: "nova-compute", - DisabledReason: "test4", - Host: "host2", - ID: 4, - State: "down", - Status: "disabled", - UpdatedAt: time.Date(2012, 9, 18, 8, 3, 38, 0, time.UTC), - Zone: "nova", +var ( + // FirstFakeService represents the first service from the ServiceListBody. + FirstFakeService = services.Service{ + Binary: "nova-scheduler", + DisabledReason: "test1", + Host: "host1", + ID: "4c720fa0-02c3-4834-8279-9eecf9edb6cb", + State: "up", + Status: "disabled", + UpdatedAt: time.Date(2012, 10, 29, 13, 42, 2, 0, time.UTC), + Zone: "internal", + } + + // SecondFakeService represents the second service from the ServiceListBody. + SecondFakeService = services.Service{ + Binary: "nova-compute", + DisabledReason: "test2", + Host: "host1", + ID: "1fdfec3e-ee03-4e36-b99b-71cf2967b70c", + State: "up", + Status: "disabled", + UpdatedAt: time.Date(2012, 10, 29, 13, 42, 5, 0, time.UTC), + Zone: "nova", + } + + // ThirdFakeService represents the third service from the ServiceListBody. + ThirdFakeService = services.Service{ + Binary: "nova-scheduler", + DisabledReason: "", + Host: "host2", + ID: "bd0b2e30-809e-4160-bd3d-f23ca30e9b68", + State: "down", + Status: "enabled", + UpdatedAt: time.Date(2012, 9, 19, 6, 55, 34, 0, time.UTC), + Zone: "internal", + } + + // FourthFakeService represents the fourth service from the ServiceListBody. + FourthFakeService = services.Service{ + Binary: "nova-compute", + DisabledReason: "test4", + Host: "host2", + ID: "fe41c476-33e2-4ac3-ad21-3ffaf1b9c644", + State: "down", + Status: "disabled", + UpdatedAt: time.Date(2012, 9, 18, 8, 3, 38, 0, time.UTC), + Zone: "nova", + } +) + +// HandleListPre253Successfully configures the test server to respond to a List +// request to a Compute server API pre 2.53 microversion release. +func HandleListPre253Successfully(t *testing.T) { + th.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ServiceListBodyPre253) + }) } -// HandleListSuccessfully configures the test server to respond to a List request. +// HandleListSuccessfully configures the test server to respond to a List +// request to a Compute server with Pike+ release. func HandleListSuccessfully(t *testing.T) { th.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/requests_test.go index 7f998814bed7..b8972c9bc3c4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services/testing/requests_test.go @@ -9,6 +9,38 @@ import ( "github.com/gophercloud/gophercloud/testhelper/client" ) +func TestListServicesPre253(t *testing.T) { + testhelper.SetupHTTP() + defer testhelper.TeardownHTTP() + HandleListPre253Successfully(t) + + pages := 0 + err := services.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := services.ExtractServices(page) + if err != nil { + return false, err + } + + 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]) + + return true, nil + }) + + testhelper.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + func TestListServices(t *testing.T) { testhelper.SetupHTTP() defer testhelper.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/doc.go index 32e8643e4dae..16b3a284ba40 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/doc.go @@ -2,26 +2,58 @@ Package usage provides information and interaction with the SimpleTenantUsage extension for the OpenStack Compute service. +Due to the way the API responses are formatted, it is not recommended to +query by using the AllPages convenience method. Instead, use the EachPage +method to view each result page-by-page. + +This is because the usage calculations are done _per page_ and not as +an aggregated total of the entire usage set. + Example to Retrieve Usage for a Single Tenant: + start := time.Date(2017, 01, 21, 10, 4, 20, 0, time.UTC) end := time.Date(2017, 01, 21, 10, 4, 20, 0, time.UTC) - singleTenantOpts := usage.SingleTenantOpts{ - Start: &start, - End: &end, - } + singleTenantOpts := usage.SingleTenantOpts{ + Start: &start, + End: &end, + } + + err := usage.SingleTenant(computeClient, tenantID, singleTenantOpts).EachPage(func(page pagination.Page) (bool, error) { + tenantUsage, err := usage.ExtractSingleTenant(page) + if err != nil { + return false, err + } + + fmt.Printf("%+v\n", tenantUsage) + + return true, nil + }) + + if err != nil { + panic(err) + } + +Example to Retrieve Usage for All Tenants: + + allTenantsOpts := usage.AllTenantsOpts{ + Detailed: true, + } + + err := usage.AllTenants(computeClient, allTenantsOpts).EachPage(func(page pagination.Page) (bool, error) { + allTenantsUsage, err := usage.ExtractAllTenants(page) + if err != nil { + return false, err + } - page, err := usage.SingleTenant(computeClient, tenantID, singleTenantOpts).AllPages() - if err != nil { - panic(err) - } + fmt.Printf("%+v\n", allTenantsUsage) - tenantUsage, err := usage.ExtractSingleTenant(page) - if err != nil { - panic(err) - } + return true, nil + }) - fmt.Printf("%+v\n", tenantUsage) + if err != nil { + panic(err) + } */ package usage diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/requests.go index ee66e2a2129c..77312227f6d3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/requests.go @@ -8,6 +8,36 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +// SingleTenantOpts are options for fetching usage of a single tenant. +type SingleTenantOpts struct { + // The ending time to calculate usage statistics on compute and storage resources. + End *time.Time `q:"end"` + + // The beginning time to calculate usage statistics on compute and storage resources. + Start *time.Time `q:"start"` +} + +// SingleTenantOptsBuilder allows extensions to add additional parameters to the +// SingleTenant request. +type SingleTenantOptsBuilder interface { + ToUsageSingleTenantQuery() (string, error) +} + +// ToUsageSingleTenantQuery formats a SingleTenantOpts into a query string. +func (opts SingleTenantOpts) ToUsageSingleTenantQuery() (string, error) { + params := make(url.Values) + if opts.Start != nil { + params.Add("start", opts.Start.Format(gophercloud.RFC3339MilliNoZ)) + } + + if opts.End != nil { + params.Add("end", opts.End.Format(gophercloud.RFC3339MilliNoZ)) + } + + q := &url.URL{RawQuery: params.Encode()} + return q.String(), nil +} + // SingleTenant returns usage data about a single tenant. func SingleTenant(client *gophercloud.ServiceClient, tenantID string, opts SingleTenantOptsBuilder) pagination.Pager { url := getTenantURL(client, tenantID) @@ -19,12 +49,15 @@ func SingleTenant(client *gophercloud.ServiceClient, tenantID string, opts Singl url += query } return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return SingleTenantPage{pagination.SinglePageBase(r)} + return SingleTenantPage{pagination.LinkedPageBase{PageResult: r}} }) } -// SingleTenantOpts are options for fetching usage of a single tenant. -type SingleTenantOpts struct { +// AllTenantsOpts are options for fetching usage of all tenants. +type AllTenantsOpts struct { + // Detailed will return detailed results. + Detailed bool + // The ending time to calculate usage statistics on compute and storage resources. End *time.Time `q:"end"` @@ -32,14 +65,14 @@ type SingleTenantOpts struct { Start *time.Time `q:"start"` } -// SingleTenantOptsBuilder allows extensions to add additional parameters to the -// SingleTenant request. -type SingleTenantOptsBuilder interface { - ToUsageSingleTenantQuery() (string, error) +// AllTenantsOptsBuilder allows extensions to add additional parameters to the +// AllTenants request. +type AllTenantsOptsBuilder interface { + ToUsageAllTenantsQuery() (string, error) } -// ToUsageSingleTenantQuery formats a SingleTenantOpts into a query string. -func (opts SingleTenantOpts) ToUsageSingleTenantQuery() (string, error) { +// ToUsageAllTenantsQuery formats a AllTenantsOpts into a query string. +func (opts AllTenantsOpts) ToUsageAllTenantsQuery() (string, error) { params := make(url.Values) if opts.Start != nil { params.Add("start", opts.Start.Format(gophercloud.RFC3339MilliNoZ)) @@ -49,6 +82,25 @@ func (opts SingleTenantOpts) ToUsageSingleTenantQuery() (string, error) { params.Add("end", opts.End.Format(gophercloud.RFC3339MilliNoZ)) } + if opts.Detailed == true { + params.Add("detailed", "1") + } + q := &url.URL{RawQuery: params.Encode()} return q.String(), nil } + +// AllTenants returns usage data about all tenants. +func AllTenants(client *gophercloud.ServiceClient, opts AllTenantsOptsBuilder) pagination.Pager { + url := allTenantsURL(client) + if opts != nil { + query, err := opts.ToUsageAllTenantsQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AllTenantsPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/results.go index 39661ba4633b..f446730ec2e4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/results.go @@ -117,21 +117,67 @@ func (u *ServerUsage) UnmarshalJSON(b []byte) error { // SingleTenantPage stores a single, only page of TenantUsage results from a // SingleTenant call. type SingleTenantPage struct { - pagination.SinglePageBase + pagination.LinkedPageBase } // IsEmpty determines whether or not a SingleTenantPage is empty. -func (page SingleTenantPage) IsEmpty() (bool, error) { - ks, err := ExtractSingleTenant(page) +func (r SingleTenantPage) IsEmpty() (bool, error) { + ks, err := ExtractSingleTenant(r) return ks == nil, err } +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r SingleTenantPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"tenant_usage_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + // ExtractSingleTenant interprets a SingleTenantPage as a TenantUsage result. func ExtractSingleTenant(page pagination.Page) (*TenantUsage, error) { var s struct { - TenantUsage *TenantUsage `json:"tenant_usage"` - TenantUsageLinks []gophercloud.Link `json:"tenant_usage_links"` + TenantUsage *TenantUsage `json:"tenant_usage"` } err := (page.(SingleTenantPage)).ExtractInto(&s) return s.TenantUsage, err } + +// AllTenantsPage stores a single, only page of TenantUsage results from a +// AllTenants call. +type AllTenantsPage struct { + pagination.LinkedPageBase +} + +// ExtractAllTenants interprets a AllTenantsPage as a TenantUsage result. +func ExtractAllTenants(page pagination.Page) ([]TenantUsage, error) { + var s struct { + TenantUsages []TenantUsage `json:"tenant_usages"` + } + err := (page.(AllTenantsPage)).ExtractInto(&s) + return s.TenantUsages, err +} + +// IsEmpty determines whether or not an AllTenantsPage is empty. +func (r AllTenantsPage) IsEmpty() (bool, error) { + usages, err := ExtractAllTenants(r) + return len(usages) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r AllTenantsPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"tenant_usages_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/fixtures.go index b7c1ae55f511..3e37947a4e9e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/fixtures.go @@ -12,6 +12,7 @@ import ( ) const FirstTenantID = "aabbccddeeff112233445566" +const SecondTenantID = "665544332211ffeeddccbbaa" // GetSingleTenant holds the fixtures for the content of the request for a // single tenant. @@ -135,3 +136,179 @@ var SingleTenantUsageResults = usage.TenantUsage{ TotalMemoryMBUsage: 644.27116544, TotalVCPUsUsage: 1.25834212, } + +// GetAllTenants holds the fixtures for the content of the request for +// all tenants. +const GetAllTenants = `{ + "tenant_usages": [ + { + "server_usages": [ + { + "ended_at": null, + "flavor": "m1.tiny", + "hours": 0.021675453333333334, + "instance_id": "a70096fd-8196-406b-86c4-045840f53ad7", + "local_gb": 1, + "memory_mb": 512, + "name": "jttest", + "started_at": "2017-11-30T03:23:43.000000", + "state": "active", + "tenant_id": "aabbccddeeff112233445566", + "uptime": 78, + "vcpus": 1 + }, + { + "ended_at": "2017-11-21T04:10:11.000000", + "flavor": "m1.acctest", + "hours": 0.33444444444444443, + "instance_id": "c04e38f2-dcee-4ca8-9466-7708d0a9b6dd", + "local_gb": 15, + "memory_mb": 512, + "name": "basic", + "started_at": "2017-11-21T03:50:07.000000", + "state": "terminated", + "tenant_id": "aabbccddeeff112233445566", + "uptime": 1204, + "vcpus": 1 + }, + { + "ended_at": "2017-11-30T03:21:21.000000", + "flavor": "m1.acctest", + "hours": 0.004166666666666667, + "instance_id": "ceb654fa-e0e8-44fb-8942-e4d0bfad3941", + "local_gb": 15, + "memory_mb": 512, + "name": "ACPTTESTJSxbPQAC34lTnBE1", + "started_at": "2017-11-30T03:21:06.000000", + "state": "terminated", + "tenant_id": "aabbccddeeff112233445566", + "uptime": 15, + "vcpus": 1 + } + ], + "start": "2017-11-02T03:25:01.000000", + "stop": "2017-11-30T03:25:01.000000", + "tenant_id": "aabbccddeeff112233445566", + "total_hours": 1.25834212, + "total_local_gb_usage": 18.571675453333334, + "total_memory_mb_usage": 644.27116544, + "total_vcpus_usage": 1.25834212 + }, + { + "server_usages": [ + { + "ended_at": null, + "flavor": "m1.tiny", + "hours": 0.021675453333333334, + "instance_id": "a70096fd-8196-406b-86c4-045840f53ad7", + "local_gb": 1, + "memory_mb": 512, + "name": "test", + "started_at": "2017-11-30T03:23:43.000000", + "state": "active", + "tenant_id": "665544332211ffeeddccbbaa", + "uptime": 78, + "vcpus": 1 + } + ], + "start": "2017-11-02T03:25:01.000000", + "stop": "2017-11-30T03:25:01.000000", + "tenant_id": "665544332211ffeeddccbbaa", + "total_hours": 0.021675453333333334, + "total_local_gb_usage": 18.571675453333334, + "total_memory_mb_usage": 644.27116544, + "total_vcpus_usage": 1.25834212 + } + ] +}` + +// HandleGetAllTenantsSuccessfully configures the test server to respond to a +// Get request for all tenants. +func HandleGetAllTenantsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/os-simple-tenant-usage", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, GetAllTenants) + }) +} + +// AllTenantsUsageResult is the code fixture for GetAllTenants. +var AllTenantsUsageResult = []usage.TenantUsage{ + { + ServerUsages: []usage.ServerUsage{ + { + Flavor: "m1.tiny", + Hours: 0.021675453333333334, + InstanceID: "a70096fd-8196-406b-86c4-045840f53ad7", + LocalGB: 1, + MemoryMB: 512, + Name: "jttest", + StartedAt: time.Date(2017, 11, 30, 3, 23, 43, 0, time.UTC), + State: "active", + TenantID: "aabbccddeeff112233445566", + Uptime: 78, + VCPUs: 1, + }, + { + Flavor: "m1.acctest", + Hours: 0.33444444444444443, + InstanceID: "c04e38f2-dcee-4ca8-9466-7708d0a9b6dd", + LocalGB: 15, + MemoryMB: 512, + Name: "basic", + StartedAt: time.Date(2017, 11, 21, 3, 50, 7, 0, time.UTC), + EndedAt: time.Date(2017, 11, 21, 4, 10, 11, 0, time.UTC), + State: "terminated", + TenantID: "aabbccddeeff112233445566", + Uptime: 1204, + VCPUs: 1, + }, + { + Flavor: "m1.acctest", + Hours: 0.004166666666666667, + InstanceID: "ceb654fa-e0e8-44fb-8942-e4d0bfad3941", + LocalGB: 15, + MemoryMB: 512, + Name: "ACPTTESTJSxbPQAC34lTnBE1", + StartedAt: time.Date(2017, 11, 30, 3, 21, 6, 0, time.UTC), + EndedAt: time.Date(2017, 11, 30, 3, 21, 21, 0, time.UTC), + State: "terminated", + TenantID: "aabbccddeeff112233445566", + Uptime: 15, + VCPUs: 1, + }, + }, + Start: time.Date(2017, 11, 2, 3, 25, 1, 0, time.UTC), + Stop: time.Date(2017, 11, 30, 3, 25, 1, 0, time.UTC), + TenantID: "aabbccddeeff112233445566", + TotalHours: 1.25834212, + TotalLocalGBUsage: 18.571675453333334, + TotalMemoryMBUsage: 644.27116544, + TotalVCPUsUsage: 1.25834212, + }, + { + ServerUsages: []usage.ServerUsage{ + { + Flavor: "m1.tiny", + Hours: 0.021675453333333334, + InstanceID: "a70096fd-8196-406b-86c4-045840f53ad7", + LocalGB: 1, + MemoryMB: 512, + Name: "test", + StartedAt: time.Date(2017, 11, 30, 3, 23, 43, 0, time.UTC), + State: "active", + TenantID: "665544332211ffeeddccbbaa", + Uptime: 78, + VCPUs: 1, + }, + }, + Start: time.Date(2017, 11, 2, 3, 25, 1, 0, time.UTC), + Stop: time.Date(2017, 11, 30, 3, 25, 1, 0, time.UTC), + TenantID: "665544332211ffeeddccbbaa", + TotalHours: 0.021675453333333334, + TotalLocalGBUsage: 18.571675453333334, + TotalMemoryMBUsage: 644.27116544, + TotalVCPUsUsage: 1.25834212, + }, +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/requests_test.go index 1b43f12aec3a..1b4f27694c93 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/testing/requests_test.go @@ -4,18 +4,49 @@ import ( "testing" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage" + "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/gophercloud/testhelper/client" ) func TestGetTenant(t *testing.T) { - var getOpts usage.SingleTenantOpts th.SetupHTTP() defer th.TeardownHTTP() HandleGetSingleTenantSuccessfully(t) - page, err := usage.SingleTenant(client.ServiceClient(), FirstTenantID, getOpts).AllPages() + + count := 0 + err := usage.SingleTenant(client.ServiceClient(), FirstTenantID, nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := usage.ExtractSingleTenant(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, &SingleTenantUsageResults, actual) + + return true, nil + }) th.AssertNoErr(t, err) - actual, err := usage.ExtractSingleTenant(page) + th.AssertEquals(t, count, 1) +} + +func TestAllTenants(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetAllTenantsSuccessfully(t) + + getOpts := usage.AllTenantsOpts{ + Detailed: true, + } + + count := 0 + err := usage.AllTenants(client.ServiceClient(), getOpts).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := usage.ExtractAllTenants(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, AllTenantsUsageResult, actual) + + return true, nil + }) th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &SingleTenantUsageResults, actual) + th.AssertEquals(t, count, 1) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/urls.go index f172b622117a..506361070101 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/usage/urls.go @@ -4,7 +4,7 @@ import "github.com/gophercloud/gophercloud" const resourcePath = "os-simple-tenant-usage" -func getURL(client *gophercloud.ServiceClient) string { +func allTenantsURL(client *gophercloud.ServiceClient) string { return client.ServiceURL(resourcePath) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go index 4b406df957dc..753024a18b72 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go @@ -52,6 +52,14 @@ type ListOpts struct { MinDisk int `q:"minDisk"` MinRAM int `q:"minRam"` + // SortDir allows to select sort direction. + // It can be "asc" or "desc" (default). + SortDir string `q:"sort_dir"` + + // SortKey allows to sort by one of the flavors attributes. + // Default is flavorid. + SortKey string `q:"sort_key"` + // Marker and Limit control paging. // Marker instructs List where to start listing from. Marker string `q:"marker"` @@ -140,7 +148,7 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } -// Get retrieves details of a single flavor. Use ExtractFlavor to convert its +// Get retrieves details of a single flavor. Use Extract to convert its // result into a Flavor. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { _, r.Err = client.Get(getURL(client, id), &r.Body, nil) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go index 525cddaea222..92fe1b1809d4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go @@ -59,7 +59,7 @@ type Flavor struct { RxTxFactor float64 `json:"rxtx_factor"` // Swap is the amount of swap space, measured in MB. - Swap int `json:"swap"` + Swap int `json:"-"` // VCPUs indicates how many (virtual) CPUs are available for this flavor. VCPUs int `json:"vcpus"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/testing/requests_test.go index fba0d4776b3f..14a9388edba8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/testing/requests_test.go @@ -35,7 +35,7 @@ func TestListFlavors(t *testing.T) { "name": "m1.tiny", "vcpus": 1, "disk": 1, - "ram": 512, + "ram": 9216000, "swap":"", "os-flavor-access:is_public": true, "OS-FLV-EXT-DATA:ephemeral": 10 @@ -87,7 +87,7 @@ func TestListFlavors(t *testing.T) { } expected := []flavors.Flavor{ - {ID: "1", Name: "m1.tiny", VCPUs: 1, Disk: 1, RAM: 512, Swap: 0, IsPublic: true, Ephemeral: 10}, + {ID: "1", Name: "m1.tiny", VCPUs: 1, Disk: 1, RAM: 9216000, Swap: 0, IsPublic: true, Ephemeral: 10}, {ID: "2", Name: "m1.small", VCPUs: 1, Disk: 20, RAM: 2048, Swap: 1000, IsPublic: true, Ephemeral: 0}, {ID: "3", Name: "m1.medium", VCPUs: 2, Disk: 40, RAM: 4096, Swap: 1000, IsPublic: false, Ephemeral: 0}, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go new file mode 100644 index 000000000000..84ec9f31d355 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go @@ -0,0 +1,11 @@ +package servers + +// ExtractTags will extract the tags of a server. +// This requires the client to be set to microversion 2.26 or later. +func (r serverResult) ExtractTags() ([]string, error) { + var s struct { + Tags []string `json:"tags"` + } + err := r.ExtractInto(&s) + return s.Tags, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go index 626eb63e9104..ee8e93b1dcbf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go @@ -183,12 +183,22 @@ type CreateOpts struct { // AccessIPv4 specifies an IPv4 address for the instance. AccessIPv4 string `json:"accessIPv4,omitempty"` - // AccessIPv6 pecifies an IPv6 address for the instance. + // AccessIPv6 specifies an IPv6 address for the instance. AccessIPv6 string `json:"accessIPv6,omitempty"` + // Min specifies Minimum number of servers to launch. + Min int `json:"min_count,omitempty"` + + // Max specifies Maximum number of servers to launch. + Max int `json:"max_count,omitempty"` + // ServiceClient will allow calls to be made to retrieve an image or // flavor ID by name. ServiceClient *gophercloud.ServiceClient `json:"-"` + + // Tags allows a server to be tagged with single-word metadata. + // Requires microversion 2.52 or later. + Tags []string `json:"tags,omitempty"` } // ToServerCreateMap assembles a request body based on the contents of a @@ -272,6 +282,14 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { b["flavorRef"] = flavorID } + if opts.Min != 0 { + b["min_count"] = opts.Min + } + + if opts.Max != 0 { + b["max_count"] = opts.Max + } + return map[string]interface{}{"server": b}, nil } @@ -383,7 +401,7 @@ type RebootOpts struct { } // ToServerRebootMap builds a body for the reboot request. -func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { +func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "reboot") } @@ -545,39 +563,6 @@ func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) return } -// RescueOptsBuilder is an interface that allows extensions to override the -// default structure of a Rescue request. -type RescueOptsBuilder interface { - ToServerRescueMap() (map[string]interface{}, error) -} - -// RescueOpts represents the configuration options used to control a Rescue -// option. -type RescueOpts struct { - // AdminPass is the desired administrative password for the instance in - // RESCUE mode. If it's left blank, the server will generate a password. - AdminPass string `json:"adminPass,omitempty"` -} - -// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON -// request body for the Rescue request. -func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "rescue") -} - -// Rescue instructs the provider to place the server into RESCUE mode. -func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) { - b, err := opts.ToServerRescueMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - // ResetMetadataOptsBuilder allows extensions to add additional parameters to // the Reset request. type ResetMetadataOptsBuilder interface { @@ -756,7 +741,12 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - allPages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + allPages, err := List(client, listOpts).AllPages() if err != nil { return "", err } @@ -789,3 +779,34 @@ func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPassw _, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) return } + +// ShowConsoleOutputOptsBuilder is the interface types must satisfy in order to be +// used as ShowConsoleOutput options +type ShowConsoleOutputOptsBuilder interface { + ToServerShowConsoleOutputMap() (map[string]interface{}, error) +} + +// ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder +type ShowConsoleOutputOpts struct { + // The number of lines to fetch from the end of console log. + // All lines will be returned if this is not specified. + Length int `json:"length,omitempty"` +} + +// ToServerShowConsoleOutputMap formats a ShowConsoleOutputOpts structure into a request body. +func (opts ShowConsoleOutputOpts) ToServerShowConsoleOutputMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-getConsoleOutput") +} + +// ShowConsoleOutput makes a request against the nova API to get console log from the server +func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowConsoleOutputOptsBuilder) (r ShowConsoleOutputResult) { + b, err := opts.ToServerShowConsoleOutputMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go index c6c1ff43f7b3..f973d1ea0e1b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go @@ -68,18 +68,27 @@ type ActionResult struct { gophercloud.ErrResult } -// RescueResult is the response from a Rescue operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type RescueResult struct { - ActionResult -} - // CreateImageResult is the response from a CreateImage operation. Call its // ExtractImageID method to retrieve the ID of the newly created image. type CreateImageResult struct { gophercloud.Result } +// ShowConsoleOutputResult represents the result of console output from a server +type ShowConsoleOutputResult struct { + gophercloud.Result +} + +// Extract will return the console output from a ShowConsoleOutput request. +func (r ShowConsoleOutputResult) Extract() (string, error) { + var s struct { + Output string `json:"output"` + } + + err := r.ExtractInto(&s) + return s.Output, err +} + // GetPasswordResult represent the result of a get os-server-password operation. // Call its ExtractPassword method to retrieve the password. type GetPasswordResult struct { @@ -134,15 +143,6 @@ func (r CreateImageResult) ExtractImageID() (string, error) { return imageID, nil } -// Extract interprets any RescueResult as an AdminPass, if possible. -func (r RescueResult) Extract() (string, error) { - var s struct { - AdminPass string `json:"adminPass"` - } - err := r.ExtractInto(&s) - return s.AdminPass, err -} - // Server represents a server/instance in the OpenStack cloud. type Server struct { // ID uniquely identifies this server amongst all other servers, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/fixtures.go index baec93efbea6..db3819abc08d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/fixtures.go @@ -387,6 +387,97 @@ const ServerPasswordBody = ` } ` +const ConsoleOutputBody = `{ + "output": "abc" +}` + +const ServerWithTagsCreateRequest = ` +{ + "server": { + "name": "derp", + "imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb", + "flavorRef": "1", + "tags": ["foo", "bar"] + } +}` + +// SingleServerWithTagsBody is the canned body of a Get request on an existing server with tags. +const SingleServerWithTagsBody = ` +{ + "server": { + "status": "ACTIVE", + "updated": "2014-09-25T13:04:49Z", + "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", + "OS-EXT-SRV-ATTR:host": "devstack", + "addresses": { + "private": [ + { + "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be", + "version": 4, + "addr": "10.0.0.31", + "OS-EXT-IPS:type": "fixed" + } + ] + }, + "links": [ + { + "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba", + "rel": "self" + }, + { + "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba", + "rel": "bookmark" + } + ], + "key_name": null, + "image": { + "id": "f90f6034-2570-4974-8351-6b49732ef2eb", + "links": [ + { + "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb", + "rel": "bookmark" + } + ] + }, + "OS-EXT-STS:task_state": null, + "OS-EXT-STS:vm_state": "active", + "OS-EXT-SRV-ATTR:instance_name": "instance-0000001d", + "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000", + "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba", + "security_groups": [ + { + "name": "default" + } + ], + "OS-SRV-USG:terminated_at": null, + "OS-EXT-AZ:availability_zone": "nova", + "user_id": "9349aff8be7545ac9d2f1d00999a23cd", + "name": "derp", + "created": "2014-09-25T13:04:41Z", + "tenant_id": "fcad67a6189847c4aecfa3c81a05783b", + "OS-DCF:diskConfig": "MANUAL", + "os-extended-volumes:volumes_attached": [], + "accessIPv4": "", + "accessIPv6": "", + "progress": 0, + "OS-EXT-STS:power_state": 1, + "config_drive": "", + "metadata": {}, + "tags": ["foo", "bar"] + } +} +` + var ( herpTimeCreated, _ = time.Parse(time.RFC3339, "2014-09-25T13:10:02Z") herpTimeUpdated, _ = time.Parse(time.RFC3339, "2014-09-25T13:10:10Z") @@ -504,6 +595,8 @@ var ( }, } + ConsoleOutput = "abc" + merpTimeCreated, _ = time.Parse(time.RFC3339, "2014-09-25T13:04:41Z") merpTimeUpdated, _ = time.Parse(time.RFC3339, "2014-09-25T13:04:49Z") // ServerMerp is a Server struct that should correspond to the second server in ServerListBody. @@ -681,6 +774,28 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { }) } +// HandleServerCreationWithCustomFieldSuccessfully sets up the test server to respond to a server creation request +// with a given response. +func HandleServersCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "server": { + "name": "derp", + "imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb", + "flavorRef": "1", + "min_count": 3, + "max_count": 3 + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + // HandleServerCreationWithCustomFieldSuccessfully sets up the test server to respond to a server creation request // with a given response. func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, response string) { @@ -847,6 +962,19 @@ func HandleRebootSuccessfully(t *testing.T) { }) } +// HandleShowConsoleOutputSuccessfully sets up the test server to respond to a os-getConsoleOutput request with success. +func HandleShowConsoleOutputSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ "os-getConsoleOutput": { "length": 50 } }`) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + // HandleRebuildSuccessfully sets up the test server to respond to a rebuild request with success. func HandleRebuildSuccessfully(t *testing.T, response string) { th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { @@ -869,18 +997,6 @@ func HandleRebuildSuccessfully(t *testing.T, response string) { }) } -// HandleServerRescueSuccessfully sets up the test server to respond to a server Rescue request. -func HandleServerRescueSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - th.TestJSONRequest(t, r, `{ "rescue": { "adminPass": "1234567890" } }`) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{ "adminPass": "1234567890" }`)) - }) -} - // HandleMetadatumGetSuccessfully sets up the test server to respond to a metadatum Get request. func HandleMetadatumGetSuccessfully(t *testing.T) { th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { @@ -1073,3 +1189,18 @@ func HandlePasswordGetSuccessfully(t *testing.T) { fmt.Fprintf(w, ServerPasswordBody) }) } + +// HandleServerWithTagsCreationSuccessfully sets up the test server to respond +// to a server creation request with a given response. +func HandleServerWithTagsCreationSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ServerWithTagsCreateRequest) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + + fmt.Fprintf(w, SingleServerWithTagsBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/requests_test.go index e039f29aef0e..b79f1e631e93 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/testing/requests_test.go @@ -100,6 +100,23 @@ func TestCreateServer(t *testing.T) { th.CheckDeepEquals(t, ServerDerp, *actual) } +func TestCreateServers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleServersCreationSuccessfully(t, SingleServerBody) + + actual, err := servers.Create(client.ServiceClient(), servers.CreateOpts{ + Name: "derp", + ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", + FlavorRef: "1", + Min: 3, + Max: 3, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ServerDerp, *actual) +} + func TestCreateServerWithCustomField(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -283,6 +300,20 @@ func TestChangeServerAdminPassword(t *testing.T) { th.AssertNoErr(t, res.Err) } +func TestShowConsoleOutput(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleShowConsoleOutputSuccessfully(t, ConsoleOutputBody) + + outputOpts := &servers.ShowConsoleOutputOpts{ + Length: 50, + } + actual, err := servers.ShowConsoleOutput(client.ServiceClient(), "1234asdf", outputOpts).Extract() + + th.AssertNoErr(t, err) + th.AssertByteArrayEquals(t, []byte(ConsoleOutput), []byte(actual)) +} + func TestGetPassword(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -297,7 +328,7 @@ func TestRebootServer(t *testing.T) { defer th.TeardownHTTP() HandleRebootSuccessfully(t) - res := servers.Reboot(client.ServiceClient(), "1234asdf", &servers.RebootOpts{ + res := servers.Reboot(client.ServiceClient(), "1234asdf", servers.RebootOpts{ Type: servers.SoftReboot, }) th.AssertNoErr(t, res.Err) @@ -369,20 +400,6 @@ func TestRevertResize(t *testing.T) { th.AssertNoErr(t, res.Err) } -func TestRescue(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - HandleServerRescueSuccessfully(t) - - res := servers.Rescue(client.ServiceClient(), "1234asdf", servers.RescueOpts{ - AdminPass: "1234567890", - }) - th.AssertNoErr(t, res.Err) - adminPass, _ := res.Extract() - th.AssertEquals(t, "1234567890", adminPass) -} - func TestGetMetadatum(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -550,3 +567,29 @@ func TestMarshalPersonality(t *testing.T) { t.Fatal("file contents incorrect") } } + +func TestCreateServerWithTags(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleServerWithTagsCreationSuccessfully(t) + + c := client.ServiceClient() + c.Microversion = "2.52" + + tags := []string{"foo", "bar"} + createOpts := servers.CreateOpts{ + Name: "derp", + ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", + FlavorRef: "1", + Tags: tags, + } + res := servers.Create(c, createOpts) + th.AssertNoErr(t, res.Err) + actualServer, err := res.Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ServerDerp, *actualServer) + + actualTags, err := res.ExtractTags() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, tags, actualTags) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/doc.go new file mode 100644 index 000000000000..5237759eb239 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/doc.go @@ -0,0 +1,4 @@ +// Package capsules contains functionality for working with Zun capsule +// resources. A capsule is a container group, as the co-located and +// co-scheduled unit, is the same like pod in Kubernetes. +package capsules diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/errors.go new file mode 100644 index 000000000000..6542e66ec835 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/errors.go @@ -0,0 +1,15 @@ +package capsules + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +type ErrInvalidDataFormat struct { + gophercloud.BaseError +} + +func (e ErrInvalidDataFormat) Error() string { + return fmt.Sprintf("Data in neither json nor yaml format.") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/microversions.go new file mode 100644 index 000000000000..da8f34a510a3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/microversions.go @@ -0,0 +1,101 @@ +package capsules + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ExtractV132 is a function that accepts a result and extracts a capsule resource. +func (r commonResult) ExtractV132() (*CapsuleV132, error) { + var s *CapsuleV132 + err := r.ExtractInto(&s) + return s, err +} + +// Represents a Capsule at microversion vXY or greater. +type CapsuleV132 struct { + // UUID for the capsule + UUID string `json:"uuid"` + + // User ID for the capsule + UserID string `json:"user_id"` + + // Project ID for the capsule + ProjectID string `json:"project_id"` + + // cpu for the capsule + CPU float64 `json:"cpu"` + + // Memory for the capsule + Memory string `json:"memory"` + + // The name of the capsule + MetaName string `json:"name"` + + // Indicates whether capsule is currently operational. + Status string `json:"status"` + + // Indicates whether capsule is currently operational. + StatusReason string `json:"status_reason"` + + // The created time of the capsule. + CreatedAt time.Time `json:"-"` + + // The updated time of the capsule. + UpdatedAt time.Time `json:"-"` + + // Links includes HTTP references to the itself, useful for passing along to + // other APIs that might want a capsule reference. + Links []interface{} `json:"links"` + + // The capsule restart policy + RestartPolicy map[string]string `json:"restart_policy"` + + // The capsule metadata labels + MetaLabels map[string]string `json:"labels"` + + // The capsule IP addresses + Addresses map[string][]Address `json:"addresses"` + + // The container object inside capsule + Containers []Container `json:"containers"` + + // The capsule host + Host string `json:"host"` +} + +// ExtractCapsulesV132 accepts a Page struct, specifically a CapsulePage struct, +// and extracts the elements into a slice of CapsuleV132 structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractCapsulesV132(r pagination.Page) ([]CapsuleV132, error) { + var s struct { + Capsules []CapsuleV132 `json:"capsules"` + } + err := (r.(CapsulePage)).ExtractInto(&s) + return s.Capsules, err +} + +func (r *CapsuleV132) UnmarshalJSON(b []byte) error { + type tmp CapsuleV132 + + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = CapsuleV132(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/requests.go new file mode 100644 index 000000000000..611c27677a0a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/requests.go @@ -0,0 +1,102 @@ +package capsules + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToCapsuleCreateMap() (map[string]interface{}, error) +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToCapsuleListQuery() (string, error) +} + +// Get requests details on a single capsule, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + }) + return +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // A structure that contains either the template file or url. Call the + // associated methods to extract the information relevant to send in a create request. + TemplateOpts *Template `json:"-" required:"true"` +} + +// ToCapsuleCreateMap assembles a request body based on the contents of +// a CreateOpts. +func (opts CreateOpts) ToCapsuleCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if err := opts.TemplateOpts.Parse(); err != nil { + return nil, err + } + b["template"] = string(opts.TemplateOpts.Bin) + + return b, nil +} + +// Create implements create capsule request. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToCapsuleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{202}}) + return +} + +// 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 capsule attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + AllProjects bool `q:"all_projects"` +} + +// ToCapsuleListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToCapsuleListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list capsules accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToCapsuleListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return CapsulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete implements Capsule delete request. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/results.go new file mode 100644 index 000000000000..ea88af4ce5d8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/results.go @@ -0,0 +1,371 @@ +package capsules + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// ExtractBase is a function that accepts a result and extracts +// a base a capsule resource. +func (r commonResult) ExtractBase() (*Capsule, error) { + var s *Capsule + err := r.ExtractInto(&s) + return s, err +} + +// Extract is a function that accepts a result and extracts a capsule result. +// The result will be returned as an interface{} where it should be able to +// be casted as either a Capsule or CapsuleV132. +func (r commonResult) Extract() (interface{}, error) { + s, err := r.ExtractBase() + if err == nil { + return s, nil + } + + if _, ok := err.(*json.UnmarshalTypeError); !ok { + return s, err + } + + return r.ExtractV132() +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// CreateResult is the response from a Create operation. Call its Extract +// method to interpret it as a Capsule. +type CreateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +type CapsulePage struct { + pagination.LinkedPageBase +} + +// Represents a Capsule +type Capsule struct { + // UUID for the capsule + UUID string `json:"uuid"` + + // User ID for the capsule + UserID string `json:"user_id"` + + // Project ID for the capsule + ProjectID string `json:"project_id"` + + // cpu for the capsule + CPU float64 `json:"cpu"` + + // Memory for the capsule + Memory string `json:"memory"` + + // The name of the capsule + MetaName string `json:"meta_name"` + + // Indicates whether capsule is currently operational. + Status string `json:"status"` + + // Indicates whether capsule is currently operational. + StatusReason string `json:"status_reason"` + + // The created time of the capsule. + CreatedAt time.Time `json:"-"` + + // The updated time of the capsule. + UpdatedAt time.Time `json:"-"` + + // Links includes HTTP references to the itself, useful for passing along to + // other APIs that might want a capsule reference. + Links []interface{} `json:"links"` + + // The capsule version + CapsuleVersion string `json:"capsule_version"` + + // The capsule restart policy + RestartPolicy string `json:"restart_policy"` + + // The capsule metadata labels + MetaLabels map[string]string `json:"meta_labels"` + + // The list of containers uuids inside capsule. + ContainersUUIDs []string `json:"containers_uuids"` + + // The capsule IP addresses + Addresses map[string][]Address `json:"addresses"` + + // The capsule volume attached information + VolumesInfo map[string][]string `json:"volumes_info"` + + // The container object inside capsule + Containers []Container `json:"containers"` + + // The capsule host + Host string `json:"host"` +} + +type Container struct { + // The Container IP addresses + Addresses map[string][]Address `json:"addresses"` + + // UUID for the container + UUID string `json:"uuid"` + + // User ID for the container + UserID string `json:"user_id"` + + // Project ID for the container + ProjectID string `json:"project_id"` + + // cpu for the container + CPU float64 `json:"cpu"` + + // Memory for the container + Memory string `json:"memory"` + + // Image for the container + Image string `json:"image"` + + // The container container + Labels map[string]string `json:"labels"` + + // The created time of the container + CreatedAt time.Time `json:"-"` + + // The updated time of the container + UpdatedAt time.Time `json:"-"` + + // The started time of the container + StartedAt time.Time `json:"-"` + + // Name for the container + Name string `json:"name"` + + // Links includes HTTP references to the itself, useful for passing along to + // other APIs that might want a capsule reference. + Links []interface{} `json:"links"` + + // auto remove flag token for the container + AutoRemove bool `json:"auto_remove"` + + // Host for the container + Host string `json:"host"` + + // Work directory for the container + WorkDir string `json:"workdir"` + + // Disk for the container + Disk int `json:"disk"` + + // Image pull policy for the container + ImagePullPolicy string `json:"image_pull_policy"` + + // Task state for the container + TaskState string `json:"task_state"` + + // Host name for the container + HostName string `json:"hostname"` + + // Environment for the container + Environment map[string]string `json:"environment"` + + // Status for the container + Status string `json:"status"` + + // Auto Heal flag for the container + AutoHeal bool `json:"auto_heal"` + + // Status details for the container + StatusDetail string `json:"status_detail"` + + // Status reason for the container + StatusReason string `json:"status_reason"` + + // Image driver for the container + ImageDriver string `json:"image_driver"` + + // Command for the container + Command []string `json:"command"` + + // Image for the container + Runtime string `json:"runtime"` + + // Interactive flag for the container + Interactive bool `json:"interactive"` + + // Restart Policy for the container + RestartPolicy map[string]string `json:"restart_policy"` + + // Ports information for the container + Ports []int `json:"ports"` + + // Security groups for the container + SecurityGroups []string `json:"security_groups"` +} + +type Address struct { + PreserveOnDelete bool `json:"preserve_on_delete"` + Addr string `json:"addr"` + Port string `json:"port"` + Version float64 `json:"version"` + SubnetID string `json:"subnet_id"` +} + +// NextPageURL is invoked when a paginated collection of capsules has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r CapsulePage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, nil +} + +// IsEmpty checks whether a CapsulePage struct is empty. +func (r CapsulePage) IsEmpty() (bool, error) { + is, err := ExtractCapsules(r) + if err != nil { + return false, err + } + + if v, ok := is.([]Capsule); ok { + return len(v) == 0, nil + } + + if v, ok := is.([]CapsuleV132); ok { + return len(v) == 0, nil + } + + return false, fmt.Errorf("Unable to determine Capsule type") +} + +// ExtractCapsulesBase accepts a Page struct, specifically a CapsulePage struct, +// and extracts the elements into a slice of Capsule structs. In other words, +// a generic collection is mapped into the relevant slice. +func ExtractCapsulesBase(r pagination.Page) ([]Capsule, error) { + var s struct { + Capsules []Capsule `json:"capsules"` + } + + err := (r.(CapsulePage)).ExtractInto(&s) + return s.Capsules, err +} + +// ExtractCapsules accepts a Page struct, specifically a CapsulePage struct, +// and extracts the elements into an interface. +// This interface should be able to be casted as either a Capsule or +// CapsuleV132 struct +func ExtractCapsules(r pagination.Page) (interface{}, error) { + s, err := ExtractCapsulesBase(r) + if err == nil { + return s, nil + } + + if _, ok := err.(*json.UnmarshalTypeError); !ok { + return nil, err + } + + return ExtractCapsulesV132(r) +} + +func (r *Capsule) UnmarshalJSON(b []byte) error { + type tmp Capsule + + // Support for "older" zun time formats. + var s1 struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoT `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoT `json:"updated_at"` + } + + err := json.Unmarshal(b, &s1) + if err == nil { + *r = Capsule(s1.tmp) + + r.CreatedAt = time.Time(s1.CreatedAt) + r.UpdatedAt = time.Time(s1.UpdatedAt) + + return nil + } + + // Support for "new" zun time formats. + var s2 struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"updated_at"` + } + + err = json.Unmarshal(b, &s2) + if err != nil { + return err + } + + *r = Capsule(s2.tmp) + + r.CreatedAt = time.Time(s2.CreatedAt) + r.UpdatedAt = time.Time(s2.UpdatedAt) + + return nil +} + +func (r *Container) UnmarshalJSON(b []byte) error { + type tmp Container + + // Support for "older" zun time formats. + var s1 struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoT `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoT `json:"updated_at"` + StartedAt gophercloud.JSONRFC3339ZNoT `json:"started_at"` + } + + err := json.Unmarshal(b, &s1) + if err == nil { + *r = Container(s1.tmp) + + r.CreatedAt = time.Time(s1.CreatedAt) + r.UpdatedAt = time.Time(s1.UpdatedAt) + r.StartedAt = time.Time(s1.StartedAt) + + return nil + } + + // Support for "new" zun time formats. + var s2 struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"updated_at"` + StartedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"started_at"` + } + + err = json.Unmarshal(b, &s2) + if err != nil { + return err + } + + *r = Container(s2.tmp) + + r.CreatedAt = time.Time(s2.CreatedAt) + r.UpdatedAt = time.Time(s2.UpdatedAt) + r.StartedAt = time.Time(s2.StartedAt) + + return nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/template.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/template.go new file mode 100644 index 000000000000..c55428fa46e4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/template.go @@ -0,0 +1,27 @@ +package capsules + +import ( + "encoding/json" + + yaml "gopkg.in/yaml.v2" +) + +// Template is a structure that represents OpenStack Zun Capsule templates +type Template struct { + // Bin stores the contents of the template or environment. + Bin []byte + // Parsed contains a parsed version of Bin. Since there are 2 different + // fields referring to the same value, you must be careful when accessing + // this filed. + Parsed map[string]interface{} +} + +// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML. +func (t *Template) Parse() error { + if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil { + if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil { + return ErrInvalidDataFormat{} + } + } + return nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/fixtures.go new file mode 100644 index 000000000000..4d6e78512421 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/fixtures.go @@ -0,0 +1,670 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" + th "github.com/gophercloud/gophercloud/testhelper" + fakeclient "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ValidJSONTemplate is a valid OpenStack Capsule template in JSON format +const ValidJSONTemplate = ` +{ + "capsuleVersion": "beta", + "kind": "capsule", + "metadata": { + "labels": { + "app": "web", + "app1": "web1" + }, + "name": "template" + }, + "spec": { + "restartPolicy": "Always", + "containers": [ + { + "command": [ + "/bin/bash" + ], + "env": { + "ENV1": "/usr/local/bin", + "ENV2": "/usr/bin" + }, + "image": "ubuntu", + "imagePullPolicy": "ifnotpresent", + "ports": [ + { + "containerPort": 80, + "hostPort": 80, + "name": "nginx-port", + "protocol": "TCP" + } + ], + "resources": { + "requests": { + "cpu": 1, + "memory": 1024 + } + }, + "workDir": "/root" + } + ] + } +} +` + +// ValidYAMLTemplate is a valid OpenStack Capsule template in YAML format +const ValidYAMLTemplate = ` +capsuleVersion: beta +kind: capsule +metadata: + name: template + labels: + app: web + app1: web1 +spec: + restartPolicy: Always + containers: + - image: ubuntu + command: + - "/bin/bash" + imagePullPolicy: ifnotpresent + workDir: /root + ports: + - name: nginx-port + containerPort: 80 + hostPort: 80 + protocol: TCP + resources: + requests: + cpu: 1 + memory: 1024 + env: + ENV1: /usr/local/bin + ENV2: /usr/bin +` + +// ValidJSONTemplateParsed is the expected parsed version of ValidJSONTemplate +var ValidJSONTemplateParsed = map[string]interface{}{ + "capsuleVersion": "beta", + "kind": "capsule", + "metadata": map[string]interface{}{ + "name": "template", + "labels": map[string]string{ + "app": "web", + "app1": "web1", + }, + }, + "spec": map[string]interface{}{ + "restartPolicy": "Always", + "containers": []map[string]interface{}{ + map[string]interface{}{ + "image": "ubuntu", + "command": []interface{}{ + "/bin/bash", + }, + "imagePullPolicy": "ifnotpresent", + "workDir": "/root", + "ports": []interface{}{ + map[string]interface{}{ + "name": "nginx-port", + "containerPort": float64(80), + "hostPort": float64(80), + "protocol": "TCP", + }, + }, + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": float64(1), + "memory": float64(1024), + }, + }, + "env": map[string]interface{}{ + "ENV1": "/usr/local/bin", + "ENV2": "/usr/bin", + }, + }, + }, + }, +} + +// ValidYAMLTemplateParsed is the expected parsed version of ValidYAMLTemplate +var ValidYAMLTemplateParsed = map[string]interface{}{ + "capsuleVersion": "beta", + "kind": "capsule", + "metadata": map[string]interface{}{ + "name": "template", + "labels": map[string]string{ + "app": "web", + "app1": "web1", + }, + }, + "spec": map[interface{}]interface{}{ + "restartPolicy": "Always", + "containers": []map[interface{}]interface{}{ + map[interface{}]interface{}{ + "image": "ubuntu", + "command": []interface{}{ + "/bin/bash", + }, + "imagePullPolicy": "ifnotpresent", + "workDir": "/root", + "ports": []interface{}{ + map[interface{}]interface{}{ + "name": "nginx-port", + "containerPort": 80, + "hostPort": 80, + "protocol": "TCP", + }, + }, + "resources": map[interface{}]interface{}{ + "requests": map[interface{}]interface{}{ + "cpu": 1, + "memory": 1024, + }, + }, + "env": map[interface{}]interface{}{ + "ENV1": "/usr/local/bin", + "ENV2": "/usr/bin", + }, + }, + }, + }, +} + +const CapsuleGetBody_OldTime = ` +{ + "uuid": "cc654059-1a77-47a3-bfcf-715bde5aad9e", + "status": "Running", + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "cpu": 1, + "memory": "1024M", + "meta_name": "test", + "meta_labels": {"web": "app"}, + "created_at": "2018-01-12 09:37:25+00:00", + "updated_at": "2018-01-12 09:37:26+00:00", + "links": [ + { + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self" + }, + { + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark" + } + ], + "capsule_version": "beta", + "restart_policy": "always", + "containers_uuids": ["1739e28a-d391-4fd9-93a5-3ba3f29a4c9b"], + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "volumes_info": { + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": [ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b" + ] + }, + "host": "test-host", + "status_reason": "No reason", + "containers": [ + { + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "image": "test", + "labels": {"foo": "bar"}, + "created_at": "2018-01-12 09:37:25+00:00", + "updated_at": "2018-01-12 09:37:26+00:00", + "started_at": "2018-01-12 09:37:26+00:00", + "workdir": "/root", + "disk": 0, + "security_groups": ["default"], + "image_pull_policy": "ifnotpresent", + "task_state": "Creating", + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "uuid": "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + "hostname": "test-hostname", + "environment": {"USER1": "test"}, + "memory": "1024M", + "status": "Running", + "auto_remove": false, + "auto_heal": false, + "host": "test-host", + "image_driver": "docker", + "status_detail": "Just created", + "status_reason": "No reason", + "name": "test-demo-omicron-13", + "restart_policy": { + "MaximumRetryCount": "0", + "Name": "always" + }, + "ports": [80], + "command": ["testcmd"], + "runtime": "runc", + "cpu": 1, + "interactive": true + } + ] +}` + +const CapsuleGetBody_NewTime = ` +{ + "uuid": "cc654059-1a77-47a3-bfcf-715bde5aad9e", + "status": "Running", + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "cpu": 1, + "memory": "1024M", + "meta_name": "test", + "meta_labels": {"web": "app"}, + "created_at": "2018-01-12 09:37:25", + "updated_at": "2018-01-12 09:37:26", + "links": [ + { + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self" + }, + { + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark" + } + ], + "capsule_version": "beta", + "restart_policy": "always", + "containers_uuids": ["1739e28a-d391-4fd9-93a5-3ba3f29a4c9b"], + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "volumes_info": { + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": [ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b" + ] + }, + "host": "test-host", + "status_reason": "No reason", + "containers": [ + { + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "image": "test", + "labels": {"foo": "bar"}, + "created_at": "2018-01-12 09:37:25", + "updated_at": "2018-01-12 09:37:26", + "started_at": "2018-01-12 09:37:26", + "workdir": "/root", + "disk": 0, + "security_groups": ["default"], + "image_pull_policy": "ifnotpresent", + "task_state": "Creating", + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "uuid": "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + "hostname": "test-hostname", + "environment": {"USER1": "test"}, + "memory": "1024M", + "status": "Running", + "auto_remove": false, + "auto_heal": false, + "host": "test-host", + "image_driver": "docker", + "status_detail": "Just created", + "status_reason": "No reason", + "name": "test-demo-omicron-13", + "restart_policy": { + "MaximumRetryCount": "0", + "Name": "always" + }, + "ports": [80], + "command": ["testcmd"], + "runtime": "runc", + "cpu": 1, + "interactive": true + } + ] +}` + +const CapsuleListBody = ` +{ + "capsules": [ + { + "uuid": "cc654059-1a77-47a3-bfcf-715bde5aad9e", + "status": "Running", + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "cpu": 1, + "memory": "1024M", + "meta_name": "test", + "meta_labels": {"web": "app"}, + "created_at": "2018-01-12 09:37:25+00:00", + "updated_at": "2018-01-12 09:37:25+01:00", + "links": [ + { + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self" + }, + { + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark" + } + ], + "capsule_version": "beta", + "restart_policy": "always", + "containers_uuids": ["1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", "d1469e8d-bcbc-43fc-b163-8b9b6a740930"], + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "volumes_info": { + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": [ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b" + ] + }, + "host": "test-host", + "status_reason": "No reason" + } + ] +}` + +const CapsuleV132ListBody = ` +{ + "capsules": [ + { + "uuid": "cc654059-1a77-47a3-bfcf-715bde5aad9e", + "status": "Running", + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "cpu": 1, + "memory": "1024M", + "name": "test", + "labels": {"web": "app"}, + "created_at": "2018-01-12 09:37:25", + "updated_at": "2018-01-12 09:37:25", + "links": [ + { + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self" + }, + { + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark" + } + ], + "restart_policy": { + "MaximumRetryCount": "0", + "Name": "always" + }, + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "host": "test-host", + "status_reason": "No reason" + } + ] +}` + +var ExpectedContainer1 = capsules.Container{ + Name: "test-demo-omicron-13", + UUID: "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + UserID: "d33b18c384574fd2a3299447aac285f0", + ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", + CPU: float64(1), + Memory: "1024M", + Host: "test-host", + Status: "Running", + Image: "test", + Labels: map[string]string{ + "foo": "bar", + }, + WorkDir: "/root", + Disk: 0, + Command: []string{ + "testcmd", + }, + Ports: []int{ + 80, + }, + SecurityGroups: []string{ + "default", + }, + ImagePullPolicy: "ifnotpresent", + Runtime: "runc", + TaskState: "Creating", + HostName: "test-hostname", + Environment: map[string]string{ + "USER1": "test", + }, + StatusReason: "No reason", + StatusDetail: "Just created", + ImageDriver: "docker", + Interactive: true, + AutoRemove: false, + AutoHeal: false, + RestartPolicy: map[string]string{ + "MaximumRetryCount": "0", + "Name": "always", + }, + Addresses: map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": []capsules.Address{ + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, + }, + }, +} + +var ExpectedCapsule = capsules.Capsule{ + UUID: "cc654059-1a77-47a3-bfcf-715bde5aad9e", + Status: "Running", + UserID: "d33b18c384574fd2a3299447aac285f0", + ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", + CPU: float64(1), + Memory: "1024M", + MetaName: "test", + Links: []interface{}{ + map[string]interface{}{ + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark", + }, + }, + CapsuleVersion: "beta", + RestartPolicy: "always", + MetaLabels: map[string]string{ + "web": "app", + }, + ContainersUUIDs: []string{ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + }, + Addresses: map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": []capsules.Address{ + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, + }, + }, + VolumesInfo: map[string][]string{ + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": []string{ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + }, + }, + Host: "test-host", + StatusReason: "No reason", + Containers: []capsules.Container{ + ExpectedContainer1, + }, +} + +var ExpectedCapsuleV132 = capsules.CapsuleV132{ + UUID: "cc654059-1a77-47a3-bfcf-715bde5aad9e", + Status: "Running", + UserID: "d33b18c384574fd2a3299447aac285f0", + ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", + CPU: float64(1), + Memory: "1024M", + MetaName: "test", + Links: []interface{}{ + map[string]interface{}{ + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark", + }, + }, + RestartPolicy: map[string]string{ + "MaximumRetryCount": "0", + "Name": "always", + }, + MetaLabels: map[string]string{ + "web": "app", + }, + Addresses: map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": []capsules.Address{ + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, + }, + }, + Host: "test-host", + StatusReason: "No reason", + Containers: []capsules.Container{ + ExpectedContainer1, + }, +} + +// HandleCapsuleGetOldTimeSuccessfully test setup +func HandleCapsuleGetOldTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, CapsuleGetBody_OldTime) + }) +} + +// HandleCapsuleGetNewTimeSuccessfully test setup +func HandleCapsuleGetNewTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, CapsuleGetBody_NewTime) + }) +} + +// HandleCapsuleCreateSuccessfully creates an HTTP handler at `/capsules` on the test handler mux +// that responds with a `Create` response. +func HandleCapsuleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + 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) + }) +} + +// HandleCapsuleListSuccessfully test setup +func HandleCapsuleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, CapsuleListBody) + }) +} + +// HandleCapsuleV132ListSuccessfully test setup +func HandleCapsuleV132ListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, CapsuleV132ListBody) + }) +} + +func HandleCapsuleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules/963a239d-3946-452b-be5a-055eab65a421", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/requests_test.go new file mode 100644 index 000000000000..2d5e7c8ea0e5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/requests_test.go @@ -0,0 +1,159 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fakeclient "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGetCapsule_OldTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCapsuleGetOldTimeSuccessfully(t) + + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+00:00") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:26+00:00") + startedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:26+00:00") + + ExpectedCapsule.CreatedAt = createdAt + ExpectedCapsule.UpdatedAt = updatedAt + ExpectedCapsule.Containers[0].CreatedAt = createdAt + ExpectedCapsule.Containers[0].UpdatedAt = updatedAt + ExpectedCapsule.Containers[0].StartedAt = startedAt + + actualCapsule, err := capsules.Get(fakeclient.ServiceClient(), ExpectedCapsule.UUID).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, &ExpectedCapsule, actualCapsule) +} + +func TestGetCapsule_NewTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCapsuleGetNewTimeSuccessfully(t) + + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") + startedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") + + ExpectedCapsule.CreatedAt = createdAt + ExpectedCapsule.UpdatedAt = updatedAt + ExpectedCapsule.Containers[0].CreatedAt = createdAt + ExpectedCapsule.Containers[0].UpdatedAt = updatedAt + ExpectedCapsule.Containers[0].StartedAt = startedAt + + actualCapsule, err := capsules.Get(fakeclient.ServiceClient(), ExpectedCapsule.UUID).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, &ExpectedCapsule, actualCapsule) +} + +func TestCreateCapsule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCapsuleCreateSuccessfully(t) + + template := new(capsules.Template) + template.Bin = []byte(ValidJSONTemplate) + + createOpts := capsules.CreateOpts{ + TemplateOpts: template, + } + actualCapsule, err := capsules.Create(fakeclient.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, &ExpectedCapsule, actualCapsule) +} + +func TestListCapsule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCapsuleListSuccessfully(t) + + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+00:00") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+01:00") + + ec := ExpectedCapsule + + ec.CreatedAt = createdAt + ec.UpdatedAt = updatedAt + ec.Containers = nil + + expected := []capsules.Capsule{ec} + + count := 0 + results := capsules.List(fakeclient.ServiceClient(), nil) + err := results.EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := capsules.ExtractCapsules(page) + if err != nil { + t.Errorf("Failed to extract capsules: %v", err) + return false, err + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestListCapsuleV132(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCapsuleV132ListSuccessfully(t) + + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") + + ec := ExpectedCapsuleV132 + + ec.CreatedAt = createdAt + ec.UpdatedAt = updatedAt + ec.Containers = nil + + expected := []capsules.CapsuleV132{ec} + + count := 0 + results := capsules.List(fakeclient.ServiceClient(), nil) + err := results.EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := capsules.ExtractCapsules(page) + if err != nil { + t.Errorf("Failed to extract capsules: %v", err) + return false, err + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCapsuleDeleteSuccessfully(t) + + res := capsules.Delete(fakeclient.ServiceClient(), "963a239d-3946-452b-be5a-055eab65a421") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/template_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/template_test.go new file mode 100644 index 000000000000..62d0de8f5dea --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/testing/template_test.go @@ -0,0 +1,29 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestTemplateParsing(t *testing.T) { + templateJSON := new(capsules.Template) + templateJSON.Bin = []byte(ValidJSONTemplate) + err := templateJSON.Parse() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ValidJSONTemplateParsed, templateJSON.Parsed) + + templateYAML := new(capsules.Template) + templateYAML.Bin = []byte(ValidYAMLTemplate) + err = templateYAML.Parse() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ValidYAMLTemplateParsed, templateYAML.Parsed) + + templateInvalid := new(capsules.Template) + templateInvalid.Bin = []byte("Keep Austin Weird") + err = templateInvalid.Parse() + if err == nil { + t.Error("Template parsing did not catch invalid template") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/urls.go new file mode 100644 index 000000000000..575fb2a712a9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/container/v1/capsules/urls.go @@ -0,0 +1,21 @@ +package capsules + +import "github.com/gophercloud/gophercloud" + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("capsules", id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("capsules") +} + +// `listURL` is a pure function. `listURL(c)` is a URL for which a GET +// request will respond with a list of capsules in the service `c`. +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("capsules") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("capsules", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/doc.go new file mode 100644 index 000000000000..9dd2edddaa77 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/doc.go @@ -0,0 +1,3 @@ +// Package apiversions provides information and interaction with the different +// API versions for the Container Infra service, code-named Magnum. +package apiversions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/errors.go new file mode 100644 index 000000000000..8f0f7628def8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/errors.go @@ -0,0 +1,23 @@ +package apiversions + +import ( + "fmt" +) + +// ErrVersionNotFound is the error when the requested API version +// could not be found. +type ErrVersionNotFound struct{} + +func (e ErrVersionNotFound) Error() string { + return fmt.Sprintf("Unable to find requested API version") +} + +// ErrMultipleVersionsFound is the error when a request for an API +// version returns multiple results. +type ErrMultipleVersionsFound struct { + Count int +} + +func (e ErrMultipleVersionsFound) Error() string { + return fmt.Sprintf("Found %d API versions", e.Count) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/requests.go new file mode 100644 index 000000000000..2e1f6639b575 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/requests.go @@ -0,0 +1,19 @@ +package apiversions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List lists all the API versions available to end-users. +func List(c *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page { + return APIVersionPage{pagination.SinglePageBase(r)} + }) +} + +// Get will get a specific API version, specified by major ID. +func Get(client *gophercloud.ServiceClient, v string) (r GetResult) { + _, r.Err = client.Get(getURL(client, v), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/results.go new file mode 100644 index 000000000000..b2959802dee1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/results.go @@ -0,0 +1,68 @@ +package apiversions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// APIVersion represents an API version for the Container Infra service. +type APIVersion struct { + // ID is the unique identifier of the API version. + ID string `json:"id"` + + // MinVersion is the minimum microversion supported. + MinVersion string `json:"min_version"` + + // Status is the API versions status. + Status string `json:"status"` + + // Version is the maximum microversion supported. + Version string `json:"max_version"` +} + +// APIVersionPage is the page returned by a pager when traversing over a +// collection of API versions. +type APIVersionPage struct { + pagination.SinglePageBase +} + +// IsEmpty checks whether an APIVersionPage struct is empty. +func (r APIVersionPage) IsEmpty() (bool, error) { + is, err := ExtractAPIVersions(r) + return len(is) == 0, err +} + +// ExtractAPIVersions takes a collection page, extracts all of the elements, +// and returns them a slice of APIVersion structs. It is effectively a cast. +func ExtractAPIVersions(r pagination.Page) ([]APIVersion, error) { + var s struct { + Versions []APIVersion `json:"versions"` + } + err := (r.(APIVersionPage)).ExtractInto(&s) + return s.Versions, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an API version resource. +func (r GetResult) Extract() (*APIVersion, error) { + var s struct { + Versions []APIVersion `json:"versions"` + } + err := r.ExtractInto(&s) + if err != nil { + return nil, err + } + + switch len(s.Versions) { + case 0: + return nil, ErrVersionNotFound{} + case 1: + return &s.Versions[0], nil + default: + return nil, ErrMultipleVersionsFound{Count: len(s.Versions)} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/doc.go new file mode 100644 index 000000000000..12e4bda0f9ed --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/doc.go @@ -0,0 +1,2 @@ +// apiversions_v1 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/fixtures.go new file mode 100644 index 000000000000..b5a13ea48631 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/fixtures.go @@ -0,0 +1,88 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const MagnumAPIVersionResponse = ` +{ + "versions":[ + { + "status":"CURRENT", + "min_version":"1.1", + "max_version":"1.7", + "id":"v1", + "links":[ + { + "href":"http://10.164.180.104:9511/v1/", + "rel":"self" + } + ] + } + ], + "name":"OpenStack Magnum API", + "description":"Magnum is an OpenStack project which aims to provide container management." + } +` + +const MagnumAllAPIVersionsResponse = ` +{ + "versions":[ + { + "status":"CURRENT", + "min_version":"1.1", + "max_version":"1.7", + "id":"v1", + "links":[ + { + "href":"http://10.164.180.104:9511/v1/", + "rel":"self" + } + ] + } + ], + "name":"OpenStack Magnum API", + "description":"Magnum is an OpenStack project which aims to provide container management." + } +` + +var MagnumAPIVersion1Result = apiversions.APIVersion{ + ID: "v1", + Status: "CURRENT", + MinVersion: "1.1", + Version: "1.7", +} + +var MagnumAllAPIVersionResults = []apiversions.APIVersion{ + MagnumAPIVersion1Result, +} + +func MockListResponse(t *testing.T) { + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, MagnumAllAPIVersionsResponse) + }) +} + +func MockGetResponse(t *testing.T) { + th.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, MagnumAPIVersionResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/requests_test.go new file mode 100644 index 000000000000..cd21ad779265 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/testing/requests_test.go @@ -0,0 +1,37 @@ +package testing + +import ( + "fmt" + "testing" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListAPIVersions(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListResponse(t) + + allVersions, err := apiversions.List(client.ServiceClient()).AllPages() + th.AssertNoErr(t, err) + + actual, err := apiversions.ExtractAPIVersions(allVersions) + th.AssertNoErr(t, err) + fmt.Println(actual) + th.AssertDeepEquals(t, MagnumAllAPIVersionResults, actual) +} + +func TestGetAPIVersion(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockGetResponse(t) + + actual, err := apiversions.Get(client.ServiceClient(), "v1").Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, MagnumAPIVersion1Result, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/urls.go new file mode 100644 index 000000000000..41aebdc5f2bd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/apiversions/urls.go @@ -0,0 +1,20 @@ +package apiversions + +import ( + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" +) + +func getURL(c *gophercloud.ServiceClient, version string) string { + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + strings.TrimRight(version, "/") + "/" + return endpoint +} + +func listURL(c *gophercloud.ServiceClient) string { + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + return endpoint +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/doc.go new file mode 100644 index 000000000000..b5c39f7e6689 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/doc.go @@ -0,0 +1,33 @@ +// Package certificates contains functionality for working with Magnum Certificate +// resources. +/* +Package certificates provides information and interaction with the certificates through +the OpenStack Container Infra service. + +Example to get certificates + + certificate, err := certificates.Get(serviceClient, "d564b18a-2890-4152-be3d-e05d784ff72").Extract() + if err != nil { + panic(err) + } + +Example to create certificates + + opts := certificates.CreateOpts{ + BayUUID: "d564b18a-2890-4152-be3d-e05d784ff727", + CSR: "-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n", + } + + response, err := certificates.Create(sc, opts).Extract() + if err != nil { + panic(err) + } + +Example to update certificates + + err := certificates.Update(client, clusterUUID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package certificates diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/requests.go new file mode 100644 index 000000000000..011546b68326 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/requests.go @@ -0,0 +1,65 @@ +package certificates + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +// CreateOptsBuilder allows extensions to add additional parameters +// to the Create request. +type CreateOptsBuilder interface { + ToCertificateCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used to create a certificate. +type CreateOpts struct { + ClusterUUID string `json:"cluster_uuid,omitempty" xor:"BayUUID"` + BayUUID string `json:"bay_uuid,omitempty" xor:"ClusterUUID"` + CSR string `json:"csr" required:"true"` +} + +// ToCertificateCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToCertificateCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Get makes a request against the API to get details for a certificate. +func Get(client *gophercloud.ServiceClient, clusterID string) (r GetResult) { + url := getURL(client, clusterID) + + _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + return +} + +// Create requests the creation of a new certificate. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToCertificateCreateMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// Update will rotate the CA certificate for a cluster +func Update(client *gophercloud.ServiceClient, clusterID string) (r UpdateResult) { + _, r.Err = client.Patch(updateURL(client, clusterID), nil, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/results.go new file mode 100644 index 000000000000..654e585fd420 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/results.go @@ -0,0 +1,40 @@ +package certificates + +import ( + "github.com/gophercloud/gophercloud" +) + +type commonResult struct { + gophercloud.Result +} + +// GetResult is the response of a Get operations. +type GetResult struct { + commonResult +} + +// CreateResult is the response of a Create operations. +type CreateResult struct { + commonResult +} + +// UpdateResult is the response of an Update operations. +type UpdateResult struct { + gophercloud.ErrResult +} + +// Extract is a function that accepts a result and extracts a certificate resource. +func (r commonResult) Extract() (*Certificate, error) { + var s *Certificate + err := r.ExtractInto(&s) + return s, err +} + +// Represents a Certificate +type Certificate struct { + ClusterUUID string `json:"cluster_uuid"` + BayUUID string `json:"bay_uuid"` + Links []gophercloud.Link `json:"links"` + PEM string `json:"pem"` + CSR string `json:"csr"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/fixtures.go new file mode 100644 index 000000000000..0634b7e25d52 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/fixtures.go @@ -0,0 +1,111 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const CertificateResponse = ` +{ + "cluster_uuid": "d564b18a-2890-4152-be3d-e05d784ff727", + "bay_uuid": "d564b18a-2890-4152-be3d-e05d784ff727", + "pem": "FAKE_CERTIFICATE", + "links": [ + { + "href": "http://10.63.176.154:9511/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff727", + "rel": "self" + }, + { + "href": "http://10.63.176.154:9511/certificates/d564b18a-2890-4152-be3d-e05d784ff727", + "rel": "bookmark" + } + ] +}` + +const CreateCertificateResponse = ` +{ + "cluster_uuid": "d564b18a-2890-4152-be3d-e05d784ff727", + "bay_uuid": "d564b18a-2890-4152-be3d-e05d784ff727", + "pem": "FAKE_CERTIFICATE_PEM", + "csr": "FAKE_CERTIFICATE_CSR", + "links": [ + { + "href": "http://10.63.176.154:9511/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff727", + "rel": "self" + }, + { + "href": "http://10.63.176.154:9511/certificates/d564b18a-2890-4152-be3d-e05d784ff727", + "rel": "bookmark" + } + ] +}` + +var ExpectedCertificate = certificates.Certificate{ + ClusterUUID: "d564b18a-2890-4152-be3d-e05d784ff727", + BayUUID: "d564b18a-2890-4152-be3d-e05d784ff727", + PEM: "FAKE_CERTIFICATE", + Links: []gophercloud.Link{ + {Href: "http://10.63.176.154:9511/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff727", Rel: "self"}, + {Href: "http://10.63.176.154:9511/certificates/d564b18a-2890-4152-be3d-e05d784ff727", Rel: "bookmark"}, + }, +} + +var ExpectedCreateCertificateResponse = certificates.Certificate{ + ClusterUUID: "d564b18a-2890-4152-be3d-e05d784ff727", + BayUUID: "d564b18a-2890-4152-be3d-e05d784ff727", + PEM: "FAKE_CERTIFICATE_PEM", + CSR: "FAKE_CERTIFICATE_CSR", + Links: []gophercloud.Link{ + {Href: "http://10.63.176.154:9511/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff727", Rel: "self"}, + {Href: "http://10.63.176.154:9511/certificates/d564b18a-2890-4152-be3d-e05d784ff727", Rel: "bookmark"}, + }, +} + +func HandleGetCertificateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff72", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") + w.Header().Add("OpenStack-API-Maximum-Version", "container-infra 1.6") + w.Header().Add("OpenStack-API-Version", "container-infra 1.1") + w.Header().Add("X-OpenStack-Request-Id", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, CertificateResponse) + }) +} + +func HandleCreateCertificateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/certificates/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") + w.Header().Add("OpenStack-API-Maximum-Version", "container-infra 1.6") + w.Header().Add("OpenStack-API-Version", "container-infra 1.1") + w.Header().Add("X-OpenStack-Request-Id", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, CreateCertificateResponse) + }) +} + +func HandleUpdateCertificateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff72", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusAccepted) + fmt.Fprintf(w, `{}`) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/requests_test.go new file mode 100644 index 000000000000..7ad95e9cc983 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/testing/requests_test.go @@ -0,0 +1,55 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGetCertificates(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetCertificateSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + + actual, err := certificates.Get(sc, "d564b18a-2890-4152-be3d-e05d784ff72").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCertificate, *actual) +} + +func TestCreateCertificates(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateCertificateSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + + opts := certificates.CreateOpts{ + BayUUID: "d564b18a-2890-4152-be3d-e05d784ff727", + CSR: "FAKE_CERTIFICATE_CSR", + } + + actual, err := certificates.Create(sc, opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCreateCertificateResponse, *actual) +} + +func TestUpdateCertificates(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateCertificateSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + + err := certificates.Update(sc, "d564b18a-2890-4152-be3d-e05d784ff72").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/urls.go new file mode 100644 index 000000000000..87c831d88eaf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/certificates/urls.go @@ -0,0 +1,23 @@ +package certificates + +import ( + "github.com/gophercloud/gophercloud" +) + +var apiName = "certificates" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiName) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiName, id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiName, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go new file mode 100644 index 000000000000..3fd28d10c62e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go @@ -0,0 +1,101 @@ +/* +Package clusters contains functionality for working with Magnum Cluster resources. + +Example to Create a Cluster + + masterCount := 1 + nodeCount := 1 + createTimeout := 30 + opts := clusters.CreateOpts{ + ClusterTemplateID: "0562d357-8641-4759-8fed-8173f02c9633", + CreateTimeout: &createTimeout, + DiscoveryURL: "", + FlavorID: "m1.small", + KeyPair: "my_keypair", + Labels: map[string]string{}, + MasterCount: &masterCount, + MasterFlavorID: "m1.small", + Name: "k8s", + NodeCount: &nodeCount, + } + + cluster, err := clusters.Create(serviceClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Cluster + + clusterName := "cluster123" + cluster, err := clusters.Get(serviceClient, clusterName).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", cluster) + +Example to List Clusters + + listOpts := clusters.ListOpts{ + Limit: 20, + } + + allPages, err := clusters.List(serviceClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allClusters, err := clusters.ExtractClusters(allPages) + if err != nil { + panic(err) + } + + for _, cluster := range allClusters { + fmt.Printf("%+v\n", cluster) + } + +Example to List Clusters with detailed information + + allPagesDetail, err := clusters.ListDetail(serviceClient, clusters.ListOpts{}).AllPages() + if err != nil { + panic(err) + } + + allClustersDetail, err := clusters.ExtractClusters(allPagesDetail) + if err != nil { + panic(err) + } + + for _, clusterDetail := range allClustersDetail { + fmt.Printf("%+v\n", clusterDetail) + } + +Example to Update a Cluster + + updateOpts := []clusters.UpdateOptsBuilder{ + clusters.UpdateOpts{ + Op: clusters.ReplaceOp, + Path: "/master_lb_enabled", + Value: "True", + }, + clusters.UpdateOpts{ + Op: clusters.ReplaceOp, + Path: "/registry_enabled", + Value: "True", + }, + } + clusterUUID, err := clusters.Update(serviceClient, clusterUUID, updateOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%s\n", clusterUUID) + +Example to Delete a Cluster + + clusterUUID := "dc6d336e3fc4c0a951b5698cd1236ee" + err := clusters.Delete(serviceClient, clusterUUID).ExtractErr() + if err != nil { + panic(err) + } + +*/ +package clusters diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go new file mode 100644 index 000000000000..5d5198018c41 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go @@ -0,0 +1,214 @@ +package clusters + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder Builder. +type CreateOptsBuilder interface { + ToClusterCreateMap() (map[string]interface{}, error) +} + +// CreateOpts params +type CreateOpts struct { + ClusterTemplateID string `json:"cluster_template_id" required:"true"` + CreateTimeout *int `json:"create_timeout"` + DiscoveryURL string `json:"discovery_url,omitempty"` + DockerVolumeSize *int `json:"docker_volume_size,omitempty"` + FlavorID string `json:"flavor_id,omitempty"` + Keypair string `json:"keypair,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + MasterCount *int `json:"master_count,omitempty"` + MasterFlavorID string `json:"master_flavor_id,omitempty"` + Name string `json:"name"` + NodeCount *int `json:"node_count,omitempty"` +} + +// ToClusterCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create requests the creation of a new cluster. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToClusterCreateMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// Get retrieves a specific clusters based on its unique ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + var result *http.Response + result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + if r.Err == nil { + r.Header = result.Header + } + return +} + +// Delete deletes the specified cluster ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(deleteURL(client, id), nil) + r.Header = result.Header + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToClustersListQuery() (string, error) +} + +// ListOpts allows the sorting of paginated collections through +// the API. SortKey allows you to sort by a particular cluster attribute. +// SortDir sets the direction, and is either `asc' or `desc'. +// Marker and Limit are used for pagination. +type ListOpts struct { + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToClustersListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToClustersListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// clusters. It accepts a ListOptsBuilder, which allows you to sort +// the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToClustersListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return ClusterPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ListDetail returns a Pager which allows you to iterate over a collection of +// clusters with detailed information. +// It accepts a ListOptsBuilder, which allows you to sort the returned +// collection for greater efficiency. +func ListDetail(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(c) + if opts != nil { + query, err := opts.ToClustersListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return ClusterPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type UpdateOp string + +const ( + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" + ReplaceOp UpdateOp = "replace" +) + +type UpdateOpts struct { + Op UpdateOp `json:"op" required:"true"` + Path string `json:"path" required:"true"` + Value interface{} `json:"value,omitempty"` +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToClustersUpdateMap() (map[string]interface{}, error) +} + +// ToClusterUpdateMap assembles a request body based on the contents of +// UpdateOpts. +func (opts UpdateOpts) ToClustersUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update implements cluster updated request. +func Update(client *gophercloud.ServiceClient, id string, opts []UpdateOptsBuilder) (r UpdateResult) { + var o []map[string]interface{} + for _, opt := range opts { + b, err := opt.ToClustersUpdateMap() + if err != nil { + r.Err = err + return r + } + o = append(o, b) + } + + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), o, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} + +// ResizeOptsBuilder allows extensions to add additional parameters to the +// Resize request. +type ResizeOptsBuilder interface { + ToClusterResizeMap() (map[string]interface{}, error) +} + +// ResizeOpts params +type ResizeOpts struct { + NodeCount *int `json:"node_count" required:"true"` + NodesToRemove []string `json:"nodes_to_remove,omitempty"` + NodeGroup string `json:"nodegroup,omitempty"` +} + +// ToClusterResizeMap constructs a request body from ResizeOpts. +func (opts ResizeOpts) ToClusterResizeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Resize an existing cluster node count. +func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ResizeResult) { + b, err := opts.ToClusterResizeMap() + if err != nil { + r.Err = err + return + } + + var result *http.Response + result, r.Err = client.Post(resizeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go new file mode 100644 index 000000000000..b4a8734c2975 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go @@ -0,0 +1,119 @@ +package clusters + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Create operations. +type CreateResult struct { + commonResult +} + +// DeleteResult is the result from a Delete operation. Call its Extract or ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts a cluster resource. +func (r commonResult) Extract() (*Cluster, error) { + var s *Cluster + err := r.ExtractInto(&s) + return s, err +} + +// UpdateResult is the response of a Update operations. +type UpdateResult struct { + commonResult +} + +// ResizeResult is the response of a Resize operations. +type ResizeResult struct { + commonResult +} + +func (r CreateResult) Extract() (string, error) { + var s struct { + UUID string + } + err := r.ExtractInto(&s) + return s.UUID, err +} + +func (r UpdateResult) Extract() (string, error) { + var s struct { + UUID string + } + err := r.ExtractInto(&s) + return s.UUID, err +} + +type Cluster struct { + APIAddress string `json:"api_address"` + COEVersion string `json:"coe_version"` + ClusterTemplateID string `json:"cluster_template_id"` + ContainerVersion string `json:"container_version"` + CreateTimeout int `json:"create_timeout"` + CreatedAt time.Time `json:"created_at"` + DiscoveryURL string `json:"discovery_url"` + DockerVolumeSize int `json:"docker_volume_size"` + Faults map[string]string `json:"faults"` + FlavorID string `json:"flavor_id"` + KeyPair string `json:"keypair"` + Labels map[string]string `json:"labels"` + Links []gophercloud.Link `json:"links"` + MasterFlavorID string `json:"master_flavor_id"` + MasterAddresses []string `json:"master_addresses"` + MasterCount int `json:"master_count"` + Name string `json:"name"` + NodeAddresses []string `json:"node_addresses"` + NodeCount int `json:"node_count"` + ProjectID string `json:"project_id"` + StackID string `json:"stack_id"` + Status string `json:"status"` + StatusReason string `json:"status_reason"` + UUID string `json:"uuid"` + UpdatedAt time.Time `json:"updated_at"` + UserID string `json:"user_id"` +} + +type ClusterPage struct { + pagination.LinkedPageBase +} + +func (r ClusterPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, nil +} + +// IsEmpty checks whether a ClusterPage struct is empty. +func (r ClusterPage) IsEmpty() (bool, error) { + is, err := ExtractClusters(r) + return len(is) == 0, err +} + +func ExtractClusters(r pagination.Page) ([]Cluster, error) { + var s struct { + Clusters []Cluster `json:"clusters"` + } + err := (r.(ClusterPage)).ExtractInto(&s) + return s.Clusters, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/fixtures.go new file mode 100644 index 000000000000..ff41a02b4d2c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/fixtures.go @@ -0,0 +1,292 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const clusterUUID = "746e779a-751a-456b-a3e9-c883d734946f" +const clusterUUID2 = "846e779a-751a-456b-a3e9-c883d734946f" +const requestUUID = "req-781e9bdc-4163-46eb-91c9-786c53188bbb" + +var ClusterCreateResponse = fmt.Sprintf(` + { + "uuid":"%s" + }`, clusterUUID) + +var ExpectedCluster = clusters.Cluster{ + APIAddress: "https://172.24.4.6:6443", + COEVersion: "v1.2.0", + ClusterTemplateID: "0562d357-8641-4759-8fed-8173f02c9633", + CreateTimeout: 60, + CreatedAt: time.Date(2016, 8, 29, 6, 51, 31, 0, time.UTC), + DiscoveryURL: "https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3", + Links: []gophercloud.Link{ + { + Href: "http://10.164.180.104:9511/v1/clusters/746e779a-751a-456b-a3e9-c883d734946f", + Rel: "self", + }, + { + Href: "http://10.164.180.104:9511/clusters/746e779a-751a-456b-a3e9-c883d734946f", + Rel: "bookmark", + }, + }, + KeyPair: "my-keypair", + MasterAddresses: []string{"172.24.4.6"}, + MasterCount: 1, + Name: "k8s", + NodeAddresses: []string{"172.24.4.13"}, + NodeCount: 1, + StackID: "9c6f1169-7300-4d08-a444-d2be38758719", + Status: "CREATE_COMPLETE", + StatusReason: "Stack CREATE completed successfully", + UpdatedAt: time.Date(2016, 8, 29, 6, 53, 24, 0, time.UTC), + UUID: clusterUUID, +} + +var ExpectedCluster2 = clusters.Cluster{ + APIAddress: "https://172.24.4.6:6443", + COEVersion: "v1.2.0", + ClusterTemplateID: "0562d357-8641-4759-8fed-8173f02c9633", + CreateTimeout: 60, + CreatedAt: time.Time{}, + DiscoveryURL: "https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3", + Links: []gophercloud.Link{ + { + Href: "http://10.164.180.104:9511/v1/clusters/746e779a-751a-456b-a3e9-c883d734946f", + Rel: "self", + }, + { + Href: "http://10.164.180.104:9511/clusters/746e779a-751a-456b-a3e9-c883d734946f", + Rel: "bookmark", + }, + }, + KeyPair: "my-keypair", + MasterAddresses: []string{"172.24.4.6"}, + MasterCount: 1, + Name: "k8s", + NodeAddresses: []string{"172.24.4.13"}, + NodeCount: 1, + StackID: "9c6f1169-7300-4d08-a444-d2be38758719", + Status: "CREATE_COMPLETE", + StatusReason: "Stack CREATE completed successfully", + UpdatedAt: time.Date(2016, 8, 29, 6, 53, 24, 0, time.UTC), + UUID: clusterUUID2, +} + +var ExpectedClusterUUID = clusterUUID + +func HandleCreateClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, ClusterCreateResponse) + }) +} + +func HandleGetClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterGetResponse) + }) +} + +var ClusterGetResponse = fmt.Sprintf(` +{ + "status":"CREATE_COMPLETE", + "uuid":"%s", + "links":[ + { + "href":"http://10.164.180.104:9511/v1/clusters/746e779a-751a-456b-a3e9-c883d734946f", + "rel":"self" + }, + { + "href":"http://10.164.180.104:9511/clusters/746e779a-751a-456b-a3e9-c883d734946f", + "rel":"bookmark" + } + ], + "stack_id":"9c6f1169-7300-4d08-a444-d2be38758719", + "created_at":"2016-08-29T06:51:31+00:00", + "api_address":"https://172.24.4.6:6443", + "discovery_url":"https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3", + "updated_at":"2016-08-29T06:53:24+00:00", + "master_count":1, + "coe_version": "v1.2.0", + "keypair":"my-keypair", + "cluster_template_id":"0562d357-8641-4759-8fed-8173f02c9633", + "master_addresses":[ + "172.24.4.6" + ], + "node_count":1, + "node_addresses":[ + "172.24.4.13" + ], + "status_reason":"Stack CREATE completed successfully", + "create_timeout":60, + "name":"k8s" +}`, clusterUUID) + +var ClusterListResponse = fmt.Sprintf(` +{ + "clusters": [ + { + "api_address":"https://172.24.4.6:6443", + "cluster_template_id":"0562d357-8641-4759-8fed-8173f02c9633", + "coe_version": "v1.2.0", + "create_timeout":60, + "created_at":"2016-08-29T06:51:31+00:00", + "discovery_url":"https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3", + "keypair":"my-keypair", + "links":[ + { + "href":"http://10.164.180.104:9511/v1/clusters/746e779a-751a-456b-a3e9-c883d734946f", + "rel":"self" + }, + { + "href":"http://10.164.180.104:9511/clusters/746e779a-751a-456b-a3e9-c883d734946f", + "rel":"bookmark" + } + ], + "master_addresses":[ + "172.24.4.6" + ], + "master_count":1, + "name":"k8s", + "node_addresses":[ + "172.24.4.13" + ], + "node_count":1, + "stack_id":"9c6f1169-7300-4d08-a444-d2be38758719", + "status":"CREATE_COMPLETE", + "status_reason":"Stack CREATE completed successfully", + "updated_at":"2016-08-29T06:53:24+00:00", + "uuid":"%s" + }, + { + "api_address":"https://172.24.4.6:6443", + "cluster_template_id":"0562d357-8641-4759-8fed-8173f02c9633", + "coe_version": "v1.2.0", + "create_timeout":60, + "created_at":null, + "discovery_url":"https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3", + "keypair":"my-keypair", + "links":[ + { + "href":"http://10.164.180.104:9511/v1/clusters/746e779a-751a-456b-a3e9-c883d734946f", + "rel":"self" + }, + { + "href":"http://10.164.180.104:9511/clusters/746e779a-751a-456b-a3e9-c883d734946f", + "rel":"bookmark" + } + ], + "master_addresses":[ + "172.24.4.6" + ], + "master_count":1, + "name":"k8s", + "node_addresses":[ + "172.24.4.13" + ], + "node_count":1, + "stack_id":"9c6f1169-7300-4d08-a444-d2be38758719", + "status":"CREATE_COMPLETE", + "status_reason":"Stack CREATE completed successfully", + "updated_at":null, + "uuid":"%s" + } + ] +}`, clusterUUID, clusterUUID2) + +var ExpectedClusters = []clusters.Cluster{ExpectedCluster} + +func HandleListClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterListResponse) + }) +} + +func HandleListDetailClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterListResponse) + }) +} + +var UpdateResponse = fmt.Sprintf(` +{ + "uuid":"%s" +}`, clusterUUID) + +func HandleUpdateClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse) + }) +} + +func HandleDeleteClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusNoContent) + }) +} + +var ResizeResponse = fmt.Sprintf(` +{ + "uuid": "%s", + "node_count": 2 +}`, clusterUUID) + +func HandleResizeClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/actions/resize", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, ResizeResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/requests_test.go new file mode 100644 index 000000000000..0efff3f437bb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/testing/requests_test.go @@ -0,0 +1,201 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateClusterSuccessfully(t) + + masterCount := 1 + nodeCount := 1 + createTimeout := 30 + opts := clusters.CreateOpts{ + ClusterTemplateID: "0562d357-8641-4759-8fed-8173f02c9633", + CreateTimeout: &createTimeout, + DiscoveryURL: "", + FlavorID: "m1.small", + Keypair: "my_keypair", + Labels: map[string]string{}, + MasterCount: &masterCount, + MasterFlavorID: "m1.small", + Name: "k8s", + NodeCount: &nodeCount, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + res := clusters.Create(sc, opts) + th.AssertNoErr(t, res.Err) + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, requestUUID, requestID) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, clusterUUID, actual) +} + +func TestGetCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetClusterSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + actual, err := clusters.Get(sc, "746e779a-751a-456b-a3e9-c883d734946f").Extract() + th.AssertNoErr(t, err) + actual.CreatedAt = actual.CreatedAt.UTC() + actual.UpdatedAt = actual.UpdatedAt.UTC() + th.AssertDeepEquals(t, ExpectedCluster, *actual) +} + +func TestListClusters(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListClusterSuccessfully(t) + + count := 0 + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + clusters.List(sc, clusters.ListOpts{Limit: 2}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := clusters.ExtractClusters(page) + th.AssertNoErr(t, err) + for idx := range actual { + actual[idx].CreatedAt = actual[idx].CreatedAt.UTC() + actual[idx].UpdatedAt = actual[idx].UpdatedAt.UTC() + } + th.AssertDeepEquals(t, ExpectedClusters, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestListDetailClusters(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListDetailClusterSuccessfully(t) + + count := 0 + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + clusters.ListDetail(sc, clusters.ListOpts{Limit: 2}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := clusters.ExtractClusters(page) + th.AssertNoErr(t, err) + for idx := range actual { + actual[idx].CreatedAt = actual[idx].CreatedAt.UTC() + actual[idx].UpdatedAt = actual[idx].UpdatedAt.UTC() + } + th.AssertDeepEquals(t, ExpectedClusters, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestUpdateCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateClusterSuccessfully(t) + + updateOpts := []clusters.UpdateOptsBuilder{ + clusters.UpdateOpts{ + Op: clusters.ReplaceOp, + Path: "/master_lb_enabled", + Value: "True", + }, + clusters.UpdateOpts{ + Op: clusters.ReplaceOp, + Path: "/registry_enabled", + Value: "True", + }, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + res := clusters.Update(sc, clusterUUID, updateOpts) + th.AssertNoErr(t, res.Err) + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, requestUUID, requestID) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, clusterUUID, actual) +} + +func TestDeleteCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteClusterSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + r := clusters.Delete(sc, clusterUUID) + err := r.ExtractErr() + th.AssertNoErr(t, err) + + uuid := "" + idKey := "X-Openstack-Request-Id" + if len(r.Header[idKey]) > 0 { + uuid = r.Header[idKey][0] + if uuid == "" { + t.Errorf("No value for header [%s]", idKey) + } + } else { + t.Errorf("Missing header [%s]", idKey) + } + + th.AssertEquals(t, requestUUID, uuid) +} + +func TestResizeCluster(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleResizeClusterSuccessfully(t) + + nodeCount := 2 + + var opts clusters.ResizeOptsBuilder + opts = clusters.ResizeOpts{ + NodeCount: &nodeCount, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + res := clusters.Resize(sc, clusterUUID, opts) + th.AssertNoErr(t, res.Err) + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, requestUUID, requestID) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, nodeCount, actual.NodeCount) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go new file mode 100644 index 000000000000..f0fcf1aeff53 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go @@ -0,0 +1,43 @@ +package clusters + +import ( + "github.com/gophercloud/gophercloud" +) + +var apiName = "clusters" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiName) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiName, id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("clusters", id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("clusters") +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("clusters", "detail") +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func resizeURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("clusters", id, "actions/resize") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go new file mode 100644 index 000000000000..12289c2abba1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go @@ -0,0 +1,90 @@ +// Package clustertemplates contains functionality for working with Magnum Cluster Templates +// resources. +/* +Package clustertemplates provides information and interaction with the cluster-templates through +the OpenStack Container Infra service. + +Example to Create Cluster Template + + boolFalse := false + boolTrue := true + createOpts := clustertemplates.CreateOpts{ + Name: "test-cluster-template", + Labels: map[string]string{}, + FixedSubnet: "", + MasterFlavorID: "", + NoProxy: "10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost", + HTTPSProxy: "http://10.164.177.169:8080", + TLSDisabled: &boolFalse, + KeyPairID: "kp", + Public: &boolFalse, + HTTPProxy: "http://10.164.177.169:8080", + ServerType: "vm", + ExternalNetworkID: "public", + ImageID: "fedora-atomic-latest", + VolumeDriver: "cinder", + RegistryEnabled: &boolFalse, + DockerStorageDriver: "devicemapper", + NetworkDriver: "flannel", + FixedNetwork: "", + COE: "kubernetes", + FlavorID: "m1.small", + MasterLBEnabled: &boolTrue, + DNSNameServer: "8.8.8.8", + } + + clustertemplate, err := clustertemplates.Create(serviceClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete Cluster Template + + clusterTemplateID := "dc6d336e3fc4c0a951b5698cd1236ee" + err := clustertemplates.Delete(serviceClient, clusterTemplateID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Clusters Templates + + listOpts := clustertemplates.ListOpts{ + Limit: 20, + } + + allPages, err := clustertemplates.List(serviceClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allClusterTemplates, err := clusters.ExtractClusterTemplates(allPages) + if err != nil { + panic(err) + } + + for _, clusterTemplate := range allClusterTemplates { + fmt.Printf("%+v\n", clusterTemplate) + } + +Example to Update Cluster Template + + updateOpts := []clustertemplates.UpdateOptsBuilder{ + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/master_lb_enabled", + Value: "True", + }, + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/registry_enabled", + Value: "True", + }, + } + + clustertemplate, err := clustertemplates.Update(serviceClient, updateOpts).Extract() + if err != nil { + panic(err) + } + +*/ +package clustertemplates diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go new file mode 100644 index 000000000000..3b2aac58cc68 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go @@ -0,0 +1,178 @@ +package clustertemplates + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder Builder. +type CreateOptsBuilder interface { + ToClusterCreateMap() (map[string]interface{}, error) +} + +// CreateOpts params +type CreateOpts struct { + APIServerPort *int `json:"apiserver_port,omitempty"` + COE string `json:"coe" required:"true"` + DNSNameServer string `json:"dns_nameserver,omitempty"` + DockerStorageDriver string `json:"docker_storage_driver,omitempty"` + DockerVolumeSize *int `json:"docker_volume_size,omitempty"` + ExternalNetworkID string `json:"external_network_id,omitempty"` + FixedNetwork string `json:"fixed_network,omitempty"` + FixedSubnet string `json:"fixed_subnet,omitempty"` + FlavorID string `json:"flavor_id,omitempty"` + FloatingIPEnabled *bool `json:"floating_ip_enabled,omitempty"` + HTTPProxy string `json:"http_proxy,omitempty"` + HTTPSProxy string `json:"https_proxy,omitempty"` + ImageID string `json:"image_id" required:"true"` + InsecureRegistry string `json:"insecure_registry,omitempty"` + KeyPairID string `json:"keypair_id,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + MasterFlavorID string `json:"master_flavor_id,omitempty"` + MasterLBEnabled *bool `json:"master_lb_enabled,omitempty"` + Name string `json:"name,omitempty"` + NetworkDriver string `json:"network_driver,omitempty"` + NoProxy string `json:"no_proxy,omitempty"` + Public *bool `json:"public,omitempty"` + RegistryEnabled *bool `json:"registry_enabled,omitempty"` + ServerType string `json:"server_type,omitempty"` + TLSDisabled *bool `json:"tls_disabled,omitempty"` + VolumeDriver string `json:"volume_driver,omitempty"` +} + +// ToClusterCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create requests the creation of a new cluster. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToClusterCreateMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} + +// Delete deletes the specified cluster ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(deleteURL(client, id), nil) + r.Header = result.Header + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToClusterTemplateListQuery() (string, error) +} + +// ListOpts allows the sorting of paginated collections through +// the API. SortKey allows you to sort by a particular cluster templates attribute. +// SortDir sets the direction, and is either `asc' or `desc'. +// Marker and Limit are used for pagination. +type ListOpts struct { + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToClusterTemplateListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToClusterTemplateListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// cluster-templates. It accepts a ListOptsBuilder, which allows you to sort +// the returned collection for greater efficiency. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToClusterTemplateListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ClusterTemplatePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific cluster-template based on its unique ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + var result *http.Response + result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + if r.Err == nil { + r.Header = result.Header + } + return +} + +type UpdateOp string + +const ( + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" + ReplaceOp UpdateOp = "replace" +) + +type UpdateOpts struct { + Op UpdateOp `json:"op" required:"true"` + Path string `json:"path" required:"true"` + Value interface{} `json:"value,omitempty"` +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToClusterTemplateUpdateMap() (map[string]interface{}, error) +} + +// ToClusterUpdateMap assembles a request body based on the contents of +// UpdateOpts. +func (opts UpdateOpts) ToClusterTemplateUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return b, nil +} + +// Update implements cluster updated request. +func Update(client *gophercloud.ServiceClient, id string, opts []UpdateOptsBuilder) (r UpdateResult) { + var o []map[string]interface{} + for _, opt := range opts { + b, err := opt.ToClusterTemplateUpdateMap() + if err != nil { + r.Err = err + return r + } + o = append(o, b) + } + var result *http.Response + result, r.Err = client.Patch(updateURL(client, id), o, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go new file mode 100644 index 000000000000..8b416f7e70c9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go @@ -0,0 +1,114 @@ +package clustertemplates + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Create operations. +type CreateResult struct { + commonResult +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult is the response of a Get operations. +type GetResult struct { + commonResult +} + +// UpdateResult is the response of a Update operations. +type UpdateResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts a cluster-template resource. +func (r commonResult) Extract() (*ClusterTemplate, error) { + var s *ClusterTemplate + err := r.ExtractInto(&s) + return s, err +} + +// Represents a template for a Cluster Template +type ClusterTemplate struct { + APIServerPort int `json:"apiserver_port"` + COE string `json:"coe"` + ClusterDistro string `json:"cluster_distro"` + CreatedAt time.Time `json:"created_at"` + DNSNameServer string `json:"dns_nameserver"` + DockerStorageDriver string `json:"docker_storage_driver"` + DockerVolumeSize int `json:"docker_volume_size"` + ExternalNetworkID string `json:"external_network_id"` + FixedNetwork string `json:"fixed_network"` + FixedSubnet string `json:"fixed_subnet"` + FlavorID string `json:"flavor_id"` + FloatingIPEnabled bool `json:"floating_ip_enabled"` + HTTPProxy string `json:"http_proxy"` + HTTPSProxy string `json:"https_proxy"` + ImageID string `json:"image_id"` + InsecureRegistry string `json:"insecure_registry"` + KeyPairID string `json:"keypair_id"` + Labels map[string]string `json:"labels"` + Links []gophercloud.Link `json:"links"` + MasterFlavorID string `json:"master_flavor_id"` + MasterLBEnabled bool `json:"master_lb_enabled"` + Name string `json:"name"` + NetworkDriver string `json:"network_driver"` + NoProxy string `json:"no_proxy"` + ProjectID string `json:"project_id"` + Public bool `json:"public"` + RegistryEnabled bool `json:"registry_enabled"` + ServerType string `json:"server_type"` + TLSDisabled bool `json:"tls_disabled"` + UUID string `json:"uuid"` + UpdatedAt time.Time `json:"updated_at"` + UserID string `json:"user_id"` + VolumeDriver string `json:"volume_driver"` +} + +// ClusterTemplatePage is the page returned by a pager when traversing over a +// collection of cluster-templates. +type ClusterTemplatePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of cluster template has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r ClusterTemplatePage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, nil +} + +// IsEmpty checks whether a ClusterTemplatePage struct is empty. +func (r ClusterTemplatePage) IsEmpty() (bool, error) { + is, err := ExtractClusterTemplates(r) + return len(is) == 0, err +} + +// ExtractClusterTemplates accepts a Page struct, specifically a ClusterTemplatePage struct, +// and extracts the elements into a slice of cluster templates structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractClusterTemplates(r pagination.Page) ([]ClusterTemplate, error) { + var s struct { + ClusterTemplates []ClusterTemplate `json:"clustertemplates"` + } + err := (r.(ClusterTemplatePage)).ExtractInto(&s) + return s.ClusterTemplates, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/fixtures.go new file mode 100644 index 000000000000..7eca134c536e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/fixtures.go @@ -0,0 +1,532 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ClusterTemplateResponse = ` +{ + "apiserver_port": 8081, + "cluster_distro": "fedora-atomic", + "coe": "kubernetes", + "created_at": "2018-06-27T16:52:21+00:00", + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": "devicemapper", + "docker_volume_size": 3, + "external_network_id": "public", + "fixed_network": null, + "fixed_subnet": null, + "flavor_id": "m1.small", + "floating_ip_enabled": 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", + "insecure_registry": null, + "keypair_id": "kp", + "labels": null, + "links": [ + { + "href": "http://10.63.176.154:9511/v1/clustertemplates/79c0f9e5-93b8-4719-8fab-063afc67bffe", + "rel": "self" + }, + { + "href": "http://10.63.176.154:9511/clustertemplates/79c0f9e5-93b8-4719-8fab-063afc67bffe", + "rel": "bookmark" + } + ], + "master_flavor_id": null, + "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", + "project_id": "76bd201dbc1641729904ab190d3390c6", + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "updated_at": null, + "user_id": "c48d66144e9c4a54ae2b164b85cfefe3", + "uuid": "79c0f9e5-93b8-4719-8fab-063afc67bffe", + "volume_driver": "cinder" +}` + +const ClusterTemplateResponse_EmptyTime = ` +{ + "apiserver_port": null, + "cluster_distro": "fedora-atomic", + "coe": "kubernetes", + "created_at": null, + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": null, + "docker_volume_size": 5, + "external_network_id": "public", + "fixed_network": null, + "fixed_subnet": null, + "flavor_id": "m1.small", + "http_proxy": null, + "https_proxy": null, + "image_id": "fedora-atomic-latest", + "insecure_registry": null, + "keypair_id": "testkey", + "labels": {}, + "links": [ + { + "href": "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "bookmark" + }, + { + "href": "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "self" + } + ], + "master_flavor_id": null, + "master_lb_enabled": false, + "name": "kubernetes-dev", + "network_driver": "flannel", + "no_proxy": null, + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "updated_at": null, + "uuid": "472807c2-f175-4946-9765-149701a5aba7", + "volume_driver": null +}` + +const ClusterTemplateListResponse = ` +{ + "clustertemplates": [ + { + "apiserver_port": 8081, + "cluster_distro": "fedora-atomic", + "coe": "kubernetes", + "created_at": "2018-06-27T16:52:21+00:00", + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": "devicemapper", + "docker_volume_size": 3, + "external_network_id": "public", + "fixed_network": null, + "fixed_subnet": null, + "flavor_id": "m1.small", + "floating_ip_enabled": 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", + "insecure_registry": null, + "keypair_id": "kp", + "labels": null, + "links": [ + { + "href": "http://10.63.176.154:9511/v1/clustertemplates/79c0f9e5-93b8-4719-8fab-063afc67bffe", + "rel": "self" + }, + { + "href": "http://10.63.176.154:9511/clustertemplates/79c0f9e5-93b8-4719-8fab-063afc67bffe", + "rel": "bookmark" + } + ], + "master_flavor_id": null, + "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", + "project_id": "76bd201dbc1641729904ab190d3390c6", + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "updated_at": null, + "user_id": "c48d66144e9c4a54ae2b164b85cfefe3", + "uuid": "79c0f9e5-93b8-4719-8fab-063afc67bffe", + "volume_driver": "cinder" + }, + { + "apiserver_port": null, + "cluster_distro": "fedora-atomic", + "coe": "kubernetes", + "created_at": null, + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": null, + "docker_volume_size": 5, + "external_network_id": "public", + "fixed_network": null, + "fixed_subnet": null, + "flavor_id": "m1.small", + "http_proxy": null, + "https_proxy": null, + "image_id": "fedora-atomic-latest", + "insecure_registry": null, + "keypair_id": "testkey", + "labels": {}, + "links": [ + { + "href": "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "bookmark" + }, + { + "href": "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "self" + } + ], + "master_flavor_id": null, + "master_lb_enabled": false, + "name": "kubernetes-dev", + "network_driver": "flannel", + "no_proxy": null, + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "updated_at": null, + "uuid": "472807c2-f175-4946-9765-149701a5aba7", + "volume_driver": null + } + ] +}` + +var ExpectedClusterTemplate = clustertemplates.ClusterTemplate{ + APIServerPort: 8081, + COE: "kubernetes", + ClusterDistro: "fedora-atomic", + CreatedAt: time.Date(2018, 6, 27, 16, 52, 21, 0, time.UTC), + DNSNameServer: "8.8.8.8", + DockerStorageDriver: "devicemapper", + DockerVolumeSize: 3, + ExternalNetworkID: "public", + FixedNetwork: "", + FixedSubnet: "", + FlavorID: "m1.small", + FloatingIPEnabled: true, + HTTPProxy: "http://10.164.177.169:8080", + HTTPSProxy: "http://10.164.177.169:8080", + ImageID: "Fedora-Atomic-27-20180212.2.x86_64", + InsecureRegistry: "", + KeyPairID: "kp", + Labels: map[string]string(nil), + Links: []gophercloud.Link{ + {Href: "http://10.63.176.154:9511/v1/clustertemplates/79c0f9e5-93b8-4719-8fab-063afc67bffe", Rel: "self"}, + {Href: "http://10.63.176.154:9511/clustertemplates/79c0f9e5-93b8-4719-8fab-063afc67bffe", Rel: "bookmark"}, + }, + MasterFlavorID: "", + MasterLBEnabled: true, + Name: "kubernetes-dev", + NetworkDriver: "flannel", + NoProxy: "10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost", + ProjectID: "76bd201dbc1641729904ab190d3390c6", + Public: false, + RegistryEnabled: false, + ServerType: "vm", + TLSDisabled: false, + UUID: "79c0f9e5-93b8-4719-8fab-063afc67bffe", + UpdatedAt: time.Time{}, + UserID: "c48d66144e9c4a54ae2b164b85cfefe3", + VolumeDriver: "cinder", +} + +var ExpectedClusterTemplate_EmptyTime = clustertemplates.ClusterTemplate{ + COE: "kubernetes", + ClusterDistro: "fedora-atomic", + CreatedAt: time.Time{}, + DNSNameServer: "8.8.8.8", + DockerStorageDriver: "", + DockerVolumeSize: 5, + ExternalNetworkID: "public", + FixedNetwork: "", + FixedSubnet: "", + FlavorID: "m1.small", + HTTPProxy: "", + HTTPSProxy: "", + ImageID: "fedora-atomic-latest", + InsecureRegistry: "", + KeyPairID: "testkey", + Labels: map[string]string{}, + Links: []gophercloud.Link{ + {Href: "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", Rel: "bookmark"}, + {Href: "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", Rel: "self"}, + }, + MasterFlavorID: "", + MasterLBEnabled: false, + Name: "kubernetes-dev", + NetworkDriver: "flannel", + NoProxy: "", + Public: false, + RegistryEnabled: false, + ServerType: "vm", + TLSDisabled: false, + UUID: "472807c2-f175-4946-9765-149701a5aba7", + UpdatedAt: time.Time{}, + VolumeDriver: "", +} + +var ExpectedClusterTemplates = []clustertemplates.ClusterTemplate{ExpectedClusterTemplate, ExpectedClusterTemplate_EmptyTime} + +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) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") + w.Header().Add("OpenStack-API-Maximum-Version", "container-infra 1.6") + w.Header().Add("OpenStack-API-Version", "container-infra 1.1") + w.Header().Add("X-OpenStack-Request-Id", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, ClusterTemplateResponse) + }) +} + +func HandleDeleteClusterSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") + w.Header().Add("OpenStack-API-Maximum-Version", "container-infra 1.6") + w.Header().Add("OpenStack-API-Version", "container-infra 1.1") + w.Header().Add("X-OpenStack-Request-Id", "req-781e9bdc-4163-46eb-91c9-786c53188bbb") + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleListClusterTemplateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterTemplateListResponse) + }) +} + +func HandleGetClusterTemplateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterTemplateResponse) + }) +} + +func HandleGetClusterTemplateEmptyTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ClusterTemplateResponse_EmptyTime) + }) +} + +const UpdateResponse = ` +{ + "apiserver_port": null, + "cluster_distro": "fedora-atomic", + "coe": "kubernetes", + "created_at": "2016-08-10T13:47:01+00:00", + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": null, + "docker_volume_size": 5, + "external_network_id": "public", + "fixed_network": null, + "fixed_subnet": null, + "flavor_id": "m1.small", + "http_proxy": null, + "https_proxy": null, + "image_id": "fedora-atomic-latest", + "insecure_registry": null, + "keypair_id": "testkey", + "labels": {}, + "links": [ + { + "href": "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "self" + }, + { + "href": "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "bookmark" + } + ], + "master_flavor_id": null, + "master_lb_enabled": false, + "name": "kubernetes-dev", + "network_driver": "flannel", + "no_proxy": null, + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "updated_at": null, + "uuid": "472807c2-f175-4946-9765-149701a5aba7", + "volume_driver": null +}` + +const UpdateResponse_EmptyTime = ` +{ + "apiserver_port": null, + "cluster_distro": "fedora-atomic", + "coe": "kubernetes", + "created_at": null, + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": null, + "docker_volume_size": 5, + "external_network_id": "public", + "fixed_network": null, + "fixed_subnet": null, + "flavor_id": "m1.small", + "http_proxy": null, + "https_proxy": null, + "image_id": "fedora-atomic-latest", + "insecure_registry": null, + "keypair_id": "testkey", + "labels": {}, + "links": [ + { + "href": "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "self" + }, + { + "href": "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", + "rel": "bookmark" + } + ], + "master_flavor_id": null, + "master_lb_enabled": false, + "name": "kubernetes-dev", + "network_driver": "flannel", + "no_proxy": null, + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "updated_at": null, + "uuid": "472807c2-f175-4946-9765-149701a5aba7", + "volume_driver": null +}` + +const UpdateResponse_InvalidUpdate = ` +{ + "errors": [{\"status\": 400, \"code\": \"client\", \"links\": [], \"title\": \"'add' and 'replace' operations needs value\", \"detail\": \"'add' and 'replace' operations needs value\", \"request_id\": \"\"}] +}` + +var ExpectedUpdateClusterTemplate = clustertemplates.ClusterTemplate{ + COE: "kubernetes", + ClusterDistro: "fedora-atomic", + CreatedAt: time.Date(2016, 8, 10, 13, 47, 01, 0, time.UTC), + DNSNameServer: "8.8.8.8", + DockerStorageDriver: "", + DockerVolumeSize: 5, + ExternalNetworkID: "public", + FixedNetwork: "", + FixedSubnet: "", + FlavorID: "m1.small", + HTTPProxy: "", + HTTPSProxy: "", + ImageID: "fedora-atomic-latest", + InsecureRegistry: "", + KeyPairID: "testkey", + Labels: map[string]string{}, + Links: []gophercloud.Link{ + {Href: "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", Rel: "self"}, + {Href: "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", Rel: "bookmark"}, + }, + MasterFlavorID: "", + MasterLBEnabled: false, + Name: "kubernetes-dev", + NetworkDriver: "flannel", + NoProxy: "", + Public: false, + RegistryEnabled: false, + ServerType: "vm", + TLSDisabled: false, + UUID: "472807c2-f175-4946-9765-149701a5aba7", + UpdatedAt: time.Time{}, + VolumeDriver: "", +} + +var ExpectedUpdateClusterTemplate_EmptyTime = clustertemplates.ClusterTemplate{ + COE: "kubernetes", + ClusterDistro: "fedora-atomic", + CreatedAt: time.Time{}, + DNSNameServer: "8.8.8.8", + DockerStorageDriver: "", + DockerVolumeSize: 5, + ExternalNetworkID: "public", + FixedNetwork: "", + FixedSubnet: "", + FlavorID: "m1.small", + HTTPProxy: "", + HTTPSProxy: "", + ImageID: "fedora-atomic-latest", + InsecureRegistry: "", + KeyPairID: "testkey", + Labels: map[string]string{}, + Links: []gophercloud.Link{ + {Href: "http://65.61.151.130:9511/v1/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", Rel: "self"}, + {Href: "http://65.61.151.130:9511/clustertemplates/472807c2-f175-4946-9765-149701a5aba7", Rel: "bookmark"}, + }, + MasterFlavorID: "", + MasterLBEnabled: false, + Name: "kubernetes-dev", + NetworkDriver: "flannel", + NoProxy: "", + Public: false, + RegistryEnabled: false, + ServerType: "vm", + TLSDisabled: false, + UUID: "472807c2-f175-4946-9765-149701a5aba7", + UpdatedAt: time.Time{}, + VolumeDriver: "", +} + +func HandleUpdateClusterTemplateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse) + }) +} + +func HandleUpdateClusterTemplateEmptyTimeSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdateResponse_EmptyTime) + }) +} + +func HandleUpdateClusterTemplateInvalidUpdate(t *testing.T) { + th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + + fmt.Fprint(w, UpdateResponse_EmptyTime) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go new file mode 100644 index 000000000000..cbed6b136548 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go @@ -0,0 +1,212 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateClusterTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateClusterTemplateSuccessfully(t) + + boolFalse := false + boolTrue := true + dockerVolumeSize := 3 + opts := clustertemplates.CreateOpts{ + Name: "kubernetes-dev", + Labels: map[string]string{}, + FixedSubnet: "", + MasterFlavorID: "", + NoProxy: "10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost", + HTTPSProxy: "http://10.164.177.169:8080", + TLSDisabled: &boolFalse, + KeyPairID: "kp", + Public: &boolFalse, + HTTPProxy: "http://10.164.177.169:8080", + DockerVolumeSize: &dockerVolumeSize, + ServerType: "vm", + ExternalNetworkID: "public", + ImageID: "Fedora-Atomic-27-20180212.2.x86_64", + VolumeDriver: "cinder", + RegistryEnabled: &boolFalse, + DockerStorageDriver: "devicemapper", + NetworkDriver: "flannel", + FixedNetwork: "", + COE: "kubernetes", + FlavorID: "m1.small", + MasterLBEnabled: &boolTrue, + DNSNameServer: "8.8.8.8", + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + res := clustertemplates.Create(sc, opts) + th.AssertNoErr(t, res.Err) + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, "req-781e9bdc-4163-46eb-91c9-786c53188bbb", requestID) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + + actual.CreatedAt = actual.CreatedAt.UTC() + th.AssertDeepEquals(t, ExpectedClusterTemplate, *actual) +} + +func TestDeleteClusterTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleDeleteClusterSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + res := clustertemplates.Delete(sc, "6dc6d336e3fc4c0a951b5698cd1236ee") + th.AssertNoErr(t, res.Err) + requestID := res.Header["X-Openstack-Request-Id"][0] + th.AssertEquals(t, "req-781e9bdc-4163-46eb-91c9-786c53188bbb", requestID) +} + +func TestListClusterTemplates(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleListClusterTemplateSuccessfully(t) + + count := 0 + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + clustertemplates.List(sc, clustertemplates.ListOpts{Limit: 2}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := clustertemplates.ExtractClusterTemplates(page) + th.AssertNoErr(t, err) + for idx, _ := range actual { + actual[idx].CreatedAt = actual[idx].CreatedAt.UTC() + } + th.AssertDeepEquals(t, ExpectedClusterTemplates, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestGetClusterTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetClusterTemplateSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + actual, err := clustertemplates.Get(sc, "7d85f602-a948-4a30-afd4-e84f47471c15").Extract() + th.AssertNoErr(t, err) + actual.CreatedAt = actual.CreatedAt.UTC() + th.AssertDeepEquals(t, ExpectedClusterTemplate, *actual) +} + +func TestGetClusterTemplateEmptyTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleGetClusterTemplateEmptyTimeSuccessfully(t) + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + actual, err := clustertemplates.Get(sc, "7d85f602-a948-4a30-afd4-e84f47471c15").Extract() + th.AssertNoErr(t, err) + actual.CreatedAt = actual.CreatedAt.UTC() + th.AssertDeepEquals(t, ExpectedClusterTemplate_EmptyTime, *actual) +} + +func TestUpdateClusterTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateClusterTemplateSuccessfully(t) + + updateOpts := []clustertemplates.UpdateOptsBuilder{ + clustertemplates.UpdateOpts{ + Path: "/master_lb_enabled", + Value: "True", + Op: clustertemplates.ReplaceOp, + }, + clustertemplates.UpdateOpts{ + Path: "/registry_enabled", + Value: "True", + Op: clustertemplates.ReplaceOp, + }, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + res := clustertemplates.Update(sc, "7d85f602-a948-4a30-afd4-e84f47471c15", updateOpts) + th.AssertNoErr(t, res.Err) + + actual, err := res.Extract() + th.AssertNoErr(t, err) + actual.CreatedAt = actual.CreatedAt.UTC() + th.AssertDeepEquals(t, ExpectedUpdateClusterTemplate, *actual) +} + +func TestUpdateClusterTemplateEmptyTime(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateClusterTemplateEmptyTimeSuccessfully(t) + + updateOpts := []clustertemplates.UpdateOptsBuilder{ + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/master_lb_enabled", + Value: "True", + }, + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/registry_enabled", + Value: "True", + }, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + actual, err := clustertemplates.Update(sc, "7d85f602-a948-4a30-afd4-e84f47471c15", updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdateClusterTemplate_EmptyTime, *actual) +} + +func TestUpdateClusterTemplateInvalidUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleUpdateClusterTemplateInvalidUpdate(t) + + updateOpts := []clustertemplates.UpdateOptsBuilder{ + clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: "/master_lb_enabled", + }, + clustertemplates.UpdateOpts{ + Op: clustertemplates.RemoveOp, + Path: "/master_lb_enabled", + }, + clustertemplates.UpdateOpts{ + Op: clustertemplates.AddOp, + Path: "/master_lb_enabled", + }, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + _, err := clustertemplates.Update(sc, "7d85f602-a948-4a30-afd4-e84f47471c15", updateOpts).Extract() + th.AssertEquals(t, true, err != nil) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go new file mode 100644 index 000000000000..f90fb85108af --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go @@ -0,0 +1,35 @@ +package clustertemplates + +import ( + "github.com/gophercloud/gophercloud" +) + +var apiName = "clustertemplates" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiName) +} + +func idURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL(apiName, id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return idURL(client, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/doc.go new file mode 100644 index 000000000000..62b13b2db096 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/doc.go @@ -0,0 +1,18 @@ +/* +Package quotas contains functionality for working with Magnum Quota API. + +Example to Create a Quota + + createOpts := quotas.CreateOpts{ + ProjectID: "aa5436ab58144c768ca4e9d2e9f5c3b2", + Resource: "Cluster", + HardLimit: 10, + } + + quota, err := quotas.Create(serviceClient, createOpts).Extract() + if err != nil { + panic(err) + } + +*/ +package quotas diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/requests.go new file mode 100644 index 000000000000..21c5af7b3929 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/requests.go @@ -0,0 +1,43 @@ +package quotas + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +// CreateOptsBuilder Builder. +type CreateOptsBuilder interface { + ToQuotaCreateMap() (map[string]interface{}, error) +} + +// CreateOpts params +type CreateOpts struct { + ProjectID string `json:"project_id"` + Resource string `json:"resource"` + HardLimit int `json:"hard_limit"` +} + +// ToQuotaCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToQuotaCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create requests the creation of a new quota. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToQuotaCreateMap() + if err != nil { + r.Err = err + return + } + var result *http.Response + result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + + if r.Err == nil { + r.Header = result.Header + } + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/results.go new file mode 100644 index 000000000000..0fc4f97d7871 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/results.go @@ -0,0 +1,57 @@ +package quotas + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gophercloud/gophercloud" +) + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Create operations. +type CreateResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts a quota resource. +func (r commonResult) Extract() (*Quotas, error) { + var s *Quotas + err := r.ExtractInto(&s) + return s, err +} + +type Quotas struct { + Resource string `json:"resource"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + HardLimit int `json:"hard_limit"` + ProjectID string `json:"project_id"` + ID string `json:"-"` +} + +func (r *Quotas) UnmarshalJSON(b []byte) error { + type tmp Quotas + var s struct { + tmp + ID interface{} `json:"id"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Quotas(s.tmp) + + switch t := s.ID.(type) { + case float64: + r.ID = fmt.Sprint(t) + case string: + r.ID = t + } + + return nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/fixtures.go new file mode 100644 index 000000000000..8e02448dad83 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/fixtures.go @@ -0,0 +1,36 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const projectID = "aa5436ab58144c768ca4e9d2e9f5c3b2" +const requestUUID = "req-781e9bdc-4163-46eb-91c9-786c53188bbb" + +var CreateResponse = fmt.Sprintf(` +{ + "resource": "Cluster", + "created_at": "2017-01-17T17:35:48+00:00", + "updated_at": null, + "hard_limit": 1, + "project_id": "%s", + "id": 26 +}`, projectID) + +func HandleCreateQuotaSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v1/quotas", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.Header().Add("X-OpenStack-Request-Id", requestUUID) + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, CreateResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/requests_test.go new file mode 100644 index 000000000000..80e19a62c742 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/testing/requests_test.go @@ -0,0 +1,36 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateQuota(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCreateQuotaSuccessfully(t) + + opts := quotas.CreateOpts{ + ProjectID: "aa5436ab58144c768ca4e9d2e9f5c3b2", + Resource: "Cluster", + HardLimit: 10, + } + + sc := fake.ServiceClient() + sc.Endpoint = sc.Endpoint + "v1/" + + res := quotas.Create(sc, opts) + th.AssertNoErr(t, res.Err) + + requestID := res.Header.Get("X-OpenStack-Request-Id") + th.AssertEquals(t, requestUUID, requestID) + + quota, err := res.Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, projectID, quota.ProjectID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/urls.go new file mode 100644 index 000000000000..332b3f3c4bb1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas/urls.go @@ -0,0 +1,15 @@ +package quotas + +import ( + "github.com/gophercloud/gophercloud" +) + +var apiName = "quotas" + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiName) +} + +func createURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go index 6851c58765c2..32bfb1dd414b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go @@ -80,7 +80,7 @@ type UpdateOpts struct { // Associates the configuration group with a particular datastore. Datastore *DatastoreOpts `json:"datastore,omitempty"` // A human-readable explanation for the group. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` } // ToConfigUpdateMap will cast an UpdateOpts struct into a JSON map. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go index 351633024925..13bcbffe8b96 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go @@ -10,8 +10,8 @@ import ( // Config represents a configuration group API resource. type Config struct { - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Created time.Time `json:"-"` + Updated time.Time `json:"-"` DatastoreName string `json:"datastore_name"` DatastoreVersionID string `json:"datastore_version_id"` DatastoreVersionName string `json:"datastore_version_name"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go index 1a570899e467..e47a740ce9be 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go @@ -26,6 +26,36 @@ type Flavor struct { Links []gophercloud.Link } +// Fault describes the fault reason in more detail when a database instance has errored +type Fault struct { + // Indicates the time when the fault occured + Created time.Time `json:"-"` + + // A message describing the fault reason + Message string + + // More details about the fault, for example a stack trace. Only filled + // in for admin users. + Details string +} + +func (r *Fault) UnmarshalJSON(b []byte) error { + type tmp Fault + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Fault(s.tmp) + + r.Created = time.Time(s.Created) + + return nil +} + // Instance represents a remote MySQL instance. type Instance struct { // Indicates the datetime that the instance was created @@ -61,6 +91,9 @@ type Instance struct { // The build status of the instance. Status string + // Fault information (only available when the instance has errored) + Fault *Fault + // Information about the attached volume of the instance. Volume Volume diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go index 93643c4fbd69..3555a008ae95 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go @@ -86,6 +86,48 @@ var createReq = ` } ` +var instanceWithFault = ` +{ + "created": "` + timestamp + `", + "datastore": { + "type": "mysql", + "version": "5.6" + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://openstack.example.com/v1.0/1234/flavors/1", + "rel": "self" + }, + { + "href": "https://openstack.example.com/v1.0/1234/flavors/1", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "https://openstack.example.com/v1.0/1234/instances/1", + "rel": "self" + } + ], + "hostname": "e09ad9a3f73309469cf1f43d11e79549caf9acf2.openstack.example.com", + "id": "{instanceID}", + "name": "json_rack_instance", + "status": "BUILD", + "updated": "` + timestamp + `", + "volume": { + "size": 2 + }, + "fault": { + "message": "some error message", + "created": "` + timestamp + `", + "details": "some details about the error" + } +} +` + var ( instanceID = "{instanceID}" configGroupID = "00000000-0000-0000-0000-000000000000" @@ -104,11 +146,12 @@ var ( ) var ( - createResp = fmt.Sprintf(`{"instance": %s}`, instance) - listInstancesResp = fmt.Sprintf(`{"instances":[%s]}`, instance) - getInstanceResp = createResp - enableUserResp = `{"user":{"name":"root","password":"secretsecret"}}` - isUserEnabledResp = `{"rootEnabled":true}` + createResp = fmt.Sprintf(`{"instance": %s}`, instance) + createWithFaultResp = fmt.Sprintf(`{"instance": %s}`, instanceWithFault) + listInstancesResp = fmt.Sprintf(`{"instances":[%s]}`, instance) + getInstanceResp = createResp + enableUserResp = `{"user":{"name":"root","password":"secretsecret"}}` + isUserEnabledResp = `{"rootEnabled":true}` ) var expectedInstance = instances.Instance{ @@ -135,10 +178,43 @@ var expectedInstance = instances.Instance{ }, } +var expectedInstanceWithFault = instances.Instance{ + Created: timeVal, + Updated: timeVal, + Flavor: instances.Flavor{ + ID: "1", + Links: []gophercloud.Link{ + {Href: "https://openstack.example.com/v1.0/1234/flavors/1", Rel: "self"}, + {Href: "https://openstack.example.com/v1.0/1234/flavors/1", Rel: "bookmark"}, + }, + }, + Hostname: "e09ad9a3f73309469cf1f43d11e79549caf9acf2.openstack.example.com", + ID: instanceID, + Links: []gophercloud.Link{ + {Href: "https://openstack.example.com/v1.0/1234/instances/1", Rel: "self"}, + }, + Name: "json_rack_instance", + Status: "BUILD", + Volume: instances.Volume{Size: 2}, + Datastore: datastores.DatastorePartial{ + Type: "mysql", + Version: "5.6", + }, + Fault: &instances.Fault{ + Created: timeVal, + Message: "some error message", + Details: "some details about the error", + }, +} + func HandleCreate(t *testing.T) { fixture.SetupHandler(t, rootURL, "POST", createReq, createResp, 200) } +func HandleCreateWithFault(t *testing.T) { + fixture.SetupHandler(t, rootURL, "POST", createReq, createWithFaultResp, 200) +} + func HandleList(t *testing.T) { fixture.SetupHandler(t, rootURL, "GET", "", listInstancesResp, 200) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go index 666cdd2e8dd8..6e6c4f127ed3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go @@ -41,6 +41,36 @@ func TestCreate(t *testing.T) { th.AssertDeepEquals(t, &expectedInstance, instance) } +func TestCreateWithFault(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateWithFault(t) + + opts := instances.CreateOpts{ + Name: "json_rack_instance", + FlavorRef: "1", + Databases: db.BatchCreateOpts{ + {CharSet: "utf8", Collate: "utf8_general_ci", Name: "sampledb"}, + {Name: "nextround"}, + }, + Users: users.BatchCreateOpts{ + { + Name: "demouser", + Password: "demopassword", + Databases: db.BatchCreateOpts{ + {Name: "sampledb"}, + }, + }, + }, + Size: 2, + } + + instance, err := instances.Create(fake.ServiceClient(), opts).Extract() + + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, &expectedInstanceWithFault, instance) +} + func TestInstanceList(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go index 2d6ecdc3dcfc..1d9a77bcf575 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go @@ -119,10 +119,10 @@ type UpdateOptsBuilder interface { // RecordSet. type UpdateOpts struct { // Description is a description of the RecordSet. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // TTL is the time to live of the RecordSet. - TTL int `json:"ttl,omitempty"` + TTL *int `json:"ttl,omitempty"` // Records are the DNS records of the RecordSet. Records []string `json:"records,omitempty"` @@ -135,10 +135,17 @@ func (opts UpdateOpts) ToRecordSetUpdateMap() (map[string]interface{}, error) { return nil, err } - if opts.TTL > 0 { - b["ttl"] = opts.TTL - } else { - b["ttl"] = nil + // If opts.TTL was actually set, use 0 as a special value to send "null", + // even though the result from the API is 0. + // + // Otherwise, don't send the TTL field. + if opts.TTL != nil { + ttl := *(opts.TTL) + if ttl > 0 { + b["ttl"] = ttl + } else { + b["ttl"] = nil + } } return b, nil diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/testing/requests_test.go index 21630152ffc9..07f8cebabfd6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/testing/requests_test.go @@ -109,9 +109,11 @@ func TestUpdate(t *testing.T) { defer th.TeardownHTTP() HandleUpdateSuccessfully(t) + var description = "Updated description" + ttl := 0 updateOpts := recordsets.UpdateOpts{ - TTL: 0, - Description: "Updated description", + TTL: &ttl, + Description: &description, Records: []string{"10.1.0.2", "10.1.0.3"}, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go index f87deadce742..78b08ae4f85a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go @@ -134,7 +134,7 @@ type UpdateOpts struct { Masters []string `json:"masters,omitempty"` // Description of the zone. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` } // ToZoneUpdateMap formats an UpdateOpts structure into a request body. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/testing/requests_test.go index 412b34933f50..0c9857cbda41 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/testing/requests_test.go @@ -72,9 +72,10 @@ func TestUpdate(t *testing.T) { defer th.TeardownHTTP() HandleUpdateSuccessfully(t) + var description = "Updated Description" updateOpts := zones.UpdateOpts{ TTL: 600, - Description: "Updated Description", + Description: &description, } UpdatedZone := CreatedZone diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go index 60f58c8ce392..f21a58f10c86 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go @@ -85,7 +85,7 @@ type UpdateOpts struct { Name string `json:"name,omitempty"` // Description is the description of the tenant. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // Enabled sets the tenant status to enabled or disabled. Enabled *bool `json:"enabled,omitempty"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/testing/requests_test.go index 86f2c94722a4..585bdcf2e9f1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/testing/requests_test.go @@ -73,9 +73,10 @@ func TestUpdateTenant(t *testing.T) { mockUpdateTenantResponse(t) id := "5c62ef576dc7444cbb73b1fe84b97648" + description := "This is new name" opts := tenants.UpdateOpts{ Name: "new_name", - Description: "This is new name", + Description: &description, Enabled: gophercloud.Enabled, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go index b11326772b12..ee5da37f465d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go @@ -135,6 +135,21 @@ func (r CreateResult) ExtractToken() (*Token, error) { }, nil } +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + var s struct { + Access struct { + Token struct { + ID string `json:"id"` + } `json:"token"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return s.Access.Token.ID, err +} + // ExtractServiceCatalog returns the ServiceCatalog that was generated along // with the user's Token. func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/requests.go new file mode 100644 index 000000000000..61111d2ef251 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/requests.go @@ -0,0 +1,95 @@ +package applicationcredentials + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToApplicationCredentialListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // Name filters the response by an application credential name + Name string `q:"name"` +} + +// ToApplicationCredentialListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToApplicationCredentialListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates the ApplicationCredentials to which the current token has access. +func List(client *gophercloud.ServiceClient, userID string, opts ListOptsBuilder) pagination.Pager { + url := listURL(client, userID) + if opts != nil { + query, err := opts.ToApplicationCredentialListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ApplicationCredentialPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single user, by ID. +func Get(client *gophercloud.ServiceClient, userID string, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, userID, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToApplicationCredentialCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create an application credential. +type CreateOpts struct { + // The name of the application credential. + Name string `json:"name,omitempty" required:"true"` + // A description of the application credential’s purpose. + Description string `json:"description,omitempty"` + // A flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts. + // Defaults to false + Unrestricted bool `json:"unrestricted"` + // The secret for the application credential, either generated by the server or provided by the user. + // This is only ever shown once in the response to a create request. It is not stored nor ever shown again. + // If the secret is lost, a new application credential must be created. + Secret string `json:"secret,omitempty"` + // A list of one or more roles that this application credential has associated with its project. + // A token using this application credential will have these same roles. + Roles []Role `json:"roles,omitempty"` + // The expiration time of the application credential, if one was specified. + ExpiresAt string `json:"expires_at,omitempty"` +} + +// ToApplicationCredentialCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToApplicationCredentialCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "application_credential") +} + +// Create creates a new ApplicationCredential. +func Create(client *gophercloud.ServiceClient, userID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToApplicationCredentialCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client, userID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete deletes an application credential. +func Delete(client *gophercloud.ServiceClient, userID string, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, userID, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/results.go new file mode 100644 index 000000000000..f687997636e6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/results.go @@ -0,0 +1,127 @@ +package applicationcredentials + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Role struct { + // DomainID is the domain ID the role belongs to. + DomainID string `json:"domain_id,omitempty"` + // ID is the unique ID of the role. + ID string `json:"id,omitempty"` + // Name is the role name + Name string `json:"name,omitempty"` +} + +// ApplicationCredential represents the application credential object +type ApplicationCredential struct { + // The ID of the application credential. + ID string `json:"id"` + // The name of the application credential. + Name string `json:"name"` + // A description of the application credential’s purpose. + Description string `json:"description"` + // A flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts. + // Defaults to false + Unrestricted bool `json:"unrestricted"` + // The secret for the application credential, either generated by the server or provided by the user. + // This is only ever shown once in the response to a create request. It is not stored nor ever shown again. + // If the secret is lost, a new application credential must be created. + Secret string `json:"secret"` + // The ID of the project the application credential was created for and that authentication requests using this application credential will be scoped to. + ProjectID string `json:"project_id"` + // A list of one or more roles that this application credential has associated with its project. + // A token using this application credential will have these same roles. + Roles []Role `json:"roles"` + // The expiration time of the application credential, if one was specified. + ExpiresAt time.Time `json:"-"` + // Links contains referencing links to the application credential. + Links map[string]interface{} `json:"links"` +} + +func (r *ApplicationCredential) UnmarshalJSON(b []byte) error { + type tmp ApplicationCredential + var s struct { + tmp + ExpiresAt gophercloud.JSONRFC3339MilliNoZ `json:"expires_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ApplicationCredential(s.tmp) + + r.ExpiresAt = time.Time(s.ExpiresAt) + + return nil +} + +type applicationCredentialResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as an ApplicationCredential. +type GetResult struct { + applicationCredentialResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as an ApplicationCredential. +type CreateResult struct { + applicationCredentialResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// an ApplicationCredentialPage is a single page of an ApplicationCredential results. +type ApplicationCredentialPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a an ApplicationCredentialPage contains any results. +func (r ApplicationCredentialPage) IsEmpty() (bool, error) { + applicationCredentials, err := ExtractApplicationCredentials(r) + return len(applicationCredentials) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r ApplicationCredentialPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// Extractan ApplicationCredentials returns a slice of ApplicationCredentials contained in a single page of results. +func ExtractApplicationCredentials(r pagination.Page) ([]ApplicationCredential, error) { + var s struct { + ApplicationCredentials []ApplicationCredential `json:"application_credentials"` + } + err := (r.(ApplicationCredentialPage)).ExtractInto(&s) + return s.ApplicationCredentials, err +} + +// Extract interprets any application_credential results as an ApplicationCredential. +func (r applicationCredentialResult) Extract() (*ApplicationCredential, error) { + var s struct { + ApplicationCredential *ApplicationCredential `json:"application_credential"` + } + err := r.ExtractInto(&s) + return s.ApplicationCredential, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/fixtures.go new file mode 100644 index 000000000000..ba6b54b379f0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/fixtures.go @@ -0,0 +1,404 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const userID = "2844b2a08be147a08ef58317d6471f1f" +const applicationCredentialID = "f741662395b249c9b8acdebf1722c5ae" + +// ListOutput provides a single page of ApplicationCredential results. +const ListOutput = ` +{ + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", + "previous": null, + "next": null + }, + "application_credentials": [ + { + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/c4859fb437df4b87a51a8f5adcfb0bc7" + }, + "description": null, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13", + "domain_id": null, + "name": "compute_viewer" + } + ], + "expires_at": null, + "unrestricted": false, + "project_id": "53c2b94f63fb4f43a21b92d119ce549f", + "id": "c4859fb437df4b87a51a8f5adcfb0bc7", + "name": "test1" + }, + { + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/6b8cc7647da64166a4a3cc0c88ebbabb" + }, + "description": null, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13", + "domain_id": null, + "name": "compute_viewer" + }, + { + "id": "4494bc5bea1a4105ad7fbba6a7eb9ef4", + "domain_id": null, + "name": "network_viewer" + } + ], + "expires_at": "2019-03-12T12:12:12.000000", + "unrestricted": true, + "project_id": "53c2b94f63fb4f43a21b92d119ce549f", + "id": "6b8cc7647da64166a4a3cc0c88ebbabb", + "name": "test2" + } + ] +} +` + +// GetOutput provides a Get result. +const GetOutput = ` +{ + "application_credential": { + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae" + }, + "description": null, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13", + "domain_id": null, + "name": "compute_viewer" + } + ], + "expires_at": null, + "unrestricted": false, + "project_id": "53c2b94f63fb4f43a21b92d119ce549f", + "id": "f741662395b249c9b8acdebf1722c5ae", + "name": "test" + } +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "application_credential": { + "secret": "mysecret", + "unrestricted": false, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13" + } + ], + "name": "test" + } +} +` + +const CreateResponse = ` +{ + "application_credential": { + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae" + }, + "description": null, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13", + "domain_id": null, + "name": "compute_viewer" + } + ], + "expires_at": null, + "secret": "mysecret", + "unrestricted": false, + "project_id": "53c2b94f63fb4f43a21b92d119ce549f", + "id": "f741662395b249c9b8acdebf1722c5ae", + "name": "test" + } +} +` + +// CreateNoOptionsRequest provides the input to a Create request with no Secret. +const CreateNoSecretRequest = ` +{ + "application_credential": { + "unrestricted": false, + "name": "test1", + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13" + } + ] + } +} +` + +const CreateNoSecretResponse = ` +{ + "application_credential": { + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/c4859fb437df4b87a51a8f5adcfb0bc7" + }, + "description": null, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13", + "domain_id": null, + "name": "compute_viewer" + } + ], + "expires_at": null, + "secret": "generated_secret", + "unrestricted": false, + "project_id": "53c2b94f63fb4f43a21b92d119ce549f", + "id": "c4859fb437df4b87a51a8f5adcfb0bc7", + "name": "test1" + } +} +` + +const CreateUnrestrictedRequest = ` +{ + "application_credential": { + "unrestricted": true, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13" + }, + { + "id": "4494bc5bea1a4105ad7fbba6a7eb9ef4" + } + ], + "expires_at": "2019-03-12T12:12:12.000000", + "name": "test2" + } +} +` + +const CreateUnrestrictedResponse = ` +{ + "application_credential": { + "links": { + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/6b8cc7647da64166a4a3cc0c88ebbabb" + }, + "description": null, + "roles": [ + { + "id": "31f87923ae4a4d119aa0b85dcdbeed13", + "domain_id": null, + "name": "compute_viewer" + }, + { + "id": "4494bc5bea1a4105ad7fbba6a7eb9ef4", + "domain_id": null, + "name": "network_viewer" + } + ], + "expires_at": "2019-03-12T12:12:12.000000", + "secret": "generated_secret", + "unrestricted": true, + "project_id": "53c2b94f63fb4f43a21b92d119ce549f", + "id": "6b8cc7647da64166a4a3cc0c88ebbabb", + "name": "test2" + } +} +` + +var nilTime time.Time +var ApplicationCredential = applicationcredentials.ApplicationCredential{ + ID: "f741662395b249c9b8acdebf1722c5ae", + Name: "test", + Description: "", + Unrestricted: false, + Secret: "", + ProjectID: "53c2b94f63fb4f43a21b92d119ce549f", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ + ID: "31f87923ae4a4d119aa0b85dcdbeed13", + Name: "compute_viewer", + }, + }, + ExpiresAt: nilTime, + Links: map[string]interface{}{ + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", + }, +} + +var ApplicationCredentialNoSecretResponse = applicationcredentials.ApplicationCredential{ + ID: "c4859fb437df4b87a51a8f5adcfb0bc7", + Name: "test1", + Description: "", + Unrestricted: false, + Secret: "generated_secret", + ProjectID: "53c2b94f63fb4f43a21b92d119ce549f", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ + ID: "31f87923ae4a4d119aa0b85dcdbeed13", + Name: "compute_viewer", + }, + }, + ExpiresAt: nilTime, + Links: map[string]interface{}{ + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/c4859fb437df4b87a51a8f5adcfb0bc7", + }, +} + +var ApplationCredentialExpiresAt, _ = time.Parse(gophercloud.RFC3339MilliNoZ, "2019-03-12T12:12:12.000000") +var UnrestrictedApplicationCredential = applicationcredentials.ApplicationCredential{ + ID: "6b8cc7647da64166a4a3cc0c88ebbabb", + Name: "test2", + Description: "", + Unrestricted: true, + Secret: "", + ProjectID: "53c2b94f63fb4f43a21b92d119ce549f", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ + ID: "31f87923ae4a4d119aa0b85dcdbeed13", + Name: "compute_viewer", + }, + applicationcredentials.Role{ + ID: "4494bc5bea1a4105ad7fbba6a7eb9ef4", + Name: "network_viewer", + }, + }, + ExpiresAt: ApplationCredentialExpiresAt, + Links: map[string]interface{}{ + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/6b8cc7647da64166a4a3cc0c88ebbabb", + }, +} + +var FirstApplicationCredential = applicationcredentials.ApplicationCredential{ + ID: "c4859fb437df4b87a51a8f5adcfb0bc7", + Name: "test1", + Description: "", + Unrestricted: false, + Secret: "", + ProjectID: "53c2b94f63fb4f43a21b92d119ce549f", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ + ID: "31f87923ae4a4d119aa0b85dcdbeed13", + Name: "compute_viewer", + }, + }, + ExpiresAt: nilTime, + Links: map[string]interface{}{ + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/c4859fb437df4b87a51a8f5adcfb0bc7", + }, +} + +var SecondApplicationCredential = applicationcredentials.ApplicationCredential{ + ID: "6b8cc7647da64166a4a3cc0c88ebbabb", + Name: "test2", + Description: "", + Unrestricted: true, + Secret: "", + ProjectID: "53c2b94f63fb4f43a21b92d119ce549f", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ + ID: "31f87923ae4a4d119aa0b85dcdbeed13", + Name: "compute_viewer", + }, + applicationcredentials.Role{ + ID: "4494bc5bea1a4105ad7fbba6a7eb9ef4", + Name: "network_viewer", + }, + }, + ExpiresAt: ApplationCredentialExpiresAt, + Links: map[string]interface{}{ + "self": "https://identity/v3/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/6b8cc7647da64166a4a3cc0c88ebbabb", + }, +} + +// ExpectedApplicationCredentialsSlice is the slice of application credentials expected to be returned from ListOutput. +var ExpectedApplicationCredentialsSlice = []applicationcredentials.ApplicationCredential{FirstApplicationCredential, SecondApplicationCredential} + +// HandleListApplicationCredentialsSuccessfully creates an HTTP handler at `/users` on the +// test handler mux that responds with a list of two applicationcredentials. +func HandleListApplicationCredentialsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the +// test handler mux that responds with a single application credential. +func HandleGetApplicationCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the +// test handler mux that tests application credential creation. +func HandleCreateApplicationCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, CreateResponse) + }) +} + +// HandleCreateNoOptionsApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the +// test handler mux that tests application credential creation. +func HandleCreateNoSecretApplicationCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateNoSecretRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, CreateNoSecretResponse) + }) +} + +func HandleCreateUnrestrictedApplicationCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateUnrestrictedRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, CreateUnrestrictedResponse) + }) +} + +// HandleDeleteApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the +// test handler mux that tests application credential deletion. +func HandleDeleteApplicationCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/requests_test.go new file mode 100644 index 000000000000..6a410e3098e8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/testing/requests_test.go @@ -0,0 +1,124 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListApplicationCredentials(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListApplicationCredentialsSuccessfully(t) + + count := 0 + err := applicationcredentials.List(client.ServiceClient(), userID, nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := applicationcredentials.ExtractApplicationCredentials(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedApplicationCredentialsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListApplicationCredentialsAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListApplicationCredentialsSuccessfully(t) + + allPages, err := applicationcredentials.List(client.ServiceClient(), userID, nil).AllPages() + th.AssertNoErr(t, err) + actual, err := applicationcredentials.ExtractApplicationCredentials(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedApplicationCredentialsSlice, actual) + th.AssertDeepEquals(t, ExpectedApplicationCredentialsSlice[0].Roles, []applicationcredentials.Role{{ID: "31f87923ae4a4d119aa0b85dcdbeed13", Name: "compute_viewer"}}) + th.AssertDeepEquals(t, ExpectedApplicationCredentialsSlice[1].Roles, []applicationcredentials.Role{applicationcredentials.Role{ID: "31f87923ae4a4d119aa0b85dcdbeed13", Name: "compute_viewer"}, applicationcredentials.Role{ID: "4494bc5bea1a4105ad7fbba6a7eb9ef4", Name: "network_viewer"}}) +} + +func TestGetApplicationCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetApplicationCredentialSuccessfully(t) + + actual, err := applicationcredentials.Get(client.ServiceClient(), userID, applicationCredentialID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ApplicationCredential, *actual) +} + +func TestCreateApplicationCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateApplicationCredentialSuccessfully(t) + + createOpts := applicationcredentials.CreateOpts{ + Name: "test", + Secret: "mysecret", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ID: "31f87923ae4a4d119aa0b85dcdbeed13"}, + }, + } + + ApplicationCredentialResponse := ApplicationCredential + ApplicationCredentialResponse.Secret = "mysecret" + + actual, err := applicationcredentials.Create(client.ServiceClient(), userID, createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ApplicationCredentialResponse, *actual) +} + +func TestCreateNoSecretApplicationCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateNoSecretApplicationCredentialSuccessfully(t) + + createOpts := applicationcredentials.CreateOpts{ + Name: "test1", + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ID: "31f87923ae4a4d119aa0b85dcdbeed13"}, + }, + } + + actual, err := applicationcredentials.Create(client.ServiceClient(), userID, createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ApplicationCredentialNoSecretResponse, *actual) +} + +func TestCreateUnrestrictedApplicationCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateUnrestrictedApplicationCredentialSuccessfully(t) + + createOpts := applicationcredentials.CreateOpts{ + Name: "test2", + Unrestricted: true, + Roles: []applicationcredentials.Role{ + applicationcredentials.Role{ID: "31f87923ae4a4d119aa0b85dcdbeed13"}, + applicationcredentials.Role{ID: "4494bc5bea1a4105ad7fbba6a7eb9ef4"}, + }, + ExpiresAt: "2019-03-12T12:12:12.000000", + } + + UnrestrictedApplicationCredentialResponse := UnrestrictedApplicationCredential + UnrestrictedApplicationCredentialResponse.Secret = "generated_secret" + + actual, err := applicationcredentials.Create(client.ServiceClient(), userID, createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, UnrestrictedApplicationCredentialResponse, *actual) +} + +func TestDeleteApplicationCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteApplicationCredentialSuccessfully(t) + + res := applicationcredentials.Delete(client.ServiceClient(), userID, applicationCredentialID) + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/urls.go new file mode 100644 index 000000000000..147620693839 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/applicationcredentials/urls.go @@ -0,0 +1,19 @@ +package applicationcredentials + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "application_credentials") +} + +func getURL(client *gophercloud.ServiceClient, userID string, id string) string { + return client.ServiceURL("users", userID, "application_credentials", id) +} + +func createURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "application_credentials") +} + +func deleteURL(client *gophercloud.ServiceClient, userID string, id string) string { + return client.ServiceURL("users", userID, "application_credentials", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/requests.go new file mode 100644 index 000000000000..bf36a955219f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/requests.go @@ -0,0 +1,125 @@ +package credentials + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToCredentialListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // UserID filters the response by a credential user_id + UserID string `q:"user_id"` + // Type filters the response by a credential type + Type string `q:"type"` +} + +// ToCredentialListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToCredentialListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates the Credentials to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToCredentialListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return CredentialPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single user, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToCredentialCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a credential. +type CreateOpts struct { + // Serialized blob containing the credentials + Blob string `json:"blob" required:"true"` + // ID of the project. + ProjectID string `json:"project_id,omitempty"` + // The type of the credential. + Type string `json:"type" required:"true"` + // ID of the user who owns the credential. + UserID string `json:"user_id" required:"true"` +} + +// ToCredentialCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToCredentialCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "credential") +} + +// Create creates a new Credential. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToCredentialCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete deletes a credential. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToCredentialsUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents parameters to update a credential. +type UpdateOpts struct { + // Serialized blob containing the credentials. + Blob string `json:"blob,omitempty"` + // ID of the project. + ProjectID string `json:"project_id,omitempty"` + // The type of the credential. + Type string `json:"type,omitempty"` + // ID of the user who owns the credential. + UserID string `json:"user_id,omitempty"` +} + +// ToUpdateCreateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToCredentialsUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "credential") +} + +// Update modifies the attributes of a Credential. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToCredentialsUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/results.go new file mode 100644 index 000000000000..fe4b413e44e6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/results.go @@ -0,0 +1,94 @@ +package credentials + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Credential represents the Credential object +type Credential struct { + // The ID of the credential. + ID string `json:"id"` + // Serialized Blob Credential. + Blob string `json:"blob"` + // ID of the user who owns the credential. + UserID string `json:"user_id"` + // The type of the credential. + Type string `json:"type"` + // The ID of the project the credential was created for. + ProjectID string `json:"project_id"` + // Links contains referencing links to the credential. + Links map[string]interface{} `json:"links"` +} + +type credentialResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Credential. +type GetResult struct { + credentialResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Credential. +type CreateResult struct { + credentialResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the result of an Update request. Call its Extract method to +// interpret it as a Credential +type UpdateResult struct { + credentialResult +} + +// a CredentialPage is a single page of a Credential results. +type CredentialPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a CredentialPage contains any results. +func (r CredentialPage) IsEmpty() (bool, error) { + credentials, err := ExtractCredentials(r) + return len(credentials) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r CredentialPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// Extract a Credential returns a slice of Credentials contained in a single page of results. +func ExtractCredentials(r pagination.Page) ([]Credential, error) { + var s struct { + Credentials []Credential `json:"credentials"` + } + err := (r.(CredentialPage)).ExtractInto(&s) + return s.Credentials, err +} + +// Extract interprets any credential results as a Credential. +func (r credentialResult) Extract() (*Credential, error) { + var s struct { + Credential *Credential `json:"credential"` + } + err := r.ExtractInto(&s) + return s.Credential, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/fixtures.go new file mode 100644 index 000000000000..b765538c0ea0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/fixtures.go @@ -0,0 +1,217 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/credentials" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const userID = "bb5476fd12884539b41d5a88f838d773" +const credentialID = "3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510" +const projectID = "731fc6f265cd486d900f16e84c5cb594" + +// ListOutput provides a single page of Credential results. +const ListOutput = ` +{ + "credentials": [ + { + "user_id": "bb5476fd12884539b41d5a88f838d773", + "links": { + "self": "http://identity/v3/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510" + }, + "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + "project_id": "731fc6f265cd486d900f16e84c5cb594", + "type": "ec2", + "id": "3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510" + }, + { + "user_id": "6f556708d04b4ea6bc72d7df2296b71a", + "links": { + "self": "http://identity/v3/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609" + }, + "blob": "{\"access\":\"7da79ff0aa364e1396f067e352b9b79a\",\"secret\":\"7a18d68ba8834b799d396f3ff6f1e98c\"}", + "project_id": "1a1d14690f3c4ec5bf5f321c5fde3c16", + "type": "ec2", + "id": "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609" + } + ], + "links": { + "self": "http://identity/v3/credentials", + "previous": null, + "next": null + } +} +` + +// GetOutput provides a Get result. +const GetOutput = ` +{ + "credential": { + "user_id": "bb5476fd12884539b41d5a88f838d773", + "links": { + "self": "http://identity/v3/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510" + }, + "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + "project_id": "731fc6f265cd486d900f16e84c5cb594", + "type": "ec2", + "id": "3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510" + } +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "credential": { + "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + "project_id": "731fc6f265cd486d900f16e84c5cb594", + "type": "ec2", + "user_id": "bb5476fd12884539b41d5a88f838d773" + } +} +` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = ` +{ + "credential": { + "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + "project_id": "731fc6f265cd486d900f16e84c5cb594", + "type": "ec2", + "user_id": "bb5476fd12884539b41d5a88f838d773" + } +} +` + +// UpdateOutput provides an update result. +const UpdateOutput = ` +{ + "credential": { + "user_id": "bb5476fd12884539b41d5a88f838d773", + "links": { + "self": "http://identity/v3/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609" + }, + "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + "project_id": "731fc6f265cd486d900f16e84c5cb594", + "type": "ec2", + "id": "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609" + } +} +` + +var Credential = credentials.Credential{ + ID: credentialID, + ProjectID: projectID, + Type: "ec2", + UserID: userID, + Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + Links: map[string]interface{}{ + "self": "http://identity/v3/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", + }, +} + +var FirstCredential = credentials.Credential{ + ID: credentialID, + ProjectID: projectID, + Type: "ec2", + UserID: userID, + Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + Links: map[string]interface{}{ + "self": "http://identity/v3/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", + }, +} + +var SecondCredential = credentials.Credential{ + ID: "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", + ProjectID: "1a1d14690f3c4ec5bf5f321c5fde3c16", + Type: "ec2", + UserID: "6f556708d04b4ea6bc72d7df2296b71a", + Blob: "{\"access\":\"7da79ff0aa364e1396f067e352b9b79a\",\"secret\":\"7a18d68ba8834b799d396f3ff6f1e98c\"}", + Links: map[string]interface{}{ + "self": "http://identity/v3/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", + }, +} + +// SecondCredentialUpdated is how SecondCredential should look after an Update. +var SecondCredentialUpdated = credentials.Credential{ + ID: "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", + ProjectID: projectID, + Type: "ec2", + UserID: userID, + Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + Links: map[string]interface{}{ + "self": "http://identity/v3/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", + }, +} + +// ExpectedCredentialsSlice is the slice of credentials expected to be returned from ListOutput. +var ExpectedCredentialsSlice = []credentials.Credential{FirstCredential, SecondCredential} + +// HandleListCredentialsSuccessfully creates an HTTP handler at `/credentials` on the +// test handler mux that responds with a list of two credentials. +func HandleListCredentialsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetCredentialSuccessfully creates an HTTP handler at `/credentials` on the +// test handler mux that responds with a single credential. +func HandleGetCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateCredentialSuccessfully creates an HTTP handler at `/credentials` on the +// test handler mux that tests credential creation. +func HandleCreateCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleDeleteCredentialSuccessfully creates an HTTP handler at `/credentials` on the +// test handler mux that tests credential deletion. +func HandleDeleteCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleUpdateCredentialsSuccessfully creates an HTTP handler at `/credentials` on the +// test handler mux that tests credentials update. +func HandleUpdateCredentialSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, UpdateOutput) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/requests_test.go new file mode 100644 index 000000000000..f901db5e8a3c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/testing/requests_test.go @@ -0,0 +1,99 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/credentials" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListCredentials(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListCredentialsSuccessfully(t) + + count := 0 + err := credentials.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := credentials.ExtractCredentials(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedCredentialsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListCredentialsAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListCredentialsSuccessfully(t) + + allPages, err := credentials.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := credentials.ExtractCredentials(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedCredentialsSlice, actual) + th.AssertDeepEquals(t, ExpectedCredentialsSlice[0].Blob, "{\"access\":\"181920\",\"secret\":\"secretKey\"}") + th.AssertDeepEquals(t, ExpectedCredentialsSlice[1].Blob, "{\"access\":\"7da79ff0aa364e1396f067e352b9b79a\",\"secret\":\"7a18d68ba8834b799d396f3ff6f1e98c\"}") +} + +func TestGetCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetCredentialSuccessfully(t) + + actual, err := credentials.Get(client.ServiceClient(), credentialID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, Credential, *actual) +} + +func TestCreateCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateCredentialSuccessfully(t) + + createOpts := credentials.CreateOpts{ + ProjectID: projectID, + Type: "ec2", + UserID: userID, + Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + } + + CredentialResponse := Credential + + actual, err := credentials.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, CredentialResponse, *actual) +} + +func TestDeleteCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteCredentialSuccessfully(t) + + res := credentials.Delete(client.ServiceClient(), credentialID) + th.AssertNoErr(t, res.Err) +} + +func TestUpdateCredential(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateCredentialSuccessfully(t) + + updateOpts := credentials.UpdateOpts{ + ProjectID: "731fc6f265cd486d900f16e84c5cb594", + Type: "ec2", + UserID: "bb5476fd12884539b41d5a88f838d773", + Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", + } + + actual, err := credentials.Update(client.ServiceClient(), "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondCredentialUpdated, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/urls.go new file mode 100644 index 000000000000..5ec51d1e3433 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/credentials/urls.go @@ -0,0 +1,23 @@ +package credentials + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("credentials") +} + +func getURL(client *gophercloud.ServiceClient, credentialID string) string { + return client.ServiceURL("credentials", credentialID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("credentials") +} + +func deleteURL(client *gophercloud.ServiceClient, credentialID string) string { + return client.ServiceURL("credentials", credentialID) +} + +func updateURL(client *gophercloud.ServiceClient, credentialID string) string { + return client.ServiceURL("credentials", credentialID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go index 14fbd27eb331..215af0fca16a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go @@ -101,7 +101,7 @@ type UpdateOpts struct { Name string `json:"name,omitempty"` // Description is the description of the domain. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // Enabled sets the domain status to enabled or disabled. Enabled *bool `json:"enabled,omitempty"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go index 73ac97aa7371..07eeb06ca08d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go @@ -79,8 +79,9 @@ func TestUpdateDomain(t *testing.T) { defer th.TeardownHTTP() HandleUpdateDomainSuccessfully(t) + var description = "Staging Domain" updateOpts := domains.UpdateOpts{ - Description: "Staging Domain", + Description: &description, } actual, err := domains.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go index 645632ddc982..0764a118e2b9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go @@ -61,11 +61,8 @@ type ListOpts struct { // ServiceID is the ID of the service the Endpoint refers to. ServiceID string `q:"service_id"` - // Page is a result page to reference in the results. - Page int `q:"page"` - - // PerPage determines how many results per page are returned. - PerPage int `q:"per_page"` + // RegionID is the ID of the region the Endpoint refers to. + RegionID int `q:"region_id"` } // ToEndpointListParams builds a list request from the List options. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go new file mode 100644 index 000000000000..98e6fe4b0e53 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go @@ -0,0 +1,17 @@ +package groups + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go index b6e74dcf976c..032b54480454 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go @@ -1,6 +1,9 @@ package groups import ( + "net/url" + "strings" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -18,11 +21,30 @@ type ListOpts struct { // Name filters the response by group name. Name string `q:"name"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` } // ToGroupListQuery formats a ListOpts into a query string. func (opts ListOpts) ToGroupListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} return q.String(), err } @@ -111,7 +133,7 @@ type UpdateOpts struct { Name string `json:"name,omitempty"` // Description is a description of the group. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // DomainID is the ID of the domain the group belongs to. DomainID string `json:"domain_id,omitempty"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go index e35c97214b93..380b9779adfd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go @@ -43,6 +43,38 @@ func TestListGroupsAllPages(t *testing.T) { th.AssertEquals(t, ExpectedGroupsSlice[1].Extra["email"], "support@example.com") } +func TestListGroupsFiltersCheck(t *testing.T) { + type test struct { + filterName string + wantErr bool + } + tests := []test{ + {"foo__contains", false}, + {"foo", true}, + {"foo_contains", true}, + {"foo__", true}, + {"__foo", true}, + } + + var listOpts groups.ListOpts + for _, _test := range tests { + listOpts.Filters = map[string]string{_test.filterName: "bar"} + _, err := listOpts.ToGroupListQuery() + + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case groups.InvalidListFilter: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + func TestGetGroup(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -79,8 +111,9 @@ func TestUpdateGroup(t *testing.T) { defer th.TeardownHTTP() HandleUpdateGroupSuccessfully(t) + var description = "L2 Support Team" updateOpts := groups.UpdateOpts{ - Description: "L2 Support Team", + Description: &description, Extra: map[string]interface{}{ "email": "supportteam@example.com", }, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/doc.go new file mode 100644 index 000000000000..0bdc7a68510e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/doc.go @@ -0,0 +1,77 @@ +/* +Package policies provides information and interaction with the policies API +resource for the OpenStack Identity service. + +Example to List Policies + + listOpts := policies.ListOpts{ + Type: "application/json", + } + + allPages, err := policies.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPolicies, err := policies.ExtractPolicies(allPages) + if err != nil { + panic(err) + } + + for _, policy := range allPolicies { + fmt.Printf("%+v\n", policy) + } + +Example to Create a Policy + + createOpts := policies.CreateOpts{ + Type: "application/json", + Blob: []byte("{'foobar_user': 'role:compute-user'}"), + Extra: map[string]interface{}{ + "description": "policy for foobar_user", + }, + } + + policy, err := policies.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Policy + + policyID := "0fe36e73809d46aeae6705c39077b1b3" + policy, err := policies.Get(identityClient, policyID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", policy) + +Example to Update a Policy + + policyID := "0fe36e73809d46aeae6705c39077b1b3" + + updateOpts := policies.UpdateOpts{ + Type: "application/json", + Blob: []byte("{'foobar_user': 'role:compute-user'}"), + Extra: map[string]interface{}{ + "description": "policy for foobar_user", + }, + } + + policy, err := policies.Update(identityClient, policyID, updateOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", policy) + +Example to Delete a Policy + + policyID := "0fe36e73809d46aeae6705c39077b1b3" + err := policies.Delete(identityClient, policyID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/errors.go new file mode 100644 index 000000000000..27fb4b1cb095 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/errors.go @@ -0,0 +1,31 @@ +package policies + +import "fmt" + +// InvalidListFilter is returned by the ToPolicyListQuery method when +// validation of a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of TYPE__COMPARATOR", + e.FilterName, + ) + return s +} + +// StringFieldLengthExceedsLimit is returned by the +// ToPolicyCreateMap/ToPolicyUpdateMap methods when validation of +// a type does not pass +type StringFieldLengthExceedsLimit struct { + Field string + Limit int +} + +func (e StringFieldLengthExceedsLimit) Error() string { + return fmt.Sprintf("String length of field [%s] exceeds limit (%d)", + e.Field, e.Limit, + ) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/requests.go new file mode 100644 index 000000000000..c9640b2278cc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/requests.go @@ -0,0 +1,195 @@ +package policies + +import ( + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +const policyTypeMaxLength = 255 + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToPolicyListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // Type filters the response by MIME media type + // of the serialized policy blob. + Type string `q:"type"` + + // Filters filters the response by custom filters such as + // 'type__contains=foo' + Filters map[string]string `q:"-"` +} + +// ToPolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// List enumerates the policies to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToPolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToPolicyCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a policy. +type CreateOpts struct { + // Type is the MIME media type of the serialized policy blob. + Type string `json:"type" required:"true"` + + // Blob is the policy rule as a serialized blob. + Blob []byte `json:"-" required:"true"` + + // Extra is free-form extra key/value pairs to describe the policy. + Extra map[string]interface{} `json:"-"` +} + +// ToPolicyCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToPolicyCreateMap() (map[string]interface{}, error) { + if len(opts.Type) > policyTypeMaxLength { + return nil, StringFieldLengthExceedsLimit{ + Field: "type", + Limit: policyTypeMaxLength, + } + } + + b, err := gophercloud.BuildRequestBody(opts, "policy") + if err != nil { + return nil, err + } + + if v, ok := b["policy"].(map[string]interface{}); ok { + v["blob"] = string(opts.Blob) + + if opts.Extra != nil { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create creates a new Policy. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Get retrieves details on a single policy, by ID. +func Get(client *gophercloud.ServiceClient, policyID string) (r GetResult) { + _, r.Err = client.Get(getURL(client, policyID), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToPolicyUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a policy. +type UpdateOpts struct { + // Type is the MIME media type of the serialized policy blob. + Type string `json:"type,omitempty"` + + // Blob is the policy rule as a serialized blob. + Blob []byte `json:"-"` + + // Extra is free-form extra key/value pairs to describe the policy. + Extra map[string]interface{} `json:"-"` +} + +// ToPolicyUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToPolicyUpdateMap() (map[string]interface{}, error) { + if len(opts.Type) > policyTypeMaxLength { + return nil, StringFieldLengthExceedsLimit{ + Field: "type", + Limit: policyTypeMaxLength, + } + } + + b, err := gophercloud.BuildRequestBody(opts, "policy") + if err != nil { + return nil, err + } + + if v, ok := b["policy"].(map[string]interface{}); ok { + if len(opts.Blob) != 0 { + v["blob"] = string(opts.Blob) + } + + if opts.Extra != nil { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing Role. +func Update(client *gophercloud.ServiceClient, policyID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPolicyUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, policyID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete deletes a policy. +func Delete(client *gophercloud.ServiceClient, policyID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, policyID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/results.go new file mode 100644 index 000000000000..131a9f810351 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/results.go @@ -0,0 +1,131 @@ +package policies + +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +// Policy is an arbitrarily serialized policy engine rule +// set to be consumed by a remote service. +type Policy struct { + // ID is the unique ID of the policy. + ID string `json:"id"` + + // Blob is the policy rule as a serialized blob. + Blob string `json:"blob"` + + // Type is the MIME media type of the serialized policy blob. + Type string `json:"type"` + + // Links contains referencing links to the policy. + Links map[string]interface{} `json:"links"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` +} + +func (r *Policy) UnmarshalJSON(b []byte) error { + type tmp Policy + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Policy(s.tmp) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + r.Extra = internal.RemainingKeys(Policy{}, resultMap) + } + } + + return err +} + +type policyResult struct { + gophercloud.Result +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Policy +type CreateResult struct { + policyResult +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Policy. +type GetResult struct { + policyResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Policy. +type UpdateResult struct { + policyResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// PolicyPage is a single page of Policy results. +type PolicyPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Policies contains any results. +func (r PolicyPage) IsEmpty() (bool, error) { + policies, err := ExtractPolicies(r) + return len(policies) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r PolicyPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractPolicies returns a slice of Policies +// contained in a single page of results. +func ExtractPolicies(r pagination.Page) ([]Policy, error) { + var s struct { + Policies []Policy `json:"policies"` + } + err := (r.(PolicyPage)).ExtractInto(&s) + return s.Policies, err +} + +// Extract interprets any policyResults as a Policy. +func (r policyResult) Extract() (*Policy, error) { + var s struct { + Policy *Policy `json:"policy"` + } + err := r.ExtractInto(&s) + return s.Policy, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/doc.go new file mode 100644 index 000000000000..80554d4418e3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/doc.go @@ -0,0 +1,2 @@ +// Package testing contains policies unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/fixtures.go new file mode 100644 index 000000000000..24bca7e17501 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/fixtures.go @@ -0,0 +1,229 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/policies" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListOutput provides a single page of Policy results. +const ListOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/policies" + }, + "policies": [ + { + "type": "text/plain", + "id": "2844b2a08be147a08ef58317d6471f1f", + "links": { + "self": "http://example.com/identity/v3/policies/2844b2a08be147a08ef58317d6471f1f" + }, + "blob": "'foo_user': 'role:compute-user'" + }, + { + "type": "application/json", + "id": "b49884da9d31494ea02aff38d4b4e701", + "links": { + "self": "http://example.com/identity/v3/policies/b49884da9d31494ea02aff38d4b4e701" + }, + "blob": "{'bar_user': 'role:network-user'}", + "description": "policy for bar_user" + } + ] +} +` + +// ListWithFilterOutput provides a single page of filtered Policy results. +const ListWithFilterOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/policies" + }, + "policies": [ + { + "type": "application/json", + "id": "b49884da9d31494ea02aff38d4b4e701", + "links": { + "self": "http://example.com/identity/v3/policies/b49884da9d31494ea02aff38d4b4e701" + }, + "blob": "{'bar_user': 'role:network-user'}", + "description": "policy for bar_user" + } + ] +} +` + +// GetOutput provides a Get result. +const GetOutput = ` +{ + "policy": { + "type": "application/json", + "id": "b49884da9d31494ea02aff38d4b4e701", + "links": { + "self": "http://example.com/identity/v3/policies/b49884da9d31494ea02aff38d4b4e701" + }, + "blob": "{'bar_user': 'role:network-user'}", + "description": "policy for bar_user" + } +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "policy": { + "blob": "{'bar_user': 'role:network-user'}", + "description": "policy for bar_user", + "type": "application/json" + } +} +` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = ` +{ + "policy": { + "description": "updated policy for bar_user" + } +} +` + +// UpdateOutput provides an update result. +const UpdateOutput = ` +{ + "policy": { + "type": "application/json", + "id": "b49884da9d31494ea02aff38d4b4e701", + "links": { + "self": "http://example.com/identity/v3/policies/b49884da9d31494ea02aff38d4b4e701" + }, + "blob": "{'bar_user': 'role:network-user'}", + "description": "updated policy for bar_user" + } +} +` + +// FirstPolicy is the first policy in the List request. +var FirstPolicy = policies.Policy{ + ID: "2844b2a08be147a08ef58317d6471f1f", + Blob: "'foo_user': 'role:compute-user'", + Type: "text/plain", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/policies/2844b2a08be147a08ef58317d6471f1f", + }, + Extra: map[string]interface{}{}, +} + +// SecondPolicy is the second policy in the List request. +var SecondPolicy = policies.Policy{ + ID: "b49884da9d31494ea02aff38d4b4e701", + Blob: "{'bar_user': 'role:network-user'}", + Type: "application/json", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/policies/b49884da9d31494ea02aff38d4b4e701", + }, + Extra: map[string]interface{}{ + "description": "policy for bar_user", + }, +} + +// SecondPolicyUpdated is the policy in the Update request. +var SecondPolicyUpdated = policies.Policy{ + ID: "b49884da9d31494ea02aff38d4b4e701", + Blob: "{'bar_user': 'role:network-user'}", + Type: "application/json", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/policies/b49884da9d31494ea02aff38d4b4e701", + }, + Extra: map[string]interface{}{ + "description": "updated policy for bar_user", + }, +} + +// ExpectedPoliciesSlice is the slice of policies expected to be returned from ListOutput. +var ExpectedPoliciesSlice = []policies.Policy{FirstPolicy, SecondPolicy} + +// HandleListPoliciesSuccessfully creates an HTTP handler at `/policies` on the +// test handler mux that responds with a list of two policies. +func HandleListPoliciesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + switch r.URL.Query().Get("type") { + case "": + fmt.Fprintf(w, ListOutput) + case "application/json": + fmt.Fprintf(w, ListWithFilterOutput) + default: + w.WriteHeader(http.StatusBadRequest) + } + }) +} + +// HandleCreatePolicySuccessfully creates an HTTP handler at `/policies` on the +// test handler mux that tests policy creation. +func HandleCreatePolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleGetPolicySuccessfully creates an HTTP handler at `/policies` on the +// test handler mux that responds with a single policy. +func HandleGetPolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/policies/b49884da9d31494ea02aff38d4b4e701", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetOutput) + }, + ) +} + +// HandleUpdatePolicySuccessfully creates an HTTP handler at `/policies` on the +// test handler mux that tests role update. +func HandleUpdatePolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/policies/b49884da9d31494ea02aff38d4b4e701", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, UpdateOutput) + }, + ) +} + +// HandleDeletePolicySuccessfully creates an HTTP handler at `/policies` on the +// test handler mux that tests policy deletion. +func HandleDeletePolicySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/policies/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/requests_test.go new file mode 100644 index 000000000000..6348fd57f172 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/testing/requests_test.go @@ -0,0 +1,229 @@ +package testing + +import ( + "fmt" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/policies" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListPolicies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListPoliciesSuccessfully(t) + + count := 0 + err := policies.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := policies.ExtractPolicies(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedPoliciesSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListPoliciesAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListPoliciesSuccessfully(t) + + allPages, err := policies.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedPoliciesSlice, actual) +} + +func TestListPoliciesWithFilter(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListPoliciesSuccessfully(t) + + listOpts := policies.ListOpts{ + Type: "application/json", + } + allPages, err := policies.List(client.ServiceClient(), listOpts).AllPages() + th.AssertNoErr(t, err) + actual, err := policies.ExtractPolicies(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, []policies.Policy{SecondPolicy}, actual) +} + +func TestListPoliciesFiltersCheck(t *testing.T) { + type test struct { + filterName string + wantErr bool + } + tests := []test{ + {"foo__contains", false}, + {"foo", true}, + {"foo_contains", true}, + {"foo__", true}, + {"__foo", true}, + } + + var listOpts policies.ListOpts + for _, _test := range tests { + listOpts.Filters = map[string]string{_test.filterName: "bar"} + _, err := listOpts.ToPolicyListQuery() + + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case policies.InvalidListFilter: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + +func TestCreatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreatePolicySuccessfully(t) + + createOpts := policies.CreateOpts{ + Type: "application/json", + Blob: []byte("{'bar_user': 'role:network-user'}"), + Extra: map[string]interface{}{ + "description": "policy for bar_user", + }, + } + + actual, err := policies.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondPolicy, *actual) +} + +func TestCreatePolicyTypeLengthCheck(t *testing.T) { + // strGenerator generates a string of fixed length filled with '0' + strGenerator := func(length int) string { + return fmt.Sprintf(fmt.Sprintf("%%0%dd", length), 0) + } + + type test struct { + length int + wantErr bool + } + + tests := []test{ + {100, false}, + {255, false}, + {256, true}, + {300, true}, + } + + createOpts := policies.CreateOpts{ + Blob: []byte("{'bar_user': 'role:network-user'}"), + } + + for _, _test := range tests { + createOpts.Type = strGenerator(_test.length) + if len(createOpts.Type) != _test.length { + t.Fatal("function strGenerator does not work properly") + } + + _, err := createOpts.ToPolicyCreateMap() + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case policies.StringFieldLengthExceedsLimit: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + +func TestGetPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetPolicySuccessfully(t) + + id := "b49884da9d31494ea02aff38d4b4e701" + actual, err := policies.Get(client.ServiceClient(), id).Extract() + + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondPolicy, *actual) +} + +func TestUpdatePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdatePolicySuccessfully(t) + + updateOpts := policies.UpdateOpts{ + Extra: map[string]interface{}{ + "description": "updated policy for bar_user", + }, + } + + id := "b49884da9d31494ea02aff38d4b4e701" + actual, err := policies.Update(client.ServiceClient(), id, updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondPolicyUpdated, *actual) +} + +func TestUpdatePolicyTypeLengthCheck(t *testing.T) { + // strGenerator generates a string of fixed length filled with '0' + strGenerator := func(length int) string { + return fmt.Sprintf(fmt.Sprintf("%%0%dd", length), 0) + } + + type test struct { + length int + wantErr bool + } + + tests := []test{ + {100, false}, + {255, false}, + {256, true}, + {300, true}, + } + + var updateOpts policies.UpdateOpts + for _, _test := range tests { + updateOpts.Type = strGenerator(_test.length) + if len(updateOpts.Type) != _test.length { + t.Fatal("function strGenerator does not work properly") + } + + _, err := updateOpts.ToPolicyUpdateMap() + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case policies.StringFieldLengthExceedsLimit: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + +func TestDeletePolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeletePolicySuccessfully(t) + + res := policies.Delete(client.ServiceClient(), "9fe1d3") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/urls.go new file mode 100644 index 000000000000..df0d183b438d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/policies/urls.go @@ -0,0 +1,25 @@ +package policies + +import "github.com/gophercloud/gophercloud" + +const policyPath = "policies" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(policyPath) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(policyPath) +} + +func getURL(client *gophercloud.ServiceClient, policyID string) string { + return client.ServiceURL(policyPath, policyID) +} + +func updateURL(client *gophercloud.ServiceClient, policyID string) string { + return client.ServiceURL(policyPath, policyID) +} + +func deleteURL(client *gophercloud.ServiceClient, policyID string) string { + return client.ServiceURL(policyPath, policyID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go new file mode 100644 index 000000000000..7be97d8594df --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go @@ -0,0 +1,17 @@ +package projects + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go index 368b7321ba7e..0e4616954115 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go @@ -1,6 +1,9 @@ package projects import ( + "net/url" + "strings" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -28,11 +31,30 @@ type ListOpts struct { // ParentID filters the response by projects of a given parent project. ParentID string `q:"parent_id"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` } // ToProjectListQuery formats a ListOpts into a query string. func (opts ListOpts) ToProjectListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} return q.String(), err } @@ -130,7 +152,7 @@ type UpdateOpts struct { ParentID string `json:"parent_id,omitempty"` // Description is the description of the project. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` } // ToUpdateCreateMap formats a UpdateOpts into an update request. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/testing/requests_test.go index 4b8af26a9c8b..746d654e1c30 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/testing/requests_test.go @@ -29,6 +29,38 @@ func TestListProjects(t *testing.T) { th.CheckEquals(t, count, 1) } +func TestListGroupsFiltersCheck(t *testing.T) { + type test struct { + filterName string + wantErr bool + } + tests := []test{ + {"foo__contains", false}, + {"foo", true}, + {"foo_contains", true}, + {"foo__", true}, + {"__foo", true}, + } + + var listOpts projects.ListOpts + for _, _test := range tests { + listOpts.Filters = map[string]string{_test.filterName: "bar"} + _, err := listOpts.ToProjectListQuery() + + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case projects.InvalidListFilter: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + func TestGetProject(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -68,9 +100,10 @@ func TestUpdateProject(t *testing.T) { defer th.TeardownHTTP() HandleUpdateProjectSuccessfully(t) + var description = "The team that is bright red" updateOpts := projects.UpdateOpts{ Name: "Bright Red Team", - Description: "The team that is bright red", + Description: &description, } actual, err := projects.Update(client.ServiceClient(), "1234", updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/requests.go index aa588fdff4a5..b5889ad3ec62 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/requests.go @@ -105,7 +105,7 @@ type UpdateOptsBuilder interface { // UpdateOpts provides options for updating a region. type UpdateOpts struct { // Description is a description of the region. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // ParentRegionID is the ID of the parent region. ParentRegionID string `json:"parent_region_id,omitempty"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/testing/requests_test.go index 7f5755704863..7210121c3beb 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/regions/testing/requests_test.go @@ -77,8 +77,9 @@ func TestUpdateRegion(t *testing.T) { defer th.TeardownHTTP() HandleUpdateRegionSuccessfully(t) + var description = "First West sub-region of RegionOne" updateOpts := regions.UpdateOpts{ - Description: "First West sub-region of RegionOne", + Description: &description, /* // Due to a bug in Keystone, the Extra column of the Region table // is not updatable, see: https://bugs.launchpad.net/keystone/+bug/1729933 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go index 2886a872d8b5..f0e4d045e961 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go @@ -79,6 +79,29 @@ Example to List Role Assignments fmt.Printf("%+v\n", role) } +Example to List Role Assignments for a User on a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + listAssignmentsOnResourceOpts := roles.ListAssignmentsOnResourceOpts{ + UserID: userID, + ProjectID: projectID, + } + + allPages, err := roles.ListAssignmentsOnResource(identityClient, listAssignmentsOnResourceOpts).AllPages() + if err != nil { + panic(err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + panic(err) + } + + for _, role := range allRoles { + fmt.Printf("%+v\n", role) + } + Example to Assign a Role to a User in a Project projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go new file mode 100644 index 000000000000..b60d7d18b611 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go @@ -0,0 +1,17 @@ +package roles + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go index 7908baa0e4dd..573db9ea201a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go @@ -1,6 +1,9 @@ package roles import ( + "net/url" + "strings" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -18,11 +21,30 @@ type ListOpts struct { // Name filters the response by role name. Name string `q:"name"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` } // ToRoleListQuery formats a ListOpts into a query string. func (opts ListOpts) ToRoleListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} return q.String(), err } @@ -201,6 +223,26 @@ func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOpts }) } +// ListAssignmentsOnResourceOpts provides options to list role assignments +// for a user/group on a project/domain +type ListAssignmentsOnResourceOpts struct { + // UserID is the ID of a user to assign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to assign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + // AssignOpts provides options to assign a role type AssignOpts struct { // UserID is the ID of a user to assign a role @@ -239,6 +281,42 @@ type UnassignOpts struct { DomainID string `xor:"ProjectID"` } +// ListAssignmentsOnResource is the operation responsible for listing role +// assignments for a user/group on a project/domain. +func ListAssignmentsOnResource(client *gophercloud.ServiceClient, opts ListAssignmentsOnResourceOpts) pagination.Pager { + // Check xor conditions + _, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return pagination.Pager{Err: err} + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + url := listAssignmentsOnResourceURL(client, targetType, targetID, actorType, actorID) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RolePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + // Assign is the operation responsible for assigning a role // to a user/group on a project/domain. func Assign(client *gophercloud.ServiceClient, roleID string, opts AssignOpts) (r AssignmentResult) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go index fa73b11ee07e..9bcc2c7d07e0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go @@ -141,6 +141,29 @@ const ListAssignmentOutput = ` } ` +// ListAssignmentsOnResourceOutput provides a result of ListAssignmentsOnResource request. +const ListAssignmentsOnResourceOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/projects/9e5a15/users/b964a9/roles" + }, + "roles": [ + { + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/roles/9fe1d3" + }, + "name": "support", + "extra": { + "description": "read-only support role" + } + } + ] +} +` + // FirstRole is the first role in the List request. var FirstRole = roles.Role{ DomainID: "default", @@ -331,3 +354,75 @@ func HandleListRoleAssignmentsSuccessfully(t *testing.T) { fmt.Fprintf(w, ListAssignmentOutput) }) } + +// RoleOnResource is the role in the ListAssignmentsOnResource request. +var RoleOnResource = roles.Role{ + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/roles/9fe1d3", + }, + Name: "support", + Extra: map[string]interface{}{ + "description": "read-only support role", + }, +} + +// ExpectedRolesOnResourceSlice is the slice of roles expected to be returned +// from ListAssignmentsOnResourceOutput. +var ExpectedRolesOnResourceSlice = []roles.Role{RoleOnResource} + +func HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t *testing.T) { + fn := func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + } + + th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles", fn) +} + +func HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t *testing.T) { + fn := func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + } + + th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles", fn) +} + +func HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t *testing.T) { + fn := func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + } + + th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles", fn) +} + +func HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t *testing.T) { + fn := func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + } + + th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles", fn) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go index c683197bfa98..c8ac5a9b0321 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go @@ -42,6 +42,38 @@ func TestListRolesAllPages(t *testing.T) { th.AssertEquals(t, ExpectedRolesSlice[1].Extra["description"], "read-only support role") } +func TestListUsersFiltersCheck(t *testing.T) { + type test struct { + filterName string + wantErr bool + } + tests := []test{ + {"foo__contains", false}, + {"foo", true}, + {"foo_contains", true}, + {"foo__", true}, + {"__foo", true}, + } + + var listOpts roles.ListOpts + for _, _test := range tests { + listOpts.Filters = map[string]string{_test.filterName: "bar"} + _, err := listOpts.ToRoleListQuery() + + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case roles.InvalidListFilter: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + func TestGetRole(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -115,6 +147,94 @@ func TestListAssignmentsSinglePage(t *testing.T) { th.CheckEquals(t, count, 1) } +func TestListAssignmentsOnResource_ProjectsUsers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t) + + count := 0 + err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + UserID: "{user_id}", + ProjectID: "{project_id}", + }).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := roles.ExtractRoles(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRolesOnResourceSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListAssignmentsOnResource_DomainsUsers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t) + + count := 0 + err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + UserID: "{user_id}", + DomainID: "{domain_id}", + }).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := roles.ExtractRoles(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRolesOnResourceSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListAssignmentsOnResource_ProjectsGroups(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t) + + count := 0 + err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + GroupID: "{group_id}", + ProjectID: "{project_id}", + }).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := roles.ExtractRoles(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRolesOnResourceSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListAssignmentsOnResource_DomainsGroups(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t) + + count := 0 + err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + GroupID: "{group_id}", + DomainID: "{domain_id}", + }).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := roles.ExtractRoles(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRolesOnResourceSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + func TestAssign(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go index 38d592dca6a5..2b82011424b0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go @@ -30,6 +30,10 @@ func listAssignmentsURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("role_assignments") } +func listAssignmentsOnResourceURL(client *gophercloud.ServiceClient, targetType, targetID, actorType, actorID string) string { + return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath) +} + func assignURL(client *gophercloud.ServiceClient, targetType, targetID, actorType, actorID, roleID string) string { return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath, roleID) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go index ca35851e4a4e..e4d766b2327e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go @@ -52,19 +52,28 @@ type AuthOptions struct { // authentication token ID. TokenID string `json:"-"` + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` + Scope Scope `json:"-"` } // ToTokenV3CreateMap builds a request body from AuthOptions. func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { gophercloudAuthOpts := gophercloud.AuthOptions{ - Username: opts.Username, - UserID: opts.UserID, - Password: opts.Password, - DomainID: opts.DomainID, - DomainName: opts.DomainName, - AllowReauth: opts.AllowReauth, - TokenID: opts.TokenID, + Username: opts.Username, + UserID: opts.UserID, + Password: opts.Password, + DomainID: opts.DomainID, + DomainName: opts.DomainName, + AllowReauth: opts.AllowReauth, + TokenID: opts.TokenID, + ApplicationCredentialID: opts.ApplicationCredentialID, + ApplicationCredentialName: opts.ApplicationCredentialName, + ApplicationCredentialSecret: opts.ApplicationCredentialSecret, } return gophercloudAuthOpts.ToTokenV3CreateMap(scope) @@ -72,72 +81,15 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s // ToTokenV3CreateMap builds a scope request body from AuthOptions. func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - if opts.Scope.ProjectName != "" { - // ProjectName provided: either DomainID or DomainName must also be supplied. - // ProjectID may not be supplied. - if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { - return nil, gophercloud.ErrScopeDomainIDOrDomainName{} - } - if opts.Scope.ProjectID != "" { - return nil, gophercloud.ErrScopeProjectIDOrProjectName{} - } - - if opts.Scope.DomainID != "" { - // ProjectName + DomainID - return map[string]interface{}{ - "project": map[string]interface{}{ - "name": &opts.Scope.ProjectName, - "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, - }, - }, nil - } - - if opts.Scope.DomainName != "" { - // ProjectName + DomainName - return map[string]interface{}{ - "project": map[string]interface{}{ - "name": &opts.Scope.ProjectName, - "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, - }, - }, nil - } - } else if opts.Scope.ProjectID != "" { - // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. - if opts.Scope.DomainID != "" { - return nil, gophercloud.ErrScopeProjectIDAlone{} - } - if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeProjectIDAlone{} - } - - // ProjectID - return map[string]interface{}{ - "project": map[string]interface{}{ - "id": &opts.Scope.ProjectID, - }, - }, nil - } else if opts.Scope.DomainID != "" { - // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. - if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeDomainIDOrDomainName{} - } - - // DomainID - return map[string]interface{}{ - "domain": map[string]interface{}{ - "id": &opts.Scope.DomainID, - }, - }, nil - } else if opts.Scope.DomainName != "" { - // DomainName - return map[string]interface{}{ - "domain": map[string]interface{}{ - "name": &opts.Scope.DomainName, - }, - }, nil + scope := gophercloud.AuthScope(opts.Scope) + + gophercloudAuthOpts := gophercloud.AuthOptions{ + Scope: &scope, + DomainID: opts.DomainID, + DomainName: opts.DomainName, } - return nil, nil + return gophercloudAuthOpts.ToTokenV3ScopeMap() } func (opts *AuthOptions) CanReauth() bool { @@ -182,15 +134,15 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { OkCodes: []int{200, 203}, }) if resp != nil { - r.Err = err r.Header = resp.Header } + r.Err = err return } // Validate determines if a specified token is valid or not. func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { - resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{ + resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{ MoreHeaders: subjectTokenHeaders(c, token), OkCodes: []int{200, 204, 404}, }) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go index ebdca58f658f..6f26c96bcdc9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go @@ -102,6 +102,13 @@ func (r commonResult) ExtractToken() (*Token, error) { return &s, err } +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + return r.Header.Get("X-Subject-Token"), r.Err +} + // ExtractServiceCatalog returns the ServiceCatalog that was generated along // with the user's Token. func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/testing/requests_test.go index 3891ae4ab544..8dc79398410f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/testing/requests_test.go @@ -275,6 +275,48 @@ func TestCreateProjectNameAndDomainNameScope(t *testing.T) { `) } +func TestCreateApplicationCredentialIDAndSecret(t *testing.T) { + authTokenPost(t, tokens.AuthOptions{ApplicationCredentialID: "12345abcdef", ApplicationCredentialSecret: "mysecret"}, nil, ` + { + "auth": { + "identity": { + "application_credential": { + "id": "12345abcdef", + "secret": "mysecret" + }, + "methods": [ + "application_credential" + ] + } + } + } + `) +} + +func TestCreateApplicationCredentialNameAndSecret(t *testing.T) { + authTokenPost(t, tokens.AuthOptions{ApplicationCredentialName: "myappcred", ApplicationCredentialSecret: "mysecret", Username: "fenris", DomainName: "evil-plans"}, nil, ` + { + "auth": { + "identity": { + "application_credential": { + "name": "myappcred", + "secret": "mysecret", + "user": { + "name": "fenris", + "domain": { + "name": "evil-plans" + } + } + }, + "methods": [ + "application_credential" + ] + } + } + } + `) +} + func TestCreateExtractsTokenFromResponse(t *testing.T) { testhelper.SetupHTTP() defer testhelper.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go index aa7ec196f5a4..994ce71bcb92 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go @@ -54,6 +54,22 @@ Example to Update a User panic(err) } +Example to Change Password of a User + + userID := "0fe36e73809d46aeae6705c39077b1b3" + originalPassword := "secretsecret" + password := "new_secretsecret" + + changePasswordOpts := users.ChangePasswordOpts{ + OriginalPassword: originalPassword, + Password: password, + } + + err := users.ChangePassword(identityClient, userID, changePasswordOpts).ExtractErr() + if err != nil { + panic(err) + } + Example to Delete a User userID := "0fe36e73809d46aeae6705c39077b1b3" @@ -80,6 +96,39 @@ Example to List Groups a User Belongs To fmt.Printf("%+v\n", group) } +Example to Add a User to a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + err := users.AddToGroup(identityClient, groupID, userID).ExtractErr() + + if err != nil { + panic(err) + } + +Example to Check Whether a User Belongs to a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + ok, err := users.IsMemberOfGroup(identityClient, groupID, userID).Extract() + if err != nil { + panic(err) + } + + if ok { + fmt.Printf("user %s is a member of group %s\n", userID, groupID) + } + +Example to Remove a User from a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + err := users.RemoveFromGroup(identityClient, groupID, userID).ExtractErr() + + if err != nil { + panic(err) + } + Example to List Projects a User Belongs To userID := "0fe36e73809d46aeae6705c39077b1b3" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go new file mode 100644 index 000000000000..0f0b79875497 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go @@ -0,0 +1,17 @@ +package users + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go index 779d116fccd5..7a40ec762d67 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go @@ -1,6 +1,10 @@ package users import ( + "net/http" + "net/url" + "strings" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" @@ -47,11 +51,30 @@ type ListOpts struct { // UniqueID filters the response by unique ID. UniqueID string `q:"unique_id"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` } // ToUserListQuery formats a ListOpts into a query string. func (opts ListOpts) ToUserListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} return q.String(), err } @@ -155,7 +178,7 @@ type UpdateOpts struct { DefaultProjectID string `json:"default_project_id,omitempty"` // Description is a description of the user. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // DomainID is the ID of the domain the user belongs to. DomainID string `json:"domain_id,omitempty"` @@ -204,6 +227,45 @@ func Update(client *gophercloud.ServiceClient, userID string, opts UpdateOptsBui return } +// ChangePasswordOptsBuilder allows extensions to add additional parameters to +// the ChangePassword request. +type ChangePasswordOptsBuilder interface { + ToUserChangePasswordMap() (map[string]interface{}, error) +} + +// ChangePasswordOpts provides options for changing password for a user. +type ChangePasswordOpts struct { + // OriginalPassword is the original password of the user. + OriginalPassword string `json:"original_password"` + + // Password is the new password of the user. + Password string `json:"password"` +} + +// ToUserChangePasswordMap formats a ChangePasswordOpts into a ChangePassword request. +func (opts ChangePasswordOpts) ToUserChangePasswordMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "user") + if err != nil { + return nil, err + } + + return b, nil +} + +// ChangePassword changes password for a user. +func ChangePassword(client *gophercloud.ServiceClient, userID string, opts ChangePasswordOptsBuilder) (r ChangePasswordResult) { + b, err := opts.ToUserChangePasswordMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(changePasswordURL(client, userID), &b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + // Delete deletes a user. func Delete(client *gophercloud.ServiceClient, userID string) (r DeleteResult) { _, r.Err = client.Delete(deleteURL(client, userID), nil) @@ -218,6 +280,40 @@ func ListGroups(client *gophercloud.ServiceClient, userID string) pagination.Pag }) } +// AddToGroup adds a user to a group. +func AddToGroup(client *gophercloud.ServiceClient, groupID, userID string) (r AddToGroupResult) { + url := addToGroupURL(client, groupID, userID) + _, r.Err = client.Put(url, nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// IsMemberOfGroup checks whether a user belongs to a group. +func IsMemberOfGroup(client *gophercloud.ServiceClient, groupID, userID string) (r IsMemberOfGroupResult) { + url := isMemberOfGroupURL(client, groupID, userID) + var response *http.Response + response, r.Err = client.Head(url, &gophercloud.RequestOpts{ + OkCodes: []int{204, 404}, + }) + if r.Err == nil && response != nil { + if (*response).StatusCode == 204 { + r.isMember = true + } + } + + return +} + +// RemoveFromGroup removes a user from a group. +func RemoveFromGroup(client *gophercloud.ServiceClient, groupID, userID string) (r RemoveFromGroupResult) { + url := removeFromGroupURL(client, groupID, userID) + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + // ListProjects enumerates groups user belongs to. func ListProjects(client *gophercloud.ServiceClient, userID string) pagination.Pager { url := listProjectsURL(client, userID) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go index c474e882b90b..e158ac723bb5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go @@ -98,12 +98,37 @@ type UpdateResult struct { userResult } +// ChangePasswordResult is the response from a ChangePassword operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type ChangePasswordResult struct { + gophercloud.ErrResult +} + // DeleteResult is the response from a Delete operation. Call its ExtractErr to // determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } +// AddToGroupResult is the response from a AddToGroup operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type AddToGroupResult struct { + gophercloud.ErrResult +} + +// IsMemberOfGroupResult is the response from a IsMemberOfGroup operation. Call its +// Extract method to determine if the request succeeded or failed. +type IsMemberOfGroupResult struct { + isMember bool + gophercloud.Result +} + +// RemoveFromGroupResult is the response from a RemoveFromGroup operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type RemoveFromGroupResult struct { + gophercloud.ErrResult +} + // UserPage is a single page of User results. type UserPage struct { pagination.LinkedPageBase @@ -147,3 +172,8 @@ func (r userResult) Extract() (*User, error) { err := r.ExtractInto(&s) return s.User, err } + +// Extract extracts IsMemberOfGroupResult as bool and error values +func (r IsMemberOfGroupResult) Extract() (bool, error) { + return r.isMember, r.Err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go index 8d8e6df64202..d68e1ac2a41c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go @@ -129,7 +129,7 @@ const CreateNoOptionsRequest = ` } ` -// UpdateRequest provides the input to as Update request. +// UpdateRequest provides the input to an Update request. const UpdateRequest = ` { "user": { @@ -164,6 +164,16 @@ const UpdateOutput = ` } ` +// ChangePasswordRequest provides the input to a ChangePassword request. +const ChangePasswordRequest = ` +{ + "user": { + "password": "new_secretsecret", + "original_password": "secretsecret" + } +} +` + // ListGroupsOutput provides a ListGroups result. const ListGroupsOutput = ` { @@ -423,6 +433,18 @@ func HandleUpdateUserSuccessfully(t *testing.T) { }) } +// HandleChangeUserPasswordSuccessfully creates an HTTP handler at `/users` on the +// test handler mux that tests change user password. +func HandleChangeUserPasswordSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/9fe1d3/password", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ChangePasswordRequest) + + w.WriteHeader(http.StatusNoContent) + }) +} + // HandleDeleteUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests user deletion. func HandleDeleteUserSuccessfully(t *testing.T) { @@ -448,6 +470,39 @@ func HandleListUserGroupsSuccessfully(t *testing.T) { }) } +// HandleAddToGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users/{userID} +// on the test handler mux that tests adding user to group. +func HandleAddToGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleIsMemberOfGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users/{userID} +// on the test handler mux that tests checking whether user belongs to group. +func HandleIsMemberOfGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleRemoveFromGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users/{userID} +// on the test handler mux that tests removing user from group. +func HandleRemoveFromGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + // HandleListUserProjectsSuccessfully creates an HTTP handler at /users/{userID}/projects // on the test handler mux that respons wit a list of two projects func HandleListUserProjectsSuccessfully(t *testing.T) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go index 15314ca61c9b..3eb1b46f5b3f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go @@ -45,6 +45,38 @@ func TestListUsersAllPages(t *testing.T) { th.AssertEquals(t, ExpectedUsersSlice[1].Extra["email"], "jsmith@example.com") } +func TestListUsersFiltersCheck(t *testing.T) { + type test struct { + filterName string + wantErr bool + } + tests := []test{ + {"foo__contains", false}, + {"foo", true}, + {"foo_contains", true}, + {"foo__", true}, + {"__foo", true}, + } + + var listOpts users.ListOpts + for _, _test := range tests { + listOpts.Filters = map[string]string{_test.filterName: "bar"} + _, err := listOpts.ToUserListQuery() + + if !_test.wantErr { + th.AssertNoErr(t, err) + } else { + switch _t := err.(type) { + case nil: + t.Fatal("error expected but got a nil") + case users.InvalidListFilter: + default: + t.Fatalf("unexpected error type: [%T]", _t) + } + } + } +} + func TestGetUser(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -128,6 +160,20 @@ func TestUpdateUser(t *testing.T) { th.CheckDeepEquals(t, SecondUserUpdated, *actual) } +func TestChangeUserPassword(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleChangeUserPasswordSuccessfully(t) + + changePasswordOpts := users.ChangePasswordOpts{ + OriginalPassword: "secretsecret", + Password: "new_secretsecret", + } + + res := users.ChangePassword(client.ServiceClient(), "9fe1d3", changePasswordOpts) + th.AssertNoErr(t, res.Err) +} + func TestDeleteUser(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -148,6 +194,31 @@ func TestListUserGroups(t *testing.T) { th.CheckDeepEquals(t, ExpectedGroupsSlice, actual) } +func TestAddToGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAddToGroupSuccessfully(t) + res := users.AddToGroup(client.ServiceClient(), "ea167b", "9fe1d3") + th.AssertNoErr(t, res.Err) +} + +func TestIsMemberOfGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleIsMemberOfGroupSuccessfully(t) + ok, err := users.IsMemberOfGroup(client.ServiceClient(), "ea167b", "9fe1d3").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, true, ok) +} + +func TestRemoveFromGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRemoveFromGroupSuccessfully(t) + res := users.RemoveFromGroup(client.ServiceClient(), "ea167b", "9fe1d3") + th.AssertNoErr(t, res.Err) +} + func TestListUserProjects(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go index 1db2831b5edf..3caa8bbb6ca9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go @@ -18,6 +18,10 @@ func updateURL(client *gophercloud.ServiceClient, userID string) string { return client.ServiceURL("users", userID) } +func changePasswordURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "password") +} + func deleteURL(client *gophercloud.ServiceClient, userID string) string { return client.ServiceURL("users", userID) } @@ -26,6 +30,18 @@ func listGroupsURL(client *gophercloud.ServiceClient, userID string) string { return client.ServiceURL("users", userID, "groups") } +func addToGroupURL(client *gophercloud.ServiceClient, groupID, userID string) string { + return client.ServiceURL("groups", groupID, "users", userID) +} + +func isMemberOfGroupURL(client *gophercloud.ServiceClient, groupID, userID string) string { + return client.ServiceURL("groups", groupID, "users", userID) +} + +func removeFromGroupURL(client *gophercloud.ServiceClient, groupID, userID string) string { + return client.ServiceURL("groups", groupID, "users", userID) +} + func listProjectsURL(client *gophercloud.ServiceClient, userID string) string { return client.ServiceURL("users", userID, "projects") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go index a2f5e58b89ad..0c12bf2e0779 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go @@ -16,6 +16,21 @@ Example to Upload Image Data panic(err) } +Example to Stage Image Data + + imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea" + + imageData, err := os.Open("/path/to/image/file") + if err != nil { + panic(err) + } + defer imageData.Close() + + err = imagedata.Stage(imageClient, imageID, imageData).ExtractErr() + if err != nil { + panic(err) + } + Example to Download Image Data imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go index 4761e488c6bc..545c561f366c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go @@ -16,6 +16,17 @@ func Upload(client *gophercloud.ServiceClient, id string, data io.Reader) (r Upl return } +// Stage performs PUT call on the existing image object in the Imageservice with +// the provided file. +// Existing image object must be in the "queued" status. +func Stage(client *gophercloud.ServiceClient, id string, data io.Reader) (r StageResult) { + _, r.Err = client.Put(stageURL(client, id), data, nil, &gophercloud.RequestOpts{ + MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"}, + OkCodes: []int{204}, + }) + return +} + // Download retrieves an image. func Download(client *gophercloud.ServiceClient, id string) (r DownloadResult) { var resp *http.Response diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go index 895d28ba8ae4..17a3f0e0f7e2 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go @@ -13,6 +13,12 @@ type UploadResult struct { gophercloud.ErrResult } +// StageResult is the result of a stage image operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type StageResult struct { + gophercloud.ErrResult +} + // DownloadResult is the result of a download image operation. Call its Extract // method to gain access to the image data. type DownloadResult struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/fixtures.go index fe93fc9730be..64c44bdf6fc9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/fixtures.go @@ -26,6 +26,23 @@ func HandlePutImageDataSuccessfully(t *testing.T) { }) } +// HandleStageImageDataSuccessfully setup +func HandleStageImageDataSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/stage", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Unable to read request body: %v", err) + } + + th.AssertByteArrayEquals(t, []byte{5, 3, 7, 24}, b) + + w.WriteHeader(http.StatusNoContent) + }) +} + // HandleGetImageDataSuccessfully setup func HandleGetImageDataSuccessfully(t *testing.T) { th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", func(w http.ResponseWriter, r *http.Request) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/requests_test.go index 4ac42d0e73b1..7155f61b6353 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/testing/requests_test.go @@ -25,6 +25,20 @@ func TestUpload(t *testing.T) { th.AssertNoErr(t, err) } +func TestStage(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleStageImageDataSuccessfully(t) + + err := imagedata.Stage( + fakeclient.ServiceClient(), + "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + readSeekerOfBytes([]byte{5, 3, 7, 24})).ExtractErr() + + th.AssertNoErr(t, err) +} + func readSeekerOfBytes(bs []byte) io.ReadSeeker { return &RS{bs: bs} } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go index ccd6416e53ea..d9615ba7fb8b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go @@ -2,10 +2,20 @@ package imagedata import "github.com/gophercloud/gophercloud" +const ( + rootPath = "images" + uploadPath = "file" + stagePath = "stage" +) + // `imageDataURL(c,i)` is the URL for the binary image data for the // image identified by ID `i` in the service `c`. func uploadURL(c *gophercloud.ServiceClient, imageID string) string { - return c.ServiceURL("images", imageID, "file") + return c.ServiceURL(rootPath, imageID, uploadPath) +} + +func stageURL(c *gophercloud.ServiceClient, imageID string) string { + return c.ServiceURL(rootPath, imageID, stagePath) } func downloadURL(c *gophercloud.ServiceClient, imageID string) string { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/doc.go new file mode 100644 index 000000000000..16f8ce32f5d4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/doc.go @@ -0,0 +1,27 @@ +/* +Package imageimport enables management of images import and retrieval of the +Imageservice Import API information. + +Example to Get an information about the Import API + + importInfo, err := imageimport.Get(imagesClient).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", importInfo) + +Example to Create a new image import + + opts := imageimport.CreateOpts{ + Name: imageimport.WebDownloadMethod, + URI: "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img", + } + imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea" + + err := imageimport.Create(imagesClient, imageID, opts).ExtractErr() + if err != nil { + panic(err) + } +*/ +package imageimport diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/requests.go new file mode 100644 index 000000000000..38920051b3b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/requests.go @@ -0,0 +1,53 @@ +package imageimport + +import "github.com/gophercloud/gophercloud" + +// ImportMethod represents valid Import API method. +type ImportMethod string + +const ( + // GlanceDirectMethod represents glance-direct Import API method. + GlanceDirectMethod ImportMethod = "glance-direct" + + // WebDownloadMethod represents web-download Import API method. + WebDownloadMethod ImportMethod = "web-download" +) + +// Get retrieves Import API information data. +func Get(c *gophercloud.ServiceClient) (r GetResult) { + _, r.Err = c.Get(infoURL(c), &r.Body, nil) + return +} + +// CreateOptsBuilder allows to add additional parameters to the Create request. +type CreateOptsBuilder interface { + ToImportCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters of a new image import. +type CreateOpts struct { + Name ImportMethod `json:"name"` + URI string `json:"uri"` +} + +// ToImportCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToImportCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return map[string]interface{}{"method": b}, nil +} + +// Create requests the creation of a new image import on the server. +func Create(client *gophercloud.ServiceClient, imageID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToImportCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(importURL(client, imageID), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/results.go new file mode 100644 index 000000000000..2158c20da699 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/results.go @@ -0,0 +1,38 @@ +package imageimport + +import "github.com/gophercloud/gophercloud" + +type commonResult struct { + gophercloud.Result +} + +// GetResult represents the result of a get operation. Call its Extract method +// to interpret it as ImportInfo. +type GetResult struct { + commonResult +} + +// CreateResult is the result of import Create operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type CreateResult struct { + gophercloud.ErrResult +} + +// ImportInfo represents information data for the Import API. +type ImportInfo struct { + ImportMethods ImportMethods `json:"import-methods"` +} + +// ImportMethods contains information about available Import API methods. +type ImportMethods struct { + Description string `json:"description"` + Type string `json:"type"` + Value []string `json:"value"` +} + +// Extract is a function that accepts a result and extracts ImportInfo. +func (r commonResult) Extract() (*ImportInfo, error) { + var s *ImportInfo + err := r.ExtractInto(&s) + return s, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/fixtures.go new file mode 100644 index 000000000000..f934d45762ec --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/fixtures.go @@ -0,0 +1,25 @@ +package testing + +// ImportGetResult represents raw server response on a Get request. +const ImportGetResult = ` +{ + "import-methods": { + "description": "Import methods available.", + "type": "array", + "value": [ + "glance-direct", + "web-download" + ] + } +} +` + +// ImportCreateRequest represents a request to create image import. +const ImportCreateRequest = ` +{ + "method": { + "name": "web-download", + "uri": "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/requests_test.go new file mode 100644 index 000000000000..d0c98df698b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/testing/requests_test.go @@ -0,0 +1,60 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport" + th "github.com/gophercloud/gophercloud/testhelper" + fakeclient "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/info/import", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ImportGetResult) + }) + + validImportMethods := []string{ + string(imageimport.GlanceDirectMethod), + string(imageimport.WebDownloadMethod), + } + + s, err := imageimport.Get(fakeclient.ServiceClient()).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.ImportMethods.Description, "Import methods available.") + th.AssertEquals(t, s.ImportMethods.Type, "array") + th.AssertDeepEquals(t, s.ImportMethods.Value, validImportMethods) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/import", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestJSONRequest(t, r, ImportCreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + fmt.Fprintf(w, `{}`) + }) + + opts := imageimport.CreateOpts{ + Name: imageimport.WebDownloadMethod, + URI: "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img", + } + err := imageimport.Create(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", opts).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/urls.go new file mode 100644 index 000000000000..20310eb09305 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport/urls.go @@ -0,0 +1,17 @@ +package imageimport + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "images" + infoPath = "info" + resourcePath = "import" +) + +func infoURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(infoPath, resourcePath) +} + +func importURL(c *gophercloud.ServiceClient, imageID string) string { + return c.ServiceURL(rootPath, imageID, resourcePath) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go index 88cd4d265ee7..4e487ea9e6df 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go @@ -129,7 +129,12 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url += query } return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - return ImagePage{pagination.LinkedPageBase{PageResult: r}} + imagePage := ImagePage{ + serviceURL: c.ServiceURL(), + LinkedPageBase: pagination.LinkedPageBase{PageResult: r}, + } + + return imagePage }) } @@ -266,11 +271,11 @@ type UpdateVisibility struct { } // ToImagePatchMap assembles a request body based on UpdateVisibility. -func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} { +func (r UpdateVisibility) ToImagePatchMap() map[string]interface{} { return map[string]interface{}{ "op": "replace", "path": "/visibility", - "value": u.Visibility, + "value": r.Visibility, } } @@ -294,11 +299,11 @@ type ReplaceImageChecksum struct { } // ReplaceImageChecksum assembles a request body based on ReplaceImageChecksum. -func (rc ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} { +func (r ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} { return map[string]interface{}{ "op": "replace", "path": "/checksum", - "value": rc.Checksum, + "value": r.Checksum, } } @@ -315,3 +320,47 @@ func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} { "value": r.NewTags, } } + +// ReplaceImageMinDisk represents an updated min_disk property request. +type ReplaceImageMinDisk struct { + NewMinDisk int +} + +// ToImagePatchMap assembles a request body based on ReplaceImageTags. +func (r ReplaceImageMinDisk) ToImagePatchMap() map[string]interface{} { + return map[string]interface{}{ + "op": "replace", + "path": "/min_disk", + "value": r.NewMinDisk, + } +} + +// UpdateOp represents a valid update operation. +type UpdateOp string + +const ( + AddOp UpdateOp = "add" + ReplaceOp UpdateOp = "replace" + RemoveOp UpdateOp = "remove" +) + +// UpdateImageProperty represents an update property request. +type UpdateImageProperty struct { + Op UpdateOp + Name string + Value string +} + +// ToImagePatchMap assembles a request body based on UpdateImageProperty. +func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} { + updateMap := map[string]interface{}{ + "op": r.Op, + "path": fmt.Sprintf("/%s", r.Name), + } + + if r.Value != "" { + updateMap["value"] = r.Value + } + + return updateMap +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go index cd819ec9c820..676181e1f4e5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go @@ -57,7 +57,7 @@ type Image struct { Checksum string `json:"checksum"` // SizeBytes is the size of the data that's associated with the image. - SizeBytes int64 `json:"size"` + SizeBytes int64 `json:"-"` // Metadata is a set of metadata associated with the image. // Image metadata allow for meaningfully define the image properties @@ -67,7 +67,7 @@ type Image struct { // Properties is a set of key-value pairs, if any, that are associated with // the image. - Properties map[string]interface{} `json:"-"` + Properties map[string]interface{} // CreatedAt is the date when the image has been created. CreatedAt time.Time `json:"created_at"` @@ -102,7 +102,7 @@ func (r *Image) UnmarshalJSON(b []byte) error { switch t := s.SizeBytes.(type) { case nil: - return nil + r.SizeBytes = 0 case float32: r.SizeBytes = int64(t) case float64: @@ -119,6 +119,7 @@ func (r *Image) UnmarshalJSON(b []byte) error { } if resultMap, ok := result.(map[string]interface{}); ok { delete(resultMap, "self") + delete(resultMap, "size") r.Properties = internal.RemainingKeys(Image{}, resultMap) } @@ -162,6 +163,7 @@ type DeleteResult struct { // ImagePage represents the results of a List request. type ImagePage struct { + serviceURL string pagination.LinkedPageBase } @@ -186,7 +188,7 @@ func (r ImagePage) NextPageURL() (string, error) { return "", nil } - return nextPageURL(r.URL.String(), s.Next) + return nextPageURL(r.serviceURL, s.Next) } // ExtractImages interprets the results of a single page from a List() call, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/fixtures.go index 29757d203b8c..0ff03be95c47 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/fixtures.go @@ -212,6 +212,7 @@ func HandleImageCreationSuccessfullyNulls(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) th.TestJSONRequest(t, r, `{ "id": "e7db3b45-8db7-47ad-8109-3fb55c2c24fd", + "architecture": "x86_64", "name": "Ubuntu 12.10", "tags": [ "ubuntu", @@ -222,6 +223,7 @@ func HandleImageCreationSuccessfullyNulls(t *testing.T) { w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") fmt.Fprintf(w, `{ + "architecture": "x86_64", "status": "queued", "name": "Ubuntu 12.10", "protected": false, @@ -310,6 +312,11 @@ func HandleImageUpdateSuccessfully(t *testing.T) { "fedora", "beefy" ] + }, + { + "op": "replace", + "path": "/min_disk", + "value": 21 } ]`) @@ -335,7 +342,7 @@ func HandleImageUpdateSuccessfully(t *testing.T) { "schema": "/v2/schemas/image", "owner": "", "min_ram": 0, - "min_disk": 0, + "min_disk": 21, "disk_format": "", "virtual_size": 0, "container_format": "", @@ -386,3 +393,60 @@ func HandleImageListByTagsSuccessfully(t *testing.T) { }`) }) } + +// HandleImageUpdatePropertiesSuccessfully setup +func HandleImageUpdatePropertiesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + th.TestJSONRequest(t, r, `[ + { + "op": "add", + "path": "/hw_disk_bus", + "value": "scsi" + }, + { + "op": "add", + "path": "/hw_disk_bus_model", + "value": "virtio-scsi" + }, + { + "op": "add", + "path": "/hw_scsi_model", + "value": "virtio-scsi" + } + ]`) + + th.AssertEquals(t, "application/openstack-images-v2.1-json-patch", r.Header.Get("Content-Type")) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, `{ + "id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + "name": "Fedora 17", + "status": "active", + "visibility": "public", + "size": 2254249, + "checksum": "2cec138d7dae2aa59038ef8c9aec2390", + "tags": [ + "fedora", + "beefy" + ], + "created_at": "2012-08-10T19:23:50Z", + "updated_at": "2012-08-12T11:11:33Z", + "self": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + "file": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", + "schema": "/v2/schemas/image", + "owner": "", + "min_ram": 0, + "min_disk": 0, + "disk_format": "", + "virtual_size": 0, + "container_format": "", + "hw_disk_bus": "scsi", + "hw_disk_bus_model": "virtio-scsi", + "hw_scsi_model": "virtio-scsi" + }`) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/requests_test.go index 487247b110f7..86126c76d91e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/testing/requests_test.go @@ -16,8 +16,6 @@ func TestListImage(t *testing.T) { HandleImageListSuccessfully(t) - t.Logf("Test setup %+v\n", th.Server) - t.Logf("Id\tName\tOwner\tChecksum\tSizeBytes") pager := images.List(fakeclient.ServiceClient(), images.ListOpts{Limit: 1}) @@ -132,6 +130,9 @@ func TestCreateImageNulls(t *testing.T) { ID: id, Name: name, Tags: []string{"ubuntu", "quantal"}, + Properties: map[string]string{ + "architecture": "x86_64", + }, }).Extract() th.AssertNoErr(t, err) @@ -145,6 +146,10 @@ func TestCreateImageNulls(t *testing.T) { createdDate := actualImage.CreatedAt lastUpdate := actualImage.UpdatedAt schema := "/v2/schemas/image" + properties := map[string]interface{}{ + "architecture": "x86_64", + } + sizeBytes := int64(0) expectedImage := images.Image{ ID: "e7db3b45-8db7-47ad-8109-3fb55c2c24fd", @@ -166,6 +171,8 @@ func TestCreateImageNulls(t *testing.T) { CreatedAt: createdDate, UpdatedAt: lastUpdate, Schema: schema, + Properties: properties, + SizeBytes: sizeBytes, } th.AssertDeepEquals(t, &expectedImage, actualImage) @@ -247,6 +254,7 @@ func TestUpdateImage(t *testing.T) { actualImage, err := images.Update(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", images.UpdateOpts{ images.ReplaceImageName{NewName: "Fedora 17"}, images.ReplaceImageTags{NewTags: []string{"fedora", "beefy"}}, + images.ReplaceImageMinDisk{NewMinDisk: 21}, }).Extract() th.AssertNoErr(t, err) @@ -274,7 +282,7 @@ func TestUpdateImage(t *testing.T) { Owner: "", MinRAMMegabytes: 0, - MinDiskGigabytes: 0, + MinDiskGigabytes: 21, DiskFormat: "", ContainerFormat: "", @@ -379,3 +387,71 @@ func TestImageListByTags(t *testing.T) { th.AssertDeepEquals(t, expectedImage, allImages[0]) } + +func TestUpdateImageProperties(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleImageUpdatePropertiesSuccessfully(t) + + actualImage, err := images.Update(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", images.UpdateOpts{ + images.UpdateImageProperty{ + Op: images.AddOp, + Name: "hw_disk_bus", + Value: "scsi", + }, + images.UpdateImageProperty{ + Op: images.AddOp, + Name: "hw_disk_bus_model", + Value: "virtio-scsi", + }, + images.UpdateImageProperty{ + Op: images.AddOp, + Name: "hw_scsi_model", + Value: "virtio-scsi", + }, + }).Extract() + + th.AssertNoErr(t, err) + + sizebytes := int64(2254249) + checksum := "2cec138d7dae2aa59038ef8c9aec2390" + file := actualImage.File + createdDate := actualImage.CreatedAt + lastUpdate := actualImage.UpdatedAt + schema := "/v2/schemas/image" + + expectedImage := images.Image{ + ID: "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + Name: "Fedora 17", + Status: images.ImageStatusActive, + Visibility: images.ImageVisibilityPublic, + + SizeBytes: sizebytes, + Checksum: checksum, + + Tags: []string{ + "fedora", + "beefy", + }, + + Owner: "", + MinRAMMegabytes: 0, + MinDiskGigabytes: 0, + + DiskFormat: "", + ContainerFormat: "", + File: file, + CreatedAt: createdDate, + UpdatedAt: lastUpdate, + Schema: schema, + VirtualSize: 0, + Properties: map[string]interface{}{ + "hw_disk_bus": "scsi", + "hw_disk_bus_model": "virtio-scsi", + "hw_scsi_model": "virtio-scsi", + }, + } + + th.AssertDeepEquals(t, &expectedImage, actualImage) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go index bf7cea1ef89b..1780c3c6ca70 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go @@ -2,8 +2,10 @@ package images import ( "net/url" + "strings" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" ) // `listURL` is a pure function. `listURL(c)` is a URL for which a GET @@ -38,14 +40,26 @@ func deleteURL(c *gophercloud.ServiceClient, imageID string) string { } // builds next page full url based on current url -func nextPageURL(currentURL string, next string) (string, error) { - base, err := url.Parse(currentURL) +func nextPageURL(serviceURL, requestedNext string) (string, error) { + base, err := utils.BaseEndpoint(serviceURL) if err != nil { return "", err } - rel, err := url.Parse(next) + + requestedNextURL, err := url.Parse(requestedNext) + if err != nil { + return "", err + } + + base = gophercloud.NormalizeURL(base) + nextPath := base + strings.TrimPrefix(requestedNextURL.Path, "/") + + nextURL, err := url.Parse(nextPath) if err != nil { return "", err } - return base.ResolveReference(rel).String(), nil + + nextURL.RawQuery = requestedNextURL.RawQuery + + return nextURL.String(), nil } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/doc.go new file mode 100644 index 000000000000..28ed82e55c0c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/doc.go @@ -0,0 +1,55 @@ +/* +Package tasks enables management and retrieval of tasks from the OpenStack +Imageservice. + +Example to List Tasks + + listOpts := tasks.ListOpts{ + Owner: "424e7cf0243c468ca61732ba45973b3e", + } + + allPages, err := tasks.List(imagesClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allTasks, err := tasks.ExtractTasks(allPages) + if err != nil { + panic(err) + } + + for _, task := range allTasks { + fmt.Printf("%+v\n", task) + } + +Example to Get a Task + + task, err := tasks.Get(imagesClient, "1252f636-1246-4319-bfba-c47cde0efbe0").Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", task) + +Example to Create a Task + + createOpts := tasks.CreateOpts{ + Type: "import", + Input: map[string]interface{}{ + "image_properties": map[string]interface{}{ + "container_format": "bare", + "disk_format": "raw", + }, + "import_from_format": "raw", + "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img", + }, + } + + task, err := tasks.Create(imagesClient, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", task) +*/ +package tasks diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/requests.go new file mode 100644 index 000000000000..94fe45d9c5e2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/requests.go @@ -0,0 +1,128 @@ +package tasks + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// TaskStatus represents valid task status. +// You can use this type to compare the actual status of a task to a one of the +// pre-defined statuses. +type TaskStatus string + +const ( + // TaskStatusPending represents status of the pending task. + TaskStatusPending TaskStatus = "pending" + + // TaskStatusProcessing represents status of the processing task. + TaskStatusProcessing TaskStatus = "processing" + + // TaskStatusSuccess represents status of the success task. + TaskStatusSuccess TaskStatus = "success" + + // TaskStatusFailure represents status of the failure task. + TaskStatusFailure TaskStatus = "failure" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToTaskListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the OpenStack Imageservice tasks API. +type ListOpts struct { + // Integer value for the limit of values to return. + Limit int `q:"limit"` + + // ID of the task at which you want to set a marker. + Marker string `q:"marker"` + + // SortDir allows to select sort direction. + // It can be "asc" or "desc" (default). + SortDir string `q:"sort_dir"` + + // SortKey allows to sort by one of the following tTask attributes: + // - created_at + // - expires_at + // - status + // - type + // - updated_at + // Default is created_at. + SortKey string `q:"sort_key"` + + // ID filters on the identifier of the task. + ID string `json:"id"` + + // Type filters on the type of the task. + Type string `json:"type"` + + // Status filters on the status of the task. + Status TaskStatus `q:"status"` +} + +// ToTaskListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTaskListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of the tasks. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToTaskListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + taskPage := TaskPage{ + serviceURL: c.ServiceURL(), + LinkedPageBase: pagination.LinkedPageBase{PageResult: r}, + } + + return taskPage + }) +} + +// Get retrieves a specific Imageservice task based on its ID. +func Get(c *gophercloud.ServiceClient, taskID string) (r GetResult) { + _, r.Err = c.Get(getURL(c, taskID), &r.Body, nil) + return +} + +// CreateOptsBuilder allows to add additional parameters to the Create request. +type CreateOptsBuilder interface { + ToTaskCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters of a new Imageservice task. +type CreateOpts struct { + Type string `json:"type" required:"true"` + Input map[string]interface{} `json:"input"` +} + +// ToTaskCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToTaskCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return b, nil +} + +// Create requests the creation of a new Imageservice task on the server. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTaskCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/results.go new file mode 100644 index 000000000000..3a7f5ca12b34 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/results.go @@ -0,0 +1,114 @@ +package tasks + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret it as a Task. +type GetResult struct { + commonResult +} + +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret it as a Task. +type CreateResult struct { + commonResult +} + +// Task represents a single task of the OpenStack Image service. +type Task struct { + // ID is a unique identifier of the task. + ID string `json:"id"` + + // Type represents the type of the task. + Type string `json:"type"` + + // Status represents current status of the task. + // You can use the TaskStatus custom type to unmarshal raw JSON response into + // the pre-defined valid task status. + Status string `json:"status"` + + // Input represents different parameters for the task. + Input map[string]interface{} `json:"input"` + + // Result represents task result details. + Result map[string]interface{} `json:"result"` + + // Owner is a unique identifier of the task owner. + Owner string `json:"owner"` + + // Message represents human-readable message that is usually populated + // on task failure. + Message string `json:"message"` + + // ExpiresAt contains the timestamp of when the task will become a subject of + // removal. + ExpiresAt time.Time `json:"expires_at"` + + // CreatedAt contains the task creation timestamp. + CreatedAt time.Time `json:"created_at"` + + // UpdatedAt contains the latest timestamp of when the task was updated. + UpdatedAt time.Time `json:"updated_at"` + + // Self contains URI for the task. + Self string `json:"self"` + + // Schema the path to the JSON-schema that represent the task. + Schema string `json:"schema"` +} + +// Extract interprets any commonResult as a Task. +func (r commonResult) Extract() (*Task, error) { + var s *Task + err := r.ExtractInto(&s) + return s, err +} + +// TaskPage represents the results of a List request. +type TaskPage struct { + serviceURL string + pagination.LinkedPageBase +} + +// IsEmpty returns true if a TaskPage contains no Tasks results. +func (r TaskPage) IsEmpty() (bool, error) { + tasks, err := ExtractTasks(r) + return len(tasks) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to +// the next page of results. +func (r TaskPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + + if s.Next == "" { + return "", nil + } + + return nextPageURL(r.serviceURL, s.Next) +} + +// ExtractTasks interprets the results of a single page from a List() call, +// producing a slice of Task entities. +func ExtractTasks(r pagination.Page) ([]Task, error) { + var s struct { + Tasks []Task `json:"tasks"` + } + err := (r.(TaskPage)).ExtractInto(&s) + return s.Tasks, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/doc.go new file mode 100644 index 000000000000..8d4a768447ec --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/doc.go @@ -0,0 +1,2 @@ +// tasks unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/fixtures.go new file mode 100644 index 000000000000..6a1a9d3e93d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/fixtures.go @@ -0,0 +1,124 @@ +package testing + +import ( + "time" + + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks" +) + +// TasksListResult represents raw server response from a server to a list call. +const TasksListResult = ` +{ + "schema": "/v2/schemas/tasks", + "tasks": [ + { + "status": "pending", + "self": "/v2/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", + "updated_at": "2018-07-25T08:59:14Z", + "id": "1252f636-1246-4319-bfba-c47cde0efbe0", + "owner": "424e7cf0243c468ca61732ba45973b3e", + "type": "import", + "created_at": "2018-07-25T08:59:13Z", + "schema": "/v2/schemas/task" + }, + { + "status": "processing", + "self": "/v2/tasks/349a51f4-d51d-47b6-82da-4fa516f0ca32", + "updated_at": "2018-07-25T08:56:19Z", + "id": "349a51f4-d51d-47b6-82da-4fa516f0ca32", + "owner": "fb57277ef2f84a0e85b9018ec2dedbf7", + "type": "import", + "created_at": "2018-07-25T08:56:17Z", + "schema": "/v2/schemas/task" + } + ], + "first": "/v2/tasks?sort_key=status&sort_dir=desc&limit=20" +} +` + +// Task1 is an expected representation of a first task from the TasksListResult. +var Task1 = tasks.Task{ + ID: "1252f636-1246-4319-bfba-c47cde0efbe0", + Status: string(tasks.TaskStatusPending), + Type: "import", + Owner: "424e7cf0243c468ca61732ba45973b3e", + CreatedAt: time.Date(2018, 7, 25, 8, 59, 13, 0, time.UTC), + UpdatedAt: time.Date(2018, 7, 25, 8, 59, 14, 0, time.UTC), + Self: "/v2/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", + Schema: "/v2/schemas/task", +} + +// Task2 is an expected representation of a first task from the TasksListResult. +var Task2 = tasks.Task{ + ID: "349a51f4-d51d-47b6-82da-4fa516f0ca32", + Status: string(tasks.TaskStatusProcessing), + Type: "import", + Owner: "fb57277ef2f84a0e85b9018ec2dedbf7", + CreatedAt: time.Date(2018, 7, 25, 8, 56, 17, 0, time.UTC), + UpdatedAt: time.Date(2018, 7, 25, 8, 56, 19, 0, time.UTC), + Self: "/v2/tasks/349a51f4-d51d-47b6-82da-4fa516f0ca32", + Schema: "/v2/schemas/task", +} + +// TasksGetResult represents raw server response from a server to a get call. +const TasksGetResult = ` +{ + "status": "pending", + "created_at": "2018-07-25T08:59:13Z", + "updated_at": "2018-07-25T08:59:14Z", + "self": "/v2/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", + "result": null, + "owner": "424e7cf0243c468ca61732ba45973b3e", + "input": { + "image_properties": { + "container_format": "bare", + "disk_format": "raw" + }, + "import_from_format": "raw", + "import_from": "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + }, + "message": "", + "type": "import", + "id": "1252f636-1246-4319-bfba-c47cde0efbe0", + "schema": "/v2/schemas/task" +} +` + +// TaskCreateRequest represents a request to create a task. +const TaskCreateRequest = ` +{ + "input": { + "image_properties": { + "container_format": "bare", + "disk_format": "raw" + }, + "import_from_format": "raw", + "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img" + }, + "type": "import" +} +` + +// TaskCreateResult represents a raw server response to the TaskCreateRequest. +const TaskCreateResult = ` +{ + "status": "pending", + "created_at": "2018-07-25T11:07:54Z", + "updated_at": "2018-07-25T11:07:54Z", + "self": "/v2/tasks/d550c87d-86ed-430a-9895-c7a1f5ce87e9", + "result": null, + "owner": "fb57277ef2f84a0e85b9018ec2dedbf7", + "input": { + "image_properties": { + "container_format": "bare", + "disk_format": "raw" + }, + "import_from_format": "raw", + "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img" + }, + "message": "", + "type": "import", + "id": "d550c87d-86ed-430a-9895-c7a1f5ce87e9", + "schema": "/v2/schemas/task" +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/requests_test.go new file mode 100644 index 000000000000..bc9af17aca1e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/testing/requests_test.go @@ -0,0 +1,138 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fakeclient "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, TasksListResult) + }) + + count := 0 + + tasks.List(fakeclient.ServiceClient(), tasks.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := tasks.ExtractTasks(page) + if err != nil { + t.Errorf("Failed to extract tasks: %v", err) + return false, nil + } + + expected := []tasks.Task{ + Task1, + Task2, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, TasksGetResult) + }) + + s, err := tasks.Get(fakeclient.ServiceClient(), "1252f636-1246-4319-bfba-c47cde0efbe0").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Status, string(tasks.TaskStatusPending)) + th.AssertEquals(t, s.CreatedAt, time.Date(2018, 7, 25, 8, 59, 13, 0, time.UTC)) + th.AssertEquals(t, s.UpdatedAt, time.Date(2018, 7, 25, 8, 59, 14, 0, time.UTC)) + th.AssertEquals(t, s.Self, "/v2/tasks/1252f636-1246-4319-bfba-c47cde0efbe0") + th.AssertEquals(t, s.Owner, "424e7cf0243c468ca61732ba45973b3e") + th.AssertEquals(t, s.Message, "") + th.AssertEquals(t, s.Type, "import") + th.AssertEquals(t, s.ID, "1252f636-1246-4319-bfba-c47cde0efbe0") + th.AssertEquals(t, s.Schema, "/v2/schemas/task") + th.AssertDeepEquals(t, s.Result, map[string]interface{}(nil)) + th.AssertDeepEquals(t, s.Input, map[string]interface{}{ + "import_from": "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img", + "import_from_format": "raw", + "image_properties": map[string]interface{}{ + "container_format": "bare", + "disk_format": "raw", + }, + }) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestJSONRequest(t, r, TaskCreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, TaskCreateResult) + }) + + opts := tasks.CreateOpts{ + Type: "import", + Input: map[string]interface{}{ + "image_properties": map[string]interface{}{ + "container_format": "bare", + "disk_format": "raw", + }, + "import_from_format": "raw", + "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img", + }, + } + s, err := tasks.Create(fakeclient.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Status, string(tasks.TaskStatusPending)) + th.AssertEquals(t, s.CreatedAt, time.Date(2018, 7, 25, 11, 7, 54, 0, time.UTC)) + th.AssertEquals(t, s.UpdatedAt, time.Date(2018, 7, 25, 11, 7, 54, 0, time.UTC)) + th.AssertEquals(t, s.Self, "/v2/tasks/d550c87d-86ed-430a-9895-c7a1f5ce87e9") + th.AssertEquals(t, s.Owner, "fb57277ef2f84a0e85b9018ec2dedbf7") + th.AssertEquals(t, s.Message, "") + th.AssertEquals(t, s.Type, "import") + th.AssertEquals(t, s.ID, "d550c87d-86ed-430a-9895-c7a1f5ce87e9") + th.AssertEquals(t, s.Schema, "/v2/schemas/task") + th.AssertDeepEquals(t, s.Result, map[string]interface{}(nil)) + th.AssertDeepEquals(t, s.Input, map[string]interface{}{ + "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img", + "import_from_format": "raw", + "image_properties": map[string]interface{}{ + "container_format": "bare", + "disk_format": "raw", + }, + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/urls.go new file mode 100644 index 000000000000..8133f383567a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/tasks/urls.go @@ -0,0 +1,55 @@ +package tasks + +import ( + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" +) + +const resourcePath = "tasks" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, taskID string) string { + return c.ServiceURL(resourcePath, taskID) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, taskID string) string { + return resourceURL(c, taskID) +} + +func createURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func nextPageURL(serviceURL, requestedNext string) (string, error) { + base, err := utils.BaseEndpoint(serviceURL) + if err != nil { + return "", err + } + + requestedNextURL, err := url.Parse(requestedNext) + if err != nil { + return "", err + } + + base = gophercloud.NormalizeURL(base) + nextPath := base + strings.TrimPrefix(requestedNextURL.Path, "/") + + nextURL, err := url.Parse(nextPath) + if err != nil { + return "", err + } + + nextURL.RawQuery = requestedNextURL.RawQuery + + return nextURL.String(), nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/doc.go new file mode 100644 index 000000000000..d61940cb080d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/doc.go @@ -0,0 +1,54 @@ +/* +Package acls manages acls in the OpenStack Key Manager Service. + +All functions have a Secret and Container equivalent. + +Example to Get a Secret's ACL + + acl, err := acls.GetSecretACL(client, secretID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", acl) + +Example to Set a Secret's ACL + + users := []string{"uuid", "uuid"} + iFalse := false + setOpts := acls.SetOpts{ + Type: "read", + users: &users, + ProjectAccess: &iFalse, + } + + aclRef, err := acls.SetSecretACL(client, secretID, setOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", aclRef) + +Example to Update a Secret's ACL + + users := []string{} + setOpts := acls.SetOpts{ + Type: "read", + users: &users, + } + + aclRef, err := acls.UpdateSecretACL(client, secretID, setOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", aclRef) + +Example to Delete a Secret's ACL + + err := acls.DeleteSecretACL(client, secretID).ExtractErr() + if err != nil { + panci(err) + } +*/ +package acls diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/requests.go new file mode 100644 index 000000000000..b83c56075d02 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/requests.go @@ -0,0 +1,112 @@ +package acls + +import ( + "github.com/gophercloud/gophercloud" +) + +// GetContainerACL retrieves the ACL of a container. +func GetContainerACL(client *gophercloud.ServiceClient, containerID string) (r ACLResult) { + _, r.Err = client.Get(containerURL(client, containerID), &r.Body, nil) + return +} + +// GetSecretACL retrieves the ACL of a secret. +func GetSecretACL(client *gophercloud.ServiceClient, secretID string) (r ACLResult) { + _, r.Err = client.Get(secretURL(client, secretID), &r.Body, nil) + return +} + +// SetOptsBuilder allows extensions to add additional parameters to the +// Set request. +type SetOptsBuilder interface { + ToACLSetMap() (map[string]interface{}, error) +} + +// SetOpts represents options to set an ACL on a resource. +type SetOpts struct { + // Type is the type of ACL to set. ie: read. + Type string `json:"-" required:"true"` + + // Users are the list of Keystone user UUIDs. + Users *[]string `json:"users,omitempty"` + + // ProjectAccess toggles if all users in a project can access the resource. + ProjectAccess *bool `json:"project-access,omitempty"` +} + +// ToACLSetMap formats a SetOpts into a set request. +func (opts SetOpts) ToACLSetMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, opts.Type) +} + +// SetContainerACL will set an ACL on a container. +func SetContainerACL(client *gophercloud.ServiceClient, containerID string, opts SetOptsBuilder) (r ACLRefResult) { + b, err := opts.ToACLSetMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(containerURL(client, containerID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// SetSecretACL will set an ACL on a secret. +func SetSecretACL(client *gophercloud.ServiceClient, secretID string, opts SetOptsBuilder) (r ACLRefResult) { + b, err := opts.ToACLSetMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(secretURL(client, secretID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// UpdateContainerACL will update an ACL on a container. +func UpdateContainerACL(client *gophercloud.ServiceClient, containerID string, opts SetOptsBuilder) (r ACLRefResult) { + b, err := opts.ToACLSetMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Patch(containerURL(client, containerID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// UpdateSecretACL will update an ACL on a secret. +func UpdateSecretACL(client *gophercloud.ServiceClient, secretID string, opts SetOptsBuilder) (r ACLRefResult) { + b, err := opts.ToACLSetMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Patch(secretURL(client, secretID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteContainerACL will delete an ACL from a conatiner. +func DeleteContainerACL(client *gophercloud.ServiceClient, containerID string) (r DeleteResult) { + _, r.Err = client.Delete(containerURL(client, containerID), &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteSecretACL will delete an ACL from a secret. +func DeleteSecretACL(client *gophercloud.ServiceClient, secretID string) (r DeleteResult) { + _, r.Err = client.Delete(secretURL(client, secretID), &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/results.go new file mode 100644 index 000000000000..959742da9524 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/results.go @@ -0,0 +1,85 @@ +package acls + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" +) + +// ACL represents an ACL on a resource. +type ACL map[string]ACLDetails + +// ACLDetails represents the details of an ACL. +type ACLDetails struct { + // Created is when the ACL was created. + Created time.Time `json:"-"` + + // ProjectAccess denotes project-level access of the resource. + ProjectAccess bool `json:"project-access"` + + // Updated is when the ACL was updated + Updated time.Time `json:"-"` + + // Users are the UserIDs who have access to the resource. + Users []string `json:"users"` +} + +func (r *ACLDetails) UnmarshalJSON(b []byte) error { + type tmp ACLDetails + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + Updated gophercloud.JSONRFC3339NoZ `json:"updated"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ACLDetails(s.tmp) + + r.Created = time.Time(s.Created) + r.Updated = time.Time(s.Updated) + + return nil +} + +// ACLRef represents an ACL reference. +type ACLRef string + +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult as an ACL. +func (r commonResult) Extract() (*ACL, error) { + var s *ACL + err := r.ExtractInto(&s) + return s, err +} + +// ACLResult is the response from a Get operation. Call its Extract method +// to interpret it as an ACL. +type ACLResult struct { + commonResult +} + +// ACLRefResult is the response from a Set or Update operation. Call its +// Extract method to interpret it as an ACLRef. +type ACLRefResult struct { + gophercloud.Result +} + +func (r ACLRefResult) Extract() (*ACLRef, error) { + var s struct { + ACLRef ACLRef `json:"acl_ref"` + } + err := r.ExtractInto(&s) + return &s.ACLRef, err +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/fixtures.go new file mode 100644 index 000000000000..7ae5b5971c92 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/fixtures.go @@ -0,0 +1,168 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const GetResponse = ` +{ + "read": { + "created": "2018-06-22T17:54:24", + "project-access": false, + "updated": "2018-06-22T17:54:24", + "users": [ + "GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW" + ] + } +}` + +const SetRequest = ` +{ + "read": { + "project-access": false, + "users": [ + "GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW" + ] + } +}` + +const SecretSetResponse = ` +{ + "acl_ref": "http://barbican:9311/v1/secrets/4befede0-fbde-4480-982c-b160c1014a47/acl" +}` + +const ContainerSetResponse = ` +{ + "acl_ref": "http://barbican:9311/v1/containers/4befede0-fbde-4480-982c-b160c1014a47/acl" +}` + +var ExpectedACL = acls.ACL{ + "read": acls.ACLDetails{ + Created: time.Date(2018, 6, 22, 17, 54, 24, 0, time.UTC), + ProjectAccess: false, + Updated: time.Date(2018, 6, 22, 17, 54, 24, 0, time.UTC), + Users: []string{ + "GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW", + }, + }, +} + +var ExpectedSecretACLRef = acls.ACLRef("http://barbican:9311/v1/secrets/4befede0-fbde-4480-982c-b160c1014a47/acl") + +var ExpectedContainerACLRef = acls.ACLRef("http://barbican:9311/v1/containers/4befede0-fbde-4480-982c-b160c1014a47/acl") + +const UpdateRequest = ` +{ + "read": { + "users": [] + } +}` + +// HandleGetSecretACLSuccessfully creates an HTTP handler at `/secrets/uuid/acl` +// on the test handler mux that responds with an acl. +func HandleGetSecretACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleGetContainerACLSuccessfully creates an HTTP handler at `/secrets/uuid/acl` +// on the test handler mux that responds with an acl. +func HandleGetContainerACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleSetSecretACLSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret creation. +func HandleSetSecretACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, SetRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, SecretSetResponse) + }) +} + +// HandleSetContainerACLSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret creation. +func HandleSetContainerACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, SetRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ContainerSetResponse) + }) +} + +// HandleUpdateSecretACLSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret creation. +func HandleUpdateSecretACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, SecretSetResponse) + }) +} + +// HandleUpdateContainerACLSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret creation. +func HandleUpdateContainerACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ContainerSetResponse) + }) +} + +// HandleDeleteSecretACLSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret deletion. +func HandleDeleteSecretACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusOK) + }) +} + +// HandleDeleteContainerACLSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret deletion. +func HandleDeleteContainerACLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusOK) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/requests_test.go new file mode 100644 index 000000000000..b978f405f100 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/testing/requests_test.go @@ -0,0 +1,115 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGetSecretACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSecretACLSuccessfully(t) + + actual, err := acls.GetSecretACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedACL, *actual) +} + +func TestGetContainerACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetContainerACLSuccessfully(t) + + actual, err := acls.GetContainerACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedACL, *actual) +} + +func TestSetSecretACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleSetSecretACLSuccessfully(t) + + users := []string{"GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW"} + iFalse := false + setOpts := acls.SetOpts{ + Type: "read", + Users: &users, + ProjectAccess: &iFalse, + } + + actual, err := acls.SetSecretACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", setOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedSecretACLRef, *actual) +} + +func TestSetContainerACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleSetContainerACLSuccessfully(t) + + users := []string{"GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW"} + iFalse := false + setOpts := acls.SetOpts{ + Type: "read", + Users: &users, + ProjectAccess: &iFalse, + } + + actual, err := acls.SetContainerACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", setOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedContainerACLRef, *actual) +} + +func TestDeleteSecretACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSecretACLSuccessfully(t) + + res := acls.DeleteSecretACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") + th.AssertNoErr(t, res.Err) +} + +func TestDeleteContainerACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteContainerACLSuccessfully(t) + + res := acls.DeleteContainerACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateSecretACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateSecretACLSuccessfully(t) + + newUsers := []string{} + updateOpts := acls.SetOpts{ + Type: "read", + Users: &newUsers, + } + + actual, err := acls.UpdateSecretACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedSecretACLRef, *actual) +} + +func TestUpdateContainerACL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateContainerACLSuccessfully(t) + + newUsers := []string{} + updateOpts := acls.SetOpts{ + Type: "read", + Users: &newUsers, + } + + actual, err := acls.UpdateContainerACL(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedContainerACLRef, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/urls.go new file mode 100644 index 000000000000..5cb4439a8c9a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/acls/urls.go @@ -0,0 +1,11 @@ +package acls + +import "github.com/gophercloud/gophercloud" + +func containerURL(client *gophercloud.ServiceClient, containerID string) string { + return client.ServiceURL("containers", containerID, "acl") +} + +func secretURL(client *gophercloud.ServiceClient, secretID string) string { + return client.ServiceURL("secrets", secretID, "acl") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/doc.go new file mode 100644 index 000000000000..cb11920f29ef --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/doc.go @@ -0,0 +1,86 @@ +/* +Package containers manages and retrieves containers in the OpenStack Key Manager +Service. + +Example to List Containers + + allPages, err := containers.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + allContainers, err := containers.ExtractContainers(allPages) + if err != nil { + panic(err) + } + + for _, v := range allContainers { + fmt.Printf("%v\n", v) + } + +Example to Create a Container + + createOpts := containers.CreateOpts{ + Type: containers.GenericContainer, + Name: "mycontainer", + SecretRefs: []containers.SecretRef{ + { + Name: secret.Name, + SecretRef: secret.SecretRef, + }, + }, + } + + container, err := containers.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", container) + +Example to Delete a Container + + err := containers.Delete(client, containerID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Consumers of a Container + + allPages, err := containers.ListConsumers(client, containerID, nil).AllPages() + if err != nil { + panic(err) + } + + allConsumers, err := containers.ExtractConsumers(allPages) + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", allConsumers) + +Example to Create a Consumer of a Container + + createOpts := containers.CreateConsumerOpts{ + Name: "jdoe", + URL: "http://example.com", + } + + container, err := containers.CreateConsumer(client, containerID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Consumer of a Container + + deleteOpts := containers.DeleteConsumerOpts{ + Name: "jdoe", + URL: "http://example.com", + } + + container, err := containers.DeleteConsumer(client, containerID, deleteOpts).Extract() + if err != nil { + panic(err) + } +*/ +package containers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/requests.go new file mode 100644 index 000000000000..190438ed280a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/requests.go @@ -0,0 +1,212 @@ +package containers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ContainerType represents the valid types of containers. +type ContainerType string + +const ( + GenericContainer ContainerType = "generic" + RSAContainer ContainerType = "rsa" + CertificateContainer ContainerType = "certificate" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToContainerListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // Limit is the amount of containers to retrieve. + Limit int `q:"limit"` + + // Name is the name of the container + Name string `q:"name"` + + // Offset is the index within the list to retrieve. + Offset int `q:"offset"` +} + +// ToContainerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToContainerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List retrieves a list of containers. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToContainerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ContainerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details of a container. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToContainerCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a container. +type CreateOpts struct { + // Type represents the type of container. + Type ContainerType `json:"type" required:"true"` + + // Name is the name of the container. + Name string `json:"name"` + + // SecretRefs is a list of secret refs for the container. + SecretRefs []SecretRef `json:"secret_refs"` +} + +// ToContainerCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToContainerCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create creates a new container. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToContainerCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete deletes a container. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListConsumersOptsBuilder allows extensions to add additional parameters to +// the ListConsumers request +type ListConsumersOptsBuilder interface { + ToContainerListConsumersQuery() (string, error) +} + +// ListConsumersOpts provides options to filter the List results. +type ListConsumersOpts struct { + // Limit is the amount of consumers to retrieve. + Limit int `q:"limit"` + + // Offset is the index within the list to retrieve. + Offset int `q:"offset"` +} + +// ToContainerListConsumersQuery formats a ListConsumersOpts into a query +// string. +func (opts ListOpts) ToContainerListConsumersQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListConsumers retrieves a list of consumers from a container. +func ListConsumers(client *gophercloud.ServiceClient, containerID string, opts ListConsumersOptsBuilder) pagination.Pager { + url := listConsumersURL(client, containerID) + if opts != nil { + query, err := opts.ToContainerListConsumersQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ConsumerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateConsumerOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateConsumerOptsBuilder interface { + ToContainerConsumerCreateMap() (map[string]interface{}, error) +} + +// CreateConsumerOpts provides options used to create a container. +type CreateConsumerOpts struct { + // Name is the name of the consumer. + Name string `json:"name"` + + // URL is the URL to the consumer resource. + URL string `json:"URL"` +} + +// ToContainerConsumerCreateMap formats a CreateConsumerOpts into a create +// request. +func (opts CreateConsumerOpts) ToContainerConsumerCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// CreateConsumer creates a new consumer. +func CreateConsumer(client *gophercloud.ServiceClient, containerID string, opts CreateConsumerOptsBuilder) (r CreateConsumerResult) { + b, err := opts.ToContainerConsumerCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createConsumerURL(client, containerID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteConsumerOptsBuilder allows extensions to add additional parameters to +// the Delete request. +type DeleteConsumerOptsBuilder interface { + ToContainerConsumerDeleteMap() (map[string]interface{}, error) +} + +// DeleteConsumerOpts represents options used for deleting a consumer. +type DeleteConsumerOpts struct { + // Name is the name of the consumer. + Name string `json:"name"` + + // URL is the URL to the consumer resource. + URL string `json:"URL"` +} + +// ToContainerConsumerDeleteMap formats a DeleteConsumerOpts into a create +// request. +func (opts DeleteConsumerOpts) ToContainerConsumerDeleteMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// DeleteConsumer deletes a consumer. +func DeleteConsumer(client *gophercloud.ServiceClient, containerID string, opts DeleteConsumerOptsBuilder) (r DeleteConsumerResult) { + url := deleteConsumerURL(client, containerID) + + b, err := opts.ToContainerConsumerDeleteMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Request("DELETE", url, &gophercloud.RequestOpts{ + JSONBody: b, + JSONResponse: &r.Body, + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/results.go new file mode 100644 index 000000000000..c94c9ac67108 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/results.go @@ -0,0 +1,232 @@ +package containers + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Container represents a container in the key manager service. +type Container struct { + // Consumers are the consumers of the container. + Consumers []ConsumerRef `json:"consumers"` + + // ContainerRef is the URL to the container + ContainerRef string `json:"container_ref"` + + // Created is the date the container was created. + Created time.Time `json:"-"` + + // CreatorID is the creator of the container. + CreatorID string `json:"creator_id"` + + // Name is the name of the container. + Name string `json:"name"` + + // SecretRefs are the secret references of the container. + SecretRefs []SecretRef `json:"secret_refs"` + + // Status is the status of the container. + Status string `json:"status"` + + // Type is the type of container. + Type string `json:"type"` + + // Updated is the date the container was updated. + Updated time.Time `json:"-"` +} + +func (r *Container) UnmarshalJSON(b []byte) error { + type tmp Container + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + Updated gophercloud.JSONRFC3339NoZ `json:"updated"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Container(s.tmp) + + r.Created = time.Time(s.Created) + r.Updated = time.Time(s.Updated) + + return nil +} + +// ConsumerRef represents a consumer reference in a container. +type ConsumerRef struct { + // Name is the name of the consumer. + Name string `json:"name"` + + // URL is the URL to the consumer resource. + URL string `json:"url"` +} + +// SecretRef is a reference to a secret. +type SecretRef struct { + SecretRef string `json:"secret_ref"` + Name string `json:"name"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult as a Container. +func (r commonResult) Extract() (*Container, error) { + var s *Container + err := r.ExtractInto(&s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a container. +type GetResult struct { + commonResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a container. +type CreateResult struct { + commonResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ContainerPage is a single page of container results. +type ContainerPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Container contains any results. +func (r ContainerPage) IsEmpty() (bool, error) { + containers, err := ExtractContainers(r) + return len(containers) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r ContainerPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + Previous string `json:"previous"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, err +} + +// ExtractContainers returns a slice of Containers contained in a single page of +// results. +func ExtractContainers(r pagination.Page) ([]Container, error) { + var s struct { + Containers []Container `json:"containers"` + } + err := (r.(ContainerPage)).ExtractInto(&s) + return s.Containers, err +} + +// Consumer represents a consumer in a container. +type Consumer struct { + // Created is the date the container was created. + Created time.Time `json:"-"` + + // Name is the name of the container. + Name string `json:"name"` + + // Status is the status of the container. + Status string `json:"status"` + + // Updated is the date the container was updated. + Updated time.Time `json:"-"` + + // URL is the url to the consumer. + URL string `json:"url"` +} + +func (r *Consumer) UnmarshalJSON(b []byte) error { + type tmp Consumer + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + Updated gophercloud.JSONRFC3339NoZ `json:"updated"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Consumer(s.tmp) + + r.Created = time.Time(s.Created) + r.Updated = time.Time(s.Updated) + + return nil +} + +type consumerResult struct { + gophercloud.Result +} + +// Extract interprets any consumerResult as a Consumer. +func (r consumerResult) Extract() (*Consumer, error) { + var s *Consumer + err := r.ExtractInto(&s) + return s, err +} + +// CreateConsumerResult is the response from a CreateConsumer operation. +// Call its Extract method to interpret it as a container. +type CreateConsumerResult struct { + // This is not a typo. + commonResult +} + +// DeleteConsumerResult is the response from a DeleteConsumer operation. +// Call its Extract to interpret it as a container. +type DeleteConsumerResult struct { + // This is not a typo. + commonResult +} + +// ConsumerPage is a single page of consumer results. +type ConsumerPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of consumers contains any results. +func (r ConsumerPage) IsEmpty() (bool, error) { + consumers, err := ExtractConsumers(r) + return len(consumers) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r ConsumerPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + Previous string `json:"previous"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, err +} + +// ExtractConsumers returns a slice of Consumers contained in a single page of +// results. +func ExtractConsumers(r pagination.Page) ([]Consumer, error) { + var s struct { + Consumers []Consumer `json:"consumers"` + } + err := (r.(ConsumerPage)).ExtractInto(&s) + return s.Consumers, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/fixtures.go new file mode 100644 index 000000000000..3c73e38b1ce7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/fixtures.go @@ -0,0 +1,321 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListResponse provides a single page of container results. +const ListResponse = ` +{ + "containers": [ + { + "consumers": [], + "container_ref": "http://barbican:9311/v1/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", + "created": "2018-06-21T21:28:37", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "name": "mycontainer", + "secret_refs": [ + { + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c" + } + ], + "status": "ACTIVE", + "type": "generic", + "updated": "2018-06-21T21:28:37" + }, + { + "consumers": [], + "container_ref": "http://barbican:9311/v1/containers/47b20e73-335b-4867-82dc-3796524d5e20", + "created": "2018-06-21T21:30:09", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "name": "anothercontainer", + "secret_refs": [ + { + "name": "another", + "secret_ref": "http://barbican:9311/v1/secrets/1b12b69a-8822-442e-a303-da24ade648ac" + } + ], + "status": "ACTIVE", + "type": "generic", + "updated": "2018-06-21T21:30:09" + } + ], + "total": 2 +}` + +// GetResponse provides a Get result. +const GetResponse = ` +{ + "consumers": [], + "container_ref": "http://barbican:9311/v1/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", + "created": "2018-06-21T21:28:37", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "name": "mycontainer", + "secret_refs": [ + { + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c" + } + ], + "status": "ACTIVE", + "type": "generic", + "updated": "2018-06-21T21:28:37" +}` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "name": "mycontainer", + "secret_refs": [ + { + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c" + } + ], + "type": "generic" +}` + +// CreateResponse is the response of a Create request. +const CreateResponse = ` +{ + "consumers": [], + "container_ref": "http://barbican:9311/v1/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", + "created": "2018-06-21T21:28:37", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "name": "mycontainer", + "secret_refs": [ + { + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c" + } + ], + "status": "ACTIVE", + "type": "generic", + "updated": "2018-06-21T21:28:37" +}` + +// FirstContainer is the first resource in the List request. +var FirstContainer = containers.Container{ + Consumers: []containers.ConsumerRef{}, + ContainerRef: "http://barbican:9311/v1/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", + Created: time.Date(2018, 6, 21, 21, 28, 37, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Name: "mycontainer", + SecretRefs: []containers.SecretRef{ + { + Name: "mysecret", + SecretRef: "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", + }, + }, + Status: "ACTIVE", + Type: "generic", + Updated: time.Date(2018, 6, 21, 21, 28, 37, 0, time.UTC), +} + +// SecondContainer is the second resource in the List request. +var SecondContainer = containers.Container{ + Consumers: []containers.ConsumerRef{}, + ContainerRef: "http://barbican:9311/v1/containers/47b20e73-335b-4867-82dc-3796524d5e20", + Created: time.Date(2018, 6, 21, 21, 30, 9, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Name: "anothercontainer", + SecretRefs: []containers.SecretRef{ + { + Name: "another", + SecretRef: "http://barbican:9311/v1/secrets/1b12b69a-8822-442e-a303-da24ade648ac", + }, + }, + Status: "ACTIVE", + Type: "generic", + Updated: time.Date(2018, 6, 21, 21, 30, 9, 0, time.UTC), +} + +// ExpectedContainersSlice is the slice of containers expected to be returned from ListResponse. +var ExpectedContainersSlice = []containers.Container{FirstContainer, SecondContainer} + +const ListConsumersResponse = ` +{ + "consumers": [ + { + "URL": "http://example.com", + "created": "2018-06-22T16:26:25", + "name": "CONSUMER-LZILN1zq", + "status": "ACTIVE", + "updated": "2018-06-22T16:26:25" + } + ], + "total": 1 +}` + +// ExpectedConsumer is the expected result of a consumer retrieval. +var ExpectedConsumer = containers.Consumer{ + URL: "http://example.com", + Created: time.Date(2018, 6, 22, 16, 26, 25, 0, time.UTC), + Name: "CONSUMER-LZILN1zq", + Status: "ACTIVE", + Updated: time.Date(2018, 6, 22, 16, 26, 25, 0, time.UTC), +} + +// ExpectedConsumersSlice is an expected slice of consumers. +var ExpectedConsumersSlice = []containers.Consumer{ExpectedConsumer} + +const CreateConsumerRequest = ` +{ + "URL": "http://example.com", + "name": "CONSUMER-LZILN1zq" +}` + +const CreateConsumerResponse = ` +{ + "consumers": [ + { + "URL": "http://example.com", + "name": "CONSUMER-LZILN1zq" + } + ], + "container_ref": "http://barbican:9311/v1/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", + "created": "2018-06-21T21:28:37", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "name": "mycontainer", + "secret_refs": [ + { + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c" + } + ], + "status": "ACTIVE", + "type": "generic", + "updated": "2018-06-21T21:28:37" +}` + +// ExpectedCreatedConsumer is the expected result of adding a consumer. +var ExpectedCreatedConsumer = containers.Container{ + Consumers: []containers.ConsumerRef{ + { + Name: "CONSUMER-LZILN1zq", + URL: "http://example.com", + }, + }, + ContainerRef: "http://barbican:9311/v1/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", + Created: time.Date(2018, 6, 21, 21, 28, 37, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Name: "mycontainer", + SecretRefs: []containers.SecretRef{ + { + Name: "mysecret", + SecretRef: "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", + }, + }, + Status: "ACTIVE", + Type: "generic", + Updated: time.Date(2018, 6, 21, 21, 28, 37, 0, time.UTC), +} + +const DeleteConsumerRequest = ` +{ + "URL": "http://example.com", + "name": "CONSUMER-LZILN1zq" +}` + +// HandleListContainersSuccessfully creates an HTTP handler at `/containers` on the +// test handler mux that responds with a list of two containers. +func HandleListContainersSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleGetContainerSuccessfully creates an HTTP handler at `/containers` on the +// test handler mux that responds with a single resource. +func HandleGetContainerSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleCreateContainerSuccessfully creates an HTTP handler at `/containers` on the +// test handler mux that tests resource creation. +func HandleCreateContainerSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetResponse) + }) +} + +// HandleDeleteContainerSuccessfully creates an HTTP handler at `/containers` on the +// test handler mux that tests resource deletion. +func HandleDeleteContainerSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleListConsumersSuccessfully creates an HTTP handler at +// `/containers/uuid/consumers` on the test handler mux that responds with +// a list of consumers. +func HandleListConsumersSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleCreateConsumerSuccessfully creates an HTTP handler at +// `/containers/uuid/consumers` on the test handler mux that tests resource +// creation. +func HandleCreateConsumerSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateConsumerRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, CreateConsumerResponse) + }) +} + +// HandleDeleteConsumerSuccessfully creates an HTTP handler at +// `/containers/uuid/consumers` on the test handler mux that tests resource +// deletion. +func HandleDeleteConsumerSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateConsumerRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/requests_test.go new file mode 100644 index 000000000000..2f405e3c55d5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/testing/requests_test.go @@ -0,0 +1,144 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListContainers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListContainersSuccessfully(t) + + count := 0 + err := containers.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := containers.ExtractContainers(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedContainersSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, count, 1) +} + +func TestListContainersAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListContainersSuccessfully(t) + + allPages, err := containers.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := containers.ExtractContainers(allPages) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedContainersSlice, actual) +} + +func TestGetContainer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetContainerSuccessfully(t) + + actual, err := containers.Get(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, FirstContainer, *actual) +} + +func TestCreateContainer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateContainerSuccessfully(t) + + createOpts := containers.CreateOpts{ + Type: containers.GenericContainer, + Name: "mycontainer", + SecretRefs: []containers.SecretRef{ + { + Name: "mysecret", + SecretRef: "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", + }, + }, + } + + actual, err := containers.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, FirstContainer, *actual) +} + +func TestDeleteContainer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteContainerSuccessfully(t) + + res := containers.Delete(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0") + th.AssertNoErr(t, res.Err) +} + +func TestListConsumers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListConsumersSuccessfully(t) + + count := 0 + err := containers.ListConsumers(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := containers.ExtractConsumers(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedConsumersSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, count, 1) +} + +func TestListConsumersAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListConsumersSuccessfully(t) + + allPages, err := containers.ListConsumers(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", nil).AllPages() + th.AssertNoErr(t, err) + actual, err := containers.ExtractConsumers(allPages) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedConsumersSlice, actual) +} + +func TestCreateConsumer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateConsumerSuccessfully(t) + + createOpts := containers.CreateConsumerOpts{ + Name: "CONSUMER-LZILN1zq", + URL: "http://example.com", + } + + actual, err := containers.CreateConsumer(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCreatedConsumer, *actual) +} + +func TestDeleteConsumer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteConsumerSuccessfully(t) + + deleteOpts := containers.DeleteConsumerOpts{ + Name: "CONSUMER-LZILN1zq", + URL: "http://example.com", + } + + actual, err := containers.DeleteConsumer(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", deleteOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, FirstContainer, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/urls.go new file mode 100644 index 000000000000..236ebc8133d2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers/urls.go @@ -0,0 +1,31 @@ +package containers + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("containers") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("containers", id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("containers") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("containers", id) +} + +func listConsumersURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("containers", id, "consumers") +} + +func createConsumerURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("containers", id, "consumers") +} + +func deleteConsumerURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("containers", id, "consumers") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/doc.go new file mode 100644 index 000000000000..fcbeccf4e50b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/doc.go @@ -0,0 +1,45 @@ +/* +Package orders manages and retrieves orders in the OpenStack Key Manager +Service. + +Example to List Orders + + allPages, err := orders.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + allOrders, err := orders.ExtractOrders(allPages) + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", allOrders) + +Example to Create a Order + + createOpts := orders.CreateOpts{ + Type: orders.KeyOrder, + Meta: orders.MetaOpts{ + Name: "order-name", + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + }, + } + + order, err := orders.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", order) + +Example to Delete a Order + + err := orders.Delete(client, orderID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package orders diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/requests.go new file mode 100644 index 000000000000..7a55f9ddb813 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/requests.go @@ -0,0 +1,129 @@ +package orders + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// OrderType represents the valid types of orders. +type OrderType string + +const ( + KeyOrder OrderType = "key" + AsymmetricOrder OrderType = "asymmetric" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToOrderListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // Limit is the amount of containers to retrieve. + Limit int `q:"limit"` + + // Offset is the index within the list to retrieve. + Offset int `q:"offset"` +} + +// ToOrderListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToOrderListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List retrieves a list of orders. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToOrderListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return OrderPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details of a orders. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToOrderCreateMap() (map[string]interface{}, error) +} + +// MetaOpts represents options used for creating an order. +type MetaOpts struct { + // Algorithm is the algorithm of the secret. + Algorithm string `json:"algorithm"` + + // BitLength is the bit length of the secret. + BitLength int `json:"bit_length"` + + // Expiration is the expiration date of the order. + Expiration *time.Time `json:"-"` + + // Mode is the mode of the secret. + Mode string `json:"mode"` + + // Name is the name of the secret. + Name string `json:"name,omitempty"` + + // PayloadContentType is the content type of the secret payload. + PayloadContentType string `json:"payload_content_type,omitempty"` +} + +// CreateOpts provides options used to create a orders. +type CreateOpts struct { + // Type is the type of order to create. + Type OrderType `json:"type"` + + // Meta contains secrets data to create a secret. + Meta MetaOpts `json:"meta"` +} + +// ToOrderCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToOrderCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.Meta.Expiration != nil { + meta := b["meta"].(map[string]interface{}) + meta["expiration"] = opts.Meta.Expiration.Format(gophercloud.RFC3339NoZ) + b["meta"] = meta + } + + return b, nil +} + +// Create creates a new orders. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToOrderCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// Delete deletes a orders. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/results.go new file mode 100644 index 000000000000..35e89b50ba56 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/results.go @@ -0,0 +1,170 @@ +package orders + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Order represents an order in the key manager service. +type Order struct { + // ContainerRef is the container URL. + ContainerRef string `json:"container_ref"` + + // Created is when the order was created. + Created time.Time `json:"-"` + + // CreatorID is the creator of the order. + CreatorID string `json:"creator_id"` + + // ErrorReason is the reason of the error. + ErrorReason string `json:"error_reason"` + + // ErrorStatusCode is the error status code. + ErrorStatusCode string `json:"error_status_code"` + + // OrderRef is the order URL. + OrderRef string `json:"order_ref"` + + // Meta is secret data about the order. + Meta Meta `json:"meta"` + + // SecretRef is the secret URL. + SecretRef string `json:"secret_ref"` + + // Status is the status of the order. + Status string `json:"status"` + + // SubStatus is the status of the order. + SubStatus string `json:"sub_status"` + + // SubStatusMessage is the message of the sub status. + SubStatusMessage string `json:"sub_status_message"` + + // Type is the order type. + Type string `json:"type"` + + // Updated is when the order was updated. + Updated time.Time `json:"-"` +} + +func (r *Order) UnmarshalJSON(b []byte) error { + type tmp Order + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + Updated gophercloud.JSONRFC3339NoZ `json:"updated"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Order(s.tmp) + + r.Created = time.Time(s.Created) + r.Updated = time.Time(s.Updated) + + return nil +} + +type Meta struct { + // Algorithm is the algorithm of the secret. + Algorithm string `json:"algorithm"` + + // BitLength is the bit length of the secret. + BitLength int `json:"bit_length"` + + // Expiration is the expiration date of the order. + Expiration time.Time `json:"-"` + + // Mode is the mode of the secret. + Mode string `json:"mode"` + + // Name is the name of the secret. + Name string `json:"name"` + + // PayloadContentType is the content type of the secret payload. + PayloadContentType string `json:"payload_content_type"` +} + +func (r *Meta) UnmarshalJSON(b []byte) error { + type tmp Meta + var s struct { + tmp + Expiration gophercloud.JSONRFC3339NoZ `json:"expiration"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Meta(s.tmp) + + r.Expiration = time.Time(s.Expiration) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a orders. +type GetResult struct { + commonResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a orders. +type CreateResult struct { + commonResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// OrderPage is a single page of orders results. +type OrderPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of ordersS contains any results. +func (r OrderPage) IsEmpty() (bool, error) { + orders, err := ExtractOrders(r) + return len(orders) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r OrderPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + Previous string `json:"previous"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, err +} + +// ExtractOrders returns a slice of Orders contained in a single page of +// results. +func ExtractOrders(r pagination.Page) ([]Order, error) { + var s struct { + Orders []Order `json:"orders"` + } + err := (r.(OrderPage)).ExtractInto(&s) + return s.Orders, err +} + +// Extract interprets any commonResult as a Order. +func (r commonResult) Extract() (*Order, error) { + var s *Order + err := r.ExtractInto(&s) + return s, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/fixtures.go new file mode 100644 index 000000000000..bdab5427b0e2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/fixtures.go @@ -0,0 +1,186 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListResponse provides a single page of RESOURCE results. +const ListResponse = ` +{ + "orders": [ + { + "created": "2018-06-22T05:05:43", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "meta": { + "algorithm": "aes", + "bit_length": 256, + "expiration": null, + "mode": "cbc", + "name": null, + "payload_content_type": "application/octet-stream" + }, + "order_ref": "http://barbican:9311/v1/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", + "secret_ref": "http://barbican:9311/v1/secrets/22dfef44-1046-4549-a86d-95af462e8fa0", + "status": "ACTIVE", + "sub_status": "Unknown", + "sub_status_message": "Unknown", + "type": "key", + "updated": "2018-06-22T05:05:43" + }, + { + "created": "2018-06-22T05:08:15", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "meta": { + "algorithm": "aes", + "bit_length": 256, + "expiration": null, + "mode": "cbc", + "name": null, + "payload_content_type": "application/octet-stream" + }, + "order_ref": "http://barbican:9311/v1/orders/07fba88b-3dcf-44e3-a4a3-0bad7f56f01c", + "secret_ref": "http://barbican:9311/v1/secrets/a31ad551-1aa5-4ba0-810e-0865163e0fa9", + "status": "ACTIVE", + "sub_status": "Unknown", + "sub_status_message": "Unknown", + "type": "key", + "updated": "2018-06-22T05:08:15" + } + ], + "total": 2 +}` + +// GetResponse provides a Get result. +const GetResponse = ` +{ + "created": "2018-06-22T05:08:15", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "meta": { + "algorithm": "aes", + "bit_length": 256, + "expiration": null, + "mode": "cbc", + "name": null, + "payload_content_type": "application/octet-stream" + }, + "order_ref": "http://barbican:9311/v1/orders/07fba88b-3dcf-44e3-a4a3-0bad7f56f01c", + "secret_ref": "http://barbican:9311/v1/secrets/a31ad551-1aa5-4ba0-810e-0865163e0fa9", + "status": "ACTIVE", + "sub_status": "Unknown", + "sub_status_message": "Unknown", + "type": "key", + "updated": "2018-06-22T05:08:15" +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "meta": { + "algorithm": "aes", + "bit_length": 256, + "mode": "cbc", + "payload_content_type": "application/octet-stream" + }, + "type": "key" +}` + +// FirstOrder is the first resource in the List request. +var FirstOrder = orders.Order{ + Created: time.Date(2018, 6, 22, 5, 5, 43, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Meta: orders.Meta{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + PayloadContentType: "application/octet-stream", + }, + OrderRef: "http://barbican:9311/v1/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", + SecretRef: "http://barbican:9311/v1/secrets/22dfef44-1046-4549-a86d-95af462e8fa0", + Status: "ACTIVE", + SubStatus: "Unknown", + SubStatusMessage: "Unknown", + Type: "key", + Updated: time.Date(2018, 6, 22, 5, 5, 43, 0, time.UTC), +} + +// SecondOrder is the second resource in the List request. +var SecondOrder = orders.Order{ + Created: time.Date(2018, 6, 22, 5, 8, 15, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Meta: orders.Meta{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + PayloadContentType: "application/octet-stream", + }, + OrderRef: "http://barbican:9311/v1/orders/07fba88b-3dcf-44e3-a4a3-0bad7f56f01c", + SecretRef: "http://barbican:9311/v1/secrets/a31ad551-1aa5-4ba0-810e-0865163e0fa9", + Status: "ACTIVE", + SubStatus: "Unknown", + SubStatusMessage: "Unknown", + Type: "key", + Updated: time.Date(2018, 6, 22, 5, 8, 15, 0, time.UTC), +} + +// ExpectedOrdersSlice is the slice of orders expected to be returned from ListResponse. +var ExpectedOrdersSlice = []orders.Order{FirstOrder, SecondOrder} + +// HandleListOrdersSuccessfully creates an HTTP handler at `/orders` on the +// test handler mux that responds with a list of two orders. +func HandleListOrdersSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleGetOrderSuccessfully creates an HTTP handler at `/orders` on the +// test handler mux that responds with a single resource. +func HandleGetOrderSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleCreateOrderSuccessfully creates an HTTP handler at `/orders` on the +// test handler mux that tests resource creation. +func HandleCreateOrderSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusAccepted) + fmt.Fprintf(w, GetResponse) + }) +} + +// HandleDeleteOrderSuccessfully creates an HTTP handler at `/orders` on the +// test handler mux that tests resource deletion. +func HandleDeleteOrderSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/requests_test.go new file mode 100644 index 000000000000..5c9fcb313354 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/testing/requests_test.go @@ -0,0 +1,81 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListOrders(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListOrdersSuccessfully(t) + + count := 0 + err := orders.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := orders.ExtractOrders(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedOrdersSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, count, 1) +} + +func TestListOrdersAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListOrdersSuccessfully(t) + + allPages, err := orders.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := orders.ExtractOrders(allPages) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedOrdersSlice, actual) +} + +func TestGetOrder(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetOrderSuccessfully(t) + + actual, err := orders.Get(client.ServiceClient(), "46f73695-82bb-447a-bf96-6635f0fb0ce7").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, SecondOrder, *actual) +} + +func TestCreateOrder(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateOrderSuccessfully(t) + + createOpts := orders.CreateOpts{ + Type: orders.KeyOrder, + Meta: orders.MetaOpts{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + PayloadContentType: "application/octet-stream", + }, + } + + actual, err := orders.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, SecondOrder, *actual) +} + +func TestDeleteOrder(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteOrderSuccessfully(t) + + res := orders.Delete(client.ServiceClient(), "46f73695-82bb-447a-bf96-6635f0fb0ce7") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/urls.go new file mode 100644 index 000000000000..36890e533ac2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/orders/urls.go @@ -0,0 +1,19 @@ +package orders + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("orders") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("orders", id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("orders") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("orders", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/doc.go new file mode 100644 index 000000000000..4167e425d21c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/doc.go @@ -0,0 +1,142 @@ +/* +Package secrets manages and retrieves secrets in the OpenStack Key Manager +Service. + +Example to List Secrets + + createdQuery := &secrets.DateQuery{ + Date: time.Date(2049, 6, 7, 1, 2, 3, 0, time.UTC), + Filter: secrets.DateFilterLT, + } + + listOpts := secrets.ListOpts{ + CreatedQuery: createdQuery, + } + + allPages, err := secrets.List(client, listOpts).AllPages() + if err != nil { + panic(err) + } + + allSecrets, err := secrets.ExtractSecrets(allPages) + if err != nil { + panic(err) + } + + for _, v := range allSecrets { + fmt.Printf("%v\n", v) + } + +Example to Get a Secret + + secret, err := secrets.Get(client, secretID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", secret) + +Example to Get a Payload + + payload, err := secrets.GetPayload(client, secretID).Extract() + if err != nil { + panic(err) + } + + fmt.Println(string(payload)) + +Example to Create a Secrets + + createOpts := secrets.CreateOpts{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + Name: "mysecret", + Payload: "super-secret", + PayloadContentType: "text/plain", + SecretType: secrets.OpaqueSecret, + } + + secret, err := secrets.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Println(secret.SecretRef) + +Example to Add a Payload + + updateOpts := secrets.UpdateOpts{ + ContentType: "text/plain", + Payload: "super-secret", + } + + err := secrets.Update(client, secretID, updateOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Delete a Secrets + + err := secrets.Delete(client, secretID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Create Metadata for a Secret + + createOpts := secrets.MetadataOpts{ + "foo": "bar", + "something": "something else", + } + + ref, err := secrets.CreateMetadata(client, secretID, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", ref) + +Example to Get Metadata for a Secret + + metadata, err := secrets.GetMetadata(client, secretID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", metadata) + +Example to Add Metadata to a Secret + + metadatumOpts := secrets.MetadatumOpts{ + Key: "foo", + Value: "bar", + } + + err := secrets.CreateMetadatum(client, secretID, metadatumOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update Metadata of a Secret + + metadatumOpts := secrets.MetadatumOpts{ + Key: "foo", + Value: "bar", + } + + metadatum, err := secrets.UpdateMetadatum(client, secretID, metadatumOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%v\n", metadatum) + +Example to Delete Metadata of a Secret + + err := secrets.DeleteMetadatum(client, secretID, "foo").ExtractErr() + if err != nil { + panic(err) + } +*/ +package secrets diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/requests.go new file mode 100644 index 000000000000..1b1dfc5b82b3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/requests.go @@ -0,0 +1,424 @@ +package secrets + +import ( + "fmt" + "net/url" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// DateFilter represents a valid filter to use for filtering +// secrets by their date during a list. +type DateFilter string + +const ( + DateFilterGT DateFilter = "gt" + DateFilterGTE DateFilter = "gte" + DateFilterLT DateFilter = "lt" + DateFilterLTE DateFilter = "lte" +) + +// DateQuery represents a date field to be used for listing secrets. +// If no filter is specified, the query will act as if "equal" is used. +type DateQuery struct { + Date time.Time + Filter DateFilter +} + +// SecretType represents a valid secret type. +type SecretType string + +const ( + SymmetricSecret SecretType = "symmetric" + PublicSecret SecretType = "public" + PrivateSecret SecretType = "private" + PassphraseSecret SecretType = "passphrase" + CertificateSecret SecretType = "certificate" + OpaqueSecret SecretType = "opaque" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToSecretListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // Offset is the starting index within the total list of the secrets that + // you would like to retrieve. + Offset int `q:"offset"` + + // Limit is the maximum number of records to return. + Limit int `q:"limit"` + + // Name will select all secrets with a matching name. + Name string `q:"name"` + + // Alg will select all secrets with a matching algorithm. + Alg string `q:"alg"` + + // Mode will select all secrets with a matching mode. + Mode string `q:"mode"` + + // Bits will select all secrets with a matching bit length. + Bits int `q:"bits"` + + // SecretType will select all secrets with a matching secret type. + SecretType SecretType `q:"secret_type"` + + // ACLOnly will select all secrets with an ACL that contains the user. + ACLOnly *bool `q:"acl_only"` + + // CreatedQuery will select all secrets with a created date matching + // the query. + CreatedQuery *DateQuery + + // UpdatedQuery will select all secrets with an updated date matching + // the query. + UpdatedQuery *DateQuery + + // ExpirationQuery will select all secrets with an expiration date + // matching the query. + ExpirationQuery *DateQuery + + // Sort will sort the results in the requested order. + Sort string `q:"sort"` +} + +// ToSecretListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToSecretListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + params := q.Query() + + if opts.CreatedQuery != nil { + created := opts.CreatedQuery.Date.Format(time.RFC3339) + if v := opts.CreatedQuery.Filter; v != "" { + created = fmt.Sprintf("%s:%s", v, created) + } + + params.Add("created", created) + } + + if opts.UpdatedQuery != nil { + updated := opts.UpdatedQuery.Date.Format(time.RFC3339) + if v := opts.UpdatedQuery.Filter; v != "" { + updated = fmt.Sprintf("%s:%s", v, updated) + } + + params.Add("updated", updated) + } + + if opts.ExpirationQuery != nil { + expiration := opts.ExpirationQuery.Date.Format(time.RFC3339) + if v := opts.ExpirationQuery.Filter; v != "" { + expiration = fmt.Sprintf("%s:%s", v, expiration) + } + + params.Add("expiration", expiration) + } + + q = &url.URL{RawQuery: params.Encode()} + + return q.String(), err +} + +// List retrieves a list of Secrets. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToSecretListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return SecretPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details of a secrets. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// GetPayloadOpts represents options used for obtaining a payload. +type GetPayloadOpts struct { + PayloadContentType string `h:"Accept"` +} + +// GetPayloadOptsBuilder allows extensions to add additional parameters to +// the GetPayload request. +type GetPayloadOptsBuilder interface { + ToSecretPayloadGetParams() (map[string]string, error) +} + +// ToSecretPayloadGetParams formats a GetPayloadOpts into a query string. +func (opts GetPayloadOpts) ToSecretPayloadGetParams() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + +// GetPayload retrieves the payload of a secret. +func GetPayload(client *gophercloud.ServiceClient, id string, opts GetPayloadOptsBuilder) (r PayloadResult) { + h := map[string]string{"Accept": "text/plain"} + + if opts != nil { + headers, err := opts.ToSecretPayloadGetParams() + if err != nil { + r.Err = err + return + } + for k, v := range headers { + h[k] = v + } + } + + url := payloadURL(client, id) + resp, err := client.Get(url, nil, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200}, + }) + + if resp != nil { + r.Header = resp.Header + r.Body = resp.Body + } + r.Err = err + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToSecretCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a secrets. +type CreateOpts struct { + // Algorithm is the algorithm of the secret. + Algorithm string `json:"algorithm,omitempty"` + + // BitLength is the bit length of the secret. + BitLength int `json:"bit_length,omitempty"` + + // Mode is the mode of encryption for the secret. + Mode string `json:"mode,omitempty"` + + // Name is the name of the secret + Name string `json:"name,omitempty"` + + // Payload is the secret. + Payload string `json:"payload,omitempty"` + + // PayloadContentType is the content type of the payload. + PayloadContentType string `json:"payload_content_type,omitempty"` + + // PayloadContentEncoding is the content encoding of the payload. + PayloadContentEncoding string `json:"payload_content_encoding,omitempty"` + + // SecretType is the type of secret. + SecretType SecretType `json:"secret_type,omitempty"` + + // Expiration is the expiration date of the secret. + Expiration *time.Time `json:"-"` +} + +// ToSecretCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToSecretCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.Expiration != nil { + b["expiration"] = opts.Expiration.Format(gophercloud.RFC3339NoZ) + } + + return b, nil +} + +// Create creates a new secrets. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSecretCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete deletes a secrets. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToSecretUpdateRequest() (string, map[string]string, error) +} + +// UpdateOpts represents parameters to add a payload to an existing +// secret which does not already contain a payload. +type UpdateOpts struct { + // ContentType represents the content type of the payload. + ContentType string `h:"Content-Type"` + + // ContentEncoding represents the content encoding of the payload. + ContentEncoding string `h:"Content-Encoding"` + + // Payload is the payload of the secret. + Payload string +} + +// ToUpdateCreateRequest formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToSecretUpdateRequest() (string, map[string]string, error) { + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return "", nil, err + } + + return opts.Payload, h, nil +} + +// Update modifies the attributes of a secrets. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + url := updateURL(client, id) + h := make(map[string]string) + var b string + + if opts != nil { + payload, headers, err := opts.ToSecretUpdateRequest() + if err != nil { + r.Err = err + return + } + + for k, v := range headers { + h[k] = v + } + + b = payload + } + + resp, err := client.Put(url, nil, nil, &gophercloud.RequestOpts{ + RawBody: strings.NewReader(b), + MoreHeaders: h, + OkCodes: []int{204}, + }) + r.Err = err + if resp != nil { + r.Header = resp.Header + } + + return +} + +// GetMetadata will list metadata for a given secret. +func GetMetadata(client *gophercloud.ServiceClient, secretID string) (r MetadataResult) { + _, r.Err = client.Get(metadataURL(client, secretID), &r.Body, nil) + return +} + +// MetadataOpts is a map that contains key-value pairs for secret metadata. +type MetadataOpts map[string]string + +// CreateMetadataOptsBuilder allows extensions to add additional parameters to +// the CreateMetadata request. +type CreateMetadataOptsBuilder interface { + ToMetadataCreateMap() (map[string]interface{}, error) +} + +// ToMetadataCreateMap converts a MetadataOpts into a request body. +func (opts MetadataOpts) ToMetadataCreateMap() (map[string]interface{}, error) { + return map[string]interface{}{"metadata": opts}, nil +} + +// CreateMetadata will set metadata for a given secret. +func CreateMetadata(client *gophercloud.ServiceClient, secretID string, opts CreateMetadataOptsBuilder) (r MetadataCreateResult) { + b, err := opts.ToMetadataCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(metadataURL(client, secretID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// GetMetadatum will get a single key/value metadata from a secret. +func GetMetadatum(client *gophercloud.ServiceClient, secretID string, key string) (r MetadatumResult) { + _, r.Err = client.Get(metadatumURL(client, secretID, key), &r.Body, nil) + return +} + +// MetadatumOpts represents a single metadata. +type MetadatumOpts struct { + Key string `json:"key" required:"true"` + Value string `json:"value" required:"true"` +} + +// CreateMetadatumOptsBuilder allows extensions to add additional parameters to +// the CreateMetadatum request. +type CreateMetadatumOptsBuilder interface { + ToMetadatumCreateMap() (map[string]interface{}, error) +} + +// ToMetadatumCreateMap converts a MetadatumOpts into a request body. +func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// CreateMetadatum will add a single key/value metadata to a secret. +func CreateMetadatum(client *gophercloud.ServiceClient, secretID string, opts CreateMetadatumOptsBuilder) (r MetadatumCreateResult) { + b, err := opts.ToMetadatumCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(metadataURL(client, secretID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateMetadatumOptsBuilder allows extensions to add additional parameters to +// the UpdateMetadatum request. +type UpdateMetadatumOptsBuilder interface { + ToMetadatumUpdateMap() (map[string]interface{}, string, error) +} + +// ToMetadatumUpdateMap converts a MetadataOpts into a request body. +func (opts MetadatumOpts) ToMetadatumUpdateMap() (map[string]interface{}, string, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + return b, opts.Key, err +} + +// UpdateMetadatum will update a single key/value metadata to a secret. +func UpdateMetadatum(client *gophercloud.ServiceClient, secretID string, opts UpdateMetadatumOptsBuilder) (r MetadatumResult) { + b, key, err := opts.ToMetadatumUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(metadatumURL(client, secretID, key), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteMetadatum will delete an individual metadatum from a secret. +func DeleteMetadatum(client *gophercloud.ServiceClient, secretID string, key string) (r MetadatumDeleteResult) { + _, r.Err = client.Delete(metadatumURL(client, secretID, key), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/results.go new file mode 100644 index 000000000000..a3230a8b4fbd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/results.go @@ -0,0 +1,227 @@ +package secrets + +import ( + "encoding/json" + "io" + "io/ioutil" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Secret represents a secret stored in the key manager service. +type Secret struct { + // BitLength is the bit length of the secret. + BitLength int `json:"bit_length"` + + // Algorithm is the algorithm type of the secret. + Algorithm string `json:"algorithm"` + + // Expiration is the expiration date of the secret. + Expiration time.Time `json:"-"` + + // ContentTypes are the content types of the secret. + ContentTypes map[string]string `json:"content_types"` + + // Created is the created date of the secret. + Created time.Time `json:"-"` + + // CreatorID is the creator of the secret. + CreatorID string `json:"creator_id"` + + // Mode is the mode of the secret. + Mode string `json:"mode"` + + // Name is the name of the secret. + Name string `json:"name"` + + // SecretRef is the URL to the secret. + SecretRef string `json:"secret_ref"` + + // SecretType represents the type of secret. + SecretType string `json:"secret_type"` + + // Status represents the status of the secret. + Status string `json:"status"` + + // Updated is the updated date of the secret. + Updated time.Time `json:"-"` +} + +func (r *Secret) UnmarshalJSON(b []byte) error { + type tmp Secret + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + Updated gophercloud.JSONRFC3339NoZ `json:"updated"` + Expiration gophercloud.JSONRFC3339NoZ `json:"expiration"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Secret(s.tmp) + + r.Created = time.Time(s.Created) + r.Updated = time.Time(s.Updated) + r.Expiration = time.Time(s.Expiration) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// Extract interprets any commonResult as a Secret. +func (r commonResult) Extract() (*Secret, error) { + var s *Secret + err := r.ExtractInto(&s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a secrets. +type GetResult struct { + commonResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a secrets. +type CreateResult struct { + commonResult +} + +// UpdateResult is the response from an Update operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type UpdateResult struct { + gophercloud.ErrResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// PayloadResult is the response from a GetPayload operation. Call its Extract +// method to extract the payload as a string. +type PayloadResult struct { + gophercloud.Result + Body io.ReadCloser +} + +// Extract is a function that takes a PayloadResult's io.Reader body +// and reads all available data into a slice of bytes. Please be aware that due +// to the nature of io.Reader is forward-only - meaning that it can only be read +// once and not rewound. You can recreate a reader from the output of this +// function by using bytes.NewReader(downloadBytes) +func (r PayloadResult) Extract() ([]byte, error) { + if r.Err != nil { + return nil, r.Err + } + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + r.Body.Close() + return body, nil +} + +// SecretPage is a single page of secrets results. +type SecretPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of secrets contains any results. +func (r SecretPage) IsEmpty() (bool, error) { + secrets, err := ExtractSecrets(r) + return len(secrets) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r SecretPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + Previous string `json:"previous"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, err +} + +// ExtractSecrets returns a slice of Secrets contained in a single page of +// results. +func ExtractSecrets(r pagination.Page) ([]Secret, error) { + var s struct { + Secrets []Secret `json:"secrets"` + } + err := (r.(SecretPage)).ExtractInto(&s) + return s.Secrets, err +} + +// MetadataResult is the result of a metadata request. Call its Extract method +// to interpret it as a map[string]string. +type MetadataResult struct { + gophercloud.Result +} + +// Extract interprets any MetadataResult as map[string]string. +func (r MetadataResult) Extract() (map[string]string, error) { + var s struct { + Metadata map[string]string `json:"metadata"` + } + err := r.ExtractInto(&s) + return s.Metadata, err +} + +// MetadataCreateResult is the result of a metadata create request. Call its +// Extract method to interpret it as a map[string]string. +type MetadataCreateResult struct { + gophercloud.Result +} + +// Extract interprets any MetadataCreateResult as a map[string]string. +func (r MetadataCreateResult) Extract() (map[string]string, error) { + var s map[string]string + err := r.ExtractInto(&s) + return s, err +} + +// Metadatum represents an individual metadata. +type Metadatum struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// MetadatumResult is the result of a metadatum request. Call its +// Extract method to interpret it as a map[string]string. +type MetadatumResult struct { + gophercloud.Result +} + +// Extract interprets any MetadatumResult as a map[string]string. +func (r MetadatumResult) Extract() (*Metadatum, error) { + var s *Metadatum + err := r.ExtractInto(&s) + return s, err +} + +// MetadatumCreateResult is the response from a metadata Create operation. Call +// it's ExtractErr to determine if the request succeeded or failed. +// +// NOTE: This could be a MetadatumResponse but, at the time of testing, it looks +// like Barbican was returning errneous JSON in the response. +type MetadatumCreateResult struct { + gophercloud.ErrResult +} + +// MetadatumDeleteResult is the response from a metadatum Delete operation. Call +// its ExtractErr to determine if the request succeeded or failed. +type MetadatumDeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/fixtures.go new file mode 100644 index 000000000000..f62fa8360827 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/fixtures.go @@ -0,0 +1,356 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListResponse provides a single page of RESOURCE results. +const ListResponse = ` +{ + "secrets": [ + { + "algorithm": "aes", + "bit_length": 256, + "content_types": { + "default": "text/plain" + }, + "created": "2018-06-21T02:49:48", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "expiration": null, + "mode": "cbc", + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", + "secret_type": "opaque", + "status": "ACTIVE", + "updated": "2018-06-21T02:49:48" + }, + { + "algorithm": "aes", + "bit_length": 256, + "content_types": { + "default": "text/plain" + }, + "created": "2018-06-21T05:18:45", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "expiration": null, + "mode": "cbc", + "name": "anothersecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b12b69a-8822-442e-a303-da24ade648ac", + "secret_type": "opaque", + "status": "ACTIVE", + "updated": "2018-06-21T05:18:45" + } + ], + "total": 2 +}` + +// GetResponse provides a Get result. +const GetResponse = ` +{ + "algorithm": "aes", + "bit_length": 256, + "content_types": { + "default": "text/plain" + }, + "created": "2018-06-21T02:49:48", + "creator_id": "5c70d99f4a8641c38f8084b32b5e5c0e", + "expiration": null, + "mode": "cbc", + "name": "mysecret", + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", + "secret_type": "opaque", + "status": "ACTIVE", + "updated": "2018-06-21T02:49:48" +}` + +// GetPayloadResponse provides a payload result. +const GetPayloadResponse = `foobar` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "algorithm": "aes", + "bit_length": 256, + "mode": "cbc", + "name": "mysecret", + "payload": "foobar", + "payload_content_type": "text/plain", + "secret_type": "opaque", + "expiration": "2028-06-21T02:49:48" +}` + +// CreateResponse provides a Create result. +const CreateResponse = ` +{ + "secret_ref": "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c" +}` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = `foobar` + +// FirstSecret is the first secret in the List request. +var FirstSecret = secrets.Secret{ + Algorithm: "aes", + BitLength: 256, + ContentTypes: map[string]string{ + "default": "text/plain", + }, + Created: time.Date(2018, 6, 21, 2, 49, 48, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Mode: "cbc", + Name: "mysecret", + SecretRef: "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", + SecretType: "opaque", + Status: "ACTIVE", + Updated: time.Date(2018, 6, 21, 2, 49, 48, 0, time.UTC), +} + +// SecondSecret is the second secret in the List request. +var SecondSecret = secrets.Secret{ + Algorithm: "aes", + BitLength: 256, + ContentTypes: map[string]string{ + "default": "text/plain", + }, + Created: time.Date(2018, 6, 21, 5, 18, 45, 0, time.UTC), + CreatorID: "5c70d99f4a8641c38f8084b32b5e5c0e", + Mode: "cbc", + Name: "anothersecret", + SecretRef: "http://barbican:9311/v1/secrets/1b12b69a-8822-442e-a303-da24ade648ac", + SecretType: "opaque", + Status: "ACTIVE", + Updated: time.Date(2018, 6, 21, 5, 18, 45, 0, time.UTC), +} + +// ExpectedSecretsSlice is the slice of secrets expected to be returned from ListResponse. +var ExpectedSecretsSlice = []secrets.Secret{FirstSecret, SecondSecret} + +// ExpectedCreateResult is the result of a create request +var ExpectedCreateResult = secrets.Secret{ + SecretRef: "http://barbican:9311/v1/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", +} + +const GetMetadataResponse = ` +{ + "metadata": { + "foo": "bar", + "something": "something else" + } +}` + +// ExpectedMetadata is the result of a Get or Create request. +var ExpectedMetadata = map[string]string{ + "foo": "bar", + "something": "something else", +} + +const CreateMetadataRequest = ` +{ + "metadata": { + "foo": "bar", + "something": "something else" + } +}` + +const CreateMetadataResponse = ` +{ + "metadata_ref": "http://barbican:9311/v1/secrets/1b12b69a-8822-442e-a303-da24ade648ac/metadata" +}` + +// ExpectedCreateMetadataResult is the result of a Metadata create request. +var ExpectedCreateMetadataResult = map[string]string{ + "metadata_ref": "http://barbican:9311/v1/secrets/1b12b69a-8822-442e-a303-da24ade648ac/metadata", +} + +const MetadatumRequest = ` +{ + "key": "foo", + "value": "bar" +}` + +const MetadatumResponse = ` +{ + "key": "foo", + "value": "bar" +}` + +// ExpectedMetadatum is the result of a Metadatum Get, Create, or Update +// request +var ExpectedMetadatum = secrets.Metadatum{ + Key: "foo", + Value: "bar", +} + +// HandleListSecretsSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that responds with a list of two secrets. +func HandleListSecretsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleGetSecretSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that responds with a single secret. +func HandleGetSecretSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { + 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) + }) +} + +// HandleGetPayloadSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that responds with a single secret. +func HandleGetPayloadSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/payload", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetPayloadResponse) + }) +} + +// HandleCreateSecretSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret creation. +func HandleCreateSecretSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, CreateResponse) + }) +} + +// HandleDeleteSecretSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret deletion. +func HandleDeleteSecretSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleUpdateSecretSuccessfully creates an HTTP handler at `/secrets` on the +// test handler mux that tests secret updates. +func HandleUpdateSecretSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleGetMetadataSuccessfully creates an HTTP handler at +// `/secrets/uuid/metadata` on the test handler mux that responds with +// retrieved metadata. +func HandleGetMetadataSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetMetadataResponse) + }) +} + +// HandleCreateMetadataSuccessfully creates an HTTP handler at +// `/secrets/uuid/metadata` on the test handler mux that responds with +// a metadata reference URL. +func HandleCreateMetadataSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateMetadataRequest) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, CreateMetadataResponse) + }) +} + +// HandleGetMetadatumSuccessfully creates an HTTP handler at +// `/secrets/uuid/metadata/foo` on the test handler mux that responds with a +// single metadatum. +func HandleGetMetadatumSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, MetadatumResponse) + }) +} + +// HandleCreateMetadatumSuccessfully creates an HTTP handler at +// `/secrets/uuid/metadata` on the test handler mux that responds with +// a single created metadata. +func HandleCreateMetadatumSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, MetadatumRequest) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, MetadatumResponse) + }) +} + +// HandleUpdateMetadatumSuccessfully creates an HTTP handler at +// `/secrets/uuid/metadata/foo` on the test handler mux that responds with a +// single updated metadatum. +func HandleUpdateMetadatumSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, MetadatumRequest) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, MetadatumResponse) + }) +} + +// HandleDeleteMetadatumSuccessfully creates an HTTP handler at +// `/secrets/uuid/metadata/key` on the test handler mux that tests metadata +// deletion. +func HandleDeleteMetadatumSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/requests_test.go new file mode 100644 index 000000000000..0b486f800d9f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/testing/requests_test.go @@ -0,0 +1,182 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListSecrets(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSecretsSuccessfully(t) + + count := 0 + err := secrets.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := secrets.ExtractSecrets(page) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, ExpectedSecretsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, count, 1) +} + +func TestListSecretsAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSecretsSuccessfully(t) + + allPages, err := secrets.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := secrets.ExtractSecrets(allPages) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedSecretsSlice, actual) +} + +func TestGetSecret(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSecretSuccessfully(t) + + actual, err := secrets.Get(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, FirstSecret, *actual) +} + +func TestCreateSecret(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSecretSuccessfully(t) + + expiration := time.Date(2028, 6, 21, 2, 49, 48, 0, time.UTC) + createOpts := secrets.CreateOpts{ + Algorithm: "aes", + BitLength: 256, + Mode: "cbc", + Name: "mysecret", + Payload: "foobar", + PayloadContentType: "text/plain", + SecretType: secrets.OpaqueSecret, + Expiration: &expiration, + } + + actual, err := secrets.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCreateResult, *actual) +} + +func TestDeleteSecret(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSecretSuccessfully(t) + + res := secrets.Delete(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateSecret(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateSecretSuccessfully(t) + + updateOpts := secrets.UpdateOpts{ + Payload: "foobar", + } + + err := secrets.Update(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetPayloadSecret(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetPayloadSuccessfully(t) + + res := secrets.GetPayload(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", nil) + th.AssertNoErr(t, res.Err) + payload, err := res.Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, GetPayloadResponse, string(payload)) +} + +func TestGetMetadataSuccessfully(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetMetadataSuccessfully(t) + + actual, err := secrets.GetMetadata(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedMetadata, actual) +} + +func TestCreateMetadataSuccessfully(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateMetadataSuccessfully(t) + + createOpts := secrets.MetadataOpts{ + "foo": "bar", + "something": "something else", + } + + actual, err := secrets.CreateMetadata(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", createOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedCreateMetadataResult, actual) +} + +func TestGetMetadatumSuccessfully(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetMetadatumSuccessfully(t) + + actual, err := secrets.GetMetadatum(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", "foo").Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedMetadatum, *actual) +} + +func TestCreateMetadatumSuccessfully(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateMetadatumSuccessfully(t) + + createOpts := secrets.MetadatumOpts{ + Key: "foo", + Value: "bar", + } + + err := secrets.CreateMetadatum(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", createOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUpdateMetadatumSuccessfully(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateMetadatumSuccessfully(t) + + updateOpts := secrets.MetadatumOpts{ + Key: "foo", + Value: "bar", + } + + actual, err := secrets.UpdateMetadatum(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedMetadatum, *actual) +} + +func TestDeleteMetadatumSuccessfully(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteMetadatumSuccessfully(t) + + err := secrets.DeleteMetadatum(client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", "foo").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/urls.go new file mode 100644 index 000000000000..ebd636a66e8a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/keymanager/v1/secrets/urls.go @@ -0,0 +1,35 @@ +package secrets + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("secrets") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("secrets", id) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("secrets") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("secrets", id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("secrets", id) +} + +func payloadURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("secrets", id, "payload") +} + +func metadataURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("secrets", id, "metadata") +} + +func metadatumURL(client *gophercloud.ServiceClient, id, key string) string { + return client.ServiceURL("secrets", id, "metadata", key) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/doc.go new file mode 100644 index 000000000000..61b0b8b6b279 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/doc.go @@ -0,0 +1,25 @@ +/* +Package amphorae provides information and interaction with Amphorae +of OpenStack Load-balancing service. + +Example to List Amphorae + + listOpts := amphorae.ListOpts{ + LoadbalancerID: "6bd55cd3-802e-447e-a518-1e74e23bb106", + } + + allPages, err := amphorae.List(octaviaClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allAmphorae, err := amphorae.ExtractAmphorae(allPages) + if err != nil { + panic(err) + } + + for _, amphora := range allAmphorae { + fmt.Printf("%+v\n", amphora) + } +*/ +package amphorae diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/requests.go new file mode 100644 index 000000000000..557d052fb634 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/requests.go @@ -0,0 +1,59 @@ +package amphorae + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAmphoraListQuery() (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 Amphorae attributes you want to see returned. SortKey allows you to +// sort by a particular attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + LoadbalancerID string `q:"loadbalancer_id"` + ImageID string `q:"image_id"` + Role string `q:"role"` + Status string `q:"status"` + HAPortID string `q:"ha_port_id"` + VRRPPortID string `q:"vrrp_port_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToAmphoraListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAmphoraListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// amphorae. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToAmphoraListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AmphoraPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a particular amphora based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/results.go new file mode 100644 index 000000000000..9560a53df107 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/results.go @@ -0,0 +1,148 @@ +package amphorae + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Amphora is virtual machine, container, dedicated hardware, appliance or device that actually performs the task of +// load balancing in the Octavia system. +type Amphora struct { + // The unique ID for the Amphora. + ID string `json:"id"` + + // The ID of the load balancer. + LoadbalancerID string `json:"loadbalancer_id"` + + // The management IP of the amphora. + LBNetworkIP string `json:"lb_network_ip"` + + // The ID of the amphora resource in the compute system. + ComputeID string `json:"compute_id"` + + // The IP address of the Virtual IP (VIP). + HAIP string `json:"ha_ip"` + + // The ID of the Virtual IP (VIP) port. + HAPortID string `json:"ha_port_id"` + + // The date the certificate for the amphora expires. + CertExpiration time.Time `json:"-"` + + // Whether the certificate is in the process of being replaced. + CertBusy bool `json:"cert_busy"` + + // The role of the amphora. One of STANDALONE, MASTER, BACKUP. + Role string `json:"role"` + + // The status of the amphora. One of: BOOTING, ALLOCATED, READY, PENDING_CREATE, PENDING_DELETE, DELETED, ERROR. + Status string `json:"status"` + + // The vrrp port’s ID in the networking system. + VRRPPortID string `json:"vrrp_port_id"` + + // The address of the vrrp port on the amphora. + VRRPIP string `json:"vrrp_ip"` + + // The bound interface name of the vrrp port on the amphora. + VRRPInterface string `json:"vrrp_interface"` + + // The vrrp group’s ID for the amphora. + VRRPID int `json:"vrrp_id"` + + // The priority of the amphora in the vrrp group. + VRRPPriority int `json:"vrrp_priority"` + + // The availability zone of a compute instance, cached at create time. This is not guaranteed to be current. May be + // an empty-string if the compute service does not use zones. + CachedZone string `json:"cached_zone"` + + // The ID of the glance image used for the amphora. + ImageID string `json:"image_id"` + + // The UTC date and timestamp when the resource was created. + CreatedAt time.Time `json:"-"` + + // The UTC date and timestamp when the resource was last updated. + UpdatedAt time.Time `json:"-"` +} + +func (a *Amphora) UnmarshalJSON(b []byte) error { + type tmp Amphora + var s struct { + tmp + CertExpiration gophercloud.JSONRFC3339NoZ `json:"cert_expiration"` + CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *a = Amphora(s.tmp) + + a.CreatedAt = time.Time(s.CreatedAt) + a.UpdatedAt = time.Time(s.UpdatedAt) + a.CertExpiration = time.Time(s.CertExpiration) + + return nil +} + +// AmphoraPage is the page returned by a pager when traversing over a +// collection of amphorae. +type AmphoraPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of amphoraes has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r AmphoraPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"amphorae_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a AmphoraPage struct is empty. +func (r AmphoraPage) IsEmpty() (bool, error) { + is, err := ExtractAmphorae(r) + return len(is) == 0, err +} + +// ExtractAmphorae accepts a Page struct, specifically a AmphoraPage +// struct, and extracts the elements into a slice of Amphora structs. In +// other words, a generic collection is mapped into a relevant slice. +func ExtractAmphorae(r pagination.Page) ([]Amphora, error) { + var s struct { + Amphorae []Amphora `json:"amphorae"` + } + err := (r.(AmphoraPage)).ExtractInto(&s) + return s.Amphorae, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an amphora. +func (r commonResult) Extract() (*Amphora, error) { + var s struct { + Amphora *Amphora `json:"amphora"` + } + err := r.ExtractInto(&s) + return s.Amphora, err +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as an amphora. +type GetResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/doc.go new file mode 100644 index 000000000000..7603f836a002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/fixtures.go new file mode 100644 index 000000000000..45744cf6bb09 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/fixtures.go @@ -0,0 +1,169 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// AmphoraeListBody contains the canned body of a amphora list response. +const AmphoraeListBody = ` +{ + "amphorae": [ + { + "cached_zone": "nova", + "cert_busy": false, + "cert_expiration": "2020-08-08T23:44:31", + "compute_id": "667bb225-69aa-44b1-8908-694dc624c267", + "created_at": "2018-08-09T23:44:31", + "ha_ip": "10.0.0.6", + "ha_port_id": "35254b63-9361-4561-9b8f-2bb4e3be60e3", + "id": "45f40289-0551-483a-b089-47214bc2a8a4", + "image_id": "5d1aed06-2624-43f5-a413-9212263c3d53", + "lb_network_ip": "192.168.0.6", + "loadbalancer_id": "882f2a9d-9d53-4bd0-b0e9-08e9d0de11f9", + "role": "MASTER", + "status": "READY", + "updated_at": "2018-08-09T23:51:06", + "vrrp_id": 1, + "vrrp_interface": "eth1", + "vrrp_ip": "10.0.0.4", + "vrrp_port_id": "dcf0c8b5-6a08-4658-997d-eac97f2b9bbd", + "vrrp_priority": 100 + }, + { + "cached_zone": "nova", + "cert_busy": false, + "cert_expiration": "2020-08-08T23:44:30", + "compute_id": "9cd0f9a2-fe12-42fc-a7e3-5b6fbbe20395", + "created_at": "2018-08-09T23:44:31", + "ha_ip": "10.0.0.6", + "ha_port_id": "35254b63-9361-4561-9b8f-2bb4e3be60e3", + "id": "7f890893-ced0-46ed-8697-33415d070e5a", + "image_id": "5d1aed06-2624-43f5-a413-9212263c3d53", + "lb_network_ip": "192.168.0.17", + "loadbalancer_id": "882f2a9d-9d53-4bd0-b0e9-08e9d0de11f9", + "role": "BACKUP", + "status": "READY", + "updated_at": "2018-08-09T23:51:06", + "vrrp_id": 1, + "vrrp_interface": "eth1", + "vrrp_ip": "10.0.0.21", + "vrrp_port_id": "13c88c77-207d-4f85-8f7a-84344592e367", + "vrrp_priority": 90 + } + ], + "amphorae_links": [] +} +` + +const SingleAmphoraBody = ` +{ + "amphora": { + "cached_zone": "nova", + "cert_busy": false, + "cert_expiration": "2020-08-08T23:44:31", + "compute_id": "667bb225-69aa-44b1-8908-694dc624c267", + "created_at": "2018-08-09T23:44:31", + "ha_ip": "10.0.0.6", + "ha_port_id": "35254b63-9361-4561-9b8f-2bb4e3be60e3", + "id": "45f40289-0551-483a-b089-47214bc2a8a4", + "image_id": "5d1aed06-2624-43f5-a413-9212263c3d53", + "lb_network_ip": "192.168.0.6", + "loadbalancer_id": "882f2a9d-9d53-4bd0-b0e9-08e9d0de11f9", + "role": "MASTER", + "status": "READY", + "updated_at": "2018-08-09T23:51:06", + "vrrp_id": 1, + "vrrp_interface": "eth1", + "vrrp_ip": "10.0.0.4", + "vrrp_port_id": "dcf0c8b5-6a08-4658-997d-eac97f2b9bbd", + "vrrp_priority": 100 + } +} +` + +// FirstAmphora is the first resource in the List request. +var FirstAmphora = amphorae.Amphora{ + CachedZone: "nova", + CertBusy: false, + CertExpiration: time.Date(2020, 8, 8, 23, 44, 31, 0, time.UTC), + ComputeID: "667bb225-69aa-44b1-8908-694dc624c267", + CreatedAt: time.Date(2018, 8, 9, 23, 44, 31, 0, time.UTC), + HAIP: "10.0.0.6", + HAPortID: "35254b63-9361-4561-9b8f-2bb4e3be60e3", + ID: "45f40289-0551-483a-b089-47214bc2a8a4", + ImageID: "5d1aed06-2624-43f5-a413-9212263c3d53", + LBNetworkIP: "192.168.0.6", + LoadbalancerID: "882f2a9d-9d53-4bd0-b0e9-08e9d0de11f9", + Role: "MASTER", + Status: "READY", + UpdatedAt: time.Date(2018, 8, 9, 23, 51, 6, 0, time.UTC), + VRRPID: 1, + VRRPInterface: "eth1", + VRRPIP: "10.0.0.4", + VRRPPortID: "dcf0c8b5-6a08-4658-997d-eac97f2b9bbd", + VRRPPriority: 100, +} + +// SecondAmphora is the second resource in the List request. +var SecondAmphora = amphorae.Amphora{ + CachedZone: "nova", + CertBusy: false, + CertExpiration: time.Date(2020, 8, 8, 23, 44, 30, 0, time.UTC), + ComputeID: "9cd0f9a2-fe12-42fc-a7e3-5b6fbbe20395", + CreatedAt: time.Date(2018, 8, 9, 23, 44, 31, 0, time.UTC), + HAIP: "10.0.0.6", + HAPortID: "35254b63-9361-4561-9b8f-2bb4e3be60e3", + ID: "7f890893-ced0-46ed-8697-33415d070e5a", + ImageID: "5d1aed06-2624-43f5-a413-9212263c3d53", + LBNetworkIP: "192.168.0.17", + LoadbalancerID: "882f2a9d-9d53-4bd0-b0e9-08e9d0de11f9", + Role: "BACKUP", + Status: "READY", + UpdatedAt: time.Date(2018, 8, 9, 23, 51, 6, 0, time.UTC), + VRRPID: 1, + VRRPInterface: "eth1", + VRRPIP: "10.0.0.21", + VRRPPortID: "13c88c77-207d-4f85-8f7a-84344592e367", + VRRPPriority: 90, +} + +// ExpectedAmphoraeSlice is the slice of amphorae expected to be returned from ListResponse. +var ExpectedAmphoraeSlice = []amphorae.Amphora{FirstAmphora, SecondAmphora} + +// HandleAmphoraListSuccessfully sets up the test server to respond to a amphorae List request. +func HandleAmphoraListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/octavia/amphorae", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, AmphoraeListBody) + case "7f890893-ced0-46ed-8697-33415d070e5a": + fmt.Fprintf(w, `{ "amphorae": [] }`) + default: + t.Fatalf("/v2.0/octavia/amphorae invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleAmphoraGetSuccessfully sets up the test server to respond to am amphora Get request. +func HandleAmphoraGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/octavia/amphorae/45f40289-0551-483a-b089-47214bc2a8a4", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleAmphoraBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/requests_test.go new file mode 100644 index 000000000000..bc500425868e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/testing/requests_test.go @@ -0,0 +1,65 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae" + fake "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListAmphorae(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAmphoraListSuccessfully(t) + + pages := 0 + err := amphorae.List(fake.ServiceClient(), amphorae.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := amphorae.ExtractAmphorae(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 amphorae, got %d", len(actual)) + } + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllAmphorae(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAmphoraListSuccessfully(t) + + allPages, err := amphorae.List(fake.ServiceClient(), amphorae.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := amphorae.ExtractAmphorae(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 2, len(actual)) + th.AssertDeepEquals(t, ExpectedAmphoraeSlice, actual) +} + +func TestGetAmphora(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAmphoraGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := amphorae.Get(client, "45f40289-0551-483a-b089-47214bc2a8a4").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, FirstAmphora, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/urls.go new file mode 100644 index 000000000000..2f9fb7e8fb77 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/amphorae/urls.go @@ -0,0 +1,16 @@ +package amphorae + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "octavia" + resourcePath = "amphorae" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/doc.go new file mode 100644 index 000000000000..d9b5eff9d9be --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/doc.go @@ -0,0 +1,22 @@ +/* +Package apiversions provides information and interaction with the different +API versions for the OpenStack Load Balancer service. This functionality is not +restricted to this particular version. + +Example to List API Versions + + allPages, err := apiversions.List(loadbalancerClient).AllPages() + if err != nil { + panic(err) + } + + allVersions, err := apiversions.ExtractAPIVersions(allPages) + if err != nil { + panic(err) + } + + for _, version := range allVersions { + fmt.Printf("%+v\n", version) + } +*/ +package apiversions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/requests.go new file mode 100644 index 000000000000..18c88de077d2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/requests.go @@ -0,0 +1,13 @@ +package apiversions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List lists all the load balancer API versions available to end-users. +func List(c *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page { + return APIVersionPage{pagination.SinglePageBase(r)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/results.go new file mode 100644 index 000000000000..b031cb82361b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/results.go @@ -0,0 +1,32 @@ +package apiversions + +import "github.com/gophercloud/gophercloud/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"` + ID string `json:"id"` +} + +// APIVersionPage is the page returned by a pager when traversing over a +// collection of API versions. +type APIVersionPage struct { + pagination.SinglePageBase +} + +// IsEmpty checks whether an APIVersionPage struct is empty. +func (r APIVersionPage) IsEmpty() (bool, error) { + is, err := ExtractAPIVersions(r) + return len(is) == 0, err +} + +// ExtractAPIVersions takes a collection page, extracts all of the elements, +// and returns them a slice of APIVersion structs. It is effectively a cast. +func ExtractAPIVersions(r pagination.Page) ([]APIVersion, error) { + var s struct { + Versions []APIVersion `json:"versions"` + } + err := (r.(APIVersionPage)).ExtractInto(&s) + return s.Versions, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/doc.go new file mode 100644 index 000000000000..cc76de0a6238 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/doc.go @@ -0,0 +1,2 @@ +// apiversions unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/fixture.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/fixture.go new file mode 100644 index 000000000000..4c78c58cbcd3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/fixture.go @@ -0,0 +1,93 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const OctaviaAllAPIVersionsResponse = ` +{ + "versions": [ + { + "id": "v1", + "links": [ + { + "href": "http://10.0.0.105:9876/v1", + "rel": "self" + } + ], + "status": "DEPRECATED", + "updated": "2014-12-11T00:00:00Z" + }, + { + "id": "v2.0", + "links": [ + { + "href": "http://10.0.0.105:9876/v2", + "rel": "self" + } + ], + "status": "SUPPORTED", + "updated": "2016-12-11T00:00:00Z" + }, + { + "id": "v2.1", + "links": [ + { + "href": "http://10.0.0.105:9876/v2", + "rel": "self" + } + ], + "status": "SUPPORTED", + "updated": "2018-04-20T00:00:00Z" + }, + { + "id": "v2.2", + "links": [ + { + "href": "http://10.0.0.105:9876/v2", + "rel": "self" + } + ], + "status": "CURRENT", + "updated": "2018-07-31T00:00:00Z" + } + ] +} +` + +var OctaviaAllAPIVersionResults = []apiversions.APIVersion{ + apiversions.APIVersion{ + ID: "v1", + Status: "DEPRECATED", + }, + apiversions.APIVersion{ + ID: "v2.0", + Status: "SUPPORTED", + }, + apiversions.APIVersion{ + ID: "v2.1", + Status: "SUPPORTED", + }, + apiversions.APIVersion{ + ID: "v2.2", + Status: "CURRENT", + }, +} + +func MockListResponse(t *testing.T) { + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, OctaviaAllAPIVersionsResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/requests_test.go new file mode 100644 index 000000000000..016c64da72fc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/testing/requests_test.go @@ -0,0 +1,24 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListVersions(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListResponse(t) + + allVersions, err := apiversions.List(client.ServiceClient()).AllPages() + th.AssertNoErr(t, err) + + actual, err := apiversions.ExtractAPIVersions(allVersions) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, OctaviaAllAPIVersionResults, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/urls.go new file mode 100644 index 000000000000..a6a35d4225c2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions/urls.go @@ -0,0 +1,14 @@ +package apiversions + +import ( + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" +) + +func listURL(c *gophercloud.ServiceClient) string { + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + return endpoint +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/doc.go new file mode 100644 index 000000000000..ec7f9d6f049a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/doc.go @@ -0,0 +1,3 @@ +// Package lbaas_v2 provides information and interaction with the Load Balancer +// as a Service v2 extension for the OpenStack Networking service. +package lbaas_v2 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/doc.go new file mode 100644 index 000000000000..813579905c25 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/doc.go @@ -0,0 +1,123 @@ +/* +Package l7policies provides information and interaction with L7Policies and +Rules of the LBaaS v2 extension for the OpenStack Networking service. + +Example to Create a L7Policy + + createOpts := l7policies.CreateOpts{ + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + } + l7policy, err := l7policies.Create(lbClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List L7Policies + + listOpts := l7policies.ListOpts{ + ListenerID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + allPages, err := l7policies.List(lbClient, listOpts).AllPages() + if err != nil { + panic(err) + } + allL7Policies, err := l7policies.ExtractL7Policies(allPages) + if err != nil { + panic(err) + } + for _, l7policy := range allL7Policies { + fmt.Printf("%+v\n", l7policy) + } + +Example to Get a L7Policy + + l7policy, err := l7policies.Get(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d").Extract() + if err != nil { + panic(err) + } + +Example to Delete a L7Policy + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := l7policies.Delete(lbClient, l7policyID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a L7Policy + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + name := "new-name" + updateOpts := l7policies.UpdateOpts{ + Name: &name, + } + l7policy, err := l7policies.Update(lbClient, l7policyID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create a Rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + createOpts := l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + } + rule, err := l7policies.CreateRule(lbClient, l7policyID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List L7 Rules + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + listOpts := l7policies.ListRulesOpts{ + RuleType: l7policies.TypePath, + } + allPages, err := l7policies.ListRules(lbClient, l7policyID, listOpts).AllPages() + if err != nil { + panic(err) + } + allRules, err := l7policies.ExtractRules(allPages) + if err != nil { + panic(err) + } + for _, rule := allRules { + fmt.Printf("%+v\n", rule) + } + +Example to Get a l7 rule + + l7rule, err := l7policies.GetRule(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d", "53ad8ab8-40fa-11e8-a508-00224d6b7bc1").Extract() + if err != nil { + panic(err) + } + +Example to Delete a l7 rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e" + err := l7policies.DeleteRule(lbClient, l7policyID, ruleID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a Rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e" + updateOpts := l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", + } + rule, err := l7policies.UpdateRule(lbClient, l7policyID, ruleID, updateOpts).Extract() + if err != nil { + panic(err) + } +*/ +package l7policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/requests.go new file mode 100644 index 000000000000..fc1c809aface --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/requests.go @@ -0,0 +1,376 @@ +package l7policies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToL7PolicyCreateMap() (map[string]interface{}, error) +} + +type Action string +type RuleType string +type CompareType string + +const ( + ActionRedirectToPool Action = "REDIRECT_TO_POOL" + ActionRedirectToURL Action = "REDIRECT_TO_URL" + ActionReject Action = "REJECT" + + TypeCookie RuleType = "COOKIE" + TypeFileType RuleType = "FILE_TYPE" + TypeHeader RuleType = "HEADER" + TypeHostName RuleType = "HOST_NAME" + TypePath RuleType = "PATH" + + CompareTypeContains CompareType = "CONTAINS" + CompareTypeEndWith CompareType = "ENDS_WITH" + CompareTypeEqual CompareType = "EQUAL_TO" + CompareTypeRegex CompareType = "REGEX" + CompareTypeStartWith CompareType = "STARTS_WITH" +) + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // Name of the L7 policy. + Name string `json:"name,omitempty"` + + // The ID of the listener. + ListenerID string `json:"listener_id" required:"true"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action Action `json:"action" required:"true"` + + // The position of this policy on the listener. + Position int32 `json:"position,omitempty"` + + // A human-readable description for the resource. + Description string `json:"description,omitempty"` + + // ProjectID is the UUID of the project who owns the L7 policy in octavia. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID string `json:"redirect_pool_id,omitempty"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL string `json:"redirect_url,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToL7PolicyCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToL7PolicyCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "l7policy") +} + +// Create accepts a CreateOpts struct and uses the values to create a new l7policy. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToL7PolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToL7PolicyListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. +type ListOpts struct { + Name string `q:"name"` + Description string `q:"description"` + ListenerID string `q:"listener_id"` + Action string `q:"action"` + ProjectID string `q:"project_id"` + RedirectPoolID string `q:"redirect_pool_id"` + RedirectURL string `q:"redirect_url"` + Position int32 `q:"position"` + AdminStateUp bool `q:"admin_state_up"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToL7PolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToL7PolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// l7policies. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those l7policies that are owned by the +// project who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToL7PolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return L7PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a particular l7policy based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// Delete will permanently delete a particular l7policy based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToL7PolicyUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // Name of the L7 policy, empty string is allowed. + Name *string `json:"name,omitempty"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action Action `json:"action,omitempty"` + + // The position of this policy on the listener. + Position int32 `json:"position,omitempty"` + + // A human-readable description for the resource, empty string is allowed. + Description *string `json:"description,omitempty"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID *string `json:"redirect_pool_id,omitempty"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL *string `json:"redirect_url,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToL7PolicyUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToL7PolicyUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "l7policy") + if err != nil { + return nil, err + } + + m := b["l7policy"].(map[string]interface{}) + + if m["redirect_pool_id"] == "" { + m["redirect_pool_id"] = nil + } + + if m["redirect_url"] == "" { + m["redirect_url"] = nil + } + + return b, nil +} + +// Update allows l7policy to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToL7PolicyUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// CreateRuleOpts is the common options struct used in this package's CreateRule +// operation. +type CreateRuleOpts struct { + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType RuleType `json:"type" required:"true"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType CompareType `json:"compare_type" required:"true"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value" required:"true"` + + // ProjectID is the UUID of the project who owns the rule in octavia. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key string `json:"key,omitempty"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert bool `json:"invert,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToRuleCreateMap builds a request body from CreateRuleOpts. +func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "rule") +} + +// CreateRule will create and associate a Rule with a particular L7Policy. +func CreateRule(c *gophercloud.ServiceClient, policyID string, opts CreateRuleOpts) (r CreateRuleResult) { + b, err := opts.ToRuleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(ruleRootURL(c, policyID), b, &r.Body, nil) + return +} + +// ListRulesOptsBuilder allows extensions to add additional parameters to the +// ListRules request. +type ListRulesOptsBuilder interface { + ToRulesListQuery() (string, error) +} + +// ListRulesOpts allows the filtering and sorting of paginated collections +// through the API. +type ListRulesOpts struct { + RuleType RuleType `q:"type"` + ProjectID string `q:"project_id"` + CompareType CompareType `q:"compare_type"` + Value string `q:"value"` + Key string `q:"key"` + Invert bool `q:"invert"` + AdminStateUp bool `q:"admin_state_up"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToRulesListQuery formats a ListOpts into a query string. +func (opts ListRulesOpts) ToRulesListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListRules returns a Pager which allows you to iterate over a collection of +// rules. It accepts a ListRulesOptsBuilder, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only those rules that are owned by the +// project who submits the request, unless an admin user submits the request. +func ListRules(c *gophercloud.ServiceClient, policyID string, opts ListRulesOptsBuilder) pagination.Pager { + url := ruleRootURL(c, policyID) + if opts != nil { + query, err := opts.ToRulesListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return RulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// GetRule retrieves a particular L7Policy Rule based on its unique ID. +func GetRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r GetRuleResult) { + _, r.Err = c.Get(ruleResourceURL(c, policyID, ruleID), &r.Body, nil) + return +} + +// DeleteRule will remove a Rule from a particular L7Policy. +func DeleteRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r DeleteRuleResult) { + _, r.Err = c.Delete(ruleResourceURL(c, policyID, ruleID), nil) + return +} + +// UpdateRuleOptsBuilder allows to add additional parameters to the PUT request. +type UpdateRuleOptsBuilder interface { + ToRuleUpdateMap() (map[string]interface{}, error) +} + +// UpdateRuleOpts is the common options struct used in this package's Update +// operation. +type UpdateRuleOpts struct { + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType RuleType `json:"type,omitempty"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType CompareType `json:"compare_type,omitempty"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value,omitempty"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key *string `json:"key,omitempty"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert *bool `json:"invert,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToRuleUpdateMap builds a request body from UpdateRuleOpts. +func (opts UpdateRuleOpts) ToRuleUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "rule") + if err != nil { + return nil, err + } + + if m := b["rule"].(map[string]interface{}); m["key"] == "" { + m["key"] = nil + } + + return b, nil +} + +// UpdateRule allows Rule to be updated. +func UpdateRule(c *gophercloud.ServiceClient, policyID string, ruleID string, opts UpdateRuleOptsBuilder) (r UpdateRuleResult) { + b, err := opts.ToRuleUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(ruleResourceURL(c, policyID, ruleID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/results.go new file mode 100644 index 000000000000..dafcceed141e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/results.go @@ -0,0 +1,237 @@ +package l7policies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// L7Policy is a collection of L7 rules associated with a Listener, and which +// may also have an association to a back-end pool. +type L7Policy struct { + // The unique ID for the L7 policy. + ID string `json:"id"` + + // Name of the L7 policy. + Name string `json:"name"` + + // The ID of the listener. + ListenerID string `json:"listener_id"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action string `json:"action"` + + // The position of this policy on the listener. + Position int32 `json:"position"` + + // A human-readable description for the resource. + Description string `json:"description"` + + // ProjectID is the UUID of the project who owns the L7 policy in octavia. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID string `json:"redirect_pool_id"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL string `json:"redirect_url"` + + // The administrative state of the L7 policy, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The provisioning status of the L7 policy. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the L7 policy. + OperatingStatus string `json:"operating_status"` + + // Rules are List of associated L7 rule IDs. + Rules []Rule `json:"rules"` +} + +// Rule represents layer 7 load balancing rule. +type Rule struct { + // The unique ID for the L7 rule. + ID string `json:"id"` + + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType string `json:"type"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType string `json:"compare_type"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value"` + + // ProjectID is the UUID of the project who owns the rule in octavia. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key string `json:"key"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert bool `json:"invert"` + + // The administrative state of the L7 rule, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The provisioning status of the L7 rule. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the L7 policy. + OperatingStatus string `json:"operating_status"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a l7policy. +func (r commonResult) Extract() (*L7Policy, error) { + var s struct { + L7Policy *L7Policy `json:"l7policy"` + } + err := r.ExtractInto(&s) + return s.L7Policy, err +} + +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret the result as a L7Policy. +type CreateResult struct { + commonResult +} + +// L7PolicyPage is the page returned by a pager when traversing over a +// collection of l7policies. +type L7PolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of l7policies has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r L7PolicyPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"l7policies_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a L7PolicyPage struct is empty. +func (r L7PolicyPage) IsEmpty() (bool, error) { + is, err := ExtractL7Policies(r) + return len(is) == 0, err +} + +// ExtractL7Policies accepts a Page struct, specifically a L7PolicyPage struct, +// and extracts the elements into a slice of L7Policy structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractL7Policies(r pagination.Page) ([]L7Policy, error) { + var s struct { + L7Policies []L7Policy `json:"l7policies"` + } + err := (r.(L7PolicyPage)).ExtractInto(&s) + return s.L7Policies, err +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret the result as a L7Policy. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret the result as a L7Policy. +type UpdateResult struct { + commonResult +} + +type commonRuleResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a rule. +func (r commonRuleResult) Extract() (*Rule, error) { + var s struct { + Rule *Rule `json:"rule"` + } + err := r.ExtractInto(&s) + return s.Rule, err +} + +// CreateRuleResult represents the result of a CreateRule operation. +// Call its Extract method to interpret it as a Rule. +type CreateRuleResult struct { + commonRuleResult +} + +// RulePage is the page returned by a pager when traversing over a +// collection of Rules in a L7Policy. +type RulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of rules has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r RulePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"rules_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a RulePage struct is empty. +func (r RulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(r) + return len(is) == 0, err +} + +// ExtractRules accepts a Page struct, specifically a RulePage struct, +// and extracts the elements into a slice of Rules structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(r pagination.Page) ([]Rule, error) { + var s struct { + Rules []Rule `json:"rules"` + } + err := (r.(RulePage)).ExtractInto(&s) + return s.Rules, err +} + +// GetRuleResult represents the result of a GetRule operation. +// Call its Extract method to interpret it as a Rule. +type GetRuleResult struct { + commonRuleResult +} + +// DeleteRuleResult represents the result of a DeleteRule operation. +// Call its ExtractErr method to determine if the request succeeded or failed. +type DeleteRuleResult struct { + gophercloud.ErrResult +} + +// UpdateRuleResult represents the result of an UpdateRule operation. +// Call its Extract method to interpret it as a Rule. +type UpdateRuleResult struct { + commonRuleResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/doc.go new file mode 100644 index 000000000000..f8068dfb6bb9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/doc.go @@ -0,0 +1,2 @@ +// l7policies unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/fixtures.go new file mode 100644 index 000000000000..fea5bad570b4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/fixtures.go @@ -0,0 +1,426 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// SingleL7PolicyBody is the canned body of a Get request on an existing l7policy. +const SingleL7PolicyBody = ` +{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "description": "", + "admin_state_up": true, + "redirect_pool_id": null, + "redirect_url": "http://www.example.com", + "action": "REDIRECT_TO_URL", + "position": 1, + "project_id": "e3cd678b11784734bc366148aa37580e", + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "redirect-example.com", + "rules": [] + } +} +` + +var ( + L7PolicyToURL = l7policies.L7Policy{ + ID: "8a1412f0-4c32-4257-8b07-af4770b604fd", + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: "REDIRECT_TO_URL", + Position: 1, + Description: "", + ProjectID: "e3cd678b11784734bc366148aa37580e", + RedirectPoolID: "", + RedirectURL: "http://www.example.com", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + L7PolicyToPool = l7policies.L7Policy{ + ID: "964f4ba4-f6cd-405c-bebd-639460af7231", + Name: "redirect-pool", + ListenerID: "be3138a3-5cf7-4513-a4c2-bb137e668bab", + Action: "REDIRECT_TO_POOL", + Position: 1, + Description: "", + ProjectID: "c1f7910086964990847dc6c8b128f63c", + RedirectPoolID: "bac433c6-5bea-4311-80da-bd1cd90fbd25", + RedirectURL: "", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + L7PolicyUpdated = l7policies.L7Policy{ + ID: "8a1412f0-4c32-4257-8b07-af4770b604fd", + Name: "NewL7PolicyName", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: "REDIRECT_TO_URL", + Position: 1, + Description: "Redirect requests to example.com", + ProjectID: "e3cd678b11784734bc366148aa37580e", + RedirectPoolID: "", + RedirectURL: "http://www.new-example.com", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + L7PolicyNullRedirectURLUpdated = l7policies.L7Policy{ + ID: "8a1412f0-4c32-4257-8b07-af4770b604fd", + Name: "NewL7PolicyName", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: "REDIRECT_TO_URL", + Position: 1, + Description: "Redirect requests to example.com", + ProjectID: "e3cd678b11784734bc366148aa37580e", + RedirectPoolID: "", + RedirectURL: "", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + RulePath = l7policies.Rule{ + ID: "16621dbb-a736-4888-a57a-3ecd53df784c", + RuleType: "PATH", + CompareType: "REGEX", + Value: "/images*", + ProjectID: "e3cd678b11784734bc366148aa37580e", + Key: "", + Invert: true, + AdminStateUp: true, + } + RuleHostName = l7policies.Rule{ + ID: "d24521a0-df84-4468-861a-a531af116d1e", + RuleType: "HOST_NAME", + CompareType: "EQUAL_TO", + Value: "www.example.com", + ProjectID: "e3cd678b11784734bc366148aa37580e", + Key: "", + Invert: false, + AdminStateUp: true, + } + RuleUpdated = l7policies.Rule{ + ID: "16621dbb-a736-4888-a57a-3ecd53df784c", + RuleType: "PATH", + CompareType: "REGEX", + Value: "/images/special*", + ProjectID: "e3cd678b11784734bc366148aa37580e", + Key: "", + Invert: false, + AdminStateUp: true, + } +) + +// HandleL7PolicyCreationSuccessfully sets up the test server to respond to a l7policy creation request +// with a given response. +func HandleL7PolicyCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "redirect_url": "http://www.example.com", + "name": "redirect-example.com", + "action": "REDIRECT_TO_URL" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// L7PoliciesListBody contains the canned body of a l7policy list response. +const L7PoliciesListBody = ` +{ + "l7policies": [ + { + "redirect_pool_id": null, + "description": "", + "admin_state_up": true, + "rules": [], + "project_id": "e3cd678b11784734bc366148aa37580e", + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "redirect_url": "http://www.example.com", + "action": "REDIRECT_TO_URL", + "position": 1, + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "redirect-example.com" + }, + { + "redirect_pool_id": "bac433c6-5bea-4311-80da-bd1cd90fbd25", + "description": "", + "admin_state_up": true, + "rules": [], + "project_id": "c1f7910086964990847dc6c8b128f63c", + "listener_id": "be3138a3-5cf7-4513-a4c2-bb137e668bab", + "action": "REDIRECT_TO_POOL", + "position": 1, + "id": "964f4ba4-f6cd-405c-bebd-639460af7231", + "name": "redirect-pool" + } + ] +} +` + +// PostUpdateL7PolicyBody is the canned response body of a Update request on an existing l7policy. +const PostUpdateL7PolicyBody = ` +{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "description": "Redirect requests to example.com", + "admin_state_up": true, + "redirect_url": "http://www.new-example.com", + "action": "REDIRECT_TO_URL", + "position": 1, + "project_id": "e3cd678b11784734bc366148aa37580e", + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "NewL7PolicyName", + "rules": [] + } +} +` + +// PostUpdateL7PolicyNullRedirectURLBody is the canned response body of a Update request +// on an existing l7policy with a null redirect_url . +const PostUpdateL7PolicyNullRedirectURLBody = ` +{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "description": "Redirect requests to example.com", + "admin_state_up": true, + "redirect_url": null, + "action": "REDIRECT_TO_URL", + "position": 1, + "project_id": "e3cd678b11784734bc366148aa37580e", + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "NewL7PolicyName", + "rules": [] + } +} +` + +// HandleL7PolicyListSuccessfully sets up the test server to respond to a l7policy List request. +func HandleL7PolicyListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, L7PoliciesListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "l7policies": [] }`) + default: + t.Fatalf("/v2.0/lbaas/l7policies invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleL7PolicyGetSuccessfully sets up the test server to respond to a l7policy Get request. +func HandleL7PolicyGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleL7PolicyBody) + }) +} + +// HandleL7PolicyDeletionSuccessfully sets up the test server to respond to a l7policy deletion request. +func HandleL7PolicyDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleL7PolicyUpdateSuccessfully sets up the test server to respond to a l7policy Update request. +func HandleL7PolicyUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "l7policy": { + "name": "NewL7PolicyName", + "action": "REDIRECT_TO_URL", + "redirect_url": "http://www.new-example.com" + } + }`) + + fmt.Fprintf(w, PostUpdateL7PolicyBody) + }) +} + +// HandleL7PolicyUpdateNullRedirectURLSuccessfully sets up the test server to respond to a l7policy Update request. +func HandleL7PolicyUpdateNullRedirectURLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "l7policy": { + "name": "NewL7PolicyName", + "redirect_url": null + } + }`) + + fmt.Fprintf(w, PostUpdateL7PolicyNullRedirectURLBody) + }) +} + +// SingleRuleBody is the canned body of a Get request on an existing rule. +const SingleRuleBody = ` +{ + "rule": { + "compare_type": "REGEX", + "invert": true, + "admin_state_up": true, + "value": "/images*", + "key": null, + "project_id": "e3cd678b11784734bc366148aa37580e", + "type": "PATH", + "id": "16621dbb-a736-4888-a57a-3ecd53df784c" + } +} +` + +// HandleRuleCreationSuccessfully sets up the test server to respond to a rule creation request +// with a given response. +func HandleRuleCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "rule": { + "compare_type": "REGEX", + "type": "PATH", + "value": "/images*" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// RulesListBody contains the canned body of a rule list response. +const RulesListBody = ` +{ + "rules":[ + { + "compare_type": "REGEX", + "invert": true, + "admin_state_up": true, + "value": "/images*", + "key": null, + "project_id": "e3cd678b11784734bc366148aa37580e", + "type": "PATH", + "id": "16621dbb-a736-4888-a57a-3ecd53df784c" + }, + { + "compare_type": "EQUAL_TO", + "invert": false, + "admin_state_up": true, + "value": "www.example.com", + "key": null, + "project_id": "e3cd678b11784734bc366148aa37580e", + "type": "HOST_NAME", + "id": "d24521a0-df84-4468-861a-a531af116d1e" + } + ] +} +` + +// HandleRuleListSuccessfully sets up the test server to respond to a rule List request. +func HandleRuleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, RulesListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "rules": [] }`) + default: + t.Fatalf("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleRuleGetSuccessfully sets up the test server to respond to a rule Get request. +func HandleRuleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleRuleBody) + }) +} + +// HandleRuleDeletionSuccessfully sets up the test server to respond to a rule deletion request. +func HandleRuleDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// PostUpdateRuleBody is the canned response body of a Update request on an existing rule. +const PostUpdateRuleBody = ` +{ + "rule": { + "compare_type": "REGEX", + "invert": false, + "admin_state_up": true, + "value": "/images/special*", + "key": null, + "project_id": "e3cd678b11784734bc366148aa37580e", + "type": "PATH", + "id": "16621dbb-a736-4888-a57a-3ecd53df784c" + } +} +` + +// HandleRuleUpdateSuccessfully sets up the test server to respond to a rule Update request. +func HandleRuleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "rule": { + "compare_type": "REGEX", + "invert": false, + "key": null, + "type": "PATH", + "value": "/images/special*" + } + }`) + + fmt.Fprintf(w, PostUpdateRuleBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/requests_test.go new file mode 100644 index 000000000000..76a10c55f2ab --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing/requests_test.go @@ -0,0 +1,317 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCreateL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyCreationSuccessfully(t, SingleL7PolicyBody) + + actual, err := l7policies.Create(fake.ServiceClient(), l7policies.CreateOpts{ + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + }).Extract() + + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, L7PolicyToURL, *actual) +} + +func TestRequiredL7PolicyCreateOpts(t *testing.T) { + // no param specified. + res := l7policies.Create(fake.ServiceClient(), l7policies.CreateOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + + // Action is invalid. + res = l7policies.Create(fake.ServiceClient(), l7policies.CreateOpts{ + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.Action("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} + +func TestListL7Policies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyListSuccessfully(t) + + pages := 0 + err := l7policies.List(fake.ServiceClient(), l7policies.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := l7policies.ExtractL7Policies(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 l7policies, got %d", len(actual)) + } + th.CheckDeepEquals(t, L7PolicyToURL, actual[0]) + th.CheckDeepEquals(t, L7PolicyToPool, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllL7Policies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyListSuccessfully(t) + + allPages, err := l7policies.List(fake.ServiceClient(), l7policies.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := l7policies.ExtractL7Policies(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, L7PolicyToURL, actual[0]) + th.CheckDeepEquals(t, L7PolicyToPool, actual[1]) +} + +func TestGetL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := l7policies.Get(client, "8a1412f0-4c32-4257-8b07-af4770b604fd").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, L7PolicyToURL, *actual) +} + +func TestDeleteL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyDeletionSuccessfully(t) + + res := l7policies.Delete(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyUpdateSuccessfully(t) + + client := fake.ServiceClient() + newName := "NewL7PolicyName" + redirectURL := "http://www.new-example.com" + actual, err := l7policies.Update(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", + l7policies.UpdateOpts{ + Name: &newName, + Action: l7policies.ActionRedirectToURL, + RedirectURL: &redirectURL, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, L7PolicyUpdated, *actual) +} + +func TestUpdateL7PolicyNullRedirectURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyUpdateNullRedirectURLSuccessfully(t) + + client := fake.ServiceClient() + newName := "NewL7PolicyName" + redirectURL := "" + actual, err := l7policies.Update(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", + l7policies.UpdateOpts{ + Name: &newName, + RedirectURL: &redirectURL, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, L7PolicyNullRedirectURLUpdated, *actual) +} + +func TestUpdateL7PolicyWithInvalidOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := l7policies.Update(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.UpdateOpts{ + Action: l7policies.Action("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestCreateRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleCreationSuccessfully(t, SingleRuleBody) + + actual, err := l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, RulePath, *actual) +} + +func TestRequiredRuleCreateOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := l7policies.CreateRule(fake.ServiceClient(), "", l7policies.CreateRuleOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + res = l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.RuleType("invalid"), + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + res = l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareType("invalid"), + Value: "/images*", + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} + +func TestListRules(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleListSuccessfully(t) + + pages := 0 + err := l7policies.ListRules(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := l7policies.ExtractRules(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 rules, got %d", len(actual)) + } + th.CheckDeepEquals(t, RulePath, actual[0]) + th.CheckDeepEquals(t, RuleHostName, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllRules(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleListSuccessfully(t) + + allPages, err := l7policies.ListRules(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).AllPages() + th.AssertNoErr(t, err) + + actual, err := l7policies.ExtractRules(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, RulePath, actual[0]) + th.CheckDeepEquals(t, RuleHostName, actual[1]) +} + +func TestGetRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := l7policies.GetRule(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, RulePath, *actual) +} + +func TestDeleteRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleDeletionSuccessfully(t) + + res := l7policies.DeleteRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleUpdateSuccessfully(t) + + client := fake.ServiceClient() + invert := false + key := "" + actual, err := l7policies.UpdateRule(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c", l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", + Key: &key, + Invert: &invert, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, RuleUpdated, *actual) +} + +func TestUpdateRuleWithInvalidOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := l7policies.UpdateRule(fake.ServiceClient(), "", "", l7policies.UpdateRuleOpts{ + RuleType: l7policies.RuleType("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + + res = l7policies.UpdateRule(fake.ServiceClient(), "", "", l7policies.UpdateRuleOpts{ + CompareType: l7policies.CompareType("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/urls.go new file mode 100644 index 000000000000..ecb607a8e89d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/urls.go @@ -0,0 +1,25 @@ +package l7policies + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "l7policies" + rulePath = "rules" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func ruleRootURL(c *gophercloud.ServiceClient, policyID string) string { + return c.ServiceURL(rootPath, resourcePath, policyID, rulePath) +} + +func ruleResourceURL(c *gophercloud.ServiceClient, policyID string, ruleID string) string { + return c.ServiceURL(rootPath, resourcePath, policyID, rulePath, ruleID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/doc.go new file mode 100644 index 000000000000..eeea6eea82c7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/doc.go @@ -0,0 +1,74 @@ +/* +Package listeners provides information and interaction with Listeners of the +LBaaS v2 extension for the OpenStack Networking service. + +Example to List Listeners + + listOpts := listeners.ListOpts{ + LoadbalancerID : "ca430f80-1737-4712-8dc6-3f640d55594b", + } + + allPages, err := listeners.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allListeners, err := listeners.ExtractListeners(allPages) + if err != nil { + panic(err) + } + + for _, listener := range allListeners { + fmt.Printf("%+v\n", listener) + } + +Example to Create a Listener + + createOpts := listeners.CreateOpts{ + Protocol: "TCP", + Name: "db", + LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", + AdminStateUp: gophercloud.Enabled, + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ProtocolPort: 3306, + } + + listener, err := listeners.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Listener + + listenerID := "d67d56a6-4a86-4688-a282-f46444705c64" + + i1001 := 1001 + i181000 := 181000 + updateOpts := listeners.UpdateOpts{ + ConnLimit: &i1001, + TimeoutClientData: &i181000, + TimeoutMemberData: &i181000, + } + + listener, err := listeners.Update(networkClient, listenerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Listener + + listenerID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := listeners.Delete(networkClient, listenerID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get the Statistics of a Listener + + listenerID := "d67d56a6-4a86-4688-a282-f46444705c64" + stats, err := listeners.GetStats(networkClient, listenerID).Extract() + if err != nil { + panic(err) + } +*/ +package listeners diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/requests.go new file mode 100644 index 000000000000..c512e4d72f68 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/requests.go @@ -0,0 +1,245 @@ +package listeners + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Type Protocol represents a listener protocol. +type Protocol string + +// Supported attributes for create/update operations. +const ( + ProtocolTCP Protocol = "TCP" + ProtocolUDP Protocol = "UDP" + ProtocolPROXY Protocol = "PROXY" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToListenerListQuery() (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 listener 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"` + AdminStateUp *bool `q:"admin_state_up"` + ProjectID string `q:"project_id"` + LoadbalancerID string `q:"loadbalancer_id"` + DefaultPoolID string `q:"default_pool_id"` + Protocol string `q:"protocol"` + ProtocolPort int `q:"protocol_port"` + ConnectionLimit int `q:"connection_limit"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + TimeoutClientData *int `q:"timeout_client_data"` + TimeoutMemberData *int `q:"timeout_member_data"` + TimeoutMemberConnect *int `q:"timeout_member_connect"` + TimeoutTCPInspect *int `q:"timeout_tcp_inspect"` +} + +// ToListenerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToListenerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// listeners. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those listeners that are owned by the +// project who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToListenerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return ListenerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToListenerCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options for creating a listener. +type CreateOpts struct { + // The load balancer on which to provision this listener. + LoadbalancerID string `json:"loadbalancer_id" required:"true"` + + // The protocol - can either be TCP, HTTP or HTTPS. + Protocol Protocol `json:"protocol" required:"true"` + + // The port on which to listen for client traffic. + ProtocolPort int `json:"protocol_port" required:"true"` + + // ProjectID is only required if the caller has an admin role and wants + // to create a pool for another project. + ProjectID string `json:"project_id,omitempty"` + + // Human-readable name for the Listener. Does not have to be unique. + Name string `json:"name,omitempty"` + + // The ID of the default pool with which the Listener is associated. + DefaultPoolID string `json:"default_pool_id,omitempty"` + + // Human-readable description for the Listener. + Description string `json:"description,omitempty"` + + // The maximum number of connections allowed for the Listener. + ConnLimit *int `json:"connection_limit,omitempty"` + + // A reference to a Barbican container of TLS secrets. + DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"` + + // A list of references to TLS secrets. + SniContainerRefs []string `json:"sni_container_refs,omitempty"` + + // The administrative state of the Listener. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // Frontend client inactivity timeout in milliseconds + TimeoutClientData *int `json:"timeout_client_data,omitempty"` + + // Backend member inactivity timeout in milliseconds + TimeoutMemberData *int `json:"timeout_member_data,omitempty"` + + // Backend member connection timeout in milliseconds + TimeoutMemberConnect *int `json:"timeout_member_connect,omitempty"` + + // Time, in milliseconds, to wait for additional TCP packets for content inspection + TimeoutTCPInspect *int `json:"timeout_tcp_inspect,omitempty"` + + // A dictionary of optional headers to insert into the request before it is sent to the backend member. + InsertHeaders map[string]string `json:"insert_headers,omitempty"` +} + +// ToListenerCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToListenerCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "listener") +} + +// Create is an operation which provisions a new Listeners based on the +// configuration defined in the CreateOpts struct. Once the request is +// validated and progress has started on the provisioning process, a +// CreateResult will be returned. +// +// Users with an admin role can create Listeners on behalf of other projects by +// specifying a ProjectID attribute different than their own. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToListenerCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular Listeners based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToListenerUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options for updating a Listener. +type UpdateOpts struct { + // Human-readable name for the Listener. Does not have to be unique. + Name *string `json:"name,omitempty"` + + // The ID of the default pool with which the Listener is associated. + DefaultPoolID *string `json:"default_pool_id,omitempty"` + + // Human-readable description for the Listener. + Description *string `json:"description,omitempty"` + + // The maximum number of connections allowed for the Listener. + ConnLimit *int `json:"connection_limit,omitempty"` + + // A reference to a Barbican container of TLS secrets. + DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"` + + // A list of references to TLS secrets. + SniContainerRefs []string `json:"sni_container_refs,omitempty"` + + // The administrative state of the Listener. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // Frontend client inactivity timeout in milliseconds + TimeoutClientData *int `json:"timeout_client_data,omitempty"` + + // Backend member inactivity timeout in milliseconds + TimeoutMemberData *int `json:"timeout_member_data,omitempty"` + + // Backend member connection timeout in milliseconds + TimeoutMemberConnect *int `json:"timeout_member_connect,omitempty"` + + // Time, in milliseconds, to wait for additional TCP packets for content inspection + TimeoutTCPInspect *int `json:"timeout_tcp_inspect,omitempty"` +} + +// ToListenerUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "listener") + if err != nil { + return nil, err + } + + if m := b["listener"].(map[string]interface{}); m["default_pool_id"] == "" { + m["default_pool_id"] = nil + } + + return b, nil +} + +// Update is an operation which modifies the attributes of the specified +// Listener. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { + b, err := opts.ToListenerUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} + +// Delete will permanently delete a particular Listeners based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// GetStats will return the shows the current statistics of a particular Listeners. +func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) { + _, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/results.go new file mode 100644 index 000000000000..a8f768210184 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/results.go @@ -0,0 +1,187 @@ +package listeners + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + "github.com/gophercloud/gophercloud/pagination" +) + +type LoadBalancerID struct { + ID string `json:"id"` +} + +// Listener is the primary load balancing configuration object that specifies +// the loadbalancer and port on which client traffic is received, as well +// as other details such as the load balancing method to be use, protocol, etc. +type Listener struct { + // The unique ID for the Listener. + ID string `json:"id"` + + // Owner of the Listener. + ProjectID string `json:"project_id"` + + // Human-readable name for the Listener. Does not have to be unique. + Name string `json:"name"` + + // Human-readable description for the Listener. + Description string `json:"description"` + + // The protocol to loadbalance. A valid value is TCP, HTTP, or HTTPS. + Protocol string `json:"protocol"` + + // The port on which to listen to client traffic that is associated with the + // Loadbalancer. A valid value is from 0 to 65535. + ProtocolPort int `json:"protocol_port"` + + // The UUID of default pool. Must have compatible protocol with listener. + DefaultPoolID string `json:"default_pool_id"` + + // A list of load balancer IDs. + Loadbalancers []LoadBalancerID `json:"loadbalancers"` + + // The maximum number of connections allowed for the Loadbalancer. + // Default is -1, meaning no limit. + ConnLimit int `json:"connection_limit"` + + // The list of references to TLS secrets. + SniContainerRefs []string `json:"sni_container_refs"` + + // A reference to a Barbican container of TLS secrets. + DefaultTlsContainerRef string `json:"default_tls_container_ref"` + + // The administrative state of the Listener. A valid value is true (UP) or false (DOWN). + AdminStateUp bool `json:"admin_state_up"` + + // Pools are the pools which are part of this listener. + Pools []pools.Pool `json:"pools"` + + // L7policies are the L7 policies which are part of this listener. + L7Policies []l7policies.L7Policy `json:"l7policies"` + + // The provisioning status of the Listener. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // Frontend client inactivity timeout in milliseconds + TimeoutClientData int `json:"timeout_client_data"` + + // Backend member inactivity timeout in milliseconds + TimeoutMemberData int `json:"timeout_member_data"` + + // Backend member connection timeout in milliseconds + TimeoutMemberConnect int `json:"timeout_member_connect"` + + // Time, in milliseconds, to wait for additional TCP packets for content inspection + TimeoutTCPInspect int `json:"timeout_tcp_inspect"` + + // A dictionary of optional headers to insert into the request before it is sent to the backend member. + InsertHeaders map[string]string `json:"insert_headers"` +} + +type Stats struct { + // The currently active connections. + ActiveConnections int `json:"active_connections"` + + // The total bytes received. + BytesIn int `json:"bytes_in"` + + // The total bytes sent. + BytesOut int `json:"bytes_out"` + + // The total requests that were unable to be fulfilled. + RequestErrors int `json:"request_errors"` + + // The total connections handled. + TotalConnections int `json:"total_connections"` +} + +// ListenerPage is the page returned by a pager when traversing over a +// collection of listeners. +type ListenerPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of listeners has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r ListenerPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"listeners_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a ListenerPage struct is empty. +func (r ListenerPage) IsEmpty() (bool, error) { + is, err := ExtractListeners(r) + return len(is) == 0, err +} + +// ExtractListeners accepts a Page struct, specifically a ListenerPage struct, +// and extracts the elements into a slice of Listener structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractListeners(r pagination.Page) ([]Listener, error) { + var s struct { + Listeners []Listener `json:"listeners"` + } + err := (r.(ListenerPage)).ExtractInto(&s) + return s.Listeners, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a listener. +func (r commonResult) Extract() (*Listener, error) { + var s struct { + Listener *Listener `json:"listener"` + } + err := r.ExtractInto(&s) + return s.Listener, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Listener. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Listener. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Listener. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// StatsResult represents the result of a GetStats operation. +// Call its Extract method to interpret it as a Stats. +type StatsResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts the status of +// a Listener. +func (r StatsResult) Extract() (*Stats, error) { + var s struct { + Stats *Stats `json:"stats"` + } + err := r.ExtractInto(&s) + return s.Stats, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/doc.go new file mode 100644 index 000000000000..f41387e827c5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/doc.go @@ -0,0 +1,2 @@ +// listeners unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/fixtures.go new file mode 100644 index 000000000000..bd65d3ac0291 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/fixtures.go @@ -0,0 +1,280 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListenersListBody contains the canned body of a listeners list response. +const ListenersListBody = ` +{ + "listeners":[ + { + "id": "db902c0c-d5ff-4753-b465-668ad9656918", + "project_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "web", + "description": "listener config for the web tier", + "loadbalancers": [{"id": "53306cda-815d-4354-9444-59e09da9c3c5"}], + "protocol": "HTTP", + "protocol_port": 80, + "default_pool_id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"] + }, + { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "project_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "db", + "description": "listener config for the db tier", + "loadbalancers": [{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "protocol": "TCP", + "protocol_port": 3306, + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "connection_limit": 2000, + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"], + "timeout_client_data": 50000, + "timeout_member_data": 50000, + "timeout_member_connect": 5000, + "timeout_tcp_inspect": 0, + "insert_headers": { + "X-Forwarded-For": "true" + } + } + ] +} +` + +// SingleServerBody is the canned body of a Get request on an existing listener. +const SingleListenerBody = ` +{ + "listener": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "project_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "db", + "description": "listener config for the db tier", + "loadbalancers": [{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "protocol": "TCP", + "protocol_port": 3306, + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "connection_limit": 2000, + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"], + "timeout_client_data": 50000, + "timeout_member_data": 50000, + "timeout_member_connect": 5000, + "timeout_tcp_inspect": 0, + "insert_headers": { + "X-Forwarded-For": "true" + } + } +} +` + +// PostUpdateListenerBody is the canned response body of a Update request on an existing listener. +const PostUpdateListenerBody = ` +{ + "listener": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "project_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "NewListenerName", + "description": "listener config for the db tier", + "loadbalancers": [{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "protocol": "TCP", + "protocol_port": 3306, + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "connection_limit": 1000, + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"], + "timeout_client_data": 181000, + "timeout_member_data": 181000, + "timeout_member_connect": 181000, + "timeout_tcp_inspect": 181000 + + } +} +` + +// GetListenerStatsBody is the canned request body of a Get request on listener's statistics. +const GetListenerStatsBody = ` +{ + "stats": { + "active_connections": 0, + "bytes_in": 9532, + "bytes_out": 22033, + "request_errors": 46, + "total_connections": 112 + } +} +` + +var ( + ListenerWeb = listeners.Listener{ + ID: "db902c0c-d5ff-4753-b465-668ad9656918", + ProjectID: "310df60f-2a10-4ee5-9554-98393092194c", + Name: "web", + Description: "listener config for the web tier", + Loadbalancers: []listeners.LoadBalancerID{{ID: "53306cda-815d-4354-9444-59e09da9c3c5"}}, + Protocol: "HTTP", + ProtocolPort: 80, + DefaultPoolID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + AdminStateUp: true, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, + } + ListenerDb = listeners.Listener{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + ProjectID: "310df60f-2a10-4ee5-9554-98393092194c", + Name: "db", + Description: "listener config for the db tier", + Loadbalancers: []listeners.LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Protocol: "TCP", + ProtocolPort: 3306, + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ConnLimit: 2000, + AdminStateUp: true, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, + TimeoutClientData: 50000, + TimeoutMemberData: 50000, + TimeoutMemberConnect: 5000, + TimeoutTCPInspect: 0, + InsertHeaders: map[string]string{"X-Forwarded-For": "true"}, + } + ListenerUpdated = listeners.Listener{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + ProjectID: "310df60f-2a10-4ee5-9554-98393092194c", + Name: "NewListenerName", + Description: "listener config for the db tier", + Loadbalancers: []listeners.LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Protocol: "TCP", + ProtocolPort: 3306, + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ConnLimit: 1000, + AdminStateUp: true, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, + TimeoutClientData: 181000, + TimeoutMemberData: 181000, + TimeoutMemberConnect: 181000, + TimeoutTCPInspect: 181000, + } + ListenerStatsTree = listeners.Stats{ + ActiveConnections: 0, + BytesIn: 9532, + BytesOut: 22033, + RequestErrors: 46, + TotalConnections: 112, + } +) + +// HandleListenerListSuccessfully sets up the test server to respond to a listener List request. +func HandleListenerListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, ListenersListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "listeners": [] }`) + default: + t.Fatalf("/v2.0/lbaas/listeners invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleListenerCreationSuccessfully sets up the test server to respond to a listener creation request +// with a given response. +func HandleListenerCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "listener": { + "loadbalancer_id": "79e05663-7f03-45d2-a092-8b94062f22ab", + "protocol": "TCP", + "name": "db", + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "protocol_port": 3306, + "insert_headers": { + "X-Forwarded-For": "true" + } + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleListenerGetSuccessfully sets up the test server to respond to a listener Get request. +func HandleListenerGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleListenerBody) + }) +} + +// HandleListenerDeletionSuccessfully sets up the test server to respond to a listener deletion request. +func HandleListenerDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleListenerUpdateSuccessfully sets up the test server to respond to a listener Update request. +func HandleListenerUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "listener": { + "name": "NewListenerName", + "default_pool_id": null, + "connection_limit": 1001, + "timeout_client_data": 181000, + "timeout_member_data": 181000, + "timeout_member_connect": 181000, + "timeout_tcp_inspect": 181000 + } + }`) + + fmt.Fprintf(w, PostUpdateListenerBody) + }) +} + +// HandleListenerGetStatsTree sets up the test server to respond to a listener Get stats tree request. +func HandleListenerGetStatsTree(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304/stats", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, GetListenerStatsBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/requests_test.go new file mode 100644 index 000000000000..775810951dc8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/testing/requests_test.go @@ -0,0 +1,160 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" + fake "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListListeners(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerListSuccessfully(t) + + pages := 0 + err := listeners.List(fake.ServiceClient(), listeners.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := listeners.ExtractListeners(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 listeners, got %d", len(actual)) + } + th.CheckDeepEquals(t, ListenerWeb, actual[0]) + th.CheckDeepEquals(t, ListenerDb, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllListeners(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerListSuccessfully(t) + + allPages, err := listeners.List(fake.ServiceClient(), listeners.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := listeners.ExtractListeners(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ListenerWeb, actual[0]) + th.CheckDeepEquals(t, ListenerDb, actual[1]) +} + +func TestCreateListener(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerCreationSuccessfully(t, SingleListenerBody) + + actual, err := listeners.Create(fake.ServiceClient(), listeners.CreateOpts{ + Protocol: "TCP", + Name: "db", + LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", + AdminStateUp: gophercloud.Enabled, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ProtocolPort: 3306, + InsertHeaders: map[string]string{"X-Forwarded-For": "true"}, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ListenerDb, *actual) +} + +func TestRequiredCreateOpts(t *testing.T) { + res := listeners.Create(fake.ServiceClient(), listeners.CreateOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = listeners.Create(fake.ServiceClient(), listeners.CreateOpts{Name: "foo"}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = listeners.Create(fake.ServiceClient(), listeners.CreateOpts{Name: "foo", ProjectID: "bar"}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = listeners.Create(fake.ServiceClient(), listeners.CreateOpts{Name: "foo", ProjectID: "bar", Protocol: "bar"}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = listeners.Create(fake.ServiceClient(), listeners.CreateOpts{Name: "foo", ProjectID: "bar", Protocol: "bar", ProtocolPort: 80}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestGetListener(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := listeners.Get(client, "4ec89087-d057-4e2c-911f-60a3b47ee304").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, ListenerDb, *actual) +} + +func TestDeleteListener(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerDeletionSuccessfully(t) + + res := listeners.Delete(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateListener(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerUpdateSuccessfully(t) + + client := fake.ServiceClient() + i1001 := 1001 + i181000 := 181000 + name := "NewListenerName" + defaultPoolID := "" + actual, err := listeners.Update(client, "4ec89087-d057-4e2c-911f-60a3b47ee304", listeners.UpdateOpts{ + Name: &name, + ConnLimit: &i1001, + DefaultPoolID: &defaultPoolID, + TimeoutMemberData: &i181000, + TimeoutClientData: &i181000, + TimeoutMemberConnect: &i181000, + TimeoutTCPInspect: &i181000, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, ListenerUpdated, *actual) +} + +func TestGetListenerStatsTree(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListenerGetStatsTree(t) + + client := fake.ServiceClient() + actual, err := listeners.GetStats(client, "4ec89087-d057-4e2c-911f-60a3b47ee304").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, ListenerStatsTree, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/urls.go new file mode 100644 index 000000000000..e9e3bccd3e72 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners/urls.go @@ -0,0 +1,21 @@ +package listeners + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "listeners" + statisticsPath = "stats" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func statisticsRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, statisticsPath) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/doc.go new file mode 100644 index 000000000000..83cc6c44f47b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/doc.go @@ -0,0 +1,92 @@ +/* +Package loadbalancers provides information and interaction with Load Balancers +of the LBaaS v2 extension for the OpenStack Networking service. + +Example to List Load Balancers + + listOpts := loadbalancers.ListOpts{ + Provider: "haproxy", + } + + allPages, err := loadbalancers.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allLoadbalancers, err := loadbalancers.ExtractLoadBalancers(allPages) + if err != nil { + panic(err) + } + + for _, lb := range allLoadbalancers { + fmt.Printf("%+v\n", lb) + } + +Example to Create a Load Balancer + + createOpts := loadbalancers.CreateOpts{ + Name: "db_lb", + AdminStateUp: gophercloud.Enabled, + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + Flavor: "medium", + Provider: "haproxy", + Tags: []string{"test", "stage"}, + } + + lb, err := loadbalancers.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + name := "new-name" + updateOpts := loadbalancers.UpdateOpts{ + Name: &name, + } + lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Load Balancers + + deleteOpts := loadbalancers.DeleteOpts{ + Cascade: true, + } + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + + err := loadbalancers.Delete(networkClient, lbID, deleteOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get the Status of a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + status, err := loadbalancers.GetStatuses(networkClient, LBID).Extract() + if err != nil { + panic(err) + } + +Example to Get the Statistics of a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + stats, err := loadbalancers.GetStats(networkClient, LBID).Extract() + if err != nil { + panic(err) + } + +Example to Failover a Load Balancers + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + + err := loadbalancers.Failover(networkClient, lbID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package loadbalancers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/requests.go new file mode 100644 index 000000000000..4ff4210fa250 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/requests.go @@ -0,0 +1,241 @@ +package loadbalancers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToLoadBalancerListQuery() (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 Loadbalancer attributes you want to see returned. SortKey allows you to +// sort by a particular attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + ProjectID string `q:"project_id"` + ProvisioningStatus string `q:"provisioning_status"` + VipAddress string `q:"vip_address"` + VipPortID string `q:"vip_port_id"` + VipSubnetID string `q:"vip_subnet_id"` + VipNetworkID string `q:"vip_network_id"` + ID string `q:"id"` + OperatingStatus string `q:"operating_status"` + Name string `q:"name"` + Flavor string `q:"flavor"` + Provider string `q:"provider"` + 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"` + TagsNot []string `q:"not-tags"` + TagsNotAny []string `q:"not-tags-any"` +} + +// ToLoadBalancerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToLoadBalancerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// load balancers. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +// +// Default policy settings return only those load balancers that are owned by +// the project who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToLoadBalancerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return LoadBalancerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToLoadBalancerCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // Human-readable name for the Loadbalancer. Does not have to be unique. + Name string `json:"name,omitempty"` + + // Human-readable description for the Loadbalancer. + Description string `json:"description,omitempty"` + + // Providing a neutron port ID for the vip_port_id tells Octavia to use this + // port for the VIP. If the port has more than one subnet you must specify + // either the vip_subnet_id or vip_address to clarify which address should + // be used for the VIP. + VipPortID string `json:"vip_port_id,omitempty"` + + // The subnet on which to allocate the Loadbalancer's address. A project can + // only create Loadbalancers on networks authorized by policy (e.g. networks + // that belong to them or networks that are shared). + VipSubnetID string `json:"vip_subnet_id,omitempty"` + + // The network on which to allocate the Loadbalancer's address. A tenant can + // only create Loadbalancers on networks authorized by policy (e.g. networks + // that belong to them or networks that are shared). + VipNetworkID string `json:"vip_network_id,omitempty"` + + // ProjectID is the UUID of the project who owns the Loadbalancer. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // The IP address of the Loadbalancer. + VipAddress string `json:"vip_address,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // The UUID of a flavor. + Flavor string `json:"flavor,omitempty"` + + // The name of the provider. + Provider string `json:"provider,omitempty"` + + // Tags is a set of resource tags. + Tags []string `json:"tags,omitempty"` +} + +// ToLoadBalancerCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToLoadBalancerCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "loadbalancer") +} + +// Create is an operation which provisions a new loadbalancer based on the +// configuration defined in the CreateOpts struct. Once the request is +// validated and progress has started on the provisioning process, a +// CreateResult will be returned. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToLoadBalancerCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular Loadbalancer based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToLoadBalancerUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // Human-readable name for the Loadbalancer. Does not have to be unique. + Name *string `json:"name,omitempty"` + + // Human-readable description for the Loadbalancer. + Description *string `json:"description,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // Tags is a set of resource tags. + Tags *[]string `json:"tags,omitempty"` +} + +// ToLoadBalancerUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToLoadBalancerUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "loadbalancer") +} + +// Update is an operation which modifies the attributes of the specified +// LoadBalancer. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { + b, err := opts.ToLoadBalancerUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} + +// DeleteOptsBuilder allows extensions to add additional parameters to the +// Delete request. +type DeleteOptsBuilder interface { + ToLoadBalancerDeleteQuery() (string, error) +} + +// DeleteOpts is the common options struct used in this package's Delete +// operation. +type DeleteOpts struct { + // Cascade will delete all children of the load balancer (listners, monitors, etc). + Cascade bool `q:"cascade"` +} + +// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string. +func (opts DeleteOpts) ToLoadBalancerDeleteQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Delete will permanently delete a particular LoadBalancer based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) { + url := resourceURL(c, id) + if opts != nil { + query, err := opts.ToLoadBalancerDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = c.Delete(url, nil) + return +} + +// GetStatuses will return the status of a particular LoadBalancer. +func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) { + _, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil) + return +} + +// GetStats will return the shows the current statistics of a particular LoadBalancer. +func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) { + _, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil) + return +} + +// Failover performs a failover of a load balancer. +func Failover(c *gophercloud.ServiceClient, id string) (r FailoverResult) { + _, r.Err = c.Put(failoverRootURL(c, id), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/results.go new file mode 100644 index 000000000000..73372938cf43 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/results.go @@ -0,0 +1,200 @@ +package loadbalancers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + "github.com/gophercloud/gophercloud/pagination" +) + +// LoadBalancer is the primary load balancing configuration object that +// specifies the virtual IP address on which client traffic is received, as well +// as other details such as the load balancing method to be use, protocol, etc. +type LoadBalancer struct { + // Human-readable description for the Loadbalancer. + Description string `json:"description"` + + // The administrative state of the Loadbalancer. + // A valid value is true (UP) or false (DOWN). + AdminStateUp bool `json:"admin_state_up"` + + // Owner of the LoadBalancer. + ProjectID string `json:"project_id"` + + // The provisioning status of the LoadBalancer. + // This value is ACTIVE, PENDING_CREATE or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The IP address of the Loadbalancer. + VipAddress string `json:"vip_address"` + + // The UUID of the port associated with the IP address. + VipPortID string `json:"vip_port_id"` + + // The UUID of the subnet on which to allocate the virtual IP for the + // Loadbalancer address. + VipSubnetID string `json:"vip_subnet_id"` + + // The UUID of the network on which to allocate the virtual IP for the + // Loadbalancer address. + VipNetworkID string `json:"vip_network_id"` + + // The unique ID for the LoadBalancer. + ID string `json:"id"` + + // The operating status of the LoadBalancer. This value is ONLINE or OFFLINE. + OperatingStatus string `json:"operating_status"` + + // Human-readable name for the LoadBalancer. Does not have to be unique. + Name string `json:"name"` + + // The UUID of a flavor if set. + Flavor string `json:"flavor"` + + // The name of the provider. + Provider string `json:"provider"` + + // Listeners are the listeners related to this Loadbalancer. + Listeners []listeners.Listener `json:"listeners"` + + // Pools are the pools related to this Loadbalancer. + Pools []pools.Pool `json:"pools"` + + // Tags is a list of resource tags. Tags are arbitrarily defined strings + // attached to the resource. + Tags []string `json:"tags"` +} + +// StatusTree represents the status of a loadbalancer. +type StatusTree struct { + Loadbalancer *LoadBalancer `json:"loadbalancer"` +} + +type Stats struct { + // The currently active connections. + ActiveConnections int `json:"active_connections"` + + // The total bytes received. + BytesIn int `json:"bytes_in"` + + // The total bytes sent. + BytesOut int `json:"bytes_out"` + + // The total requests that were unable to be fulfilled. + RequestErrors int `json:"request_errors"` + + // The total connections handled. + TotalConnections int `json:"total_connections"` +} + +// LoadBalancerPage is the page returned by a pager when traversing over a +// collection of load balancers. +type LoadBalancerPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of load balancers has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r LoadBalancerPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"loadbalancers_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a LoadBalancerPage struct is empty. +func (r LoadBalancerPage) IsEmpty() (bool, error) { + is, err := ExtractLoadBalancers(r) + return len(is) == 0, err +} + +// ExtractLoadBalancers accepts a Page struct, specifically a LoadbalancerPage +// struct, and extracts the elements into a slice of LoadBalancer structs. In +// other words, a generic collection is mapped into a relevant slice. +func ExtractLoadBalancers(r pagination.Page) ([]LoadBalancer, error) { + var s struct { + LoadBalancers []LoadBalancer `json:"loadbalancers"` + } + err := (r.(LoadBalancerPage)).ExtractInto(&s) + return s.LoadBalancers, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a loadbalancer. +func (r commonResult) Extract() (*LoadBalancer, error) { + var s struct { + LoadBalancer *LoadBalancer `json:"loadbalancer"` + } + err := r.ExtractInto(&s) + return s.LoadBalancer, err +} + +// GetStatusesResult represents the result of a GetStatuses operation. +// Call its Extract method to interpret it as a StatusTree. +type GetStatusesResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts the status of +// a Loadbalancer. +func (r GetStatusesResult) Extract() (*StatusTree, error) { + var s struct { + Statuses *StatusTree `json:"statuses"` + } + err := r.ExtractInto(&s) + return s.Statuses, err +} + +// StatsResult represents the result of a GetStats operation. +// Call its Extract method to interpret it as a Stats. +type StatsResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts the status of +// a Loadbalancer. +func (r StatsResult) Extract() (*Stats, error) { + var s struct { + Stats *Stats `json:"stats"` + } + err := r.ExtractInto(&s) + return s.Stats, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a LoadBalancer. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a LoadBalancer. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a LoadBalancer. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// FailoverResult represents the result of a failover operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type FailoverResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/doc.go new file mode 100644 index 000000000000..b54468c82f15 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/doc.go @@ -0,0 +1,2 @@ +// loadbalancers unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/fixtures.go new file mode 100644 index 000000000000..a9562e8ffa03 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/fixtures.go @@ -0,0 +1,346 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// LoadbalancersListBody contains the canned body of a loadbalancer list response. +const LoadbalancersListBody = ` +{ + "loadbalancers":[ + { + "id": "c331058c-6a40-4144-948e-b9fb1df9db4b", + "project_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "web_lb", + "description": "lb config for the web tier", + "vip_subnet_id": "8a49c438-848f-467b-9655-ea1548708154", + "vip_address": "10.30.176.47", + "vip_port_id": "2a22e552-a347-44fd-b530-1f2b1b2a6735", + "flavor": "small", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "ACTIVE", + "operating_status": "ONLINE", + "tags": ["test", "stage"] + }, + { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "project_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "db_lb", + "description": "lb config for the db tier", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "PENDING_CREATE", + "operating_status": "OFFLINE", + "tags": ["test", "stage"] + } + ] +} +` + +// SingleLoadbalancerBody is the canned body of a Get request on an existing loadbalancer. +const SingleLoadbalancerBody = ` +{ + "loadbalancer": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "project_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "db_lb", + "description": "lb config for the db tier", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "PENDING_CREATE", + "operating_status": "OFFLINE", + "tags": ["test", "stage"] + } +} +` + +// PostUpdateLoadbalancerBody is the canned response body of a Update request on an existing loadbalancer. +const PostUpdateLoadbalancerBody = ` +{ + "loadbalancer": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "project_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "NewLoadbalancerName", + "description": "lb config for the db tier", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "PENDING_CREATE", + "operating_status": "OFFLINE", + "tags": ["test"] + } +} +` + +// GetLoadbalancerStatusesBody is the canned request body of a Get request on loadbalancer's status. +const GetLoadbalancerStatusesBody = ` +{ + "statuses" : { + "loadbalancer": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "name": "db_lb", + "provisioning_status": "PENDING_UPDATE", + "operating_status": "ACTIVE", + "tags": ["test", "stage"], + "listeners": [{ + "id": "db902c0c-d5ff-4753-b465-668ad9656918", + "name": "db", + "provisioning_status": "ACTIVE", + "pools": [{ + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "name": "db", + "provisioning_status": "ACTIVE", + "healthmonitor": { + "id": "67306cda-815d-4354-9fe4-59e09da9c3c5", + "type":"PING", + "provisioning_status": "ACTIVE" + }, + "members":[{ + "id": "2a280670-c202-4b0b-a562-34077415aabf", + "name": "db", + "address": "10.0.2.11", + "protocol_port": 80, + "provisioning_status": "ACTIVE" + }] + }] + }] + } + } +} +` + +// LoadbalancerStatsTree is the canned request body of a Get request on loadbalancer's statistics. +const GetLoadbalancerStatsBody = ` +{ + "stats": { + "active_connections": 0, + "bytes_in": 9532, + "bytes_out": 22033, + "request_errors": 46, + "total_connections": 112 + } +} +` + +var ( + LoadbalancerWeb = loadbalancers.LoadBalancer{ + ID: "c331058c-6a40-4144-948e-b9fb1df9db4b", + ProjectID: "54030507-44f7-473c-9342-b4d14a95f692", + Name: "web_lb", + Description: "lb config for the web tier", + VipSubnetID: "8a49c438-848f-467b-9655-ea1548708154", + VipAddress: "10.30.176.47", + VipPortID: "2a22e552-a347-44fd-b530-1f2b1b2a6735", + Flavor: "small", + Provider: "haproxy", + AdminStateUp: true, + ProvisioningStatus: "ACTIVE", + OperatingStatus: "ONLINE", + Tags: []string{"test", "stage"}, + } + LoadbalancerDb = loadbalancers.LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + ProjectID: "54030507-44f7-473c-9342-b4d14a95f692", + Name: "db_lb", + Description: "lb config for the db tier", + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + VipPortID: "2bf413c8-41a9-4477-b505-333d5cbe8b55", + Flavor: "medium", + Provider: "haproxy", + AdminStateUp: true, + ProvisioningStatus: "PENDING_CREATE", + OperatingStatus: "OFFLINE", + Tags: []string{"test", "stage"}, + } + LoadbalancerUpdated = loadbalancers.LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + ProjectID: "54030507-44f7-473c-9342-b4d14a95f692", + Name: "NewLoadbalancerName", + Description: "lb config for the db tier", + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + VipPortID: "2bf413c8-41a9-4477-b505-333d5cbe8b55", + Flavor: "medium", + Provider: "haproxy", + AdminStateUp: true, + ProvisioningStatus: "PENDING_CREATE", + OperatingStatus: "OFFLINE", + Tags: []string{"test"}, + } + LoadbalancerStatusesTree = loadbalancers.StatusTree{ + Loadbalancer: &loadbalancers.LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + Name: "db_lb", + ProvisioningStatus: "PENDING_UPDATE", + OperatingStatus: "ACTIVE", + Tags: []string{"test", "stage"}, + Listeners: []listeners.Listener{{ + ID: "db902c0c-d5ff-4753-b465-668ad9656918", + Name: "db", + ProvisioningStatus: "ACTIVE", + Pools: []pools.Pool{{ + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Name: "db", + ProvisioningStatus: "ACTIVE", + Monitor: monitors.Monitor{ + ID: "67306cda-815d-4354-9fe4-59e09da9c3c5", + Type: "PING", + ProvisioningStatus: "ACTIVE", + }, + Members: []pools.Member{{ + ID: "2a280670-c202-4b0b-a562-34077415aabf", + Name: "db", + Address: "10.0.2.11", + ProtocolPort: 80, + ProvisioningStatus: "ACTIVE", + }}, + }}, + }}, + }, + } + LoadbalancerStatsTree = loadbalancers.Stats{ + ActiveConnections: 0, + BytesIn: 9532, + BytesOut: 22033, + RequestErrors: 46, + TotalConnections: 112, + } +) + +// HandleLoadbalancerListSuccessfully sets up the test server to respond to a loadbalancer List request. +func HandleLoadbalancerListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, LoadbalancersListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "loadbalancers": [] }`) + default: + t.Fatalf("/v2.0/lbaas/loadbalancers invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleLoadbalancerCreationSuccessfully sets up the test server to respond to a loadbalancer creation request +// with a given response. +func HandleLoadbalancerCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "loadbalancer": { + "name": "db_lb", + "vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "tags": ["test", "stage"] + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleLoadbalancerGetSuccessfully sets up the test server to respond to a loadbalancer Get request. +func HandleLoadbalancerGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleLoadbalancerBody) + }) +} + +// HandleLoadbalancerGetStatusesTree sets up the test server to respond to a loadbalancer Get statuses tree request. +func HandleLoadbalancerGetStatusesTree(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/status", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, GetLoadbalancerStatusesBody) + }) +} + +// HandleLoadbalancerDeletionSuccessfully sets up the test server to respond to a loadbalancer deletion request. +func HandleLoadbalancerDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleLoadbalancerUpdateSuccessfully sets up the test server to respond to a loadbalancer Update request. +func HandleLoadbalancerUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "loadbalancer": { + "name": "NewLoadbalancerName", + "tags": ["test"] + } + }`) + + fmt.Fprintf(w, PostUpdateLoadbalancerBody) + }) +} + +// HandleLoadbalancerGetStatsTree sets up the test server to respond to a loadbalancer Get stats tree request. +func HandleLoadbalancerGetStatsTree(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/stats", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, GetLoadbalancerStatsBody) + }) +} + +// HandleLoadbalancerFailoverSuccessfully sets up the test server to respond to a loadbalancer failover request. +func HandleLoadbalancerFailoverSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/failover", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go new file mode 100644 index 000000000000..1965bd9fa8b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go @@ -0,0 +1,171 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" + fake "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListLoadbalancers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerListSuccessfully(t) + + pages := 0 + err := loadbalancers.List(fake.ServiceClient(), loadbalancers.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := loadbalancers.ExtractLoadBalancers(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 loadbalancers, got %d", len(actual)) + } + th.CheckDeepEquals(t, LoadbalancerWeb, actual[0]) + th.CheckDeepEquals(t, LoadbalancerDb, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllLoadbalancers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerListSuccessfully(t) + + allPages, err := loadbalancers.List(fake.ServiceClient(), loadbalancers.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := loadbalancers.ExtractLoadBalancers(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, LoadbalancerWeb, actual[0]) + th.CheckDeepEquals(t, LoadbalancerDb, actual[1]) +} + +func TestCreateLoadbalancer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerCreationSuccessfully(t, SingleLoadbalancerBody) + + actual, err := loadbalancers.Create(fake.ServiceClient(), loadbalancers.CreateOpts{ + Name: "db_lb", + AdminStateUp: gophercloud.Enabled, + VipPortID: "2bf413c8-41a9-4477-b505-333d5cbe8b55", + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + Flavor: "medium", + Provider: "haproxy", + Tags: []string{"test", "stage"}, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, LoadbalancerDb, *actual) +} + +func TestGetLoadbalancer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := loadbalancers.Get(client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, LoadbalancerDb, *actual) +} + +func TestGetLoadbalancerStatusesTree(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerGetStatusesTree(t) + + client := fake.ServiceClient() + actual, err := loadbalancers.GetStatuses(client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, LoadbalancerStatusesTree, *actual) +} + +func TestDeleteLoadbalancer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerDeletionSuccessfully(t) + + res := loadbalancers.Delete(fake.ServiceClient(), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", nil) + th.AssertNoErr(t, res.Err) +} + +func TestUpdateLoadbalancer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerUpdateSuccessfully(t) + + client := fake.ServiceClient() + name := "NewLoadbalancerName" + tags := []string{"test"} + actual, err := loadbalancers.Update(client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", loadbalancers.UpdateOpts{ + Name: &name, + Tags: &tags, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, LoadbalancerUpdated, *actual) +} + +func TestCascadingDeleteLoadbalancer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerDeletionSuccessfully(t) + + sc := fake.ServiceClient() + deleteOpts := loadbalancers.DeleteOpts{ + Cascade: true, + } + + query, err := deleteOpts.ToLoadBalancerDeleteQuery() + th.AssertNoErr(t, err) + th.AssertEquals(t, query, "?cascade=true") + + err = loadbalancers.Delete(sc, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", deleteOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetLoadbalancerStatsTree(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerGetStatsTree(t) + + client := fake.ServiceClient() + actual, err := loadbalancers.GetStats(client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, LoadbalancerStatsTree, *actual) +} + +func TestFailoverLoadbalancer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerFailoverSuccessfully(t) + + res := loadbalancers.Failover(fake.ServiceClient(), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/urls.go new file mode 100644 index 000000000000..7b184e35f6af --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers/urls.go @@ -0,0 +1,31 @@ +package loadbalancers + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "loadbalancers" + statusPath = "status" + statisticsPath = "stats" + failoverPath = "failover" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func statusRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, statusPath) +} + +func statisticsRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, statisticsPath) +} + +func failoverRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, failoverPath) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/doc.go new file mode 100644 index 000000000000..6ed8c8fb5fff --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/doc.go @@ -0,0 +1,69 @@ +/* +Package monitors provides information and interaction with Monitors +of the LBaaS v2 extension for the OpenStack Networking service. + +Example to List Monitors + + listOpts := monitors.ListOpts{ + PoolID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + + allPages, err := monitors.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allMonitors, err := monitors.ExtractMonitors(allPages) + if err != nil { + panic(err) + } + + for _, monitor := range allMonitors { + fmt.Printf("%+v\n", monitor) + } + +Example to Create a Monitor + + createOpts := monitors.CreateOpts{ + Type: "HTTP", + Name: "db", + PoolID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", + Delay: 20, + Timeout: 10, + MaxRetries: 5, + URLPath: "/check", + ExpectedCodes: "200-299", + } + + monitor, err := monitors.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Monitor + + monitorID := "d67d56a6-4a86-4688-a282-f46444705c64" + + updateOpts := monitors.UpdateOpts{ + Name: "NewHealthmonitorName", + Delay: 3, + Timeout: 20, + MaxRetries: 10, + URLPath: "/another_check", + ExpectedCodes: "301", + } + + monitor, err := monitors.Update(networkClient, monitorID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Monitor + + monitorID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := monitors.Delete(networkClient, monitorID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package monitors diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/requests.go new file mode 100644 index 000000000000..f728f5a82378 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/requests.go @@ -0,0 +1,257 @@ +package monitors + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToMonitorListQuery() (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 Monitor attributes you want to see returned. SortKey allows you to +// sort by a particular Monitor 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"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + PoolID string `q:"pool_id"` + Type string `q:"type"` + Delay int `q:"delay"` + Timeout int `q:"timeout"` + MaxRetries int `q:"max_retries"` + HTTPMethod string `q:"http_method"` + URLPath string `q:"url_path"` + ExpectedCodes string `q:"expected_codes"` + AdminStateUp *bool `q:"admin_state_up"` + Status string `q:"status"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToMonitorListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToMonitorListQuery() (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 +// health monitors. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those health monitors that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToMonitorListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return MonitorPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Constants that represent approved monitoring types. +const ( + TypePING = "PING" + TypeTCP = "TCP" + TypeHTTP = "HTTP" + TypeHTTPS = "HTTPS" +) + +var ( + errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout") +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// List request. +type CreateOptsBuilder interface { + ToMonitorCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // The Pool to Monitor. + PoolID string `json:"pool_id" required:"true"` + + // The type of probe, which is PING, TCP, HTTP, or HTTPS, that is + // sent by the load balancer to verify the member state. + Type string `json:"type" required:"true"` + + // The time, in seconds, between sending probes to members. + Delay int `json:"delay" required:"true"` + + // Maximum number of seconds for a Monitor to wait for a ping reply + // before it times out. The value must be less than the delay value. + Timeout int `json:"timeout" required:"true"` + + // Number of permissible ping failures before changing the member's + // status to INACTIVE. Must be a number between 1 and 10. + MaxRetries int `json:"max_retries" required:"true"` + + // URI path that will be accessed if Monitor type is HTTP or HTTPS. + // Required for HTTP(S) types. + URLPath string `json:"url_path,omitempty"` + + // The HTTP method used for requests by the Monitor. If this attribute + // is not specified, it defaults to "GET". Required for HTTP(S) types. + HTTPMethod string `json:"http_method,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. + ExpectedCodes string `json:"expected_codes,omitempty"` + + // TenantID is the UUID of the project who owns the Monitor. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the UUID of the project who owns the Monitor. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // The Name of the Monitor. + Name string `json:"name,omitempty"` + + // The administrative state of the Monitor. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToMonitorCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToMonitorCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "healthmonitor") + if err != nil { + return nil, err + } + + switch opts.Type { + case TypeHTTP, TypeHTTPS: + switch opts.URLPath { + case "": + return nil, fmt.Errorf("URLPath must be provided for HTTP and HTTPS") + } + switch opts.ExpectedCodes { + case "": + return nil, fmt.Errorf("ExpectedCodes must be provided for HTTP and HTTPS") + } + } + + return b, nil +} + +/* + Create is an operation which provisions a new Health Monitor. There are + different types of Monitor you can provision: PING, TCP or HTTP(S). Below + are examples of how to create each one. + + Here is an example config struct to use when creating a PING or TCP Monitor: + + CreateOpts{Type: TypePING, Delay: 20, Timeout: 10, MaxRetries: 3} + CreateOpts{Type: TypeTCP, Delay: 20, Timeout: 10, MaxRetries: 3} + + Here is an example config struct to use when creating a HTTP(S) Monitor: + + CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3, + HttpMethod: "HEAD", ExpectedCodes: "200", PoolID: "2c946bfc-1804-43ab-a2ff-58f6a762b505"} +*/ +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToMonitorCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular Health Monitor based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToMonitorUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // The time, in seconds, between sending probes to members. + Delay int `json:"delay,omitempty"` + + // Maximum number of seconds for a Monitor to wait for a ping reply + // before it times out. The value must be less than the delay value. + Timeout int `json:"timeout,omitempty"` + + // Number of permissible ping failures before changing the member's + // status to INACTIVE. Must be a number between 1 and 10. + MaxRetries int `json:"max_retries,omitempty"` + + // URI path that will be accessed if Monitor type is HTTP or HTTPS. + // Required for HTTP(S) types. + URLPath string `json:"url_path,omitempty"` + + // The HTTP method used for requests by the Monitor. If this attribute + // is not specified, it defaults to "GET". Required for HTTP(S) types. + HTTPMethod string `json:"http_method,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. + ExpectedCodes string `json:"expected_codes,omitempty"` + + // The Name of the Monitor. + Name *string `json:"name,omitempty"` + + // The administrative state of the Monitor. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToMonitorUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToMonitorUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "healthmonitor") +} + +// Update is an operation which modifies the attributes of the specified +// Monitor. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToMonitorUpdateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} + +// Delete will permanently delete a particular Monitor based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/results.go new file mode 100644 index 000000000000..2c3a815bae86 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/results.go @@ -0,0 +1,156 @@ +package monitors + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type PoolID struct { + ID string `json:"id"` +} + +// Monitor represents a load balancer health monitor. A health monitor is used +// to determine whether or not back-end members of the VIP's pool are usable +// for processing a request. A pool can have several health monitors associated +// with it. There are different types of health monitors supported: +// +// PING: used to ping the members using ICMP. +// TCP: used to connect to the members using TCP. +// HTTP: used to send an HTTP request to the member. +// HTTPS: used to send a secure HTTP request to the member. +// +// When a pool has several monitors associated with it, each member of the pool +// is monitored by all these monitors. If any monitor declares the member as +// unhealthy, then the member status is changed to INACTIVE and the member +// won't participate in its pool's load balancing. In other words, ALL monitors +// must declare the member to be healthy for it to stay ACTIVE. +type Monitor struct { + // The unique ID for the Monitor. + ID string `json:"id"` + + // The Name of the Monitor. + Name string `json:"name"` + + // The owner of the Monitor. + ProjectID string `json:"project_id"` + + // The type of probe sent by the load balancer to verify the member state, + // which is PING, TCP, HTTP, or HTTPS. + Type string `json:"type"` + + // The time, in seconds, between sending probes to members. + Delay int `json:"delay"` + + // The maximum number of seconds for a monitor to wait for a connection to be + // established before it times out. This value must be less than the delay + // value. + Timeout int `json:"timeout"` + + // Number of allowed connection failures before changing the status of the + // member to INACTIVE. A valid value is from 1 to 10. + MaxRetries int `json:"max_retries"` + + // The HTTP method that the monitor uses for requests. + HTTPMethod string `json:"http_method"` + + // 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" ` + + // Expected HTTP codes for a passing HTTP(S) monitor. + ExpectedCodes string `json:"expected_codes"` + + // The administrative state of the health monitor, which is up (true) or + // down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The status of the health monitor. Indicates whether the health monitor is + // operational. + Status string `json:"status"` + + // List of pools that are associated with the health monitor. + Pools []PoolID `json:"pools"` + + // The provisioning status of the Monitor. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the monitor. + OperatingStatus string `json:"operating_status"` +} + +// MonitorPage is the page returned by a pager when traversing over a +// collection of health monitors. +type MonitorPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of monitors has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r MonitorPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"healthmonitors_links"` + } + + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a MonitorPage struct is empty. +func (r MonitorPage) IsEmpty() (bool, error) { + is, err := ExtractMonitors(r) + return len(is) == 0, err +} + +// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct, +// and extracts the elements into a slice of Monitor structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractMonitors(r pagination.Page) ([]Monitor, error) { + var s struct { + Monitors []Monitor `json:"healthmonitors"` + } + err := (r.(MonitorPage)).ExtractInto(&s) + return s.Monitors, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a monitor. +func (r commonResult) Extract() (*Monitor, error) { + var s struct { + Monitor *Monitor `json:"healthmonitor"` + } + err := r.ExtractInto(&s) + return s.Monitor, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Monitor. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Monitor. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Monitor. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the result succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/doc.go new file mode 100644 index 000000000000..e2b6f12a9285 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/doc.go @@ -0,0 +1,2 @@ +// monitors unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/fixtures.go new file mode 100644 index 000000000000..23c097b90990 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/fixtures.go @@ -0,0 +1,215 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// HealthmonitorsListBody contains the canned body of a healthmonitor list response. +const HealthmonitorsListBody = ` +{ + "healthmonitors":[ + { + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":10, + "name":"web", + "max_retries":1, + "timeout":1, + "type":"PING", + "pools": [{"id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}], + "id":"466c8345-28d8-4f84-a246-e04380b0461d" + }, + { + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":5, + "name":"db", + "expected_codes":"200", + "max_retries":2, + "http_method":"GET", + "timeout":2, + "url_path":"/", + "type":"HTTP", + "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}], + "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7" + } + ] +} +` + +// SingleHealthmonitorBody is the canned body of a Get request on an existing healthmonitor. +const SingleHealthmonitorBody = ` +{ + "healthmonitor": { + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":5, + "name":"db", + "expected_codes":"200", + "max_retries":2, + "http_method":"GET", + "timeout":2, + "url_path":"/", + "type":"HTTP", + "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}], + "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7" + } +} +` + +// PostUpdateHealthmonitorBody is the canned response body of a Update request on an existing healthmonitor. +const PostUpdateHealthmonitorBody = ` +{ + "healthmonitor": { + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":3, + "name":"NewHealthmonitorName", + "expected_codes":"301", + "max_retries":10, + "http_method":"GET", + "timeout":20, + "url_path":"/another_check", + "type":"HTTP", + "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}], + "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7" + } +} +` + +var ( + HealthmonitorWeb = monitors.Monitor{ + AdminStateUp: true, + Name: "web", + ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", + Delay: 10, + MaxRetries: 1, + Timeout: 1, + Type: "PING", + ID: "466c8345-28d8-4f84-a246-e04380b0461d", + Pools: []monitors.PoolID{{ID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}}, + } + HealthmonitorDb = monitors.Monitor{ + AdminStateUp: true, + Name: "db", + ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", + Delay: 5, + ExpectedCodes: "200", + MaxRetries: 2, + Timeout: 2, + URLPath: "/", + Type: "HTTP", + HTTPMethod: "GET", + ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7", + Pools: []monitors.PoolID{{ID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}}, + } + HealthmonitorUpdated = monitors.Monitor{ + AdminStateUp: true, + Name: "NewHealthmonitorName", + ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", + Delay: 3, + ExpectedCodes: "301", + MaxRetries: 10, + Timeout: 20, + URLPath: "/another_check", + Type: "HTTP", + HTTPMethod: "GET", + ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7", + Pools: []monitors.PoolID{{ID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}}, + } +) + +// HandleHealthmonitorListSuccessfully sets up the test server to respond to a healthmonitor List request. +func HandleHealthmonitorListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, HealthmonitorsListBody) + case "556c8345-28d8-4f84-a246-e04380b0461d": + fmt.Fprintf(w, `{ "healthmonitors": [] }`) + default: + t.Fatalf("/v2.0/lbaas/healthmonitors invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleHealthmonitorCreationSuccessfully sets up the test server to respond to a healthmonitor creation request +// with a given response. +func HandleHealthmonitorCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "healthmonitor": { + "type":"HTTP", + "pool_id":"84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", + "project_id":"453105b9-1754-413f-aab1-55f1af620750", + "delay":20, + "name":"db", + "timeout":10, + "max_retries":5, + "url_path":"/check", + "expected_codes":"200-299" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleHealthmonitorGetSuccessfully sets up the test server to respond to a healthmonitor Get request. +func HandleHealthmonitorGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleHealthmonitorBody) + }) +} + +// HandleHealthmonitorDeletionSuccessfully sets up the test server to respond to a healthmonitor deletion request. +func HandleHealthmonitorDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleHealthmonitorUpdateSuccessfully sets up the test server to respond to a healthmonitor Update request. +func HandleHealthmonitorUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "healthmonitor": { + "name": "NewHealthmonitorName", + "delay": 3, + "timeout": 20, + "max_retries": 10, + "url_path": "/another_check", + "expected_codes": "301" + } + }`) + + fmt.Fprintf(w, PostUpdateHealthmonitorBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/requests_test.go new file mode 100644 index 000000000000..8ccfb950dc8b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/testing/requests_test.go @@ -0,0 +1,155 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" + fake "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListHealthmonitors(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHealthmonitorListSuccessfully(t) + + pages := 0 + err := monitors.List(fake.ServiceClient(), monitors.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := monitors.ExtractMonitors(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 healthmonitors, got %d", len(actual)) + } + th.CheckDeepEquals(t, HealthmonitorWeb, actual[0]) + th.CheckDeepEquals(t, HealthmonitorDb, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllHealthmonitors(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHealthmonitorListSuccessfully(t) + + allPages, err := monitors.List(fake.ServiceClient(), monitors.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := monitors.ExtractMonitors(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, HealthmonitorWeb, actual[0]) + th.CheckDeepEquals(t, HealthmonitorDb, actual[1]) +} + +func TestCreateHealthmonitor(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHealthmonitorCreationSuccessfully(t, SingleHealthmonitorBody) + + actual, err := monitors.Create(fake.ServiceClient(), monitors.CreateOpts{ + Type: "HTTP", + Name: "db", + PoolID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", + ProjectID: "453105b9-1754-413f-aab1-55f1af620750", + Delay: 20, + Timeout: 10, + MaxRetries: 5, + URLPath: "/check", + ExpectedCodes: "200-299", + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, HealthmonitorDb, *actual) +} + +func TestRequiredCreateOpts(t *testing.T) { + res := monitors.Create(fake.ServiceClient(), monitors.CreateOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = monitors.Create(fake.ServiceClient(), monitors.CreateOpts{Type: monitors.TypeHTTP}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestGetHealthmonitor(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHealthmonitorGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := monitors.Get(client, "5d4b5228-33b0-4e60-b225-9b727c1a20e7").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, HealthmonitorDb, *actual) +} + +func TestDeleteHealthmonitor(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHealthmonitorDeletionSuccessfully(t) + + res := monitors.Delete(fake.ServiceClient(), "5d4b5228-33b0-4e60-b225-9b727c1a20e7") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateHealthmonitor(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHealthmonitorUpdateSuccessfully(t) + + client := fake.ServiceClient() + name := "NewHealthmonitorName" + actual, err := monitors.Update(client, "5d4b5228-33b0-4e60-b225-9b727c1a20e7", monitors.UpdateOpts{ + Name: &name, + Delay: 3, + Timeout: 20, + MaxRetries: 10, + URLPath: "/another_check", + ExpectedCodes: "301", + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, HealthmonitorUpdated, *actual) +} + +func TestDelayMustBeGreaterOrEqualThanTimeout(t *testing.T) { + _, err := monitors.Create(fake.ServiceClient(), monitors.CreateOpts{ + Type: "HTTP", + PoolID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d", + Delay: 1, + Timeout: 10, + MaxRetries: 5, + URLPath: "/check", + ExpectedCodes: "200-299", + }).Extract() + + if err == nil { + t.Fatalf("Expected error, got none") + } + + _, err = monitors.Update(fake.ServiceClient(), "453105b9-1754-413f-aab1-55f1af620750", monitors.UpdateOpts{ + Delay: 1, + Timeout: 10, + }).Extract() + + if err == nil { + t.Fatalf("Expected error, got none") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/urls.go new file mode 100644 index 000000000000..a222e52a93dd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors/urls.go @@ -0,0 +1,16 @@ +package monitors + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "healthmonitors" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/doc.go new file mode 100644 index 000000000000..d26de312cbb8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/doc.go @@ -0,0 +1,154 @@ +/* +Package pools provides information and interaction with Pools and +Members of the LBaaS v2 extension for the OpenStack Networking service. + +Example to List Pools + + listOpts := pools.ListOpts{ + LoadbalancerID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + + allPages, err := pools.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPools, err := pools.ExtractMonitors(allPages) + if err != nil { + panic(err) + } + + for _, pools := range allPools { + fmt.Printf("%+v\n", pool) + } + +Example to Create a Pool + + createOpts := pools.CreateOpts{ + LBMethod: pools.LBMethodRoundRobin, + Protocol: "HTTP", + Name: "Example pool", + LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", + } + + pool, err := pools.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Pool + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + updateOpts := pools.UpdateOpts{ + Name: "new-name", + } + + pool, err := pools.Update(networkClient, poolID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Pool + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := pools.Delete(networkClient, poolID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Pool Members + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + listOpts := pools.ListMemberOpts{ + ProtocolPort: 80, + } + + allPages, err := pools.ListMembers(networkClient, poolID, listOpts).AllPages() + if err != nil { + panic(err) + } + + allMembers, err := pools.ExtractMembers(allPages) + if err != nil { + panic(err) + } + + for _, member := allMembers { + fmt.Printf("%+v\n", member) + } + +Example to Create a Member + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + weight := 10 + createOpts := pools.CreateMemberOpts{ + Name: "db", + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + Address: "10.0.2.11", + ProtocolPort: 80, + Weight: &weight, + } + + member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Member + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + memberID := "64dba99f-8af8-4200-8882-e32a0660f23e" + + weight := 4 + updateOpts := pools.UpdateMemberOpts{ + Name: "new-name", + Weight: &weight, + } + + member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Member + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + memberID := "64dba99f-8af8-4200-8882-e32a0660f23e" + + err := pools.DeleteMember(networkClient, poolID, memberID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update Members: + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + weight_1 := 20 + member1 := pools.BatchUpdateMemberOpts{ + Address: "192.0.2.16", + ProtocolPort: 80, + Name: "web-server-1", + SubnetID: "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa", + Weight: &weight_1, + } + + weight_2 := 10 + member2 := pools.BatchUpdateMemberOpts{ + Address: "192.0.2.17", + ProtocolPort: 80, + Name: "web-server-2", + Weight: &weight_2, + SubnetID: "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa", + } + members := []pools.BatchUpdateMemberOpts{member1, member2} + + err := pools.BatchUpdateMembers(networkClient, poolID, members).ExtractErr() + if err != nil { + panic(err) + } +*/ +package pools diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/requests.go new file mode 100644 index 000000000000..fbf16dc2880f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/requests.go @@ -0,0 +1,378 @@ +package pools + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPoolListQuery() (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 Pool attributes you want to see returned. SortKey allows you to +// sort by a particular Pool attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + LBMethod string `q:"lb_algorithm"` + Protocol string `q:"protocol"` + ProjectID string `q:"project_id"` + AdminStateUp *bool `q:"admin_state_up"` + Name string `q:"name"` + ID string `q:"id"` + LoadbalancerID string `q:"loadbalancer_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToPoolListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPoolListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// pools. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those pools that are owned by the +// project who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToPoolListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PoolPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type LBMethod string +type Protocol string + +// Supported attributes for create/update operations. +const ( + LBMethodRoundRobin LBMethod = "ROUND_ROBIN" + LBMethodLeastConnections LBMethod = "LEAST_CONNECTIONS" + LBMethodSourceIp LBMethod = "SOURCE_IP" + + ProtocolTCP Protocol = "TCP" + ProtocolUDP Protocol = "UDP" + ProtocolPROXY Protocol = "PROXY" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPoolCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // The algorithm used to distribute load between the members of the pool. The + // current specification supports LBMethodRoundRobin, LBMethodLeastConnections + // and LBMethodSourceIp as valid values for this attribute. + LBMethod LBMethod `json:"lb_algorithm" required:"true"` + + // The protocol used by the pool members, you can use either + // ProtocolTCP, ProtocolUDP, ProtocolPROXY, ProtocolHTTP, or ProtocolHTTPS. + Protocol Protocol `json:"protocol" required:"true"` + + // The Loadbalancer on which the members of the pool will be associated with. + // Note: one of LoadbalancerID or ListenerID must be provided. + LoadbalancerID string `json:"loadbalancer_id,omitempty" xor:"ListenerID"` + + // The Listener on which the members of the pool will be associated with. + // Note: one of LoadbalancerID or ListenerID must be provided. + ListenerID string `json:"listener_id,omitempty" xor:"LoadbalancerID"` + + // ProjectID is the UUID of the project who owns the Pool. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // Name of the pool. + Name string `json:"name,omitempty"` + + // Human-readable description for the pool. + Description string `json:"description,omitempty"` + + // Persistence is the session persistence of the pool. + // Omit this field to prevent session persistence. + Persistence *SessionPersistence `json:"session_persistence,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToPoolCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToPoolCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "pool") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// load balancer pool. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPoolCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular pool based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPoolUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // Name of the pool. + Name *string `json:"name,omitempty"` + + // Human-readable description for the pool. + Description *string `json:"description,omitempty"` + + // The algorithm used to distribute load between the members of the pool. The + // current specification supports LBMethodRoundRobin, LBMethodLeastConnections + // and LBMethodSourceIp as valid values for this attribute. + LBMethod LBMethod `json:"lb_algorithm,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToPoolUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToPoolUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "pool") +} + +// Update allows pools to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPoolUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete will permanently delete a particular pool based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// ListMemberOptsBuilder allows extensions to add additional parameters to the +// ListMembers request. +type ListMembersOptsBuilder interface { + ToMembersListQuery() (string, error) +} + +// ListMembersOpts allows the filtering and sorting of paginated collections +// through the API. Filtering is achieved by passing in struct field values +// that map to the Member attributes you want to see returned. SortKey allows +// you to sort by a particular Member attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. +type ListMembersOpts struct { + Name string `q:"name"` + Weight int `q:"weight"` + AdminStateUp *bool `q:"admin_state_up"` + ProjectID string `q:"project_id"` + Address string `q:"address"` + ProtocolPort int `q:"protocol_port"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToMemberListQuery formats a ListOpts into a query string. +func (opts ListMembersOpts) ToMembersListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListMembers returns a Pager which allows you to iterate over a collection of +// members. It accepts a ListMembersOptsBuilder, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only those members that are owned by the +// project who submits the request, unless an admin user submits the request. +func ListMembers(c *gophercloud.ServiceClient, poolID string, opts ListMembersOptsBuilder) pagination.Pager { + url := memberRootURL(c, poolID) + if opts != nil { + query, err := opts.ToMembersListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return MemberPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateMemberOptsBuilder allows extensions to add additional parameters to the +// CreateMember request. +type CreateMemberOptsBuilder interface { + ToMemberCreateMap() (map[string]interface{}, error) +} + +// CreateMemberOpts is the common options struct used in this package's CreateMember +// operation. +type CreateMemberOpts struct { + // The IP address of the member to receive traffic from the load balancer. + Address string `json:"address" required:"true"` + + // The port on which to listen for client traffic. + ProtocolPort int `json:"protocol_port" required:"true"` + + // Name of the Member. + Name string `json:"name,omitempty"` + + // ProjectID is the UUID of the project who owns the Member. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // A positive integer value that indicates the relative portion of traffic + // that this member should receive from the pool. For example, a member with + // a weight of 10 receives five times as much traffic as a member with a + // weight of 2. + Weight *int `json:"weight,omitempty"` + + // If you omit this parameter, LBaaS uses the vip_subnet_id parameter value + // for the subnet UUID. + SubnetID string `json:"subnet_id,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToMemberCreateMap builds a request body from CreateMemberOpts. +func (opts CreateMemberOpts) ToMemberCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "member") +} + +// CreateMember will create and associate a Member with a particular Pool. +func CreateMember(c *gophercloud.ServiceClient, poolID string, opts CreateMemberOpts) (r CreateMemberResult) { + b, err := opts.ToMemberCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(memberRootURL(c, poolID), b, &r.Body, nil) + return +} + +// GetMember retrieves a particular Pool Member based on its unique ID. +func GetMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r GetMemberResult) { + _, r.Err = c.Get(memberResourceURL(c, poolID, memberID), &r.Body, nil) + return +} + +// UpdateMemberOptsBuilder allows extensions to add additional parameters to the +// List request. +type UpdateMemberOptsBuilder interface { + ToMemberUpdateMap() (map[string]interface{}, error) +} + +// UpdateMemberOpts is the common options struct used in this package's Update +// operation. +type UpdateMemberOpts struct { + // Name of the Member. + Name *string `json:"name,omitempty"` + + // A positive integer value that indicates the relative portion of traffic + // that this member should receive from the pool. For example, a member with + // a weight of 10 receives five times as much traffic as a member with a + // weight of 2. + Weight *int `json:"weight,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToMemberUpdateMap builds a request body from UpdateMemberOpts. +func (opts UpdateMemberOpts) ToMemberUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "member") +} + +// Update allows Member to be updated. +func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string, opts UpdateMemberOptsBuilder) (r UpdateMemberResult) { + b, err := opts.ToMemberUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(memberResourceURL(c, poolID, memberID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// BatchUpdateMemberOptsBuilder allows extensions to add additional parameters to the BatchUpdateMembers request. +type BatchUpdateMemberOptsBuilder interface { + ToBatchMemberUpdateMap() (map[string]interface{}, error) +} + +type BatchUpdateMemberOpts CreateMemberOpts + +// ToBatchMemberUpdateMap builds a request body from BatchUpdateMemberOpts. +func (opts BatchUpdateMemberOpts) ToBatchMemberUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// BatchUpdateMembers updates the pool members in batch +func BatchUpdateMembers(c *gophercloud.ServiceClient, poolID string, opts []BatchUpdateMemberOpts) (r UpdateMembersResult) { + var members []map[string]interface{} + for _, opt := range opts { + b, err := opt.ToBatchMemberUpdateMap() + if err != nil { + r.Err = err + return + } + members = append(members, b) + } + + b := map[string]interface{}{"members": members} + + _, r.Err = c.Put(memberRootURL(c, poolID), b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}}) + return +} + +// DisassociateMember will remove and disassociate a Member from a particular +// Pool. +func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) { + _, r.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/results.go new file mode 100644 index 000000000000..1dc0ed90ac25 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/results.go @@ -0,0 +1,328 @@ +package pools + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" + "github.com/gophercloud/gophercloud/pagination" +) + +// SessionPersistence represents the session persistence feature of the load +// balancing service. It attempts to force connections or requests in the same +// session to be processed by the same member as long as it is ative. Three +// types of persistence are supported: +// +// SOURCE_IP: With this mode, all connections originating from the same source +// IP address, will be handled by the same Member of the Pool. +// HTTP_COOKIE: With this persistence mode, the load balancing function will +// create a cookie on the first request from a client. Subsequent +// requests containing the same cookie value will be handled by +// the same Member of the Pool. +// APP_COOKIE: With this persistence mode, the load balancing function will +// rely on a cookie established by the backend application. All +// requests carrying the same cookie value will be handled by the +// same Member of the Pool. +type SessionPersistence struct { + // The type of persistence mode. + Type string `json:"type"` + + // Name of cookie if persistence mode is set appropriately. + CookieName string `json:"cookie_name,omitempty"` +} + +// LoadBalancerID represents a load balancer. +type LoadBalancerID struct { + ID string `json:"id"` +} + +// ListenerID represents a listener. +type ListenerID struct { + ID string `json:"id"` +} + +// Pool represents a logical set of devices, such as web servers, that you +// group together to receive and process traffic. The load balancing function +// chooses a Member of the Pool according to the configured load balancing +// method to handle the new requests or connections received on the VIP address. +type Pool struct { + // The load-balancer algorithm, which is round-robin, least-connections, and + // so on. This value, which must be supported, is dependent on the provider. + // Round-robin must be supported. + LBMethod string `json:"lb_algorithm"` + + // The protocol of the Pool, which is TCP, HTTP, or HTTPS. + Protocol string `json:"protocol"` + + // Description for the Pool. + Description string `json:"description"` + + // A list of listeners objects IDs. + Listeners []ListenerID `json:"listeners"` //[]map[string]interface{} + + // A list of member objects IDs. + Members []Member `json:"members"` + + // The ID of associated health monitor. + MonitorID string `json:"healthmonitor_id"` + + // The network on which the members of the Pool will be located. Only members + // that are on this network can be added to the Pool. + SubnetID string `json:"subnet_id"` + + // Owner of the Pool. + ProjectID string `json:"project_id"` + + // The administrative state of the Pool, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // Pool name. Does not have to be unique. + Name string `json:"name"` + + // The unique ID for the Pool. + ID string `json:"id"` + + // A list of load balancer objects IDs. + Loadbalancers []LoadBalancerID `json:"loadbalancers"` + + // Indicates whether connections in the same session will be processed by the + // same Pool member or not. + Persistence SessionPersistence `json:"session_persistence"` + + // The load balancer provider. + Provider string `json:"provider"` + + // The Monitor associated with this Pool. + Monitor monitors.Monitor `json:"healthmonitor"` + + // The provisioning status of the pool. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the pool. + OperatingStatus string `json:"operating_status"` +} + +// PoolPage is the page returned by a pager when traversing over a +// collection of pools. +type PoolPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of pools has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r PoolPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"pools_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a PoolPage struct is empty. +func (r PoolPage) IsEmpty() (bool, error) { + is, err := ExtractPools(r) + return len(is) == 0, err +} + +// ExtractPools accepts a Page struct, specifically a PoolPage struct, +// and extracts the elements into a slice of Pool structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPools(r pagination.Page) ([]Pool, error) { + var s struct { + Pools []Pool `json:"pools"` + } + err := (r.(PoolPage)).ExtractInto(&s) + return s.Pools, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a pool. +func (r commonResult) Extract() (*Pool, error) { + var s struct { + Pool *Pool `json:"pool"` + } + err := r.ExtractInto(&s) + return s.Pool, err +} + +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret the result as a Pool. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret the result as a Pool. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret the result as a Pool. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// Member represents the application running on a backend server. +type Member struct { + // Name of the Member. + Name string `json:"name"` + + // Weight of Member. + Weight int `json:"weight"` + + // The administrative state of the member, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // Owner of the Member. + ProjectID string `json:"project_id"` + + // Parameter value for the subnet UUID. + SubnetID string `json:"subnet_id"` + + // The Pool to which the Member belongs. + PoolID string `json:"pool_id"` + + // The IP address of the Member. + Address string `json:"address"` + + // The port on which the application is hosted. + ProtocolPort int `json:"protocol_port"` + + // The unique ID for the Member. + ID string `json:"id"` + + // The provisioning status of the pool. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // DateTime when the member was created + CreatedAt time.Time `json:"-"` + + // DateTime when the member was updated + UpdatedAt time.Time `json:"-"` + + // The operating status of the member + OperatingStatus string `json:"operating_status"` + + // Is the member a backup? Backup members only receive traffic when all non-backup members are down. + Backup bool `json:"backup"` + + // An alternate IP address used for health monitoring a backend member. + MonitorAddress string `json:"monitor_address"` + + // An alternate protocol port used for health monitoring a backend member. + MonitorPort int `json:"monitor_port"` +} + +// MemberPage is the page returned by a pager when traversing over a +// collection of Members in a Pool. +type MemberPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of members has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r MemberPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"members_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a MemberPage struct is empty. +func (r MemberPage) IsEmpty() (bool, error) { + is, err := ExtractMembers(r) + return len(is) == 0, err +} + +// ExtractMembers accepts a Page struct, specifically a MemberPage struct, +// and extracts the elements into a slice of Members structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractMembers(r pagination.Page) ([]Member, error) { + var s struct { + Members []Member `json:"members"` + } + err := (r.(MemberPage)).ExtractInto(&s) + return s.Members, err +} + +type commonMemberResult struct { + gophercloud.Result +} + +func (r *Member) UnmarshalJSON(b []byte) error { + type tmp Member + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Member(s.tmp) + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + return nil +} + +// ExtractMember is a function that accepts a result and extracts a member. +func (r commonMemberResult) Extract() (*Member, error) { + var s struct { + Member *Member `json:"member"` + } + err := r.ExtractInto(&s) + return s.Member, err +} + +// CreateMemberResult represents the result of a CreateMember operation. +// Call its Extract method to interpret it as a Member. +type CreateMemberResult struct { + commonMemberResult +} + +// GetMemberResult represents the result of a GetMember operation. +// Call its Extract method to interpret it as a Member. +type GetMemberResult struct { + commonMemberResult +} + +// UpdateMemberResult represents the result of an UpdateMember operation. +// Call its Extract method to interpret it as a Member. +type UpdateMemberResult struct { + commonMemberResult +} + +// UpdateMembersResult represents the result of an UpdateMembers operation. +// Call its ExtractErr method to determine if the request succeeded or failed. +type UpdateMembersResult struct { + gophercloud.ErrResult +} + +// DeleteMemberResult represents the result of a DeleteMember operation. +// Call its ExtractErr method to determine if the request succeeded or failed. +type DeleteMemberResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/doc.go new file mode 100644 index 000000000000..46e335f3f2d6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/doc.go @@ -0,0 +1,2 @@ +// pools unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/fixtures.go new file mode 100644 index 000000000000..4c1b8f18c1d1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/fixtures.go @@ -0,0 +1,440 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// PoolsListBody contains the canned body of a pool list response. +const PoolsListBody = ` +{ + "pools":[ + { + "lb_algorithm":"ROUND_ROBIN", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "466c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "53306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"72741b06-df4d-4715-b142-276b6bce75ab", + "name":"web", + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + }, + { + "lb_algorithm":"LEAST_CONNECTION", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "5f6c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "67306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"c3741b06-df4d-4715-b142-276b6bce75ab", + "name":"db", + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + } + ] +} +` + +// SinglePoolBody is the canned body of a Get request on an existing pool. +const SinglePoolBody = ` +{ + "pool": { + "lb_algorithm":"LEAST_CONNECTION", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "5f6c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "67306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"c3741b06-df4d-4715-b142-276b6bce75ab", + "name":"db", + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + } +} +` + +// PostUpdatePoolBody is the canned response body of a Update request on an existing pool. +const PostUpdatePoolBody = ` +{ + "pool": { + "lb_algorithm":"LEAST_CONNECTION", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "5f6c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "67306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"c3741b06-df4d-4715-b142-276b6bce75ab", + "name":"db", + "admin_state_up":true, + "project_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + } +} +` + +var ( + PoolWeb = pools.Pool{ + LBMethod: "ROUND_ROBIN", + Protocol: "HTTP", + Description: "", + MonitorID: "466c8345-28d8-4f84-a246-e04380b0461d", + ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", + AdminStateUp: true, + Name: "web", + Members: []pools.Member{{ID: "53306cda-815d-4354-9fe4-59e09da9c3c5"}}, + ID: "72741b06-df4d-4715-b142-276b6bce75ab", + Loadbalancers: []pools.LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Listeners: []pools.ListenerID{{ID: "2a280670-c202-4b0b-a562-34077415aabf"}}, + Provider: "haproxy", + } + PoolDb = pools.Pool{ + LBMethod: "LEAST_CONNECTION", + Protocol: "HTTP", + Description: "", + MonitorID: "5f6c8345-28d8-4f84-a246-e04380b0461d", + ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", + AdminStateUp: true, + Name: "db", + Members: []pools.Member{{ID: "67306cda-815d-4354-9fe4-59e09da9c3c5"}}, + ID: "c3741b06-df4d-4715-b142-276b6bce75ab", + Loadbalancers: []pools.LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Listeners: []pools.ListenerID{{ID: "2a280670-c202-4b0b-a562-34077415aabf"}}, + Provider: "haproxy", + } + PoolUpdated = pools.Pool{ + LBMethod: "LEAST_CONNECTION", + Protocol: "HTTP", + Description: "", + MonitorID: "5f6c8345-28d8-4f84-a246-e04380b0461d", + ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", + AdminStateUp: true, + Name: "db", + Members: []pools.Member{{ID: "67306cda-815d-4354-9fe4-59e09da9c3c5"}}, + ID: "c3741b06-df4d-4715-b142-276b6bce75ab", + Loadbalancers: []pools.LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Listeners: []pools.ListenerID{{ID: "2a280670-c202-4b0b-a562-34077415aabf"}}, + Provider: "haproxy", + } +) + +// HandlePoolListSuccessfully sets up the test server to respond to a pool List request. +func HandlePoolListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, PoolsListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "pools": [] }`) + default: + t.Fatalf("/v2.0/lbaas/pools invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandlePoolCreationSuccessfully sets up the test server to respond to a pool creation request +// with a given response. +func HandlePoolCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "pool": { + "lb_algorithm": "ROUND_ROBIN", + "protocol": "HTTP", + "name": "Example pool", + "project_id": "2ffc6e22aae24e4795f87155d24c896f", + "loadbalancer_id": "79e05663-7f03-45d2-a092-8b94062f22ab" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandlePoolGetSuccessfully sets up the test server to respond to a pool Get request. +func HandlePoolGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SinglePoolBody) + }) +} + +// HandlePoolDeletionSuccessfully sets up the test server to respond to a pool deletion request. +func HandlePoolDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandlePoolUpdateSuccessfully sets up the test server to respond to a pool Update request. +func HandlePoolUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "pool": { + "name": "NewPoolName", + "lb_algorithm": "LEAST_CONNECTIONS" + } + }`) + + fmt.Fprintf(w, PostUpdatePoolBody) + }) +} + +// MembersListBody contains the canned body of a member list response. +const MembersListBody = ` +{ + "members":[ + { + "id": "2a280670-c202-4b0b-a562-34077415aabf", + "address": "10.0.2.10", + "weight": 5, + "name": "web", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "project_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":true, + "protocol_port": 80 + }, + { + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "project_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":false, + "protocol_port": 80, + "provisioning_status": "ACTIVE", + "created_at": "2018-08-23T20:05:21", + "updated_at": "2018-08-23T21:22:53", + "operating_status": "ONLINE", + "backup": false, + "monitor_address": "192.168.1.111", + "monitor_port": 80 + } + ] +} +` + +// SingleMemberBody is the canned body of a Get request on an existing member. +const SingleMemberBody = ` +{ + "member": { + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "project_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":false, + "protocol_port": 80, + "provisioning_status": "ACTIVE", + "created_at": "2018-08-23T20:05:21", + "updated_at": "2018-08-23T21:22:53", + "operating_status": "ONLINE", + "backup": false, + "monitor_address": "192.168.1.111", + "monitor_port": 80 + } +} +` + +// PostUpdateMemberBody is the canned response body of a Update request on an existing member. +const PostUpdateMemberBody = ` +{ + "member": { + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "project_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":false, + "protocol_port": 80 + } +} +` + +var ( + MemberWeb = pools.Member{ + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + ProjectID: "2ffc6e22aae24e4795f87155d24c896f", + AdminStateUp: true, + Name: "web", + ID: "2a280670-c202-4b0b-a562-34077415aabf", + Address: "10.0.2.10", + Weight: 5, + ProtocolPort: 80, + } + MemberDb = pools.Member{ + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + ProjectID: "2ffc6e22aae24e4795f87155d24c896f", + AdminStateUp: false, + Name: "db", + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Address: "10.0.2.11", + Weight: 10, + ProtocolPort: 80, + ProvisioningStatus: "ACTIVE", + CreatedAt: time.Date(2018, 8, 23, 20, 05, 21, 0, time.UTC), + UpdatedAt: time.Date(2018, 8, 23, 21, 22, 53, 0, time.UTC), + OperatingStatus: "ONLINE", + Backup: false, + MonitorAddress: "192.168.1.111", + MonitorPort: 80, + } + MemberUpdated = pools.Member{ + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + ProjectID: "2ffc6e22aae24e4795f87155d24c896f", + AdminStateUp: false, + Name: "db", + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Address: "10.0.2.11", + Weight: 10, + ProtocolPort: 80, + } +) + +// HandleMemberListSuccessfully sets up the test server to respond to a member List request. +func HandleMemberListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, MembersListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "members": [] }`) + default: + t.Fatalf("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleMemberCreationSuccessfully sets up the test server to respond to a member creation request +// with a given response. +func HandleMemberCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "member": { + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "project_id": "2ffc6e22aae24e4795f87155d24c896f", + "protocol_port": 80 + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleMemberGetSuccessfully sets up the test server to respond to a member Get request. +func HandleMemberGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleMemberBody) + }) +} + +// HandleMemberDeletionSuccessfully sets up the test server to respond to a member deletion request. +func HandleMemberDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleMemberUpdateSuccessfully sets up the test server to respond to a member Update request. +func HandleMemberUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "member": { + "name": "newMemberName", + "weight": 4 + } + }`) + + fmt.Fprintf(w, PostUpdateMemberBody) + }) +} + +// HandleMembersUpdateSuccessfully sets up the test server to respond to a batch member Update request. +func HandleMembersUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "members": [ + { + "name": "web-server-1", + "weight": 20, + "subnet_id": "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa", + "address": "192.0.2.16", + "protocol_port": 80 + }, + { + "name": "web-server-2", + "weight": 10, + "subnet_id": "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa", + "address": "192.0.2.17", + "protocol_port": 80 + } + ] + }`) + + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/requests_test.go new file mode 100644 index 000000000000..106cb0303cda --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/testing/requests_test.go @@ -0,0 +1,328 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" + fake "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListPools(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePoolListSuccessfully(t) + + pages := 0 + err := pools.List(fake.ServiceClient(), pools.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := pools.ExtractPools(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 pools, got %d", len(actual)) + } + th.CheckDeepEquals(t, PoolWeb, actual[0]) + th.CheckDeepEquals(t, PoolDb, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllPools(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePoolListSuccessfully(t) + + allPages, err := pools.List(fake.ServiceClient(), pools.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := pools.ExtractPools(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, PoolWeb, actual[0]) + th.CheckDeepEquals(t, PoolDb, actual[1]) +} + +func TestCreatePool(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePoolCreationSuccessfully(t, SinglePoolBody) + + actual, err := pools.Create(fake.ServiceClient(), pools.CreateOpts{ + LBMethod: pools.LBMethodRoundRobin, + Protocol: "HTTP", + Name: "Example pool", + ProjectID: "2ffc6e22aae24e4795f87155d24c896f", + LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, PoolDb, *actual) +} + +func TestGetPool(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePoolGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := pools.Get(client, "c3741b06-df4d-4715-b142-276b6bce75ab").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, PoolDb, *actual) +} + +func TestDeletePool(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePoolDeletionSuccessfully(t) + + res := pools.Delete(fake.ServiceClient(), "c3741b06-df4d-4715-b142-276b6bce75ab") + th.AssertNoErr(t, res.Err) +} + +func TestUpdatePool(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePoolUpdateSuccessfully(t) + + client := fake.ServiceClient() + name := "NewPoolName" + actual, err := pools.Update(client, "c3741b06-df4d-4715-b142-276b6bce75ab", pools.UpdateOpts{ + Name: &name, + LBMethod: pools.LBMethodLeastConnections, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, PoolUpdated, *actual) +} + +func TestRequiredPoolCreateOpts(t *testing.T) { + res := pools.Create(fake.ServiceClient(), pools.CreateOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = pools.Create(fake.ServiceClient(), pools.CreateOpts{ + LBMethod: pools.LBMethod("invalid"), + Protocol: pools.ProtocolHTTPS, + LoadbalancerID: "69055154-f603-4a28-8951-7cc2d9e54a9a", + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + + res = pools.Create(fake.ServiceClient(), pools.CreateOpts{ + LBMethod: pools.LBMethodRoundRobin, + Protocol: pools.Protocol("invalid"), + LoadbalancerID: "69055154-f603-4a28-8951-7cc2d9e54a9a", + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + + res = pools.Create(fake.ServiceClient(), pools.CreateOpts{ + LBMethod: pools.LBMethodRoundRobin, + Protocol: pools.ProtocolHTTPS, + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} + +func TestListMembers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMemberListSuccessfully(t) + + pages := 0 + err := pools.ListMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := pools.ExtractMembers(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 members, got %d", len(actual)) + } + th.CheckDeepEquals(t, MemberWeb, actual[0]) + th.CheckDeepEquals(t, MemberDb, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllMembers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMemberListSuccessfully(t) + + allPages, err := pools.ListMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.ListMembersOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := pools.ExtractMembers(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, MemberWeb, actual[0]) + th.CheckDeepEquals(t, MemberDb, actual[1]) +} + +func TestCreateMember(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMemberCreationSuccessfully(t, SingleMemberBody) + + weight := 10 + actual, err := pools.CreateMember(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ + Name: "db", + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + ProjectID: "2ffc6e22aae24e4795f87155d24c896f", + Address: "10.0.2.11", + ProtocolPort: 80, + Weight: &weight, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, MemberDb, *actual) +} + +func TestRequiredMemberCreateOpts(t *testing.T) { + res := pools.CreateMember(fake.ServiceClient(), "", pools.CreateMemberOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = pools.CreateMember(fake.ServiceClient(), "", pools.CreateMemberOpts{Address: "1.2.3.4", ProtocolPort: 80}) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + res = pools.CreateMember(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ProtocolPort: 80}) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + res = pools.CreateMember(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{Address: "1.2.3.4"}) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} + +func TestGetMember(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMemberGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := pools.GetMember(client, "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, MemberDb, *actual) +} + +func TestDeleteMember(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMemberDeletionSuccessfully(t) + + res := pools.DeleteMember(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateMember(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMemberUpdateSuccessfully(t) + + weight := 4 + client := fake.ServiceClient() + name := "newMemberName" + actual, err := pools.UpdateMember(client, "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf", pools.UpdateMemberOpts{ + Name: &name, + Weight: &weight, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, MemberUpdated, *actual) +} + +func TestBatchUpdateMembers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMembersUpdateSuccessfully(t) + + weight_1 := 20 + member1 := pools.BatchUpdateMemberOpts{ + Address: "192.0.2.16", + ProtocolPort: 80, + Name: "web-server-1", + SubnetID: "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa", + Weight: &weight_1, + } + + weight_2 := 10 + member2 := pools.BatchUpdateMemberOpts{ + Address: "192.0.2.17", + ProtocolPort: 80, + Name: "web-server-2", + Weight: &weight_2, + SubnetID: "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa", + } + members := []pools.BatchUpdateMemberOpts{member1, member2} + + res := pools.BatchUpdateMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", members) + th.AssertNoErr(t, res.Err) +} + +func TestRequiredBatchUpdateMemberOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := pools.BatchUpdateMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ + { + Name: "web-server-1", + }, + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + + res = pools.BatchUpdateMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ + { + Address: "192.0.2.17", + Name: "web-server-1", + }, + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + + res = pools.BatchUpdateMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ + { + ProtocolPort: 80, + Name: "web-server-1", + }, + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/urls.go new file mode 100644 index 000000000000..bceca67707f2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools/urls.go @@ -0,0 +1,25 @@ +package pools + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "pools" + memberPath = "members" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func memberRootURL(c *gophercloud.ServiceClient, poolId string) string { + return c.ServiceURL(rootPath, resourcePath, poolId, memberPath) +} + +func memberResourceURL(c *gophercloud.ServiceClient, poolID string, memeberID string) string { + return c.ServiceURL(rootPath, resourcePath, poolID, memberPath, memeberID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper/client.go new file mode 100644 index 000000000000..7e1d917280cf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/testhelper/client.go @@ -0,0 +1,14 @@ +package common + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const TokenID = client.TokenID + +func ServiceClient() *gophercloud.ServiceClient { + sc := client.ServiceClient() + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/doc.go new file mode 100644 index 000000000000..1d9b1d0e8377 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/doc.go @@ -0,0 +1,53 @@ +/* +Package claims provides information and interaction with the Zaqar API +claims resource for the OpenStack Messaging service. + +Example to Create a Claim on a specified Zaqar queue + + createOpts := claims.CreateOpts{ + TTL: 60, + Grace: 120, + Limit: 20, + } + + queueName := "my_queue" + + messages, err := claims.Create(messagingClient, queueName, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to get a claim for a specified Zaqar queue + + queueName := "my_queue" + claimID := "123456789012345678" + + claim, err := claims.Get(messagingClient, queueName, claimID).Extract() + if err != nil { + panic(err) + } + +Example to update a claim for a specified Zaqar queue + + updateOpts := claims.UpdateOpts{ + TTL: 600 + Grace: 1200 + } + + queueName := "my_queue" + + err := claims.Update(messagingClient, queueName, claimID, updateOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to delete a claim for a specified Zaqar queue + + queueName := "my_queue" + + err := claims.Delete(messagingClient, queueName, claimID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package claims diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/requests.go new file mode 100644 index 000000000000..d3da9889df0c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/requests.go @@ -0,0 +1,123 @@ +package claims + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +// CreateOptsBuilder Builder. +type CreateOptsBuilder interface { + ToClaimCreateRequest() (map[string]interface{}, string, error) +} + +// CreateOpts params to be used with Create. +type CreateOpts struct { + // Sets the TTL for the claim. When the claim expires un-deleted messages will be able to be claimed again. + TTL int `json:"ttl,omitempty"` + + // Sets the Grace period for the claimed messages. The server extends the lifetime of claimed messages + // to be at least as long as the lifetime of the claim itself, plus the specified grace period. + Grace int `json:"grace,omitempty"` + + // Set the limit of messages returned by create. + Limit int `q:"limit" json:"-"` +} + +// ToClaimCreateRequest assembles a body and URL for a Create request based on +// the contents of a CreateOpts. +func (opts CreateOpts) ToClaimCreateRequest() (map[string]interface{}, string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return nil, q.String(), err + } + + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return b, "", err + } + return b, q.String(), err +} + +// Create creates a Claim that claims messages on a specified queue. +func Create(client *gophercloud.ServiceClient, queueName string, opts CreateOptsBuilder) (r CreateResult) { + b, q, err := opts.ToClaimCreateRequest() + if err != nil { + r.Err = err + return + } + + url := createURL(client, queueName) + if q != "" { + url += q + } + + var resp *http.Response + resp, r.Err = client.Post(url, b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201, 204}, + }) + if r.Err != nil { + return + } + // If the Claim has no content return an empty CreateResult + if resp.StatusCode == 204 { + r.Body = CreateResult{} + } else { + r.Body = resp.Body + } + return +} + +// Get queries the specified claim for the specified queue. +func Get(client *gophercloud.ServiceClient, queueName string, claimID string) (r GetResult) { + _, r.Err = client.Get(getURL(client, queueName, claimID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToClaimUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts implements UpdateOpts. +type UpdateOpts struct { + // Update the TTL for the specified Claim. + TTL int `json:"ttl,omitempty"` + + // Update the grace period for Messages in a specified Claim. + Grace int `json:"grace,omitempty"` +} + +// ToClaimUpdateMap assembles a request body based on the contents of +// UpdateOpts. +func (opts UpdateOpts) ToClaimUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return b, nil +} + +// Update will update the options for a specified claim. +func Update(client *gophercloud.ServiceClient, queueName string, claimID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToClaimUpdateMap() + if err != nil { + r.Err = err + return r + } + _, r.Err = client.Patch(updateURL(client, queueName, claimID), &b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Delete will delete a Claim for a specified Queue. +func Delete(client *gophercloud.ServiceClient, queueName string, claimID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, queueName, claimID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/results.go new file mode 100644 index 000000000000..ec43e582c0ac --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/results.go @@ -0,0 +1,52 @@ +package claims + +import "github.com/gophercloud/gophercloud" + +func (r CreateResult) Extract() ([]Messages, error) { + var s struct { + Messages []Messages `json:"messages"` + } + err := r.ExtractInto(&s) + return s.Messages, err +} + +func (r GetResult) Extract() (*Claim, error) { + var s *Claim + err := r.ExtractInto(&s) + return s, err +} + +// CreateResult is the response of a Create operations. +type CreateResult struct { + gophercloud.Result +} + +// GetResult is the response of a Get operations. +type GetResult struct { + gophercloud.Result +} + +// UpdateResult is the response of a Update operations. +type UpdateResult struct { + gophercloud.ErrResult +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +type Messages struct { + Age float32 `json:"age"` + Href string `json:"href"` + TTL int `json:"ttl"` + Body map[string]interface{} `json:"body"` +} + +type Claim struct { + Age float32 `json:"age"` + Href string `json:"href"` + Messages []Messages `json:"messages"` + TTL int `json:"ttl"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/doc.go new file mode 100644 index 000000000000..787309df54ed --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/doc.go @@ -0,0 +1,2 @@ +// Claims unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/fixtures.go new file mode 100644 index 000000000000..95ef1468a6b1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/fixtures.go @@ -0,0 +1,133 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/messaging/v2/claims" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +// QueueName is the name of the queue +var QueueName = "FakeTestQueue" + +var ClaimID = "51db7067821e727dc24df754" + +// CreateClaimResponse is a sample response to a create claim +const CreateClaimResponse = ` +{ + "messages": [ + { + "body": {"event": "BackupStarted"}, + "href": "/v2/queues/FakeTestQueue/messages/51db6f78c508f17ddc924357?claim_id=51db7067821e727dc24df754", + "age": 57, + "ttl": 300 + } + ] +}` + +// GetClaimResponse is a sample response to a get claim +const GetClaimResponse = ` +{ + "age": 50, + "href": "/v2/queues/demoqueue/claims/51db7067821e727dc24df754", + "messages": [ + { + "body": {"event": "BackupStarted"}, + "href": "/v2/queues/FakeTestQueue/messages/51db6f78c508f17ddc924357?claim_id=51db7067821e727dc24df754", + "age": 57, + "ttl": 300 + } + ], + "ttl": 50 +}` + +// CreateClaimRequest is a sample request to create a claim. +const CreateClaimRequest = ` +{ + "ttl": 3600, + "grace": 3600 +}` + +// UpdateClaimRequest is a sample request to update a claim. +const UpdateClaimRequest = ` +{ + "ttl": 1200, + "grace": 1600 +}` + +// CreatedClaim is the result of a create request. +var CreatedClaim = []claims.Messages{ + { + Age: 57, + Href: fmt.Sprintf("/v2/queues/%s/messages/51db6f78c508f17ddc924357?claim_id=%s", QueueName, ClaimID), + TTL: 300, + Body: map[string]interface{}{"event": "BackupStarted"}, + }, +} + +// FirstClaim is the result of a get claim. +var FirstClaim = claims.Claim{ + Age: 50, + Href: "/v2/queues/demoqueue/claims/51db7067821e727dc24df754", + Messages: []claims.Messages{ + { + Age: 57, + Href: fmt.Sprintf("/v2/queues/%s/messages/51db6f78c508f17ddc924357?claim_id=%s", QueueName, ClaimID), + TTL: 300, + Body: map[string]interface{}{"event": "BackupStarted"}, + }, + }, + TTL: 50, +} + +// HandleCreateSuccessfully configures the test server to respond to a Create request. +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreateClaimRequest) + + w.WriteHeader(http.StatusCreated) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, CreateClaimResponse) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetClaimResponse) + }) +} + +// HandleUpdateSuccessfully configures the test server to respond to a Update request. +func HandleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, UpdateClaimRequest) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleDeleteSuccessfully configures the test server to respond to an Delete request. +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/requests_test.go new file mode 100644 index 000000000000..e3ee3d39a0af --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/testing/requests_test.go @@ -0,0 +1,58 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/messaging/v2/claims" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t) + + createOpts := claims.CreateOpts{ + TTL: 3600, + Grace: 3600, + Limit: 10, + } + + actual, err := claims.Create(fake.ServiceClient(), QueueName, createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, CreatedClaim, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t) + + actual, err := claims.Get(fake.ServiceClient(), QueueName, ClaimID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &FirstClaim, actual) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateSuccessfully(t) + + updateOpts := claims.UpdateOpts{ + Grace: 1600, + TTL: 1200, + } + + err := claims.Update(fake.ServiceClient(), QueueName, ClaimID, updateOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + + err := claims.Delete(fake.ServiceClient(), QueueName, ClaimID).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/urls.go new file mode 100644 index 000000000000..fae8fb4f45ad --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/claims/urls.go @@ -0,0 +1,24 @@ +package claims + +import "github.com/gophercloud/gophercloud" + +const ( + apiVersion = "v2" + apiName = "queues" +) + +func createURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "claims") +} + +func getURL(client *gophercloud.ServiceClient, queueName string, claimID string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "claims", claimID) +} + +func updateURL(client *gophercloud.ServiceClient, queueName string, claimID string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "claims", claimID) +} + +func deleteURL(client *gophercloud.ServiceClient, queueName string, claimID string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "claims", claimID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/doc.go new file mode 100644 index 000000000000..e5f5bce60fd8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/doc.go @@ -0,0 +1,121 @@ +/* +Package messages provides information and interaction with the messages through +the OpenStack Messaging(Zaqar) service. + +Example to List Messages + + listOpts := messages.ListOpts{ + Limit: 10, + } + + queueName := "my_queue" + + pager := messages.List(client, queueName, listOpts) + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + allMessages, err := queues.ExtractQueues(page) + if err != nil { + panic(err) + } + + for _, message := range allMessages { + fmt.Printf("%+v\n", message) + } + + return true, nil + }) + +Example to Create Messages + + queueName = "my_queue" + + createOpts := messages.CreateOpts{ + Messages: []messages.Messages{ + { + TTL: 300, + Delay: 20, + Body: map[string]interface{}{ + "event": "BackupStarted", + "backup_id": "c378813c-3f0b-11e2-ad92-7823d2b0f3ce", + }, + }, + { + Body: map[string]interface{}{ + "event": "BackupProgress", + "current_bytes": "0", + "total_bytes": "99614720", + }, + }, + }, + } + + resources, err := messages.Create(client, queueName, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a set of Messages + + queueName := "my_queue" + + getMessageOpts := messages.GetMessagesOpts{ + IDs: "123456", + } + + messagesList, err := messages.GetMessages(client, createdQueueName, getMessageOpts).Extract() + if err != nil { + panic(err) + } + +Example to get a singular Message + + queueName := "my_queue" + messageID := "123456" + + message, err := messages.Get(client, queueName, messageID).Extract() + if err != nil { + panic(err) + } + +Example to Delete a set of Messages + + queueName := "my_queue" + + deleteMessagesOpts := messages.DeleteMessagesOpts{ + IDs: []string{"9988776655"}, + } + + err := messages.DeleteMessages(client, queueName, deleteMessagesOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Pop a set of Messages + + queueName := "my_queue" + + popMessagesOpts := messages.PopMessagesOpts{ + Pop: 5, + } + + resources, err := messages.PopMessages(client, queueName, popMessagesOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a singular Message + + clientID := "3381af92-2b9e-11e3-b191-71861300734d" + queueName := "my_queue" + messageID := "123456" + + deleteOpts := messages.DeleteOpts{ + ClaimID: "12345", + } + + err := messages.Delete(client), queueName, messageID, deleteOpts).ExtractErr() + if err != nil { + panic(err) + } +*/ +package messages diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/requests.go new file mode 100644 index 000000000000..6aac4ac35c15 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/requests.go @@ -0,0 +1,253 @@ +package messages + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToMessageListQuery() (string, error) +} + +// ListOpts params to be used with List. +type ListOpts struct { + // Limit instructs List to refrain from sending excessively large lists of queues + Limit int `q:"limit,omitempty"` + + // Marker and Limit control paging. Marker instructs List where to start listing from. + Marker string `q:"marker,omitempty"` + + // Indicate if the messages can be echoed back to the client that posted them. + Echo bool `q:"echo,omitempty"` + + // Indicate if the messages list should include the claimed messages. + IncludeClaimed bool `q:"include_claimed,omitempty"` + + //Indicate if the messages list should include the delayed messages. + IncludeDelayed bool `q:"include_delayed,omitempty"` +} + +func (opts ListOpts) ToMessageListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListMessages lists messages on a specific queue based off queue name. +func List(client *gophercloud.ServiceClient, queueName string, opts ListOptsBuilder) pagination.Pager { + url := listURL(client, queueName) + if opts != nil { + query, err := opts.ToMessageListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + pager := pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return MessagePage{pagination.LinkedPageBase{PageResult: r}} + }) + return pager +} + +// CreateOptsBuilder Builder. +type CreateOptsBuilder interface { + ToMessageCreateMap() (map[string]interface{}, error) +} + +// BatchCreateOpts is an array of CreateOpts. +type BatchCreateOpts []CreateOpts + +// CreateOpts params to be used with Create. +type CreateOpts struct { + // TTL specifies how long the server waits before marking the message + // as expired and removing it from the queue. + TTL int `json:"ttl,omitempty"` + + // Delay specifies how long the message can be claimed. + Delay int `json:"delay,omitempty"` + + // Body specifies an arbitrary document that constitutes the body of the message being sent. + Body map[string]interface{} `json:"body" required:"true"` +} + +// ToMessageCreateMap constructs a request body from BatchCreateOpts. +func (opts BatchCreateOpts) ToMessageCreateMap() (map[string]interface{}, error) { + messages := make([]map[string]interface{}, len(opts)) + for i, message := range opts { + messageMap, err := message.ToMap() + if err != nil { + return nil, err + } + messages[i] = messageMap + } + return map[string]interface{}{"messages": messages}, nil +} + +// ToMap constructs a request body from UpdateOpts. +func (opts CreateOpts) ToMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create creates a message on a specific queue based of off queue name. +func Create(client *gophercloud.ServiceClient, queueName string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToMessageCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client, queueName), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// DeleteMessagesOptsBuilder allows extensions to add additional parameters to the +// DeleteMessages request. +type DeleteMessagesOptsBuilder interface { + ToMessagesDeleteQuery() (string, error) +} + +// DeleteMessagesOpts params to be used with DeleteMessages. +type DeleteMessagesOpts struct { + IDs []string `q:"ids,omitempty"` +} + +// ToMessagesDeleteQuery formats a DeleteMessagesOpts structure into a query string. +func (opts DeleteMessagesOpts) ToMessagesDeleteQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// DeleteMessages deletes multiple messages based off of ID. +func DeleteMessages(client *gophercloud.ServiceClient, queueName string, opts DeleteMessagesOptsBuilder) (r DeleteResult) { + url := deleteURL(client, queueName) + if opts != nil { + query, err := opts.ToMessagesDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{200, 204}, + }) + return +} + +// PopMessagesOptsBuilder allows extensions to add additional parameters to the +// DeleteMessages request. +type PopMessagesOptsBuilder interface { + ToMessagesPopQuery() (string, error) +} + +// PopMessagesOpts params to be used with PopMessages. +type PopMessagesOpts struct { + Pop int `q:"pop,omitempty"` +} + +// ToMessagesPopQuery formats a PopMessagesOpts structure into a query string. +func (opts PopMessagesOpts) ToMessagesPopQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// PopMessages deletes and returns multiple messages based off of number of messages. +func PopMessages(client *gophercloud.ServiceClient, queueName string, opts PopMessagesOptsBuilder) (r PopResult) { + url := deleteURL(client, queueName) + if opts != nil { + query, err := opts.ToMessagesPopQuery() + if err != nil { + r.Err = err + return + } + url += query + } + result, err := client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{200, 204}, + }) + r.Body = result.Body + r.Header = result.Header + r.Err = err + return +} + +// GetMessagesOptsBuilder allows extensions to add additional parameters to the +// GetMessages request. +type GetMessagesOptsBuilder interface { + ToGetMessagesListQuery() (string, error) +} + +// GetMessagesOpts params to be used with GetMessages. +type GetMessagesOpts struct { + IDs []string `q:"ids,omitempty"` +} + +// ToGetMessagesListQuery formats a GetMessagesOpts structure into a query string. +func (opts GetMessagesOpts) ToGetMessagesListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// GetMessages requests details on a multiple messages, by IDs. +func GetMessages(client *gophercloud.ServiceClient, queueName string, opts GetMessagesOptsBuilder) (r GetMessagesResult) { + url := getURL(client, queueName) + if opts != nil { + query, err := opts.ToGetMessagesListQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Get requests details on a single message, by ID. +func Get(client *gophercloud.ServiceClient, queueName string, messageID string) (r GetResult) { + _, r.Err = client.Get(messageURL(client, queueName, messageID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteOptsBuilder allows extensions to add additional parameters to the +// delete request. +type DeleteOptsBuilder interface { + ToMessageDeleteQuery() (string, error) +} + +// DeleteOpts params to be used with Delete. +type DeleteOpts struct { + // ClaimID instructs Delete to delete a message that is associated with a claim ID + ClaimID string `q:"claim_id,omitempty"` +} + +// ToMessageDeleteQuery formats a DeleteOpts structure into a query string. +func (opts DeleteOpts) ToMessageDeleteQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Delete deletes a specific message from the queue. +func Delete(client *gophercloud.ServiceClient, queueName string, messageID string, opts DeleteOptsBuilder) (r DeleteResult) { + url := DeleteMessageURL(client, queueName, messageID) + if opts != nil { + query, err := opts.ToMessageDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/results.go new file mode 100644 index 000000000000..1c361e3f6dcb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/results.go @@ -0,0 +1,132 @@ +package messages + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Create operations. +type CreateResult struct { + gophercloud.Result +} + +// MessagePage contains a single page of all clusters from a ListDetails call. +type MessagePage struct { + pagination.LinkedPageBase +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// CreateResult is the response of a Create operations. +type PopResult struct { + gophercloud.Result +} + +// GetMessagesResult is the response of a GetMessages operations. +type GetMessagesResult struct { + gophercloud.Result +} + +// GetResult is the response of a Get operations. +type GetResult struct { + gophercloud.Result +} + +// Message represents a message on a queue. +type Message struct { + Body map[string]interface{} `json:"body"` + Age int `json:"age"` + Href string `json:"href"` + ID string `json:"id"` + TTL int `json:"ttl"` + Checksum string `json:"checksum"` +} + +// PopMessage represents a message returned from PopMessages. +type PopMessage struct { + Body map[string]interface{} `json:"body"` + Age int `json:"age"` + ID string `json:"id"` + TTL int `json:"ttl"` + ClaimCount int `json:"claim_count"` + ClaimID string `json:"claim_id"` +} + +// ResourceList represents the result of creating a message. +type ResourceList struct { + Resources []string `json:"resources"` +} + +// Extract interprets any CreateResult as a ResourceList. +func (r CreateResult) Extract() (ResourceList, error) { + var s ResourceList + err := r.ExtractInto(&s) + return s, err +} + +// Extract interprets any PopResult as a list of PopMessage. +func (r PopResult) Extract() ([]PopMessage, error) { + var s struct { + PopMessages []PopMessage `json:"messages"` + } + err := r.ExtractInto(&s) + return s.PopMessages, err +} + +// Extract interprets any GetMessagesResult as a list of Message. +func (r GetMessagesResult) Extract() ([]Message, error) { + var s struct { + Messages []Message `json:"messages"` + } + err := r.ExtractInto(&s) + return s.Messages, err +} + +// Extract interprets any GetResult as a Message. +func (r GetResult) Extract() (Message, error) { + var s Message + err := r.ExtractInto(&s) + return s, err +} + +// ExtractMessage extracts message into a list of Message. +func ExtractMessages(r pagination.Page) ([]Message, error) { + var s struct { + Messages []Message `json:"messages"` + } + err := (r.(MessagePage)).ExtractInto(&s) + return s.Messages, err +} + +// IsEmpty determines if a MessagePage contains any results. +func (r MessagePage) IsEmpty() (bool, error) { + s, err := ExtractMessages(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r MessagePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + + next, err := gophercloud.ExtractNextURL(s.Links) + if err != nil { + return "", err + } + return nextPageURL(r.URL.String(), next) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/doc.go new file mode 100644 index 000000000000..05931e02352b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/doc.go @@ -0,0 +1,2 @@ +// messages unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/fixtures.go new file mode 100644 index 000000000000..000f887ffa6a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/fixtures.go @@ -0,0 +1,316 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/messaging/v2/messages" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +// QueueName is the name of the queue +var QueueName = "FakeTestQueue" + +// MessageID is the id of the message +var MessageID = "9988776655" + +// CreateMessageResponse is a sample response to a Create message. +const CreateMessageResponse = ` +{ + "resources": [ + "/v2/queues/demoqueue/messages/51db6f78c508f17ddc924357", + "/v2/queues/demoqueue/messages/51db6f78c508f17ddc924358" + ] +}` + +// CreateMessageRequest is a sample request to create a message. +const CreateMessageRequest = ` +{ + "messages": [ + { + "body": { + "backup_id": "c378813c-3f0b-11e2-ad92-7823d2b0f3ce", + "event": "BackupStarted" + }, + "delay": 20, + "ttl": 300 + }, + { + "body": { + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720" + } + } + ] +}` + +// ListMessagesResponse is a sample response to list messages. +const ListMessagesResponse1 = ` +{ + "messages": [ + { + "body": { + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720" + }, + "age": 482, + "href": "/v2/queues/FakeTestQueue/messages/578edfe6508f153f256f717b", + "id": "578edfe6508f153f256f717b", + "ttl": 3600, + "checksum": "MD5:abf7213555626e29c3cb3e5dc58b3515" + } + ], + "links": [ + { + "href": "/v2/queues/FakeTestQueue/messages?marker=1", + "rel": "next" + } + ] +}` + +// ListMessagesResponse is a sample response to list messages. +const ListMessagesResponse2 = ` +{ + "messages": [ + { + "body": { + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720" + }, + "age": 456, + "href": "/v2/queues/FakeTestQueue/messages/578ee000508f153f256f717d", + "id": "578ee000508f153f256f717d", + "ttl": 3600, + "checksum": "MD5:abf7213555626e29c3cb3e5dc58b3515" + } + ], + "links": [ + { + "href": "/v2/queues/FakeTestQueue/messages?marker=2", + "rel": "next" + } + ] + +}` + +// GetMessagesResponse is a sample response to GetMessages. +const GetMessagesResponse = ` +{ + "messages": [ + { + "body": { + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720" + }, + "age": 443, + "href": "/v2/queues/beijing/messages/578f0055508f153f256f717f", + "id": "578f0055508f153f256f717f", + "ttl": 3600 + } + ] +}` + +// GetMessageResponse is a sample response to Get. +const GetMessageResponse = ` +{ + "body": { + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720" + }, + "age": 482, + "href": "/v2/queues/FakeTestQueue/messages/578edfe6508f153f256f717b", + "id": "578edfe6508f153f256f717b", + "ttl": 3600, + "checksum": "MD5:abf7213555626e29c3cb3e5dc58b3515" +}` + +// PopMessageResponse is a sample reponse to pop messages +const PopMessageResponse = ` +{ + "messages": [ + { + "body": { + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720" + }, + "age": 20, + "ttl": 120, + "claim_count": 55, + "claim_id": "123456", + "id": "5ae7972599352b436763aee7" + } + ] +}` + +// ExpectedResources is the expected result in Create +var ExpectedResources = messages.ResourceList{ + Resources: []string{ + "/v2/queues/demoqueue/messages/51db6f78c508f17ddc924357", + "/v2/queues/demoqueue/messages/51db6f78c508f17ddc924358", + }, +} + +// FirstMessage is the first result in a List. +var FirstMessage = messages.Message{ + Body: map[string]interface{}{ + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720", + }, + Age: 482, + Href: fmt.Sprintf("/v2/queues/%s/messages/578edfe6508f153f256f717b", QueueName), + ID: "578edfe6508f153f256f717b", + TTL: 3600, + Checksum: "MD5:abf7213555626e29c3cb3e5dc58b3515", +} + +// SecondMessage is the second result in a List. +var SecondMessage = messages.Message{ + Body: map[string]interface{}{ + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720", + }, + Age: 456, + Href: fmt.Sprintf("/v2/queues/%s/messages/578ee000508f153f256f717d", QueueName), + ID: "578ee000508f153f256f717d", + TTL: 3600, + Checksum: "MD5:abf7213555626e29c3cb3e5dc58b3515", +} + +// ExpectedMessagesSlice is the expected result in a List. +var ExpectedMessagesSlice = [][]messages.Message{{FirstMessage}, {SecondMessage}} + +// ExpectedMessagesSet is the expected result in GetMessages +var ExpectedMessagesSet = []messages.Message{ + { + Body: map[string]interface{}{ + "total_bytes": "99614720", + "current_bytes": "0", + "event": "BackupProgress", + }, + Age: 443, + Href: "/v2/queues/beijing/messages/578f0055508f153f256f717f", + ID: "578f0055508f153f256f717f", + TTL: 3600, + Checksum: "", + }, +} + +// ExpectedPopMessage is the expected result of a Pop. +var ExpectedPopMessage = []messages.PopMessage{{ + Body: map[string]interface{}{ + "current_bytes": "0", + "event": "BackupProgress", + "total_bytes": "99614720", + }, + Age: 20, + TTL: 120, + ClaimID: "123456", + ClaimCount: 55, + ID: "5ae7972599352b436763aee7", +}} + +// HandleCreateSuccessfully configures the test server to respond to a Create request. +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreateMessageRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, CreateMessageResponse) + }) +} + +// HandleListSuccessfully configures the test server to respond to a List request. +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), + func(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") + next := r.RequestURI + + switch next { + case fmt.Sprintf("/v2/queues/%s/messages?limit=1", QueueName): + fmt.Fprintf(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): + fmt.Fprint(w, `{ "messages": [] }`) + } + }) +} + +// HandleGetMessagesSuccessfully configures the test server to respond to a GetMessages request. +func HandleGetMessagesSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetMessagesResponse) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages/%s", QueueName, MessageID), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetMessageResponse) + }) +} + +// HandleDeleteMessagesSuccessfully configures the test server to respond to a Delete request. +func HandleDeleteMessagesSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandlePopSuccessfully configures the test server to respond to a Pop request. +func HandlePopSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, PopMessageResponse) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages/%s", QueueName, MessageID), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/requests_test.go new file mode 100644 index 000000000000..eb839262b9a2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/testing/requests_test.go @@ -0,0 +1,126 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/messaging/v2/messages" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSuccessfully(t) + + listOpts := messages.ListOpts{ + Limit: 1, + } + + count := 0 + err := messages.List(fake.ServiceClient(), QueueName, listOpts).EachPage(func(page pagination.Page) (bool, error) { + actual, err := messages.ExtractMessages(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedMessagesSlice[count], actual) + count++ + + return true, nil + }) + th.AssertNoErr(t, err) + + th.CheckEquals(t, 2, count) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t) + + createOpts := messages.BatchCreateOpts{ + messages.CreateOpts{ + TTL: 300, + Delay: 20, + Body: map[string]interface{}{ + "event": "BackupStarted", + "backup_id": "c378813c-3f0b-11e2-ad92-7823d2b0f3ce", + }, + }, + messages.CreateOpts{ + Body: map[string]interface{}{ + "event": "BackupProgress", + "current_bytes": "0", + "total_bytes": "99614720", + }, + }, + } + + actual, err := messages.Create(fake.ServiceClient(), QueueName, createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedResources, actual) +} + +func TestGetMessages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetMessagesSuccessfully(t) + + getMessagesOpts := messages.GetMessagesOpts{ + IDs: []string{"9988776655"}, + } + + actual, err := messages.GetMessages(fake.ServiceClient(), QueueName, getMessagesOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedMessagesSet, actual) +} + +func TestGetMessage(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t) + + actual, err := messages.Get(fake.ServiceClient(), QueueName, MessageID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, FirstMessage, actual) +} + +func TestDeleteMessages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteMessagesSuccessfully(t) + + deleteMessagesOpts := messages.DeleteMessagesOpts{ + IDs: []string{"9988776655"}, + } + + err := messages.DeleteMessages(fake.ServiceClient(), QueueName, deleteMessagesOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestPopMessages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePopSuccessfully(t) + + popMessagesOpts := messages.PopMessagesOpts{ + Pop: 1, + } + + actual, err := messages.PopMessages(fake.ServiceClient(), QueueName, popMessagesOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedPopMessage, actual) +} + +func TestDeleteMessage(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + + deleteOpts := messages.DeleteOpts{ + ClaimID: "12345", + } + + err := messages.Delete(fake.ServiceClient(), QueueName, MessageID, deleteOpts).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/urls.go new file mode 100644 index 000000000000..a00dd5620d25 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/messages/urls.go @@ -0,0 +1,49 @@ +package messages + +import ( + "net/url" + + "github.com/gophercloud/gophercloud" +) + +const ( + apiVersion = "v2" + apiName = "queues" +) + +func createURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "messages") +} + +func listURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "messages") +} + +func getURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "messages") +} + +func deleteURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "messages") +} + +func DeleteMessageURL(client *gophercloud.ServiceClient, queueName string, messageID string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "messages", messageID) +} + +func messageURL(client *gophercloud.ServiceClient, queueName string, messageID string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "messages", messageID) +} + +// Builds next page full url based on current url. +func nextPageURL(currentURL string, next string) (string, error) { + base, err := url.Parse(currentURL) + if err != nil { + return "", err + } + rel, err := url.Parse(next) + if err != nil { + return "", err + } + return base.ResolveReference(rel).String(), nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/doc.go new file mode 100644 index 000000000000..ca97c52a8a34 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/doc.go @@ -0,0 +1,111 @@ +/* +Package queues provides information and interaction with the queues through +the OpenStack Messaging (Zaqar) service. + +Lists all queues and creates, shows information for updates, deletes, and actions on a queue. + +Example to List Queues + + listOpts := queues.ListOpts{ + Limit: 10, + } + + pager := queues.List(client, listOpts) + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + queues, err := queues.ExtractQueues(page) + if err != nil { + panic(err) + } + + for _, queue := range queues { + fmt.Printf("%+v\n", queue) + } + + return true, nil + }) + +Example to Create a Queue + + createOpts := queues.CreateOpts{ + QueueName: "My_Queue", + MaxMessagesPostSize: 262143, + DefaultMessageTTL: 3700, + DefaultMessageDelay: 25, + DeadLetterQueueMessageTTL: 3500, + MaxClaimCount: 10, + Extra: map[string]interface{}{"description": "Test queue."}, + } + + err := queues.Create(client, createOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a Queue + + updateOpts := queues.BatchUpdateOpts{ + queues.UpdateOpts{ + Op: "replace", + Path: "/metadata/_max_claim_count", + Value: 15, + }, + queues.UpdateOpts{ + Op: "replace", + Path: "/metadata/description", + Value: "Updated description test queue.", + }, + } + + updateResult, err := queues.Update(client, queueName, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Queue + + queue, err := queues.Get(client, queueName).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Queue + + err := queues.Delete(client, queueName).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get Message Stats of a Queue + + queueStats, err := queues.GetStats(client, queueName).Extract() + if err != nil { + panic(err) + } + +Example to Share a queue + + shareOpts := queues.ShareOpts{ + Paths: []queues.SharePath{queues.ShareMessages}, + Methods: []queues.ShareMethod{queues.MethodGet}, + } + + queueShare, err := queues.Share(client, queueName, shareOpts).Extract() + if err != nil { + panic(err) + } + +Example to Purge a queue + + purgeOpts := queues.PurgeOpts{ + ResourceTypes: []queues.PurgeResource{ + queues.ResourceMessages, + }, + } + + err := queues.Purge(client, queueName, purgeOpts).ExtractErr() + if err != nil { + panic(err) + } +*/ +package queues diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/requests.go new file mode 100644 index 000000000000..6ef53947d7fe --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/requests.go @@ -0,0 +1,292 @@ +package queues + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToQueueListQuery() (string, error) +} + +// ListOpts params to be used with List +type ListOpts struct { + // Limit instructs List to refrain from sending excessively large lists of queues + Limit int `q:"limit,omitempty"` + + // Marker and Limit control paging. Marker instructs List where to start listing from. + Marker string `q:"marker,omitempty"` + + // Specifies if showing the detailed information when querying queues + Detailed bool `q:"detailed,omitempty"` +} + +// ToQueueListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToQueueListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List instructs OpenStack to provide a list of queues. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToQueueListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + pager := pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return QueuePage{pagination.LinkedPageBase{PageResult: r}} + + }) + return pager +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToQueueCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies the queue creation parameters. +type CreateOpts struct { + // The name of the queue to create. + QueueName string `json:"queue_name" required:"true"` + + // The target incoming messages will be moved to when a message can’t + // processed successfully after meet the max claim count is met. + DeadLetterQueue string `json:"_dead_letter_queue,omitempty"` + + // The new TTL setting for messages when moved to dead letter queue. + DeadLetterQueueMessagesTTL int `json:"_dead_letter_queue_messages_ttl,omitempty"` + + // The delay of messages defined for a queue. When the messages send to + // the queue, it will be delayed for some times and means it can not be + // claimed until the delay expired. + DefaultMessageDelay int `json:"_default_message_delay,omitempty"` + + // The default TTL of messages defined for a queue, which will effect for + // any messages posted to the queue. + DefaultMessageTTL int `json:"_default_message_ttl" required:"true"` + + // The flavor name which can tell Zaqar which storage pool will be used + // to create the queue. + Flavor string `json:"_flavor,omitempty"` + + // The max number the message can be claimed. + MaxClaimCount int `json:"_max_claim_count,omitempty"` + + // The max post size of messages defined for a queue, which will effect + // for any messages posted to the queue. + MaxMessagesPostSize int `json:"_max_messages_post_size,omitempty"` + + // Extra is free-form extra key/value pairs to describe the queue. + Extra map[string]interface{} `json:"-"` +} + +// ToQueueCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToQueueCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + for key, value := range opts.Extra { + b[key] = value + } + + } + return b, nil +} + +// Create requests the creation of a new queue. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToQueueCreateMap() + if err != nil { + r.Err = err + return + } + + queueName := b["queue_name"].(string) + delete(b, "queue_name") + + _, r.Err = client.Put(createURL(client, queueName), b, r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201, 204}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// update request. +type UpdateOptsBuilder interface { + ToQueueUpdateMap() ([]map[string]interface{}, error) +} + +// BatchUpdateOpts is an array of UpdateOpts. +type BatchUpdateOpts []UpdateOpts + +// UpdateOpts is the struct responsible for updating a property of a queue. +type UpdateOpts struct { + Op UpdateOp `json:"op" required:"true"` + Path string `json:"path" required:"true"` + Value interface{} `json:"value" required:"true"` +} + +type UpdateOp string + +const ( + ReplaceOp UpdateOp = "replace" + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" +) + +// ToQueueUpdateMap constructs a request body from UpdateOpts. +func (opts BatchUpdateOpts) ToQueueUpdateMap() ([]map[string]interface{}, error) { + queuesUpdates := make([]map[string]interface{}, len(opts)) + for i, queue := range opts { + queueMap, err := queue.ToMap() + if err != nil { + return nil, err + } + queuesUpdates[i] = queueMap + } + return queuesUpdates, nil +} + +// ToMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update Updates the specified queue. +func Update(client *gophercloud.ServiceClient, queueName string, opts UpdateOptsBuilder) (r UpdateResult) { + _, r.Err = client.Patch(updateURL(client, queueName), opts, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 204}, + MoreHeaders: map[string]string{ + "Content-Type": "application/openstack-messaging-v2.0-json-patch"}, + }) + return +} + +// Get requests details on a single queue, by name. +func Get(client *gophercloud.ServiceClient, queueName string) (r GetResult) { + _, r.Err = client.Get(getURL(client, queueName), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete deletes the specified queue. +func Delete(client *gophercloud.ServiceClient, queueName string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, queueName), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// GetStats returns statistics for the specified queue. +func GetStats(client *gophercloud.ServiceClient, queueName string) (r StatResult) { + _, r.Err = client.Get(statURL(client, queueName), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}}) + return +} + +type SharePath string + +const ( + PathMessages SharePath = "messages" + PathClaims SharePath = "claims" + PathSubscriptions SharePath = "subscriptions" +) + +type ShareMethod string + +const ( + MethodGet ShareMethod = "GET" + MethodPatch ShareMethod = "PATCH" + MethodPost ShareMethod = "POST" + MethodPut ShareMethod = "PUT" +) + +// ShareOpts specifies share creation parameters. +type ShareOpts struct { + Paths []SharePath `json:"paths,omitempty"` + Methods []ShareMethod `json:"methods,omitempty"` + Expires string `json:"expires,omitempty"` +} + +// ShareOptsBuilder allows extensions to add additional attributes to the +// Share request. +type ShareOptsBuilder interface { + ToQueueShareMap() (map[string]interface{}, error) +} + +// ToShareQueueMap formats a ShareOpts structure into a request body. +func (opts ShareOpts) ToQueueShareMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return b, nil +} + +// Share creates a pre-signed URL for a given queue. +func Share(client *gophercloud.ServiceClient, queueName string, opts ShareOptsBuilder) (r ShareResult) { + b, err := opts.ToQueueShareMap() + if err != nil { + r.Err = err + return r + } + _, r.Err = client.Post(shareURL(client, queueName), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +type PurgeResource string + +const ( + ResourceMessages PurgeResource = "messages" + ResourceSubscriptions PurgeResource = "subscriptions" +) + +// PurgeOpts specifies the purge parameters. +type PurgeOpts struct { + ResourceTypes []PurgeResource `json:"resource_types" required:"true"` +} + +// PurgeOptsBuilder allows extensions to add additional attributes to the +// Purge request. +type PurgeOptsBuilder interface { + ToQueuePurgeMap() (map[string]interface{}, error) +} + +// ToPurgeQueueMap formats a PurgeOpts structure into a request body +func (opts PurgeOpts) ToQueuePurgeMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return b, nil +} + +// Purge purges particular resource of the queue. +func Purge(client *gophercloud.ServiceClient, queueName string, opts PurgeOptsBuilder) (r PurgeResult) { + b, err := opts.ToQueuePurgeMap() + if err != nil { + r.Err = err + return r + } + + _, r.Err = client.Post(purgeURL(client, queueName), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/results.go new file mode 100644 index 000000000000..61005da77854 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/results.go @@ -0,0 +1,201 @@ +package queues + +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +// commonResult is the response of a base result. +type commonResult struct { + gophercloud.Result +} + +// QueuePage contains a single page of all queues from a List operation. +type QueuePage struct { + pagination.LinkedPageBase +} + +// CreateResult is the response of a Create operation. +type CreateResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the response of a Update operation. +type UpdateResult struct { + commonResult +} + +// GetResult is the response of a Get operation. +type GetResult struct { + commonResult +} + +// StatResult contains the result of a Share operation. +type StatResult struct { + gophercloud.Result +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ShareResult contains the result of a Share operation. +type ShareResult struct { + gophercloud.Result +} + +// PurgeResult is the response of a Purge operation. +type PurgeResult struct { + gophercloud.ErrResult +} + +// Queue represents a messaging queue. +type Queue struct { + Href string `json:"href"` + Methods []string `json:"methods"` + Name string `json:"name"` + Paths []string `json:"paths"` + ResourceTypes []string `json:"resource_types"` + Metadata QueueDetails `json:"metadata"` +} + +// QueueDetails represents the metadata of a queue. +type QueueDetails struct { + // The queue the message will be moved to when the message can’t + // be processed successfully after the max claim count is met. + DeadLetterQueue string `json:"_dead_letter_queue"` + + // The TTL setting for messages when moved to dead letter queue. + DeadLetterQueueMessageTTL int `json:"_dead_letter_queue_messages_ttl"` + + // The delay of messages defined for the queue. + DefaultMessageDelay int `json:"_default_message_delay"` + + // The default TTL of messages defined for the queue. + DefaultMessageTTL int `json:"_default_message_ttl"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` + + // The max number the message can be claimed from the queue. + MaxClaimCount int `json:"_max_claim_count"` + + // The max post size of messages defined for the queue. + MaxMessagesPostSize int `json:"_max_messages_post_size"` + + // The flavor defined for the queue. + Flavor string `json:"flavor"` +} + +// Stats represents a stats response. +type Stats struct { + // Number of Claimed messages for a queue + Claimed int `json:"claimed"` + + // Total Messages for a queue + Total int `json:"total"` + + // Number of free messages + Free int `json:"free"` +} + +// QueueShare represents a share response. +type QueueShare struct { + Project string `json:"project"` + Paths []string `json:"paths"` + Expires string `json:"expires"` + Methods []string `json:"methods"` + Signature string `json:"signature"` +} + +// Extract interprets any commonResult as a Queue. +func (r commonResult) Extract() (QueueDetails, error) { + var s QueueDetails + err := r.ExtractInto(&s) + return s, err +} + +// Extract interprets any StatResult as a Stats. +func (r StatResult) Extract() (Stats, error) { + var s struct { + Stats Stats `json:"messages"` + } + err := r.ExtractInto(&s) + return s.Stats, err +} + +// Extract interprets any ShareResult as a QueueShare. +func (r ShareResult) Extract() (QueueShare, error) { + var s QueueShare + err := r.ExtractInto(&s) + return s, err +} + +// ExtractQueues interprets the results of a single page from a +// List() call, producing a map of queues. +func ExtractQueues(r pagination.Page) ([]Queue, error) { + var s struct { + Queues []Queue `json:"queues"` + } + err := (r.(QueuePage)).ExtractInto(&s) + return s.Queues, err +} + +// IsEmpty determines if a QueuesPage contains any results. +func (r QueuePage) IsEmpty() (bool, error) { + s, err := ExtractQueues(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r QueuePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + + next, err := gophercloud.ExtractNextURL(s.Links) + if err != nil { + return "", err + } + return nextPageURL(r.URL.String(), next) +} + +func (r *QueueDetails) UnmarshalJSON(b []byte) error { + type tmp QueueDetails + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = QueueDetails(s.tmp) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + r.Extra = internal.RemainingKeys(QueueDetails{}, resultMap) + } + } + + return err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/doc.go new file mode 100644 index 000000000000..093700883639 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/doc.go @@ -0,0 +1,2 @@ +// queues unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/fixtures.go new file mode 100644 index 000000000000..50e7bd4c9322 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/fixtures.go @@ -0,0 +1,315 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/messaging/v2/queues" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +// QueueName is the name of the queue +var QueueName = "FakeTestQueue" + +// CreateQueueRequest is a sample request to create a queue. +const CreateQueueRequest = ` +{ + "_max_messages_post_size": 262144, + "_default_message_ttl": 3600, + "_default_message_delay": 30, + "_dead_letter_queue": "dead_letter", + "_dead_letter_queue_messages_ttl": 3600, + "_max_claim_count": 10, + "description": "Queue for unit testing." +}` + +// CreateShareRequest is a sample request to a share. +const CreateShareRequest = ` +{ + "paths": ["messages", "claims", "subscriptions"], + "methods": ["GET", "POST", "PUT", "PATCH"], + "expires": "2016-09-01T00:00:00" +}` + +// CreatePurgeRequest is a sample request to a purge. +const CreatePurgeRequest = ` +{ + "resource_types": ["messages", "subscriptions"] +}` + +// ListQueuesResponse1 is a sample response to a List queues. +const ListQueuesResponse1 = ` +{ + "queues":[ + { + "href":"/v2/queues/london", + "name":"london", + "metadata":{ + "_dead_letter_queue":"fake_queue", + "_dead_letter_queue_messages_ttl":3500, + "_default_message_delay":25, + "_default_message_ttl":3700, + "_max_claim_count":10, + "_max_messages_post_size":262143, + "description":"Test queue." + } + } + ], + "links":[ + { + "href":"/v2/queues?marker=london", + "rel":"next" + } + ] +}` + +// ListQueuesResponse2 is a sample response to a List queues. +const ListQueuesResponse2 = ` +{ + "queues":[ + { + "href":"/v2/queues/beijing", + "name":"beijing", + "metadata":{ + "_dead_letter_queue":"fake_queue", + "_dead_letter_queue_messages_ttl":3500, + "_default_message_delay":25, + "_default_message_ttl":3700, + "_max_claim_count":10, + "_max_messages_post_size":262143, + "description":"Test queue." + } + } + ], + "links":[ + { + "href":"/v2/queues?marker=beijing", + "rel":"next" + } + ] +}` + +// UpdateQueueRequest is a sample request to update a queue. +const UpdateQueueRequest = ` +[ + { + "op": "replace", + "path": "/metadata/description", + "value": "Update queue description" + } +]` + +// UpdateQueueResponse is a sample response to a update queue. +const UpdateQueueResponse = ` +{ + "description": "Update queue description" +}` + +// GetQueueResponse is a sample response to a get queue. +const GetQueueResponse = ` +{ + "_max_messages_post_size": 262144, + "_default_message_ttl": 3600, + "description": "Queue used for unit testing." +}` + +// GetStatsResponse is a sample response to a stats request. +const GetStatsResponse = ` +{ + "messages":{ + "claimed": 10, + "total": 20, + "free": 10 + } +}` + +// CreateShareResponse is a sample response to a share request. +const CreateShareResponse = ` +{ + "project": "2887aabf368046a3bb0070f1c0413470", + "paths": [ + "/v2/queues/test/messages", + "/v2/queues/test/claims", + "/v2/queues/test/subscriptions" + ], + "expires": "2016-09-01T00:00:00", + "methods": [ + "GET", + "PATCH", + "POST", + "PUT" + ], + "signature": "6a63d63242ebd18c3518871dda6fdcb6273db2672c599bf985469241e9a1c799" +}` + +// FirstQueue is the first result in a List. +var FirstQueue = queues.Queue{ + Href: "/v2/queues/london", + Name: "london", + Metadata: queues.QueueDetails{ + DeadLetterQueue: "fake_queue", + DeadLetterQueueMessageTTL: 3500, + DefaultMessageDelay: 25, + DefaultMessageTTL: 3700, + MaxClaimCount: 10, + MaxMessagesPostSize: 262143, + Extra: map[string]interface{}{"description": "Test queue."}, + }, +} + +// SecondQueue is the second result in a List. +var SecondQueue = queues.Queue{ + Href: "/v2/queues/beijing", + Name: "beijing", + Metadata: queues.QueueDetails{ + DeadLetterQueue: "fake_queue", + DeadLetterQueueMessageTTL: 3500, + DefaultMessageDelay: 25, + DefaultMessageTTL: 3700, + MaxClaimCount: 10, + MaxMessagesPostSize: 262143, + Extra: map[string]interface{}{"description": "Test queue."}, + }, +} + +// ExpectedQueueSlice is the expected result in a List. +var ExpectedQueueSlice = [][]queues.Queue{{FirstQueue}, {SecondQueue}} + +// QueueDetails is the expected result in a Get. +var QueueDetails = queues.QueueDetails{ + DefaultMessageTTL: 3600, + MaxMessagesPostSize: 262144, + Extra: map[string]interface{}{"description": "Queue used for unit testing."}, +} + +// ExpectedStats is the expected result in a GetStats. +var ExpectedStats = queues.Stats{ + Claimed: 10, + Total: 20, + Free: 10, +} + +// ExpectedShare is the expected result in Share. +var ExpectedShare = queues.QueueShare{ + Project: "2887aabf368046a3bb0070f1c0413470", + Paths: []string{ + "/v2/queues/test/messages", + "/v2/queues/test/claims", + "/v2/queues/test/subscriptions", + }, + Expires: "2016-09-01T00:00:00", + Methods: []string{ + "GET", + "PATCH", + "POST", + "PUT", + }, + Signature: "6a63d63242ebd18c3518871dda6fdcb6273db2672c599bf985469241e9a1c799", +} + +// HandleListSuccessfully configures the test server to respond to a List request. +func HandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2/queues", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + next := r.RequestURI + + switch next { + case "/v2/queues?limit=1": + fmt.Fprintf(w, ListQueuesResponse1) + case "/v2/queues?marker=london": + fmt.Fprint(w, ListQueuesResponse2) + case "/v2/queues?marker=beijing": + fmt.Fprint(w, `{ "queues": [] }`) + } + }) +} + +// HandleCreateSuccessfully configures the test server to respond to a Create request. +func HandleCreateSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreateQueueRequest) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleUpdateSuccessfully configures the test server to respond to an Update request. +func HandleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, UpdateQueueRequest) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, UpdateQueueResponse) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetQueueResponse) + }) +} + +// HandleDeleteSuccessfully configures the test server to respond to a Delete request. +func HandleDeleteSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleGetStatsSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/stats", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, GetStatsResponse) + }) +} + +// HandleShareSuccessfully configures the test server to respond to a Share request. +func HandleShareSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/share", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreateShareRequest) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, CreateShareResponse) + }) +} + +// HandlePurgeSuccessfully configures the test server to respond to a Purge request. +func HandlePurgeSuccessfully(t *testing.T) { + th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/purge", QueueName), + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreatePurgeRequest) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/requests_test.go new file mode 100644 index 000000000000..c4502cdac818 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/testing/requests_test.go @@ -0,0 +1,133 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/messaging/v2/queues" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListSuccessfully(t) + + listOpts := queues.ListOpts{ + Limit: 1, + } + + count := 0 + err := queues.List(fake.ServiceClient(), listOpts).EachPage(func(page pagination.Page) (bool, error) { + actual, err := queues.ExtractQueues(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedQueueSlice[count], actual) + count++ + + return true, nil + }) + th.AssertNoErr(t, err) + + th.CheckEquals(t, 2, count) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t) + + createOpts := queues.CreateOpts{ + QueueName: QueueName, + MaxMessagesPostSize: 262144, + DefaultMessageTTL: 3600, + DefaultMessageDelay: 30, + DeadLetterQueue: "dead_letter", + DeadLetterQueueMessagesTTL: 3600, + MaxClaimCount: 10, + Extra: map[string]interface{}{"description": "Queue for unit testing."}, + } + + err := queues.Create(fake.ServiceClient(), createOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateSuccessfully(t) + + updateOpts := queues.BatchUpdateOpts{ + queues.UpdateOpts{ + Op: queues.ReplaceOp, + Path: "/metadata/description", + Value: "Update queue description", + }, + } + updatedQueueResult := queues.QueueDetails{ + Extra: map[string]interface{}{"description": "Update queue description"}, + } + + actual, err := queues.Update(fake.ServiceClient(), QueueName, updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, updatedQueueResult, actual) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetSuccessfully(t) + + actual, err := queues.Get(fake.ServiceClient(), QueueName).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, QueueDetails, actual) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteSuccessfully(t) + + err := queues.Delete(fake.ServiceClient(), QueueName).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetStat(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetStatsSuccessfully(t) + + actual, err := queues.GetStats(fake.ServiceClient(), QueueName).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedStats, actual) +} + +func TestShare(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleShareSuccessfully(t) + + shareOpts := queues.ShareOpts{ + Paths: []queues.SharePath{queues.PathMessages, queues.PathClaims, queues.PathSubscriptions}, + Methods: []queues.ShareMethod{queues.MethodGet, queues.MethodPost, queues.MethodPut, queues.MethodPatch}, + Expires: "2016-09-01T00:00:00", + } + + actual, err := queues.Share(fake.ServiceClient(), QueueName, shareOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedShare, actual) +} + +func TestPurge(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandlePurgeSuccessfully(t) + + purgeOpts := queues.PurgeOpts{ + ResourceTypes: []queues.PurgeResource{queues.ResourceMessages, queues.ResourceSubscriptions}, + } + + err := queues.Purge(fake.ServiceClient(), QueueName, purgeOpts).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/urls.go new file mode 100644 index 000000000000..6b5a0e325a55 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/messaging/v2/queues/urls.go @@ -0,0 +1,61 @@ +package queues + +import ( + "net/url" + + "github.com/gophercloud/gophercloud" +) + +const ( + apiVersion = "v2" + apiName = "queues" +) + +func commonURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiVersion, apiName) +} + +func createURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName) +} + +func listURL(client *gophercloud.ServiceClient) string { + return commonURL(client) +} + +func updateURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName) +} + +func getURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName) +} + +func deleteURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName) +} + +func statURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "stats") +} + +func shareURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "share") +} + +func purgeURL(client *gophercloud.ServiceClient, queueName string) string { + return client.ServiceURL(apiVersion, apiName, queueName, "purge") +} + +// builds next page full url based on current url +func nextPageURL(currentURL string, next string) (string, error) { + base, err := url.Parse(currentURL) + if err != nil { + return "", err + } + rel, err := url.Parse(next) + if err != nil { + return "", err + } + return base.ResolveReference(rel).String(), nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/requests.go index c5494fb357b3..0d027be8b674 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/requests.go @@ -7,7 +7,7 @@ import ( // ListVersions lists all the Neutron API versions available to end-users. func ListVersions(c *gophercloud.ServiceClient) pagination.Pager { - return pagination.NewPager(c, apiVersionsURL(c), func(r pagination.PageResult) pagination.Page { + return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page { return APIVersionPage{pagination.SinglePageBase(r)} }) } @@ -16,7 +16,7 @@ func ListVersions(c *gophercloud.ServiceClient) pagination.Pager { // particular API versions. Typical resources for Neutron might be: networks, // subnets, etc. func ListVersionResources(c *gophercloud.ServiceClient, v string) pagination.Pager { - return pagination.NewPager(c, apiInfoURL(c, v), func(r pagination.PageResult) pagination.Page { + return pagination.NewPager(c, getURL(c, v), func(r pagination.PageResult) pagination.Page { return APIVersionResourcePage{pagination.SinglePageBase(r)} }) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/urls.go index 0fa743776d3c..41aebdc5f2bd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions/urls.go @@ -4,12 +4,17 @@ import ( "strings" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" ) -func apiVersionsURL(c *gophercloud.ServiceClient) string { - return c.Endpoint +func getURL(c *gophercloud.ServiceClient, version string) string { + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + strings.TrimRight(version, "/") + "/" + return endpoint } -func apiInfoURL(c *gophercloud.ServiceClient, version string) string { - return c.Endpoint + strings.TrimRight(version, "/") + "/" +func listURL(c *gophercloud.ServiceClient) string { + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + return endpoint } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/doc.go new file mode 100644 index 000000000000..7706b730e5f3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/doc.go @@ -0,0 +1,32 @@ +/* +Package agents provides the ability to retrieve and manage Agents through the Neutron API. + +Example of Listing Agents + + listOpts := agents.ListOpts{ + AgentType: "Open vSwitch agent", + } + + allPages, err := agents.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allAgents, err := agents.ExtractAgents(allPages) + if err != nil { + panic(err) + } + + for _, agent := range allAgents { + fmt.Printf("%+v\n", agent) + } + +Example to Get an Agent + + agentID = "76af7b1f-d61b-4526-94f7-d2e14e2698df" + agent, err := agents.Get(networkClient, agentID).Extract() + if err != nil { + panic(err) + } +*/ +package agents diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/requests.go new file mode 100644 index 000000000000..566f4dea4611 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/requests.go @@ -0,0 +1,66 @@ +package agents + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAgentListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the Neutron API. Filtering is achieved by passing in struct field values +// that map to the agent attributes you want to see returned. +// SortKey allows you to sort by a particular agent attribute. +// SortDir sets the direction, and is either `asc' or `desc'. +// Marker and Limit are used for the pagination. +type ListOpts struct { + ID string `q:"id"` + AgentType string `q:"agent_type"` + Alive *bool `q:"alive"` + AvailabilityZone string `q:"availability_zone"` + Binary string `q:"binary"` + Description string `q:"description"` + Host string `q:"host"` + Topic string `q:"topic"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToAgentListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAgentListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// agents. It accepts a ListOpts struct, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only the agents owned by the project +// of the user submitting the request, unless the user has the administrative +// role. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToAgentListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AgentPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific agent based on its ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/results.go new file mode 100644 index 000000000000..de4ce0064649 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/results.go @@ -0,0 +1,128 @@ +package agents + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an agent resource. +func (r commonResult) Extract() (*Agent, error) { + var s struct { + Agent *Agent `json:"agent"` + } + err := r.ExtractInto(&s) + return s.Agent, err +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as an Agent. +type GetResult struct { + commonResult +} + +// Agent represents a Neutron agent. +type Agent struct { + // ID is the id of the agent. + ID string `json:"id"` + + // AdminStateUp is an administrative state of the agent. + AdminStateUp bool `json:"admin_state_up"` + + // AgentType is a type of the agent. + AgentType string `json:"agent_type"` + + // Alive indicates whether agent is alive or not. + Alive bool `json:"alive"` + + // AvailabilityZone is a zone of the agent. + AvailabilityZone string `json:"availability_zone"` + + // Binary is an executable binary of the agent. + Binary string `json:"binary"` + + // Configurations is a configuration specific key/value pairs that are + // determined by the agent binary and type. + Configurations map[string]interface{} `json:"configurations"` + + // CreatedAt is a creation timestamp. + CreatedAt time.Time `json:"-"` + + // StartedAt is a starting timestamp. + StartedAt time.Time `json:"-"` + + // HeartbeatTimestamp is a last heartbeat timestamp. + HeartbeatTimestamp time.Time `json:"-"` + + // Description contains agent description. + Description string `json:"description"` + + // Host is a hostname of the agent system. + Host string `json:"host"` + + // Topic contains name of AMQP topic. + Topic string `json:"topic"` +} + +// UnmarshalJSON helps to convert the timestamps into the time.Time type. +func (r *Agent) UnmarshalJSON(b []byte) error { + type tmp Agent + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + StartedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"started_at"` + HeartbeatTimestamp gophercloud.JSONRFC3339ZNoTNoZ `json:"heartbeat_timestamp"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Agent(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.StartedAt = time.Time(s.StartedAt) + r.HeartbeatTimestamp = time.Time(s.HeartbeatTimestamp) + + return nil +} + +// AgentPage stores a single page of Agents from a List() API call. +type AgentPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of agent has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r AgentPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"agents_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty determines whether or not a AgentPage is empty. +func (r AgentPage) IsEmpty() (bool, error) { + agents, err := ExtractAgents(r) + return len(agents) == 0, err +} + +// ExtractAgents interprets the results of a single page from a List() +// API call, producing a slice of Agents structs. +func ExtractAgents(r pagination.Page) ([]Agent, error) { + var s struct { + Agents []Agent `json:"agents"` + } + err := (r.(AgentPage)).ExtractInto(&s) + return s.Agents, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/doc.go new file mode 100644 index 000000000000..59460b8a6495 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/doc.go @@ -0,0 +1,2 @@ +// agents unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/fixtures.go new file mode 100644 index 000000000000..e47ca79003de --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/fixtures.go @@ -0,0 +1,125 @@ +package testing + +import ( + "time" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" +) + +// AgentsListResult represents raw response for the List request. +const AgentsListResult = ` +{ + "agents": [ + { + "admin_state_up": true, + "agent_type": "Open vSwitch agent", + "alive": true, + "availability_zone": null, + "binary": "neutron-openvswitch-agent", + "configurations": { + "datapath_type": "system", + "extensions": [ + "qos" + ] + }, + "created_at": "2017-07-26 23:15:44", + "description": null, + "heartbeat_timestamp": "2019-01-09 10:28:53", + "host": "compute1", + "id": "59186d7b-b512-4fdf-bbaf-5804ffde8811", + "started_at": "2018-06-26 21:46:19", + "topic": "N/A" + }, + { + "admin_state_up": true, + "agent_type": "Open vSwitch agent", + "alive": true, + "availability_zone": null, + "binary": "neutron-openvswitch-agent", + "configurations": { + "datapath_type": "system", + "extensions": [ + "qos" + ] + }, + "created_at": "2017-01-22 14:00:50", + "description": null, + "heartbeat_timestamp": "2019-01-09 10:28:50", + "host": "compute2", + "id": "76af7b1f-d61b-4526-94f7-d2e14e2698df", + "started_at": "2018-11-06 12:09:17", + "topic": "N/A" + } + ] +} +` + +// Agent1 represents first unmarshalled address scope from the +// AgentsListResult. +var Agent1 = agents.Agent{ + ID: "59186d7b-b512-4fdf-bbaf-5804ffde8811", + AdminStateUp: true, + AgentType: "Open vSwitch agent", + Alive: true, + Binary: "neutron-openvswitch-agent", + Configurations: map[string]interface{}{ + "datapath_type": "system", + "extensions": []interface{}{ + "qos", + }, + }, + CreatedAt: time.Date(2017, 7, 26, 23, 15, 44, 0, time.UTC), + StartedAt: time.Date(2018, 6, 26, 21, 46, 19, 0, time.UTC), + HeartbeatTimestamp: time.Date(2019, 1, 9, 10, 28, 53, 0, time.UTC), + Host: "compute1", + Topic: "N/A", +} + +// Agent2 represents second unmarshalled address scope from the +// AgentsListResult. +var Agent2 = agents.Agent{ + ID: "76af7b1f-d61b-4526-94f7-d2e14e2698df", + AdminStateUp: true, + AgentType: "Open vSwitch agent", + Alive: true, + Binary: "neutron-openvswitch-agent", + Configurations: map[string]interface{}{ + "datapath_type": "system", + "extensions": []interface{}{ + "qos", + }, + }, + CreatedAt: time.Date(2017, 1, 22, 14, 00, 50, 0, time.UTC), + StartedAt: time.Date(2018, 11, 6, 12, 9, 17, 0, time.UTC), + HeartbeatTimestamp: time.Date(2019, 1, 9, 10, 28, 50, 0, time.UTC), + Host: "compute2", + Topic: "N/A", +} + +// AgentsGetResult represents raw response for the Get request. +const AgentsGetResult = ` +{ + "agent": { + "binary": "neutron-openvswitch-agent", + "description": null, + "availability_zone": null, + "heartbeat_timestamp": "2019-01-09 11:43:01", + "admin_state_up": true, + "alive": true, + "id": "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", + "topic": "N/A", + "host": "compute3", + "agent_type": "Open vSwitch agent", + "started_at": "2018-06-26 21:46:20", + "created_at": "2017-07-26 23:02:05", + "configurations": { + "ovs_hybrid_plug": false, + "datapath_type": "system", + "vhostuser_socket_dir": "/var/run/openvswitch", + "log_agent_heartbeats": false, + "l2_population": true, + "enable_distributed_routing": false + } + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/requests_test.go new file mode 100644 index 000000000000..3ee2f84daca4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/testing/requests_test.go @@ -0,0 +1,90 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/agents", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AgentsListResult) + }) + + count := 0 + + agents.List(fake.ServiceClient(), agents.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := agents.ExtractAgents(page) + + if err != nil { + t.Errorf("Failed to extract agents: %v", err) + return false, nil + } + + expected := []agents.Agent{ + Agent1, + Agent2, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AgentsGetResult) + }) + + s, err := agents.Get(fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.ID, "43583cf5-472e-4dc8-af5b-6aed4c94ee3a") + th.AssertEquals(t, s.Binary, "neutron-openvswitch-agent") + th.AssertEquals(t, s.AdminStateUp, true) + th.AssertEquals(t, s.Alive, true) + th.AssertEquals(t, s.Topic, "N/A") + th.AssertEquals(t, s.Host, "compute3") + th.AssertEquals(t, s.AgentType, "Open vSwitch agent") + th.AssertEquals(t, s.HeartbeatTimestamp, time.Date(2019, 1, 9, 11, 43, 01, 0, time.UTC)) + th.AssertEquals(t, s.StartedAt, time.Date(2018, 6, 26, 21, 46, 20, 0, time.UTC)) + th.AssertEquals(t, s.CreatedAt, time.Date(2017, 7, 26, 23, 2, 5, 0, time.UTC)) + th.AssertDeepEquals(t, s.Configurations, map[string]interface{}{ + "ovs_hybrid_plug": false, + "datapath_type": "system", + "vhostuser_socket_dir": "/var/run/openvswitch", + "log_agent_heartbeats": false, + "l2_population": true, + "enable_distributed_routing": false, + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/urls.go new file mode 100644 index 000000000000..33b84ee75f99 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents/urls.go @@ -0,0 +1,21 @@ +package agents + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "agents" + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go new file mode 100644 index 000000000000..3257dd1bad50 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go @@ -0,0 +1,37 @@ +/* +Package attributestags manages Tags on Resources created by the OpenStack Neutron Service. + +This enables tagging via a standard interface for resources types which support it. + +See https://developer.openstack.org/api-ref/network/v2/#standard-attributes-tag-extension for more information on the underlying API. + +Example to ReplaceAll Resource Tags + + network, err := networks.Create(conn, createOpts).Extract() + + tagReplaceAllOpts := attributestags.ReplaceAllOpts{ + Tags: []string{"abc", "123"}, + } + attributestags.ReplaceAll(conn, "networks", network.ID, tagReplaceAllOpts) + +Example to List all Resource Tags + + tags, err = attributestags.List(conn, "networks", network.ID).Extract() + +Example to Delete all Resource Tags + + err = attributestags.DeleteAll(conn, "networks", network.ID).ExtractErr() + +Example to Add a tag to a Resource + + err = attributestags.Add(client, "networks", network.ID, "atag").ExtractErr() + +Example to Delete a tag from a Resource + + err = attributestags.Delete(client, "networks", network.ID, "atag").ExtractErr() + +Example to confirm if a tag exists on a resource + + exists, _ := attributestags.Confirm(client, "networks", network.ID, "atag").Extract() +*/ +package attributestags diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go new file mode 100644 index 000000000000..a08bccbb6834 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go @@ -0,0 +1,81 @@ +package attributestags + +import ( + "github.com/gophercloud/gophercloud" +) + +// ReplaceAllOptsBuilder allows extensions to add additional parameters to +// the ReplaceAll request. +type ReplaceAllOptsBuilder interface { + ToAttributeTagsReplaceAllMap() (map[string]interface{}, error) +} + +// ReplaceAllOpts provides options used to create Tags on a Resource +type ReplaceAllOpts struct { + Tags []string `json:"tags" required:"true"` +} + +// ToAttributeTagsReplaceAllMap formats a ReplaceAllOpts into the body of the +// replace request +func (opts ReplaceAllOpts) ToAttributeTagsReplaceAllMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// ReplaceAll updates all tags on a resource, replacing any existing tags +func ReplaceAll(client *gophercloud.ServiceClient, resourceType string, resourceID string, opts ReplaceAllOptsBuilder) (r ReplaceAllResult) { + b, err := opts.ToAttributeTagsReplaceAllMap() + url := replaceURL(client, resourceType, resourceID) + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(url, &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// List all tags on a resource +func List(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r ListResult) { + url := listURL(client, resourceType, resourceID) + _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteAll deletes all tags on a resource +func DeleteAll(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r DeleteResult) { + url := deleteAllURL(client, resourceType, resourceID) + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Add a tag on a resource +func Add(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r AddResult) { + url := addURL(client, resourceType, resourceID, tag) + _, r.Err = client.Put(url, nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete a tag on a resource +func Delete(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r DeleteResult) { + url := deleteURL(client, resourceType, resourceID, tag) + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Confirm if a tag exists on a resource +func Confirm(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r ConfirmResult) { + url := confirmURL(client, resourceType, resourceID, tag) + _, r.Err = client.Get(url, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go new file mode 100644 index 000000000000..cea8045beb01 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go @@ -0,0 +1,57 @@ +package attributestags + +import ( + "github.com/gophercloud/gophercloud" +) + +type tagResult struct { + gophercloud.Result +} + +// Extract interprets tagResult to return the list of tags +func (r tagResult) Extract() ([]string, error) { + var s struct { + Tags []string `json:"tags"` + } + err := r.ExtractInto(&s) + return s.Tags, err +} + +// ReplaceAllResult represents the result of a replace operation. +// Call its Extract method to interpret it as a slice of strings. +type ReplaceAllResult struct { + tagResult +} + +type ListResult struct { + tagResult +} + +// DeleteResult is the result from a Delete/DeleteAll operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AddResult is the result from an Add operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type AddResult struct { + gophercloud.ErrResult +} + +// ConfirmResult is the result from an Confirm operation. +type ConfirmResult struct { + gophercloud.Result +} + +func (r ConfirmResult) Extract() (bool, error) { + exists := r.Err == nil + + if r.Err != nil { + if _, ok := r.Err.(gophercloud.ErrDefault404); ok { + r.Err = nil + } + } + + return exists, r.Err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/fixtures.go new file mode 100644 index 000000000000..b17ad46e8110 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/fixtures.go @@ -0,0 +1,19 @@ +package testing + +const attributestagsReplaceAllRequest = ` +{ + "tags": ["abc", "xyz"] +} +` + +const attributestagsReplaceAllResult = ` +{ + "tags": ["abc", "xyz"] +} +` + +const attributestagsListResult = ` +{ + "tags": ["abc", "xyz"] +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/requests_test.go new file mode 100644 index 000000000000..4a005faf760d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/testing/requests_test.go @@ -0,0 +1,138 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestReplaceAll(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, attributestagsReplaceAllRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, attributestagsReplaceAllResult) + }) + + opts := attributestags.ReplaceAllOpts{ + Tags: []string{"abc", "xyz"}, + } + res, err := attributestags.ReplaceAll(fake.ServiceClient(), "networks", "fakeid", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, res, []string{"abc", "xyz"}) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, attributestagsListResult) + }) + + res, err := attributestags.List(fake.ServiceClient(), "networks", "fakeid").Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, res, []string{"abc", "xyz"}) +} + +func TestDeleteAll(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) + + err := attributestags.DeleteAll(fake.ServiceClient(), "networks", "fakeid").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestAdd(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + }) + + err := attributestags.Add(fake.ServiceClient(), "networks", "fakeid", "atag").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) + + err := attributestags.Delete(fake.ServiceClient(), "networks", "fakeid", "atag").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestConfirmTrue(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + }) + + exists, err := attributestags.Confirm(fake.ServiceClient(), "networks", "fakeid", "atag").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, true, exists) +} + +func TestConfirmFalse(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + }) + + exists, _ := attributestags.Confirm(fake.ServiceClient(), "networks", "fakeid", "atag").Extract() + th.AssertEquals(t, false, exists) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go new file mode 100644 index 000000000000..973e00c47cd7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go @@ -0,0 +1,31 @@ +package attributestags + +import "github.com/gophercloud/gophercloud" + +const ( + tagsPath = "tags" +) + +func replaceURL(c *gophercloud.ServiceClient, r_type string, id string) string { + return c.ServiceURL(r_type, id, tagsPath) +} + +func listURL(c *gophercloud.ServiceClient, r_type string, id string) string { + return c.ServiceURL(r_type, id, tagsPath) +} + +func deleteAllURL(c *gophercloud.ServiceClient, r_type string, id string) string { + return c.ServiceURL(r_type, id, tagsPath) +} + +func addURL(c *gophercloud.ServiceClient, r_type string, id string, tag string) string { + return c.ServiceURL(r_type, id, tagsPath, tag) +} + +func deleteURL(c *gophercloud.ServiceClient, r_type string, id string, tag string) string { + return c.ServiceURL(r_type, id, tagsPath, tag) +} + +func confirmURL(c *gophercloud.ServiceClient, r_type string, id string, tag string) string { + return c.ServiceURL(r_type, id, tagsPath, tag) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/requests.go new file mode 100644 index 000000000000..b7bd62d2c80d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/requests.go @@ -0,0 +1,171 @@ +package dns + +import ( + "net/url" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" +) + +// PortListOptsExt adds the DNS options to the base port ListOpts. +type PortListOptsExt struct { + ports.ListOptsBuilder + + DNSName string `q:"dns_name"` +} + +// ToPortListQuery adds the DNS options to the base port list options. +func (opts PortListOptsExt) ToPortListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder) + if err != nil { + return "", err + } + + params := q.Query() + + if opts.DNSName != "" { + params.Add("dns_name", opts.DNSName) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// PortCreateOptsExt adds port DNS options to the base ports.CreateOpts. +type PortCreateOptsExt struct { + // CreateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Create operation in this package. + ports.CreateOptsBuilder + + // Set DNS name to the port + DNSName string `json:"dns_name,omitempty"` +} + +// ToPortCreateMap casts a CreateOpts struct to a map. +func (opts PortCreateOptsExt) ToPortCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToPortCreateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]interface{}) + + if opts.DNSName != "" { + port["dns_name"] = opts.DNSName + } + + return base, nil +} + +// PortUpdateOptsExt adds DNS options to the base ports.UpdateOpts +type PortUpdateOptsExt struct { + // UpdateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Update operation in this package. + ports.UpdateOptsBuilder + + // Set DNS name to the port + DNSName *string `json:"dns_name,omitempty"` +} + +// ToPortUpdateMap casts an UpdateOpts struct to a map. +func (opts PortUpdateOptsExt) ToPortUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToPortUpdateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]interface{}) + + if opts.DNSName != nil { + port["dns_name"] = *opts.DNSName + } + + return base, nil +} + +// FloatingIPCreateOptsExt adds floating IP DNS options to the base floatingips.CreateOpts. +type FloatingIPCreateOptsExt struct { + // CreateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Create operation in this package. + floatingips.CreateOptsBuilder + + // Set DNS name to the floating IPs + DNSName string `json:"dns_name,omitempty"` + + // Set DNS domain to the floating IPs + DNSDomain string `json:"dns_domain,omitempty"` +} + +// ToFloatingIPCreateMap casts a CreateOpts struct to a map. +func (opts FloatingIPCreateOptsExt) ToFloatingIPCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToFloatingIPCreateMap() + if err != nil { + return nil, err + } + + floatingip := base["floatingip"].(map[string]interface{}) + + if opts.DNSName != "" { + floatingip["dns_name"] = opts.DNSName + } + + if opts.DNSDomain != "" { + floatingip["dns_domain"] = opts.DNSDomain + } + + return base, nil +} + +// NetworkCreateOptsExt adds network DNS options to the base networks.CreateOpts. +type NetworkCreateOptsExt struct { + // CreateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Create operation in this package. + networks.CreateOptsBuilder + + // Set DNS domain to the network + DNSDomain string `json:"dns_domain,omitempty"` +} + +// ToNetworkCreateMap casts a CreateOpts struct to a map. +func (opts NetworkCreateOptsExt) ToNetworkCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToNetworkCreateMap() + if err != nil { + return nil, err + } + + network := base["network"].(map[string]interface{}) + + if opts.DNSDomain != "" { + network["dns_domain"] = opts.DNSDomain + } + + return base, nil +} + +// NetworkUpdateOptsExt adds network DNS options to the base networks.UpdateOpts +type NetworkUpdateOptsExt struct { + // UpdateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Update operation in this package. + networks.UpdateOptsBuilder + + // Set DNS domain to the network + DNSDomain *string `json:"dns_domain,omitempty"` +} + +// ToNetworkUpdateMap casts an UpdateOpts struct to a map. +func (opts NetworkUpdateOptsExt) ToNetworkUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToNetworkUpdateMap() + if err != nil { + return nil, err + } + + network := base["network"].(map[string]interface{}) + + if opts.DNSDomain != nil { + network["dns_domain"] = *opts.DNSDomain + } + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/results.go new file mode 100644 index 000000000000..ce5752fc59d6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/results.go @@ -0,0 +1,30 @@ +package dns + +// PortDNSExt represents a decorated form of a Port with the additional +// Port DNS information. +type PortDNSExt struct { + // The DNS name of the port. + DNSName string `json:"dns_name"` + + // The DNS assignment of the port. + DNSAssignment []map[string]string `json:"dns_assignment"` +} + +// FloatingIPDNSExt represents a decorated form of a Floating IP with the +// additional Floating IP DNS information. +type FloatingIPDNSExt struct { + // The DNS name of the floating IP, assigned to the external DNS + // service. + DNSName string `json:"dns_name"` + + // The DNS domain of the floating IP, assigned to the external DNS + // service. + DNSDomain string `json:"dns_domain"` +} + +// NetworkDNSExt represents a decorated form of a Network with the additional +// Network DNS information. +type NetworkDNSExt struct { + // The DNS domain of the network. + DNSDomain string `json:"dns_domain"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/fixtures.go new file mode 100644 index 000000000000..6da93ec82c09 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/fixtures.go @@ -0,0 +1,320 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + floatingiptest "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing" + networktest "github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing" + porttest "github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing" + th "github.com/gophercloud/gophercloud/testhelper" +) + +const NetworkCreateRequest = ` +{ + "network": { + "name": "private", + "admin_state_up": true, + "dns_domain": "local." + } +}` + +const NetworkCreateResponse = ` +{ + "network": { + "status": "ACTIVE", + "subnets": ["08eae331-0402-425a-923c-34f7cfe39c1b"], + "name": "private", + "admin_state_up": true, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": 9876543210, + "provider:physical_network": null, + "provider:network_type": "local.", + "dns_domain": "local." + } +}` + +const NetworkUpdateRequest = ` +{ + "network": { + "name": "new_network_name", + "admin_state_up": false, + "dns_domain": "" + } +}` + +const NetworkUpdateResponse = ` +{ + "network": { + "status": "ACTIVE", + "subnets": ["08eae331-0402-425a-923c-34f7cfe39c1b"], + "name": "new_network_name", + "admin_state_up": false, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": 9876543210, + "provider:physical_network": null, + "provider:network_type": "local.", + "dns_domain": "" + } +}` + +func PortHandleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + th.AssertEquals(t, r.RequestURI, "/v2.0/ports?dns_name=test-port") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, porttest.ListResponse) + }) +} + +func PortHandleGet(t *testing.T) { + th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, porttest.GetResponse) + }) +} + +func PortHandleCreate(t *testing.T) { + th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "port": { + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "name": "private-port", + "admin_state_up": true, + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.2" + } + ], + "security_groups": ["foo"], + "dns_name": "test-port" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` +{ + "port": { + "status": "DOWN", + "name": "private-port", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:c9:cb:f0", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.2" + } + ], + "dns_name": "test-port", + "dns_assignment": [ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local." + } + ], + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "security_groups": [ + "f0ac4394-7e4a-4409-9701-ba8be283dbc3" + ], + "device_id": "" + } +} + `) + }) +} + +func PortHandleUpdate(t *testing.T) { + th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "port": { + "name": "new_port_name", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.3" + } + ], + "security_groups": [ + "f0ac4394-7e4a-4409-9701-ba8be283dbc3" + ], + "dns_name": "test-port1" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "port": { + "status": "DOWN", + "name": "new_port_name", + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:c9:cb:f0", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.3" + } + ], + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "security_groups": [ + "f0ac4394-7e4a-4409-9701-ba8be283dbc3" + ], + "device_id": "", + "dns_name": "test-port1", + "dns_assignment": [ + { + "hostname": "test-port1", + "ip_address": "172.24.4.2", + "fqdn": "test-port1.openstack.local." + } + ] + } +} + `) + }) +} + +func FloatingIPHandleList(t *testing.T) { + th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + th.AssertEquals(t, r.RequestURI, "/v2.0/floatingips?dns_domain=local.&dns_name=test-fip") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, floatingiptest.ListResponseDNS) + }) +} + +func FloatingIPHandleGet(t *testing.T) { + th.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) + }) +} + +func FloatingIPHandleCreate(t *testing.T) { + th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "floatingip": { + "floating_network_id": "6d67c30a-ddb4-49a1-bec3-a65b286b4170", + "dns_name": "test-fip", + "dns_domain": "local." + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) + }) +} + +func NetworkHandleList(t *testing.T) { + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + th.AssertEquals(t, r.RequestURI, "/v2.0/networks?dns_domain=local.") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, networktest.ListResponse) + }) +} + +func NetworkHandleGet(t *testing.T) { + th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, networktest.GetResponse) + }) +} + +func NetworkHandleCreate(t *testing.T) { + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, NetworkCreateRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, NetworkCreateResponse) + }) +} + +func NetworkHandleUpdate(t *testing.T) { + th.Mux.HandleFunc("/v2.0/networks/db193ab3-96e3-4cb3-8fc5-05f4296d0324", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, NetworkUpdateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, NetworkUpdateResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/requests_test.go new file mode 100644 index 000000000000..2bcb7cba8c0c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns/testing/requests_test.go @@ -0,0 +1,388 @@ +package testing + +import ( + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/dns" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" +) + +type PortDNS struct { + ports.Port + dns.PortDNSExt +} + +type FloatingIPDNS struct { + floatingips.FloatingIP + dns.FloatingIPDNSExt +} + +type NetworkDNS struct { + networks.Network + dns.NetworkDNSExt +} + +func TestPortList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + PortHandleListSuccessfully(t) + + var actual []PortDNS + + expected := []PortDNS{ + { + Port: ports.Port{ + Status: "ACTIVE", + Name: "", + AdminStateUp: true, + NetworkID: "70c1db1f-b701-45bd-96e0-a313ee3430b3", + TenantID: "", + DeviceOwner: "network:router_gateway", + MACAddress: "fa:16:3e:58:42:ed", + FixedIPs: []ports.IP{ + { + SubnetID: "008ba151-0b8c-4a67-98b5-0d2b87666062", + IPAddress: "172.24.4.2", + }, + }, + ID: "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + SecurityGroups: []string{}, + DeviceID: "9ae135f4-b6e0-4dad-9e91-3c223e385824", + }, + PortDNSExt: dns.PortDNSExt{ + DNSName: "test-port", + DNSAssignment: []map[string]string{ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local.", + }, + }, + }, + }, + } + + var listOptsBuilder ports.ListOptsBuilder + listOptsBuilder = dns.PortListOptsExt{ + ListOptsBuilder: ports.ListOpts{}, + DNSName: "test-port", + } + + allPages, err := ports.List(fake.ServiceClient(), listOptsBuilder).AllPages() + th.AssertNoErr(t, err) + + err = ports.ExtractPortsInto(allPages, &actual) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, expected, actual) +} + +func TestPortGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + PortHandleGet(t) + + var s PortDNS + + err := ports.Get(fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Status, "ACTIVE") + th.AssertEquals(t, s.Name, "") + th.AssertEquals(t, s.AdminStateUp, true) + th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") + th.AssertEquals(t, s.TenantID, "7e02058126cc4950b75f9970368ba177") + th.AssertEquals(t, s.DeviceOwner, "network:router_interface") + th.AssertEquals(t, s.MACAddress, "fa:16:3e:23:fd:d7") + th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"}, + }) + th.AssertEquals(t, s.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2") + th.AssertDeepEquals(t, s.SecurityGroups, []string{}) + th.AssertEquals(t, s.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e") + + th.AssertEquals(t, s.DNSName, "test-port") + th.AssertDeepEquals(t, s.DNSAssignment, []map[string]string{ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local.", + }, + }) +} + +func TestPortCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + PortHandleCreate(t) + + var s PortDNS + + asu := true + portCreateOpts := ports.CreateOpts{ + Name: "private-port", + AdminStateUp: &asu, + NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7", + FixedIPs: []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, + }, + SecurityGroups: &[]string{"foo"}, + } + + createOpts := dns.PortCreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + DNSName: "test-port", + } + + err := ports.Create(fake.ServiceClient(), createOpts).ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Status, "DOWN") + th.AssertEquals(t, s.Name, "private-port") + th.AssertEquals(t, s.AdminStateUp, true) + th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") + th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") + th.AssertEquals(t, s.DeviceOwner, "") + th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0") + th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, + }) + th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + + th.AssertEquals(t, s.DNSName, "test-port") + th.AssertDeepEquals(t, s.DNSAssignment, []map[string]string{ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local.", + }, + }) +} + +func TestPortRequiredCreateOpts(t *testing.T) { + res := ports.Create(fake.ServiceClient(), dns.PortCreateOptsExt{CreateOptsBuilder: ports.CreateOpts{}}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestPortUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + PortHandleUpdate(t) + + var s PortDNS + + name := "new_port_name" + portUpdateOpts := ports.UpdateOpts{ + Name: &name, + FixedIPs: []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, + }, + SecurityGroups: &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, + } + + dnsName := "test-port1" + updateOpts := dns.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + DNSName: &dnsName, + } + + err := ports.Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "new_port_name") + th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, + }) + th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + th.AssertEquals(t, s.DNSName, "test-port1") + th.AssertDeepEquals(t, s.DNSAssignment, []map[string]string{ + { + "hostname": "test-port1", + "ip_address": "172.24.4.2", + "fqdn": "test-port1.openstack.local.", + }, + }) +} + +func TestFloatingIPGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + FloatingIPHandleGet(t) + + var actual FloatingIPDNS + err := floatingips.Get(fake.ServiceClient(), "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e").ExtractInto(&actual) + th.AssertNoErr(t, err) + + expected := FloatingIPDNS{ + FloatingIP: floatingips.FloatingIP{ + FloatingNetworkID: "6d67c30a-ddb4-49a1-bec3-a65b286b4170", + FixedIP: "", + FloatingIP: "192.0.0.4", + TenantID: "017d8de156df4177889f31a9bd6edc00", + Status: "DOWN", + PortID: "", + ID: "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", + RouterID: "1117c30a-ddb4-49a1-bec3-a65b286b4170", + }, + FloatingIPDNSExt: dns.FloatingIPDNSExt{ + DNSName: "test-fip", + DNSDomain: "local.", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +func TestFloatingIPCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + FloatingIPHandleCreate(t) + + var actual FloatingIPDNS + + fipCreateOpts := floatingips.CreateOpts{ + FloatingNetworkID: "6d67c30a-ddb4-49a1-bec3-a65b286b4170", + } + + options := dns.FloatingIPCreateOptsExt{ + CreateOptsBuilder: fipCreateOpts, + DNSName: "test-fip", + DNSDomain: "local.", + } + + err := floatingips.Create(fake.ServiceClient(), options).ExtractInto(&actual) + th.AssertNoErr(t, err) + + expected := FloatingIPDNS{ + FloatingIP: floatingips.FloatingIP{ + FloatingNetworkID: "6d67c30a-ddb4-49a1-bec3-a65b286b4170", + FixedIP: "", + FloatingIP: "192.0.0.4", + TenantID: "017d8de156df4177889f31a9bd6edc00", + Status: "DOWN", + PortID: "", + ID: "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", + RouterID: "1117c30a-ddb4-49a1-bec3-a65b286b4170", + }, + FloatingIPDNSExt: dns.FloatingIPDNSExt{ + DNSName: "test-fip", + DNSDomain: "local.", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +func TestNetworkGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + NetworkHandleGet(t) + + var actual NetworkDNS + + err := networks.Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&actual) + th.AssertNoErr(t, err) + + expected := NetworkDNS{ + Network: networks.Network{ + Name: "public", + Subnets: []string{"54d6f61d-db07-451c-9ab3-b9609b6b6f0b"}, + Status: "ACTIVE", + TenantID: "4fd44f30292945e481c7b8a0c8908869", + AdminStateUp: true, + Shared: true, + ID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + }, + NetworkDNSExt: dns.NetworkDNSExt{ + DNSDomain: "local.", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +func TestNetworkCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + NetworkHandleCreate(t) + + var actual NetworkDNS + + iTrue := true + networkCreateOpts := networks.CreateOpts{Name: "private", AdminStateUp: &iTrue} + createOpts := dns.NetworkCreateOptsExt{ + CreateOptsBuilder: networkCreateOpts, + DNSDomain: "local.", + } + + err := networks.Create(fake.ServiceClient(), createOpts).ExtractInto(&actual) + th.AssertNoErr(t, err) + + expected := NetworkDNS{ + Network: networks.Network{ + Name: "private", + Subnets: []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, + Status: "ACTIVE", + TenantID: "26a7980765d0414dbc1fc1f88cdb7e6e", + AdminStateUp: true, + Shared: false, + ID: "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + }, + NetworkDNSExt: dns.NetworkDNSExt{ + DNSDomain: "local.", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +func TestNetworkUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + NetworkHandleUpdate(t) + + var actual NetworkDNS + + name := "new_network_name" + networkUpdateOpts := networks.UpdateOpts{Name: &name, AdminStateUp: new(bool)} + updateOpts := dns.NetworkUpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + DNSDomain: new(string), + } + + err := networks.Update(fake.ServiceClient(), "db193ab3-96e3-4cb3-8fc5-05f4296d0324", updateOpts).ExtractInto(&actual) + th.AssertNoErr(t, err) + + expected := NetworkDNS{ + Network: networks.Network{ + Name: "new_network_name", + Subnets: []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, + Status: "ACTIVE", + TenantID: "26a7980765d0414dbc1fc1f88cdb7e6e", + AdminStateUp: false, + Shared: false, + ID: "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + }, + NetworkDNSExt: dns.NetworkDNSExt{ + DNSDomain: "", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go index b8261684e700..eda010cb0c87 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go @@ -4,6 +4,13 @@ extension for the OpenStack Networking service. Example to List Networks with External Information + iTrue := true + networkListOpts := networks.ListOpts{} + listOpts := external.ListOptsExt{ + ListOptsBuilder: networkListOpts, + External: &iTrue, + } + type NetworkWithExternalExt struct { networks.Network external.NetworkExternalExt @@ -11,7 +18,7 @@ Example to List Networks with External Information var allNetworks []NetworkWithExternalExt - allPages, err := networks.List(networkClient, nil).AllPages() + allPages, err := networks.List(networkClient, listOpts).AllPages() if err != nil { panic(err) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go index f28e574612c2..ced5efed8d97 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go @@ -1,9 +1,37 @@ package external import ( + "net/url" + "strconv" + + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" ) +// ListOptsExt adds the external network options to the base ListOpts. +type ListOptsExt struct { + networks.ListOptsBuilder + External *bool `q:"router:external"` +} + +// ToNetworkListQuery adds the router:external option to the base network +// list options. +func (opts ListOptsExt) ToNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder) + if err != nil { + return "", err + } + + params := q.Query() + if opts.External != nil { + v := strconv.FormatBool(*opts.External) + params.Add("router:external", v) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + // CreateOptsExt is the structure used when creating new external network // resources. It embeds networks.CreateOpts and so inherits all of its required // and optional fields, with the addition of the External field. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/fixtures.go index 8739236d693b..f23bbea4a154 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/fixtures.go @@ -57,3 +57,5 @@ const UpdateResponse = ` "router:external": false } }` + +const ExpectedListOpts = "?id=d32019d3-bc6e-4319-9c1d-6722fc136a22&router%3Aexternal=true" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/requests_test.go new file mode 100644 index 000000000000..29236b89286b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/requests_test.go @@ -0,0 +1,26 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListExternal(t *testing.T) { + var iTrue bool = true + + networkListOpts := networks.ListOpts{ + ID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + } + + listOpts := external.ListOptsExt{ + ListOptsBuilder: networkListOpts, + External: &iTrue, + } + + actual, err := listOpts.ToNetworkListQuery() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedListOpts, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go index 64a508824d89..a64d23c3c483 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go @@ -122,8 +122,9 @@ func TestUpdate(t *testing.T) { iTrue := true iFalse := false + name := "new_network_name" networkUpdateOpts := networks.UpdateOpts{ - Name: "new_network_name", + Name: &name, AdminStateUp: &iFalse, Shared: &iTrue, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go index cbd6c1fb0d9c..c710f94a8dca 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go @@ -107,11 +107,11 @@ type UpdateOptsBuilder interface { // UpdateOpts contains the values used when updating a firewall. type UpdateOpts struct { - PolicyID string `json:"firewall_policy_id" required:"true"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` - Shared *bool `json:"shared,omitempty"` + PolicyID string `json:"firewall_policy_id" required:"true"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Shared *bool `json:"shared,omitempty"` } // ToFirewallUpdateMap casts a CreateOpts struct to a map. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/testing/requests_test.go index 13eca65b69ca..f7fae9fdc508 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/testing/requests_test.go @@ -315,9 +315,11 @@ func TestUpdate(t *testing.T) { `) }) + var name = "fw" + var description = "updated fw" options := firewalls.UpdateOpts{ - Name: "fw", - Description: "updated fw", + Name: &name, + Description: &description, AdminStateUp: gophercloud.Disabled, PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go index 40ab7a8c46fa..fcecf6a50ddf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go @@ -107,8 +107,8 @@ type UpdateOptsBuilder interface { // UpdateOpts contains the values used when updating a firewall policy. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` Shared *bool `json:"shared,omitempty"` Audited *bool `json:"audited,omitempty"` Rules []string `json:"firewall_rules,omitempty"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/testing/requests_test.go index 11b9848f5297..ed7070cc2586 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/testing/requests_test.go @@ -246,9 +246,11 @@ func TestUpdate(t *testing.T) { `) }) + var name = "policy" + var description = "Firewall policy" options := policies.UpdateOpts{ - Name: "policy", - Description: "Firewall policy", + Name: &name, + Description: &description, Rules: []string{ "98a58c87-76be-ae7c-a74e-b77fffb88d95", "11a58c87-76be-ae7c-a74e-b77fffb88a32", diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/testing/requests_test.go index ac7a2be8d260..0eff5fcd496c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/testing/requests_test.go @@ -165,9 +165,11 @@ func TestUpdate(t *testing.T) { `) }) + var name = "fw" + var description = "updated fw" firewallUpdateOpts := firewalls.UpdateOpts{ - Name: "fw", - Description: "updated fw", + Name: &name, + Description: &description, AdminStateUp: gophercloud.Disabled, PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", } @@ -219,9 +221,11 @@ func TestUpdateWithNoRouters(t *testing.T) { `) }) + var name = "fw" + var description = "updated fw" firewallUpdateOpts := firewalls.UpdateOpts{ - Name: "fw", - Description: "updated fw", + Name: &name, + Description: &description, AdminStateUp: gophercloud.Disabled, PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go new file mode 100644 index 000000000000..d68d2b764a49 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go @@ -0,0 +1,64 @@ +/* +Package addressscopes provides the ability to retrieve and manage Address scopes through the Neutron API. + +Example of Listing Address scopes + + listOpts := addressscopes.ListOpts{ + IPVersion: 6, + } + + allPages, err := addressscopes.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allAddressScopes, err := addressscopes.ExtractAddressScopes(allPages) + if err != nil { + panic(err) + } + + for _, addressScope := range allAddressScopes { + fmt.Printf("%+v\n", addressScope) + } + +Example to Get an Address scope + + addressScopeID = "9cc35860-522a-4d35-974d-51d4b011801e" + addressScope, err := addressscopes.Get(networkClient, addressScopeID).Extract() + if err != nil { + panic(err) + } + +Example to Create a new Address scope + + addressScopeOpts := addressscopes.CreateOpts{ + Name: "my_address_scope", + IPVersion: 6, + } + addressScope, err := addressscopes.Create(networkClient, addressScopeOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update an Address scope + + addressScopeID = "9cc35860-522a-4d35-974d-51d4b011801e" + newName := "awesome_name" + updateOpts := addressscopes.UpdateOpts{ + Name: &newName, + } + + addressScope, err := addressscopes.Update(networkClient, addressScopeID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Address scope + + addressScopeID = "9cc35860-522a-4d35-974d-51d4b011801e" + err := addressscopes.Delete(networkClient, addressScopeID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package addressscopes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go new file mode 100644 index 000000000000..defa8e1b5a6f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go @@ -0,0 +1,147 @@ +package addressscopes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAddressScopeListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the Neutron API. Filtering is achieved by passing in struct field values +// that map to the address-scope attributes you want to see returned. +// SortKey allows you to sort by a particular address-scope attribute. +// SortDir sets the direction, and is either `asc' or `desc'. +// Marker and Limit are used for the pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + IPVersion int `q:"ip_version"` + Shared *bool `q:"shared"` + Description string `q:"description"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToAddressScopeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAddressScopeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// address-scopes. It accepts a ListOpts struct, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only the address-scopes owned by the project +// of the user submitting the request, unless the user has the administrative +// role. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToAddressScopeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AddressScopePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific address-scope based on its ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAddressScopeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters of a new address-scope. +type CreateOpts struct { + // Name is the human-readable name of the address-scope. + Name string `json:"name"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id,omitempty"` + + // IPVersion is the IP protocol version. + IPVersion int `json:"ip_version"` + + // Shared indicates whether this address-scope is shared across all projects. + Shared bool `json:"shared,omitempty"` +} + +// ToAddressScopeCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToAddressScopeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "address_scope") +} + +// Create requests the creation of a new address-scope on the server. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToAddressScopeCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToAddressScopeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update an address-scope. +type UpdateOpts struct { + // Name is the human-readable name of the address-scope. + Name *string `json:"name,omitempty"` + + // Shared indicates whether this address-scope is shared across all projects. + Shared *bool `json:"shared,omitempty"` +} + +// ToAddressScopeUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToAddressScopeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "address_scope") +} + +// Update accepts a UpdateOpts struct and updates an existing address-scope +// using the values provided. +func Update(c *gophercloud.ServiceClient, addressScopeID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToAddressScopeUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, addressScopeID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete accepts a unique ID and deletes the address-scope associated with it. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go new file mode 100644 index 000000000000..5f78dfeef2af --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go @@ -0,0 +1,99 @@ +package addressscopes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an address-scope resource. +func (r commonResult) Extract() (*AddressScope, error) { + var s struct { + AddressScope *AddressScope `json:"address_scope"` + } + err := r.ExtractInto(&s) + return s.AddressScope, err +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SubnetPool. +type GetResult struct { + commonResult +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SubnetPool. +type CreateResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as an AddressScope. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AddressScope represents a Neutron address-scope. +type AddressScope struct { + // ID is the id of the address-scope. + ID string `json:"id"` + + // Name is the human-readable name of the address-scope. + Name string `json:"name"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id"` + + // IPVersion is the IP protocol version. + IPVersion int `json:"ip_version"` + + // Shared indicates whether this address-scope is shared across all projects. + Shared bool `json:"shared"` +} + +// AddressScopePage stores a single page of AddressScopes from a List() API call. +type AddressScopePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of address-scope has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r AddressScopePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"address_scopes_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty determines whether or not a AddressScopePage is empty. +func (r AddressScopePage) IsEmpty() (bool, error) { + addressScopes, err := ExtractAddressScopes(r) + return len(addressScopes) == 0, err +} + +// ExtractAddressScopes interprets the results of a single page from a List() +// API call, producing a slice of AddressScopes structs. +func ExtractAddressScopes(r pagination.Page) ([]AddressScope, error) { + var s struct { + AddressScopes []AddressScope `json:"address_scopes"` + } + err := (r.(AddressScopePage)).ExtractInto(&s) + return s.AddressScopes, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/doc.go new file mode 100644 index 000000000000..7787611308d9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/doc.go @@ -0,0 +1,2 @@ +// subnetpools unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/fixtures.go new file mode 100644 index 000000000000..42b896d3c3b0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/fixtures.go @@ -0,0 +1,112 @@ +package testing + +import "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes" + +// AddressScopesListResult represents raw response for the List request. +const AddressScopesListResult = ` +{ + "address_scopes": [ + { + "name": "scopev4", + "tenant_id": "4a9807b773404e979b19633f38370643", + "ip_version": 4, + "shared": false, + "project_id": "4a9807b773404e979b19633f38370643", + "id": "9cc35860-522a-4d35-974d-51d4b011801e" + }, + { + "name": "scopev6", + "tenant_id": "4a9807b773404e979b19633f38370643", + "ip_version": 6, + "shared": true, + "project_id": "4a9807b773404e979b19633f38370643", + "id": "be992b82-bf42-4ab7-bf7b-6baa8759d388" + } + ] +} +` + +// AddressScope1 represents first unmarshalled address scope from the +// AddressScopesListResult. +var AddressScope1 = addressscopes.AddressScope{ + ID: "9cc35860-522a-4d35-974d-51d4b011801e", + Name: "scopev4", + TenantID: "4a9807b773404e979b19633f38370643", + ProjectID: "4a9807b773404e979b19633f38370643", + IPVersion: 4, + Shared: false, +} + +// AddressScope2 represents second unmarshalled address scope from the +// AddressScopesListResult. +var AddressScope2 = addressscopes.AddressScope{ + ID: "be992b82-bf42-4ab7-bf7b-6baa8759d388", + Name: "scopev6", + TenantID: "4a9807b773404e979b19633f38370643", + ProjectID: "4a9807b773404e979b19633f38370643", + IPVersion: 6, + Shared: true, +} + +// AddressScopesGetResult represents raw response for the Get request. +const AddressScopesGetResult = ` +{ + "address_scope": { + "name": "scopev4", + "tenant_id": "4a9807b773404e979b19633f38370643", + "ip_version": 4, + "shared": false, + "project_id": "4a9807b773404e979b19633f38370643", + "id": "9cc35860-522a-4d35-974d-51d4b011801e" + } +} +` + +// AddressScopeCreateRequest represents raw Create request. +const AddressScopeCreateRequest = ` +{ + "address_scope": { + "ip_version": 4, + "shared": true, + "name": "test0" + } +} +` + +// AddressScopeCreateResult represents raw Create response. +const AddressScopeCreateResult = ` +{ + "address_scope": { + "name": "test0", + "tenant_id": "4a9807b773404e979b19633f38370643", + "ip_version": 4, + "shared": true, + "project_id": "4a9807b773404e979b19633f38370643", + "id": "9cc35860-522a-4d35-974d-51d4b011801e" + } +} +` + +// AddressScopeUpdateRequest represents raw Update request. +const AddressScopeUpdateRequest = ` +{ + "address_scope": { + "name": "test1", + "shared": true + } +} +` + +// AddressScopeUpdateResult represents raw Update response. +const AddressScopeUpdateResult = ` +{ + "address_scope": { + "name": "test1", + "tenant_id": "4a9807b773404e979b19633f38370643", + "ip_version": 4, + "shared": true, + "project_id": "4a9807b773404e979b19633f38370643", + "id": "9cc35860-522a-4d35-974d-51d4b011801e" + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go new file mode 100644 index 000000000000..caad3a2f3f5e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go @@ -0,0 +1,153 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/address-scopes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AddressScopesListResult) + }) + + count := 0 + + addressscopes.List(fake.ServiceClient(), addressscopes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := addressscopes.ExtractAddressScopes(page) + if err != nil { + t.Errorf("Failed to extract addressscopes: %v", err) + return false, nil + } + + expected := []addressscopes.AddressScope{ + AddressScope1, + AddressScope2, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AddressScopesGetResult) + }) + + s, err := addressscopes.Get(fake.ServiceClient(), "9cc35860-522a-4d35-974d-51d4b011801e").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.ID, "9cc35860-522a-4d35-974d-51d4b011801e") + th.AssertEquals(t, s.Name, "scopev4") + th.AssertEquals(t, s.TenantID, "4a9807b773404e979b19633f38370643") + th.AssertEquals(t, s.ProjectID, "4a9807b773404e979b19633f38370643") + th.AssertEquals(t, s.IPVersion, 4) + th.AssertEquals(t, s.Shared, false) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/address-scopes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddressScopeCreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, AddressScopeCreateResult) + }) + + opts := addressscopes.CreateOpts{ + IPVersion: 4, + Shared: true, + Name: "test0", + } + s, err := addressscopes.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "test0") + th.AssertEquals(t, s.Shared, true) + th.AssertEquals(t, s.IPVersion, 4) + th.AssertEquals(t, s.TenantID, "4a9807b773404e979b19633f38370643") + th.AssertEquals(t, s.ProjectID, "4a9807b773404e979b19633f38370643") + th.AssertEquals(t, s.ID, "9cc35860-522a-4d35-974d-51d4b011801e") +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddressScopeUpdateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AddressScopeUpdateResult) + }) + + shared := true + newName := "test1" + updateOpts := addressscopes.UpdateOpts{ + Name: &newName, + Shared: &shared, + } + s, err := addressscopes.Update(fake.ServiceClient(), "9cc35860-522a-4d35-974d-51d4b011801e", updateOpts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "test1") + th.AssertEquals(t, s.Shared, true) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := addressscopes.Delete(fake.ServiceClient(), "9cc35860-522a-4d35-974d-51d4b011801e") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go new file mode 100644 index 000000000000..9fe7e01a034d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go @@ -0,0 +1,33 @@ +package addressscopes + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "address-scopes" + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func createURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go index bf5ec6807cc6..a71a3ec88a9b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go @@ -52,7 +52,7 @@ Example to Disassociate a Floating IP with a Port fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" updateOpts := floatingips.UpdateOpts{ - PortID: nil, + PortID: new(string), } fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go index d82a1bc8e294..0c0db64d8d30 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go @@ -5,6 +5,12 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToFloatingIPListQuery() (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 @@ -12,6 +18,7 @@ import ( // either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { ID string `q:"id"` + Description string `q:"description"` FloatingNetworkID string `q:"floating_network_id"` PortID string `q:"port_id"` FixedIP string `q:"fixed_ip_address"` @@ -24,18 +31,31 @@ type ListOpts struct { SortDir string `q:"sort_dir"` RouterID string `q:"router_id"` Status string `q:"status"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` +} + +// ToNetworkListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToFloatingIPListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err } // List returns a Pager which allows you to iterate over a collection of // floating IP resources. It accepts a ListOpts struct, which allows you to // filter and sort the returned collection for greater efficiency. -func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { - q, err := gophercloud.BuildQueryString(&opts) - if err != nil { - return pagination.Pager{Err: err} +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToFloatingIPListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query } - u := rootURL(c) + q.String() - return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { return FloatingIPPage{pagination.LinkedPageBase{PageResult: r}} }) } @@ -50,6 +70,7 @@ type CreateOptsBuilder interface { // resource. The only required fields are FloatingNetworkID and PortID which // refer to the external network and internal port respectively. type CreateOpts struct { + Description string `json:"description,omitempty"` FloatingNetworkID string `json:"floating_network_id" required:"true"` FloatingIP string `json:"floating_ip_address,omitempty"` PortID string `json:"port_id,omitempty"` @@ -116,13 +137,24 @@ type UpdateOptsBuilder interface { // linked to. To associate the floating IP with a new internal port, provide its // ID. To disassociate the floating IP from all ports, provide an empty string. type UpdateOpts struct { - PortID *string `json:"port_id"` + Description *string `json:"description,omitempty"` + PortID *string `json:"port_id,omitempty"` + FixedIP string `json:"fixed_ip_address,omitempty"` } // ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder // interface func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "floatingip") + b, err := gophercloud.BuildRequestBody(opts, "floatingip") + if err != nil { + return nil, err + } + + if m := b["floatingip"].(map[string]interface{}); m["port_id"] == "" { + m["port_id"] = nil + } + + return b, nil } // Update allows floating IP resources to be updated. Currently, the only way to diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go index 8b1a51764533..a9709ccec3f7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go @@ -15,6 +15,9 @@ type FloatingIP struct { // ID is the unique identifier for the floating IP instance. ID string `json:"id"` + // Description for the floating IP instance. + Description string `json:"description"` + // FloatingNetworkID is the UUID of the external network where the floating // IP is to be created. FloatingNetworkID string `json:"floating_network_id"` @@ -42,6 +45,9 @@ type FloatingIP struct { // RouterID is the ID of the router used for this floating IP. RouterID string `json:"router_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } type commonResult struct { @@ -50,11 +56,13 @@ type commonResult struct { // Extract will extract a FloatingIP resource from a result. func (r commonResult) Extract() (*FloatingIP, error) { - var s struct { - FloatingIP *FloatingIP `json:"floatingip"` - } + var s FloatingIP err := r.ExtractInto(&s) - return s.FloatingIP, err + return &s, err +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "floatingip") } // CreateResult represents the result of a create operation. Call its Extract @@ -117,3 +125,7 @@ func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { err := (r.(FloatingIPPage)).ExtractInto(&s) return s.FloatingIPs, err } + +func ExtractFloatingIPsInto(r pagination.Page, v interface{}) error { + return r.(FloatingIPPage).Result.ExtractIntoSlicePtr(v, "floatingips") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/fixtures.go new file mode 100644 index 000000000000..0f665462abe8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/fixtures.go @@ -0,0 +1,50 @@ +package testing + +import ( + "fmt" +) + +const FipDNS = `{ + "floating_network_id": "6d67c30a-ddb4-49a1-bec3-a65b286b4170", + "router_id": null, + "fixed_ip_address": null, + "floating_ip_address": "192.0.0.4", + "tenant_id": "017d8de156df4177889f31a9bd6edc00", + "status": "DOWN", + "port_id": null, + "id": "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", + "router_id": "1117c30a-ddb4-49a1-bec3-a65b286b4170", + "dns_domain": "local.", + "dns_name": "test-fip" + }` + +const FipNoDNS = `{ + "floating_network_id": "90f742b1-6d17-487b-ba95-71881dbc0b64", + "router_id": "0a24cb83-faf5-4d7f-b723-3144ed8a2167", + "fixed_ip_address": "192.0.0.2", + "floating_ip_address": "10.0.0.3", + "tenant_id": "017d8de156df4177889f31a9bd6edc00", + "status": "DOWN", + "port_id": "74a342ce-8e07-4e91-880c-9f834b68fa25", + "id": "ada25a95-f321-4f59-b0e0-f3a970dd3d63", + "router_id": "2227c30a-ddb4-49a1-bec3-a65b286b4170", + "dns_domain": "", + "dns_name": "" + }` + +var ListResponse = fmt.Sprintf(` +{ + "floatingips": [ +%s, +%s + ] +} +`, FipDNS, FipNoDNS) + +var ListResponseDNS = fmt.Sprintf(` +{ + "floatingips": [ +%s + ] +} +`, FipDNS) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go index 89c028e8a720..ceba7a946463 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go @@ -22,34 +22,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` -{ - "floatingips": [ - { - "floating_network_id": "6d67c30a-ddb4-49a1-bec3-a65b286b4170", - "router_id": null, - "fixed_ip_address": null, - "floating_ip_address": "192.0.0.4", - "tenant_id": "017d8de156df4177889f31a9bd6edc00", - "status": "DOWN", - "port_id": null, - "id": "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", - "router_id": "1117c30a-ddb4-49a1-bec3-a65b286b4170" - }, - { - "floating_network_id": "90f742b1-6d17-487b-ba95-71881dbc0b64", - "router_id": "0a24cb83-faf5-4d7f-b723-3144ed8a2167", - "fixed_ip_address": "192.0.0.2", - "floating_ip_address": "10.0.0.3", - "tenant_id": "017d8de156df4177889f31a9bd6edc00", - "status": "DOWN", - "port_id": "74a342ce-8e07-4e91-880c-9f834b68fa25", - "id": "ada25a95-f321-4f59-b0e0-f3a970dd3d63", - "router_id": "2227c30a-ddb4-49a1-bec3-a65b286b4170" - } - ] -} - `) + fmt.Fprintf(w, ListResponse) }) count := 0 @@ -393,7 +366,7 @@ func TestDisassociate(t *testing.T) { `) }) - ip, err := floatingips.Update(fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: nil}).Extract() + ip, err := floatingips.Update(fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: new(string)}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, "", ip.FixedIP) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go index 8b2bde530e9d..cf499f9873dd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go @@ -13,6 +13,7 @@ import ( 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"` @@ -22,6 +23,10 @@ type ListOpts struct { 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"` } // List returns a Pager which allows you to iterate over a collection of @@ -51,6 +56,7 @@ type CreateOptsBuilder interface { // no required values. type CreateOpts struct { Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` Distributed *bool `json:"distributed,omitempty"` TenantID string `json:"tenant_id,omitempty"` @@ -97,6 +103,7 @@ type UpdateOptsBuilder interface { // UpdateOpts contains the values used when updating a router. type UpdateOpts struct { Name string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` Distributed *bool `json:"distributed,omitempty"` GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go index dffdce8f48f1..857e1947e1f6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go @@ -8,7 +8,7 @@ import ( // GatewayInfo represents the information of an external gateway for any // particular network router. type GatewayInfo struct { - NetworkID string `json:"network_id"` + NetworkID string `json:"network_id,omitempty"` EnableSNAT *bool `json:"enable_snat,omitempty"` ExternalFixedIPs []ExternalFixedIP `json:"external_fixed_ips,omitempty"` } @@ -16,7 +16,7 @@ type GatewayInfo struct { // ExternalFixedIP is the IP address and subnet ID of the external gateway of a // router. type ExternalFixedIP struct { - IPAddress string `json:"ip_address"` + IPAddress string `json:"ip_address,omitempty"` SubnetID string `json:"subnet_id"` } @@ -51,6 +51,9 @@ type Router struct { // unique. Name string `json:"name"` + // Description for the router. + Description string `json:"description"` + // ID is the unique identifier for the router. ID string `json:"id"` @@ -67,6 +70,9 @@ type Router struct { // Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others. // Used to make network resources highly available. AvailabilityZoneHints []string `json:"availability_zone_hints"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // RouterPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go index 2f7b93d4b92a..7bd1c4edf64e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go @@ -137,7 +137,10 @@ func TestCreate(t *testing.T) { "admin_state_up": false, "external_gateway_info":{ "enable_snat": false, - "network_id":"8ca37218-28ff-41cb-9b10-039601ea7e6b" + "network_id":"8ca37218-28ff-41cb-9b10-039601ea7e6b", + "external_fixed_ips": [ + {"subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] }, "availability_zone_hints": ["zone1", "zone2"] } @@ -171,9 +174,15 @@ func TestCreate(t *testing.T) { asu := false enableSNAT := false + efi := []routers.ExternalFixedIP{ + routers.ExternalFixedIP{ + SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def", + }, + } gwi := routers.GatewayInfo{ - NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", - EnableSNAT: &enableSNAT, + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + EnableSNAT: &enableSNAT, + ExternalFixedIPs: efi, } options := routers.CreateOpts{ Name: "foo_router", diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go index b3593548d371..f5f4e9a0dfc2 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go @@ -123,7 +123,7 @@ type UpdateOptsBuilder interface { // UpdateOpts contains the values used when updating a pool. type UpdateOpts struct { // Name of the pool. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // LBMethod is the algorithm used to distribute load between the members of // the pool. The current specification supports LBMethodRoundRobin and diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/testing/requests_test.go index de038cb8cdb6..dce13dd745d7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/testing/requests_test.go @@ -252,7 +252,8 @@ func TestUpdate(t *testing.T) { `) }) - options := pools.UpdateOpts{Name: "SuperPool", LBMethod: pools.LBMethodLeastConnections} + var name = "SuperPool" + options := pools.UpdateOpts{Name: &name, LBMethod: pools.LBMethodLeastConnections} n, err := pools.Update(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", options).Extract() th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go new file mode 100644 index 000000000000..813579905c25 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go @@ -0,0 +1,123 @@ +/* +Package l7policies provides information and interaction with L7Policies and +Rules of the LBaaS v2 extension for the OpenStack Networking service. + +Example to Create a L7Policy + + createOpts := l7policies.CreateOpts{ + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + } + l7policy, err := l7policies.Create(lbClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List L7Policies + + listOpts := l7policies.ListOpts{ + ListenerID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + allPages, err := l7policies.List(lbClient, listOpts).AllPages() + if err != nil { + panic(err) + } + allL7Policies, err := l7policies.ExtractL7Policies(allPages) + if err != nil { + panic(err) + } + for _, l7policy := range allL7Policies { + fmt.Printf("%+v\n", l7policy) + } + +Example to Get a L7Policy + + l7policy, err := l7policies.Get(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d").Extract() + if err != nil { + panic(err) + } + +Example to Delete a L7Policy + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := l7policies.Delete(lbClient, l7policyID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a L7Policy + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + name := "new-name" + updateOpts := l7policies.UpdateOpts{ + Name: &name, + } + l7policy, err := l7policies.Update(lbClient, l7policyID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create a Rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + createOpts := l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + } + rule, err := l7policies.CreateRule(lbClient, l7policyID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List L7 Rules + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + listOpts := l7policies.ListRulesOpts{ + RuleType: l7policies.TypePath, + } + allPages, err := l7policies.ListRules(lbClient, l7policyID, listOpts).AllPages() + if err != nil { + panic(err) + } + allRules, err := l7policies.ExtractRules(allPages) + if err != nil { + panic(err) + } + for _, rule := allRules { + fmt.Printf("%+v\n", rule) + } + +Example to Get a l7 rule + + l7rule, err := l7policies.GetRule(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d", "53ad8ab8-40fa-11e8-a508-00224d6b7bc1").Extract() + if err != nil { + panic(err) + } + +Example to Delete a l7 rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e" + err := l7policies.DeleteRule(lbClient, l7policyID, ruleID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a Rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e" + updateOpts := l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", + } + rule, err := l7policies.UpdateRule(lbClient, l7policyID, ruleID, updateOpts).Extract() + if err != nil { + panic(err) + } +*/ +package l7policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go new file mode 100644 index 000000000000..9d2b3a0d351a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go @@ -0,0 +1,376 @@ +package l7policies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToL7PolicyCreateMap() (map[string]interface{}, error) +} + +type Action string +type RuleType string +type CompareType string + +const ( + ActionRedirectToPool Action = "REDIRECT_TO_POOL" + ActionRedirectToURL Action = "REDIRECT_TO_URL" + ActionReject Action = "REJECT" + + TypeCookie RuleType = "COOKIE" + TypeFileType RuleType = "FILE_TYPE" + TypeHeader RuleType = "HEADER" + TypeHostName RuleType = "HOST_NAME" + TypePath RuleType = "PATH" + + CompareTypeContains CompareType = "CONTAINS" + CompareTypeEndWith CompareType = "ENDS_WITH" + CompareTypeEqual CompareType = "EQUAL_TO" + CompareTypeRegex CompareType = "REGEX" + CompareTypeStartWith CompareType = "STARTS_WITH" +) + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // Name of the L7 policy. + Name string `json:"name,omitempty"` + + // The ID of the listener. + ListenerID string `json:"listener_id" required:"true"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action Action `json:"action" required:"true"` + + // The position of this policy on the listener. + Position int32 `json:"position,omitempty"` + + // A human-readable description for the resource. + Description string `json:"description,omitempty"` + + // TenantID is the UUID of the tenant who owns the L7 policy in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID string `json:"redirect_pool_id,omitempty"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL string `json:"redirect_url,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToL7PolicyCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToL7PolicyCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "l7policy") +} + +// Create accepts a CreateOpts struct and uses the values to create a new l7policy. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToL7PolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToL7PolicyListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. +type ListOpts struct { + Name string `q:"name"` + Description string `q:"description"` + ListenerID string `q:"listener_id"` + Action string `q:"action"` + TenantID string `q:"tenant_id"` + RedirectPoolID string `q:"redirect_pool_id"` + RedirectURL string `q:"redirect_url"` + Position int32 `q:"position"` + AdminStateUp bool `q:"admin_state_up"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToL7PolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToL7PolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// l7policies. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those l7policies that are owned by the +// project who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToL7PolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return L7PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a particular l7policy based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// Delete will permanently delete a particular l7policy based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToL7PolicyUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // Name of the L7 policy, empty string is allowed. + Name *string `json:"name,omitempty"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action Action `json:"action,omitempty"` + + // The position of this policy on the listener. + Position int32 `json:"position,omitempty"` + + // A human-readable description for the resource, empty string is allowed. + Description *string `json:"description,omitempty"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID *string `json:"redirect_pool_id,omitempty"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL *string `json:"redirect_url,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToL7PolicyUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToL7PolicyUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "l7policy") + if err != nil { + return nil, err + } + + m := b["l7policy"].(map[string]interface{}) + + if m["redirect_pool_id"] == "" { + m["redirect_pool_id"] = nil + } + + if m["redirect_url"] == "" { + m["redirect_url"] = nil + } + + return b, nil +} + +// Update allows l7policy to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToL7PolicyUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// CreateRuleOpts is the common options struct used in this package's CreateRule +// operation. +type CreateRuleOpts struct { + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType RuleType `json:"type" required:"true"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType CompareType `json:"compare_type" required:"true"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value" required:"true"` + + // TenantID is the UUID of the tenant who owns the rule in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key string `json:"key,omitempty"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert bool `json:"invert,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToRuleCreateMap builds a request body from CreateRuleOpts. +func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "rule") +} + +// CreateRule will create and associate a Rule with a particular L7Policy. +func CreateRule(c *gophercloud.ServiceClient, policyID string, opts CreateRuleOpts) (r CreateRuleResult) { + b, err := opts.ToRuleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(ruleRootURL(c, policyID), b, &r.Body, nil) + return +} + +// ListRulesOptsBuilder allows extensions to add additional parameters to the +// ListRules request. +type ListRulesOptsBuilder interface { + ToRulesListQuery() (string, error) +} + +// ListRulesOpts allows the filtering and sorting of paginated collections +// through the API. +type ListRulesOpts struct { + RuleType RuleType `q:"type"` + TenantID string `q:"tenant_id"` + CompareType CompareType `q:"compare_type"` + Value string `q:"value"` + Key string `q:"key"` + Invert bool `q:"invert"` + AdminStateUp bool `q:"admin_state_up"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToRulesListQuery formats a ListOpts into a query string. +func (opts ListRulesOpts) ToRulesListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListRules returns a Pager which allows you to iterate over a collection of +// rules. It accepts a ListRulesOptsBuilder, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only those rules that are owned by the +// project who submits the request, unless an admin user submits the request. +func ListRules(c *gophercloud.ServiceClient, policyID string, opts ListRulesOptsBuilder) pagination.Pager { + url := ruleRootURL(c, policyID) + if opts != nil { + query, err := opts.ToRulesListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return RulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// GetRule retrieves a particular L7Policy Rule based on its unique ID. +func GetRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r GetRuleResult) { + _, r.Err = c.Get(ruleResourceURL(c, policyID, ruleID), &r.Body, nil) + return +} + +// DeleteRule will remove a Rule from a particular L7Policy. +func DeleteRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r DeleteRuleResult) { + _, r.Err = c.Delete(ruleResourceURL(c, policyID, ruleID), nil) + return +} + +// UpdateRuleOptsBuilder allows to add additional parameters to the PUT request. +type UpdateRuleOptsBuilder interface { + ToRuleUpdateMap() (map[string]interface{}, error) +} + +// UpdateRuleOpts is the common options struct used in this package's Update +// operation. +type UpdateRuleOpts struct { + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType RuleType `json:"type,omitempty"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType CompareType `json:"compare_type,omitempty"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value,omitempty"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key *string `json:"key,omitempty"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert *bool `json:"invert,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToRuleUpdateMap builds a request body from UpdateRuleOpts. +func (opts UpdateRuleOpts) ToRuleUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "rule") + if err != nil { + return nil, err + } + + if m := b["rule"].(map[string]interface{}); m["key"] == "" { + m["key"] = nil + } + + return b, nil +} + +// UpdateRule allows Rule to be updated. +func UpdateRule(c *gophercloud.ServiceClient, policyID string, ruleID string, opts UpdateRuleOptsBuilder) (r UpdateRuleResult) { + b, err := opts.ToRuleUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(ruleResourceURL(c, policyID, ruleID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go new file mode 100644 index 000000000000..5153b1b90c81 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go @@ -0,0 +1,245 @@ +package l7policies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// L7Policy is a collection of L7 rules associated with a Listener, and which +// may also have an association to a back-end pool. +type L7Policy struct { + // The unique ID for the L7 policy. + ID string `json:"id"` + + // Name of the L7 policy. + Name string `json:"name"` + + // The ID of the listener. + ListenerID string `json:"listener_id"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action string `json:"action"` + + // The position of this policy on the listener. + Position int32 `json:"position"` + + // A human-readable description for the resource. + Description string `json:"description"` + + // TenantID is the UUID of the tenant who owns the L7 policy in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID string `json:"redirect_pool_id"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL string `json:"redirect_url"` + + // The administrative state of the L7 policy, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The provisioning status of the L7 policy. + // This value is ACTIVE, PENDING_* or ERROR. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the L7 policy. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` + + // Rules are List of associated L7 rule IDs. + Rules []Rule `json:"rules"` +} + +// Rule represents layer 7 load balancing rule. +type Rule struct { + // The unique ID for the L7 rule. + ID string `json:"id"` + + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType string `json:"type"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType string `json:"compare_type"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value"` + + // TenantID is the UUID of the tenant who owns the rule in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key string `json:"key"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert bool `json:"invert"` + + // The administrative state of the L7 rule, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The provisioning status of the L7 rule. + // This value is ACTIVE, PENDING_* or ERROR. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the L7 policy. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a l7policy. +func (r commonResult) Extract() (*L7Policy, error) { + var s struct { + L7Policy *L7Policy `json:"l7policy"` + } + err := r.ExtractInto(&s) + return s.L7Policy, err +} + +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret the result as a L7Policy. +type CreateResult struct { + commonResult +} + +// L7PolicyPage is the page returned by a pager when traversing over a +// collection of l7policies. +type L7PolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of l7policies has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r L7PolicyPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"l7policies_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a L7PolicyPage struct is empty. +func (r L7PolicyPage) IsEmpty() (bool, error) { + is, err := ExtractL7Policies(r) + return len(is) == 0, err +} + +// ExtractL7Policies accepts a Page struct, specifically a L7PolicyPage struct, +// and extracts the elements into a slice of L7Policy structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractL7Policies(r pagination.Page) ([]L7Policy, error) { + var s struct { + L7Policies []L7Policy `json:"l7policies"` + } + err := (r.(L7PolicyPage)).ExtractInto(&s) + return s.L7Policies, err +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret the result as a L7Policy. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret the result as a L7Policy. +type UpdateResult struct { + commonResult +} + +type commonRuleResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a rule. +func (r commonRuleResult) Extract() (*Rule, error) { + var s struct { + Rule *Rule `json:"rule"` + } + err := r.ExtractInto(&s) + return s.Rule, err +} + +// CreateRuleResult represents the result of a CreateRule operation. +// Call its Extract method to interpret it as a Rule. +type CreateRuleResult struct { + commonRuleResult +} + +// RulePage is the page returned by a pager when traversing over a +// collection of Rules in a L7Policy. +type RulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of rules has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r RulePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"rules_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a RulePage struct is empty. +func (r RulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(r) + return len(is) == 0, err +} + +// ExtractRules accepts a Page struct, specifically a RulePage struct, +// and extracts the elements into a slice of Rules structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(r pagination.Page) ([]Rule, error) { + var s struct { + Rules []Rule `json:"rules"` + } + err := (r.(RulePage)).ExtractInto(&s) + return s.Rules, err +} + +// GetRuleResult represents the result of a GetRule operation. +// Call its Extract method to interpret it as a Rule. +type GetRuleResult struct { + commonRuleResult +} + +// DeleteRuleResult represents the result of a DeleteRule operation. +// Call its ExtractErr method to determine if the request succeeded or failed. +type DeleteRuleResult struct { + gophercloud.ErrResult +} + +// UpdateRuleResult represents the result of an UpdateRule operation. +// Call its Extract method to interpret it as a Rule. +type UpdateRuleResult struct { + commonRuleResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/doc.go new file mode 100644 index 000000000000..f8068dfb6bb9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/doc.go @@ -0,0 +1,2 @@ +// l7policies unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/fixtures.go new file mode 100644 index 000000000000..87c6e66d229d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/fixtures.go @@ -0,0 +1,428 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// SingleL7PolicyBody is the canned body of a Get request on an existing l7policy. +const SingleL7PolicyBody = ` +{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "description": "", + "admin_state_up": true, + "redirect_pool_id": null, + "redirect_url": "http://www.example.com", + "action": "REDIRECT_TO_URL", + "position": 1, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "redirect-example.com", + "rules": [] + } +} +` + +var ( + L7PolicyToURL = l7policies.L7Policy{ + ID: "8a1412f0-4c32-4257-8b07-af4770b604fd", + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: "REDIRECT_TO_URL", + Position: 1, + Description: "", + TenantID: "e3cd678b11784734bc366148aa37580e", + RedirectPoolID: "", + RedirectURL: "http://www.example.com", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + L7PolicyToPool = l7policies.L7Policy{ + ID: "964f4ba4-f6cd-405c-bebd-639460af7231", + Name: "redirect-pool", + ListenerID: "be3138a3-5cf7-4513-a4c2-bb137e668bab", + Action: "REDIRECT_TO_POOL", + Position: 1, + Description: "", + TenantID: "c1f7910086964990847dc6c8b128f63c", + RedirectPoolID: "bac433c6-5bea-4311-80da-bd1cd90fbd25", + RedirectURL: "", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + L7PolicyUpdated = l7policies.L7Policy{ + ID: "8a1412f0-4c32-4257-8b07-af4770b604fd", + Name: "NewL7PolicyName", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: "REDIRECT_TO_URL", + Position: 1, + Description: "Redirect requests to example.com", + TenantID: "e3cd678b11784734bc366148aa37580e", + RedirectPoolID: "", + RedirectURL: "http://www.new-example.com", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + L7PolicyNullRedirectURLUpdated = l7policies.L7Policy{ + ID: "8a1412f0-4c32-4257-8b07-af4770b604fd", + Name: "NewL7PolicyName", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: "REDIRECT_TO_URL", + Position: 1, + Description: "Redirect requests to example.com", + TenantID: "e3cd678b11784734bc366148aa37580e", + RedirectPoolID: "", + RedirectURL: "", + AdminStateUp: true, + Rules: []l7policies.Rule{}, + } + RulePath = l7policies.Rule{ + ID: "16621dbb-a736-4888-a57a-3ecd53df784c", + RuleType: "PATH", + CompareType: "REGEX", + Value: "/images*", + TenantID: "e3cd678b11784734bc366148aa37580e", + Key: "", + Invert: true, + AdminStateUp: true, + } + RuleHostName = l7policies.Rule{ + ID: "d24521a0-df84-4468-861a-a531af116d1e", + RuleType: "HOST_NAME", + CompareType: "EQUAL_TO", + Value: "www.example.com", + TenantID: "e3cd678b11784734bc366148aa37580e", + Key: "", + Invert: false, + AdminStateUp: true, + } + RuleUpdated = l7policies.Rule{ + ID: "16621dbb-a736-4888-a57a-3ecd53df784c", + RuleType: "PATH", + CompareType: "REGEX", + Value: "/images/special*", + TenantID: "e3cd678b11784734bc366148aa37580e", + Key: "", + Invert: false, + AdminStateUp: true, + } +) + +// HandleL7PolicyCreationSuccessfully sets up the test server to respond to a l7policy creation request +// with a given response. +func HandleL7PolicyCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "redirect_url": "http://www.example.com", + "name": "redirect-example.com", + "action": "REDIRECT_TO_URL" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// L7PoliciesListBody contains the canned body of a l7policy list response. +const L7PoliciesListBody = ` +{ + "l7policies": [ + { + "redirect_pool_id": null, + "description": "", + "admin_state_up": true, + "rules": [], + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "redirect_url": "http://www.example.com", + "action": "REDIRECT_TO_URL", + "position": 1, + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "redirect-example.com" + }, + { + "redirect_pool_id": "bac433c6-5bea-4311-80da-bd1cd90fbd25", + "description": "", + "admin_state_up": true, + "rules": [], + "tenant_id": "c1f7910086964990847dc6c8b128f63c", + "listener_id": "be3138a3-5cf7-4513-a4c2-bb137e668bab", + "action": "REDIRECT_TO_POOL", + "position": 1, + "id": "964f4ba4-f6cd-405c-bebd-639460af7231", + "name": "redirect-pool" + } + ] +} +` + +// PostUpdateL7PolicyBody is the canned response body of a Update request on an existing l7policy. +const PostUpdateL7PolicyBody = ` +{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "description": "Redirect requests to example.com", + "admin_state_up": true, + "redirect_pool_id": null, + "redirect_url": "http://www.new-example.com", + "action": "REDIRECT_TO_URL", + "position": 1, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "NewL7PolicyName", + "rules": [] + } +} +` + +// PostUpdateL7PolicyNullRedirectURLBody is the canned response body of a Update request +// on an existing l7policy with a null redirect_url . +const PostUpdateL7PolicyNullRedirectURLBody = ` +{ + "l7policy": { + "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", + "description": "Redirect requests to example.com", + "admin_state_up": true, + "redirect_pool_id": null, + "redirect_url": null, + "action": "REDIRECT_TO_URL", + "position": 1, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "id": "8a1412f0-4c32-4257-8b07-af4770b604fd", + "name": "NewL7PolicyName", + "rules": [] + } +} +` + +// HandleL7PolicyListSuccessfully sets up the test server to respond to a l7policy List request. +func HandleL7PolicyListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, L7PoliciesListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "l7policies": [] }`) + default: + t.Fatalf("/v2.0/lbaas/l7policies invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleL7PolicyGetSuccessfully sets up the test server to respond to a l7policy Get request. +func HandleL7PolicyGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleL7PolicyBody) + }) +} + +// HandleL7PolicyDeletionSuccessfully sets up the test server to respond to a l7policy deletion request. +func HandleL7PolicyDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleL7PolicyUpdateSuccessfully sets up the test server to respond to a l7policy Update request. +func HandleL7PolicyUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "l7policy": { + "name": "NewL7PolicyName", + "action": "REDIRECT_TO_URL", + "redirect_url": "http://www.new-example.com" + } + }`) + + fmt.Fprintf(w, PostUpdateL7PolicyBody) + }) +} + +// HandleL7PolicyUpdateNullRedirectURLSuccessfully sets up the test server to respond to a l7policy Update request. +func HandleL7PolicyUpdateNullRedirectURLSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "l7policy": { + "name": "NewL7PolicyName", + "redirect_url": null + } + }`) + + fmt.Fprintf(w, PostUpdateL7PolicyNullRedirectURLBody) + }) +} + +// SingleRuleBody is the canned body of a Get request on an existing rule. +const SingleRuleBody = ` +{ + "rule": { + "compare_type": "REGEX", + "invert": true, + "admin_state_up": true, + "value": "/images*", + "key": null, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "type": "PATH", + "id": "16621dbb-a736-4888-a57a-3ecd53df784c" + } +} +` + +// HandleRuleCreationSuccessfully sets up the test server to respond to a rule creation request +// with a given response. +func HandleRuleCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "rule": { + "compare_type": "REGEX", + "type": "PATH", + "value": "/images*" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// RulesListBody contains the canned body of a rule list response. +const RulesListBody = ` +{ + "rules":[ + { + "compare_type": "REGEX", + "invert": true, + "admin_state_up": true, + "value": "/images*", + "key": null, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "type": "PATH", + "id": "16621dbb-a736-4888-a57a-3ecd53df784c" + }, + { + "compare_type": "EQUAL_TO", + "invert": false, + "admin_state_up": true, + "value": "www.example.com", + "key": null, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "type": "HOST_NAME", + "id": "d24521a0-df84-4468-861a-a531af116d1e" + } + ] +} +` + +// HandleRuleListSuccessfully sets up the test server to respond to a rule List request. +func HandleRuleListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, RulesListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "rules": [] }`) + default: + t.Fatalf("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleRuleGetSuccessfully sets up the test server to respond to a rule Get request. +func HandleRuleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleRuleBody) + }) +} + +// HandleRuleDeletionSuccessfully sets up the test server to respond to a rule deletion request. +func HandleRuleDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// PostUpdateRuleBody is the canned response body of a Update request on an existing rule. +const PostUpdateRuleBody = ` +{ + "rule": { + "compare_type": "REGEX", + "invert": false, + "admin_state_up": true, + "value": "/images/special*", + "key": null, + "tenant_id": "e3cd678b11784734bc366148aa37580e", + "type": "PATH", + "id": "16621dbb-a736-4888-a57a-3ecd53df784c" + } +} +` + +// HandleRuleUpdateSuccessfully sets up the test server to respond to a rule Update request. +func HandleRuleUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "rule": { + "compare_type": "REGEX", + "invert": false, + "key": null, + "type": "PATH", + "value": "/images/special*" + } + }`) + + fmt.Fprintf(w, PostUpdateRuleBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/requests_test.go new file mode 100644 index 000000000000..f8e67f4b80ef --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/testing/requests_test.go @@ -0,0 +1,317 @@ +package testing + +import ( + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCreateL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyCreationSuccessfully(t, SingleL7PolicyBody) + + actual, err := l7policies.Create(fake.ServiceClient(), l7policies.CreateOpts{ + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + }).Extract() + + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, L7PolicyToURL, *actual) +} + +func TestRequiredL7PolicyCreateOpts(t *testing.T) { + // no param specified. + res := l7policies.Create(fake.ServiceClient(), l7policies.CreateOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + + // Action is invalid. + res = l7policies.Create(fake.ServiceClient(), l7policies.CreateOpts{ + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.Action("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} + +func TestListL7Policies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyListSuccessfully(t) + + pages := 0 + err := l7policies.List(fake.ServiceClient(), l7policies.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := l7policies.ExtractL7Policies(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 l7policies, got %d", len(actual)) + } + th.CheckDeepEquals(t, L7PolicyToURL, actual[0]) + th.CheckDeepEquals(t, L7PolicyToPool, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllL7Policies(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyListSuccessfully(t) + + allPages, err := l7policies.List(fake.ServiceClient(), l7policies.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := l7policies.ExtractL7Policies(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, L7PolicyToURL, actual[0]) + th.CheckDeepEquals(t, L7PolicyToPool, actual[1]) +} + +func TestGetL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := l7policies.Get(client, "8a1412f0-4c32-4257-8b07-af4770b604fd").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, L7PolicyToURL, *actual) +} + +func TestDeleteL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyDeletionSuccessfully(t) + + res := l7policies.Delete(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateL7Policy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyUpdateSuccessfully(t) + + client := fake.ServiceClient() + newName := "NewL7PolicyName" + redirectURL := "http://www.new-example.com" + actual, err := l7policies.Update(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", + l7policies.UpdateOpts{ + Name: &newName, + Action: l7policies.ActionRedirectToURL, + RedirectURL: &redirectURL, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, L7PolicyUpdated, *actual) +} + +func TestUpdateL7PolicyNullRedirectURL(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleL7PolicyUpdateNullRedirectURLSuccessfully(t) + + client := fake.ServiceClient() + newName := "NewL7PolicyName" + redirectURL := "" + actual, err := l7policies.Update(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", + l7policies.UpdateOpts{ + Name: &newName, + RedirectURL: &redirectURL, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, L7PolicyNullRedirectURLUpdated, *actual) +} + +func TestUpdateL7PolicyWithInvalidOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := l7policies.Update(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.UpdateOpts{ + Action: l7policies.Action("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestCreateRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleCreationSuccessfully(t, SingleRuleBody) + + actual, err := l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, RulePath, *actual) +} + +func TestRequiredRuleCreateOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := l7policies.CreateRule(fake.ServiceClient(), "", l7policies.CreateRuleOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + res = l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + res = l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.RuleType("invalid"), + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } + res = l7policies.CreateRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareType("invalid"), + Value: "/images*", + }) + if res.Err == nil { + t.Fatalf("Expected error, but got none") + } +} + +func TestListRules(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleListSuccessfully(t) + + pages := 0 + err := l7policies.ListRules(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).EachPage(func(page pagination.Page) (bool, error) { + pages++ + + actual, err := l7policies.ExtractRules(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 rules, got %d", len(actual)) + } + th.CheckDeepEquals(t, RulePath, actual[0]) + th.CheckDeepEquals(t, RuleHostName, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllRules(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleListSuccessfully(t) + + allPages, err := l7policies.ListRules(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).AllPages() + th.AssertNoErr(t, err) + + actual, err := l7policies.ExtractRules(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, RulePath, actual[0]) + th.CheckDeepEquals(t, RuleHostName, actual[1]) +} + +func TestGetRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleGetSuccessfully(t) + + client := fake.ServiceClient() + actual, err := l7policies.GetRule(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, RulePath, *actual) +} + +func TestDeleteRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleDeletionSuccessfully(t) + + res := l7policies.DeleteRule(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateRule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleRuleUpdateSuccessfully(t) + + client := fake.ServiceClient() + invert := false + key := "" + actual, err := l7policies.UpdateRule(client, "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c", l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", + Invert: &invert, + Key: &key, + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, RuleUpdated, *actual) +} + +func TestUpdateRuleWithInvalidOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + res := l7policies.UpdateRule(fake.ServiceClient(), "", "", l7policies.UpdateRuleOpts{ + RuleType: l7policies.RuleType("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } + + res = l7policies.UpdateRule(fake.ServiceClient(), "", "", l7policies.UpdateRuleOpts{ + CompareType: l7policies.CompareType("invalid"), + }) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go new file mode 100644 index 000000000000..ecb607a8e89d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go @@ -0,0 +1,25 @@ +package l7policies + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "l7policies" + rulePath = "rules" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func ruleRootURL(c *gophercloud.ServiceClient, policyID string) string { + return c.ServiceURL(rootPath, resourcePath, policyID, rulePath) +} + +func ruleResourceURL(c *gophercloud.ServiceClient, policyID string, ruleID string) string { + return c.ServiceURL(rootPath, resourcePath, policyID, rulePath, ruleID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go index dd190f606f20..f2966b6c44ac 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go @@ -10,9 +10,10 @@ type Protocol string // Supported attributes for create/update operations. const ( - ProtocolTCP Protocol = "TCP" - ProtocolHTTP Protocol = "HTTP" - ProtocolHTTPS Protocol = "HTTPS" + ProtocolTCP Protocol = "TCP" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" + ProtocolTerminatedHTTPS Protocol = "TERMINATED_HTTPS" ) // ListOptsBuilder allows extensions to add additional parameters to the @@ -154,10 +155,13 @@ type UpdateOptsBuilder interface { // UpdateOpts represents options for updating a Listener. type UpdateOpts struct { // Human-readable name for the Listener. Does not have to be unique. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` + + // The ID of the default pool with which the Listener is associated. + DefaultPoolID *string `json:"default_pool_id,omitempty"` // Human-readable description for the Listener. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // The maximum number of connections allowed for the Listener. ConnLimit *int `json:"connection_limit,omitempty"` @@ -175,7 +179,16 @@ type UpdateOpts struct { // ToListenerUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "listener") + b, err := gophercloud.BuildRequestBody(opts, "listener") + if err != nil { + return nil, err + } + + if m := b["listener"].(map[string]interface{}); m["default_pool_id"] == "" { + m["default_pool_id"] = nil + } + + return b, nil } // Update is an operation which modifies the attributes of the specified diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go index e0c134ed51b6..ae105793225e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go @@ -2,6 +2,7 @@ package listeners import ( "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/pagination" ) @@ -54,6 +55,15 @@ type Listener struct { // Pools are the pools which are part of this listener. Pools []pools.Pool `json:"pools"` + + // L7policies are the L7 policies which are part of this listener. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1352 + L7Policies []l7policies.L7Policy `json:"l7policies"` + + // The provisioning status of the listener. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` } // ListenerPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/fixtures.go index fa4fa25c1804..5a5f050324aa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/fixtures.go @@ -204,6 +204,7 @@ func HandleListenerUpdateSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, `{ "listener": { "name": "NewListenerName", + "default_pool_id": null, "connection_limit": 1001 } }`) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/requests_test.go index d463f6e85922..80f56709d6db 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/testing/requests_test.go @@ -125,9 +125,12 @@ func TestUpdateListener(t *testing.T) { client := fake.ServiceClient() i1001 := 1001 + name := "NewListenerName" + defaultPoolID := "" actual, err := listeners.Update(client, "4ec89087-d057-4e2c-911f-60a3b47ee304", listeners.UpdateOpts{ - Name: "NewListenerName", - ConnLimit: &i1001, + Name: &name, + ConnLimit: &i1001, + DefaultPoolID: &defaultPoolID, }).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go index eea43391a8cf..c6d53a7b0524 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go @@ -67,5 +67,13 @@ Example to Get the Status of a Load Balancer if err != nil { panic(err) } + +Example to Get the Statistics of a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + stats, err := loadbalancers.GetStats(networkClient, LBID).Extract() + if err != nil { + panic(err) + } */ package loadbalancers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go index 1ed23c3c82ae..f5b141348210 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go @@ -141,10 +141,10 @@ type UpdateOptsBuilder interface { // operation. type UpdateOpts struct { // Human-readable name for the Loadbalancer. Does not have to be unique. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // Human-readable description for the Loadbalancer. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // The administrative state of the Loadbalancer. A valid value is true (UP) // or false (DOWN). @@ -196,3 +196,9 @@ func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) _, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil) return } + +// GetStats will return the shows the current statistics of a particular LoadBalancer. +func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) { + _, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go index 42fff5713198..7f423c933dde 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go @@ -3,6 +3,7 @@ package loadbalancers import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/pagination" ) @@ -51,6 +52,9 @@ type LoadBalancer struct { // Listeners are the listeners related to this Loadbalancer. Listeners []listeners.Listener `json:"listeners"` + + // Pools are the pools related to this Loadbalancer. + Pools []pools.Pool `json:"pools"` } // StatusTree represents the status of a loadbalancer. @@ -58,6 +62,23 @@ type StatusTree struct { Loadbalancer *LoadBalancer `json:"loadbalancer"` } +type Stats struct { + // The currently active connections. + ActiveConnections int `json:"active_connections"` + + // The total bytes received. + BytesIn int `json:"bytes_in"` + + // The total bytes sent. + BytesOut int `json:"bytes_out"` + + // The total requests that were unable to be fulfilled. + RequestErrors int `json:"request_errors"` + + // The total connections handled. + TotalConnections int `json:"total_connections"` +} + // LoadBalancerPage is the page returned by a pager when traversing over a // collection of load balancers. type LoadBalancerPage struct { @@ -124,6 +145,22 @@ func (r GetStatusesResult) Extract() (*StatusTree, error) { return s.Statuses, err } +// StatsResult represents the result of a GetStats operation. +// Call its Extract method to interpret it as a Stats. +type StatsResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts the status of +// a Loadbalancer. +func (r StatsResult) Extract() (*Stats, error) { + var s struct { + Stats *Stats `json:"stats"` + } + err := r.ExtractInto(&s) + return s.Stats, err +} + // CreateResult represents the result of a create operation. Call its Extract // method to interpret it as a LoadBalancer. type CreateResult struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go index a452236566f2..8132bda303bf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go @@ -5,13 +5,12 @@ import ( "net/http" "testing" - th "github.com/gophercloud/gophercloud/testhelper" - "github.com/gophercloud/gophercloud/testhelper/client" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" ) // LoadbalancersListBody contains the canned body of a loadbalancer list response. @@ -90,8 +89,8 @@ const PostUpdateLoadbalancerBody = ` } ` -// SingleLoadbalancerBody is the canned body of a Get request on an existing loadbalancer. -const LoadbalancerStatuesesTree = ` +// GetLoadbalancerStatusesBody is the canned request body of a Get request on loadbalancer's status. +const GetLoadbalancerStatusesBody = ` { "statuses" : { "loadbalancer": { @@ -102,18 +101,22 @@ const LoadbalancerStatuesesTree = ` "listeners": [{ "id": "db902c0c-d5ff-4753-b465-668ad9656918", "name": "db", + "provisioning_status": "ACTIVE", "pools": [{ "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", "name": "db", + "provisioning_status": "ACTIVE", "healthmonitor": { "id": "67306cda-815d-4354-9fe4-59e09da9c3c5", - "type":"PING" + "type":"PING", + "provisioning_status": "ACTIVE" }, "members":[{ "id": "2a280670-c202-4b0b-a562-34077415aabf", "name": "db", "address": "10.0.2.11", - "protocol_port": 80 + "protocol_port": 80, + "provisioning_status": "ACTIVE" }] }] }] @@ -122,6 +125,19 @@ const LoadbalancerStatuesesTree = ` } ` +// LoadbalancerStatsTree is the canned request body of a Get request on loadbalancer's statistics. +const GetLoadbalancerStatsBody = ` +{ + "stats": { + "active_connections": 0, + "bytes_in": 9532, + "bytes_out": 22033, + "request_errors": 46, + "total_connections": 112 + } +} +` + var ( LoadbalancerWeb = loadbalancers.LoadBalancer{ ID: "c331058c-6a40-4144-948e-b9fb1df9db4b", @@ -165,29 +181,42 @@ var ( ProvisioningStatus: "PENDING_CREATE", OperatingStatus: "OFFLINE", } - LoadbalancerStatusesTree = loadbalancers.LoadBalancer{ - ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", - Name: "db_lb", - ProvisioningStatus: "PENDING_UPDATE", - OperatingStatus: "ACTIVE", - Listeners: []listeners.Listener{{ - ID: "db902c0c-d5ff-4753-b465-668ad9656918", - Name: "db", - Pools: []pools.Pool{{ - ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", - Name: "db", - Monitor: monitors.Monitor{ - ID: "67306cda-815d-4354-9fe4-59e09da9c3c5", - Type: "PING", - }, - Members: []pools.Member{{ - ID: "2a280670-c202-4b0b-a562-34077415aabf", - Name: "db", - Address: "10.0.2.11", - ProtocolPort: 80, + LoadbalancerStatusesTree = loadbalancers.StatusTree{ + Loadbalancer: &loadbalancers.LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + Name: "db_lb", + ProvisioningStatus: "PENDING_UPDATE", + OperatingStatus: "ACTIVE", + Listeners: []listeners.Listener{{ + ID: "db902c0c-d5ff-4753-b465-668ad9656918", + Name: "db", + ProvisioningStatus: "ACTIVE", + Pools: []pools.Pool{{ + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Name: "db", + ProvisioningStatus: "ACTIVE", + Monitor: monitors.Monitor{ + ID: "67306cda-815d-4354-9fe4-59e09da9c3c5", + Type: "PING", + ProvisioningStatus: "ACTIVE", + }, + Members: []pools.Member{{ + ID: "2a280670-c202-4b0b-a562-34077415aabf", + Name: "db", + Address: "10.0.2.11", + ProtocolPort: 80, + ProvisioningStatus: "ACTIVE", + }}, }}, }}, - }}, + }, + } + LoadbalancerStatsTree = loadbalancers.Stats{ + ActiveConnections: 0, + BytesIn: 9532, + BytesOut: 22033, + RequestErrors: 46, + TotalConnections: 112, } ) @@ -252,7 +281,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, LoadbalancerStatuesesTree) + fmt.Fprintf(w, GetLoadbalancerStatusesBody) }) } @@ -282,3 +311,14 @@ func HandleLoadbalancerUpdateSuccessfully(t *testing.T) { fmt.Fprintf(w, PostUpdateLoadbalancerBody) }) } + +// HandleLoadbalancerGetStatsTree sets up the test server to respond to a loadbalancer Get stats tree request. +func HandleLoadbalancerGetStatsTree(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/stats", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, GetLoadbalancerStatsBody) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/requests_test.go index e370c669bfdc..2d741cc3ca08 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/requests_test.go @@ -115,7 +115,7 @@ func TestGetLoadbalancerStatusesTree(t *testing.T) { t.Fatalf("Unexpected Get error: %v", err) } - th.CheckDeepEquals(t, LoadbalancerStatusesTree, *(actual.Loadbalancer)) + th.CheckDeepEquals(t, LoadbalancerStatusesTree, *actual) } func TestDeleteLoadbalancer(t *testing.T) { @@ -133,8 +133,9 @@ func TestUpdateLoadbalancer(t *testing.T) { HandleLoadbalancerUpdateSuccessfully(t) client := fake.ServiceClient() + name := "NewLoadbalancerName" actual, err := loadbalancers.Update(client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", loadbalancers.UpdateOpts{ - Name: "NewLoadbalancerName", + Name: &name, }).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) @@ -159,3 +160,17 @@ func TestCascadingDeleteLoadbalancer(t *testing.T) { err = loadbalancers.CascadingDelete(sc, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").ExtractErr() th.AssertNoErr(t, err) } + +func TestGetLoadbalancerStatsTree(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleLoadbalancerGetStatsTree(t) + + client := fake.ServiceClient() + actual, err := loadbalancers.GetStats(client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, LoadbalancerStatsTree, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go index 73cf5dc126a3..2d2a99b7797f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go @@ -3,9 +3,10 @@ package loadbalancers import "github.com/gophercloud/gophercloud" const ( - rootPath = "lbaas" - resourcePath = "loadbalancers" - statusPath = "statuses" + rootPath = "lbaas" + resourcePath = "loadbalancers" + statusPath = "statuses" + statisticsPath = "stats" ) func rootURL(c *gophercloud.ServiceClient) string { @@ -19,3 +20,7 @@ func resourceURL(c *gophercloud.ServiceClient, id string) string { func statusRootURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL(rootPath, resourcePath, id, statusPath) } + +func statisticsRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, statisticsPath) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go index c173e1c64e70..f728f5a82378 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go @@ -223,7 +223,7 @@ type UpdateOpts struct { ExpectedCodes string `json:"expected_codes,omitempty"` // The Name of the Monitor. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // The administrative state of the Monitor. A valid value is true (UP) // or false (DOWN). diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go index ea832cc5d07e..a78f7aeb0ffa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go @@ -70,6 +70,10 @@ type Monitor struct { // List of pools that are associated with the health monitor. Pools []PoolID `json:"pools"` + + // The provisioning status of the monitor. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` } // MonitorPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/testing/requests_test.go index 743d9c1c6bfc..09389c840a95 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/testing/requests_test.go @@ -113,8 +113,9 @@ func TestUpdateHealthmonitor(t *testing.T) { HandleHealthmonitorUpdateSuccessfully(t) client := fake.ServiceClient() + name := "NewHealthmonitorName" actual, err := monitors.Update(client, "5d4b5228-33b0-4e60-b225-9b727c1a20e7", monitors.UpdateOpts{ - Name: "NewHealthmonitorName", + Name: &name, Delay: 3, Timeout: 20, MaxRetries: 10, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go index 2d57ed439385..069714868066 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go @@ -13,7 +13,7 @@ Example to List Pools panic(err) } - allPools, err := pools.ExtractMonitors(allPages) + allPools, err := pools.ExtractPools(allPages) if err != nil { panic(err) } @@ -83,12 +83,13 @@ Example to Create a Member poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + weight := 10 createOpts := pools.CreateMemberOpts{ Name: "db", SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", Address: "10.0.2.11", ProtocolPort: 80, - Weight: 10, + Weight: &weight, } member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract() @@ -101,9 +102,10 @@ Example to Update a Member poolID := "d67d56a6-4a86-4688-a282-f46444705c64" memberID := "64dba99f-8af8-4200-8882-e32a0660f23e" + weight := 4 updateOpts := pools.UpdateMemberOpts{ Name: "new-name", - Weight: 4, + Weight: &weight, } member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go index 11564be83fee..f427ae7bf574 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go @@ -154,10 +154,10 @@ type UpdateOptsBuilder interface { // operation. type UpdateOpts struct { // Name of the pool. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // Human-readable description for the pool. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // The algorithm used to distribute load between the members of the pool. The // current specification supports LBMethodRoundRobin, LBMethodLeastConnections @@ -274,7 +274,7 @@ type CreateMemberOpts struct { // that this member should receive from the pool. For example, a member with // a weight of 10 receives five times as much traffic as a member with a // weight of 2. - Weight int `json:"weight,omitempty"` + Weight *int `json:"weight,omitempty"` // If you omit this parameter, LBaaS uses the vip_subnet_id parameter value // for the subnet UUID. @@ -317,13 +317,13 @@ type UpdateMemberOptsBuilder interface { // operation. type UpdateMemberOpts struct { // Name of the Member. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // A positive integer value that indicates the relative portion of traffic // that this member should receive from the pool. For example, a member with // a weight of 10 receives five times as much traffic as a member with a // weight of 2. - Weight int `json:"weight,omitempty"` + Weight *int `json:"weight,omitempty"` // The administrative state of the Pool. A valid value is true (UP) // or false (DOWN). diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go index 56790fff9903..fba0d3a87823 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go @@ -92,6 +92,15 @@ type Pool struct { // The Monitor associated with this Pool. Monitor monitors.Monitor `json:"healthmonitor"` + + // The provisioning status of the pool. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the pool. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` } // PoolPage is the page returned by a pager when traversing over a @@ -196,6 +205,15 @@ type Member struct { // The unique ID for the Member. ID string `json:"id"` + + // The provisioning status of the member. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the member. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` } // MemberPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/testing/requests_test.go index 4af00ecc2576..14802fd4c46a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/testing/requests_test.go @@ -98,8 +98,9 @@ func TestUpdatePool(t *testing.T) { HandlePoolUpdateSuccessfully(t) client := fake.ServiceClient() + name := "NewPoolName" actual, err := pools.Update(client, "c3741b06-df4d-4715-b142-276b6bce75ab", pools.UpdateOpts{ - Name: "NewPoolName", + Name: &name, LBMethod: pools.LBMethodLeastConnections, }).Extract() if err != nil { @@ -189,13 +190,14 @@ func TestCreateMember(t *testing.T) { defer th.TeardownHTTP() HandleMemberCreationSuccessfully(t, SingleMemberBody) + weight := 10 actual, err := pools.CreateMember(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ Name: "db", SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", TenantID: "2ffc6e22aae24e4795f87155d24c896f", Address: "10.0.2.11", ProtocolPort: 80, - Weight: 10, + Weight: &weight, }).Extract() th.AssertNoErr(t, err) @@ -249,10 +251,12 @@ func TestUpdateMember(t *testing.T) { defer th.TeardownHTTP() HandleMemberUpdateSuccessfully(t) + weight := 4 client := fake.ServiceClient() + name := "newMemberName" actual, err := pools.UpdateMember(client, "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf", pools.UpdateMemberOpts{ - Name: "newMemberName", - Weight: 4, + Name: &name, + Weight: &weight, }).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/requests.go new file mode 100644 index 000000000000..bffb7f5ef74e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/requests.go @@ -0,0 +1,87 @@ +package mtu + +import ( + "fmt" + "net/url" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" +) + +// ListOptsExt adds an MTU option to the base ListOpts. +type ListOptsExt struct { + networks.ListOptsBuilder + + // The maximum transmission unit (MTU) value to address fragmentation. + // Minimum value is 68 for IPv4, and 1280 for IPv6. + MTU int `q:"mtu"` +} + +// ToNetworkListQuery adds the router:external option to the base network +// list options. +func (opts ListOptsExt) ToNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder) + if err != nil { + return "", err + } + + params := q.Query() + if opts.MTU > 0 { + params.Add("mtu", fmt.Sprintf("%d", opts.MTU)) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// CreateOptsExt adds an MTU option to the base Network CreateOpts. +type CreateOptsExt struct { + networks.CreateOptsBuilder + + // The maximum transmission unit (MTU) value to address fragmentation. + // Minimum value is 68 for IPv4, and 1280 for IPv6. + MTU int `json:"mtu,omitempty"` +} + +// ToNetworkCreateMap adds an MTU to the base network creation options. +func (opts CreateOptsExt) ToNetworkCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToNetworkCreateMap() + if err != nil { + return nil, err + } + + if opts.MTU == 0 { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["mtu"] = opts.MTU + + return base, nil +} + +// CreateOptsExt adds an MTU option to the base Network UpdateOpts. +type UpdateOptsExt struct { + networks.UpdateOptsBuilder + + // The maximum transmission unit (MTU) value to address fragmentation. + // Minimum value is 68 for IPv4, and 1280 for IPv6. + MTU int `json:"mtu,omitempty"` +} + +// ToNetworkUpdateMap adds an MTU to the base network uptade options. +func (opts UpdateOptsExt) ToNetworkUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToNetworkUpdateMap() + if err != nil { + return nil, err + } + + if opts.MTU == 0 { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["mtu"] = opts.MTU + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/results.go new file mode 100644 index 000000000000..497c9c37a041 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/results.go @@ -0,0 +1,8 @@ +package mtu + +// NetworkMTUExt represents an extended form of a Network with additional MTU field. +type NetworkMTUExt struct { + // The maximum transmission unit (MTU) value to address fragmentation. + // Minimum value is 68 for IPv4, and 1280 for IPv6. + MTU int `json:"mtu"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/fixtures.go new file mode 100644 index 000000000000..c69f9013ffdc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/fixtures.go @@ -0,0 +1,55 @@ +package testing + +// These fixtures are here instead of in the underlying networks package +// because all network tests (including extensions) would have to +// implement the NetworkMTUExt extention for create/update tests +// to pass. + +const CreateRequest = ` +{ + "network": { + "name": "private", + "admin_state_up": true, + "mtu": 1500 + } +}` + +const CreateResponse = ` +{ + "network": { + "status": "ACTIVE", + "subnets": ["08eae331-0402-425a-923c-34f7cfe39c1b"], + "name": "private", + "admin_state_up": true, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "mtu": 1500 + } +}` + +const UpdateRequest = ` +{ + "network": { + "name": "new_network_name", + "admin_state_up": false, + "shared": true, + "mtu": 1350 + } +}` + +const UpdateResponse = ` +{ + "network": { + "status": "ACTIVE", + "subnets": [], + "name": "new_network_name", + "admin_state_up": false, + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "shared": true, + "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c", + "mtu": 1350 + } +}` + +const ExpectedListOpts = "?id=d32019d3-bc6e-4319-9c1d-6722fc136a22&mtu=1500" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/requests_test.go new file mode 100644 index 000000000000..1b69ce41ee6c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/requests_test.go @@ -0,0 +1,24 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestListExternal(t *testing.T) { + networkListOpts := networks.ListOpts{ + ID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + } + + listOpts := mtu.ListOptsExt{ + ListOptsBuilder: networkListOpts, + MTU: 1500, + } + + actual, err := listOpts.ToNetworkListQuery() + th.AssertNoErr(t, err) + th.AssertEquals(t, ExpectedListOpts, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/results_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/results_test.go new file mode 100644 index 000000000000..ff5f9ae35b94 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu/testing/results_test.go @@ -0,0 +1,152 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/mtu" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + nettest "github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing" + th "github.com/gophercloud/gophercloud/testhelper" +) + +type NetworkMTU struct { + networks.Network + mtu.NetworkMTUExt +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, nettest.ListResponse) + }) + + type NetworkWithMTUExt struct { + networks.Network + mtu.NetworkMTUExt + } + var actual []NetworkWithMTUExt + + allPages, err := networks.List(fake.ServiceClient(), networks.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + + err = networks.ExtractNetworksInto(allPages, &actual) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", actual[0].ID) + th.AssertEquals(t, 1500, actual[0].MTU) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, nettest.GetResponse) + }) + + var s NetworkMTU + + err := networks.Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.ID) + th.AssertEquals(t, 1500, s.MTU) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, CreateResponse) + }) + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "private", + AdminStateUp: &iTrue, + } + + mtuCreateOpts := mtu.CreateOptsExt{ + CreateOptsBuilder: &networkCreateOpts, + MTU: 1500, + } + + var s NetworkMTU + + err := networks.Create(fake.ServiceClient(), mtuCreateOpts).ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) + th.AssertEquals(t, iTrue, s.AdminStateUp) + th.AssertEquals(t, 1500, s.MTU) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, UpdateResponse) + }) + + iTrue := true + iFalse := false + name := "new_network_name" + networkUpdateOpts := networks.UpdateOpts{ + Name: &name, + AdminStateUp: &iFalse, + Shared: &iTrue, + } + + mtuUpdateOpts := mtu.UpdateOptsExt{ + UpdateOptsBuilder: &networkUpdateOpts, + MTU: 1350, + } + + var s NetworkMTU + + err := networks.Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", mtuUpdateOpts).ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", s.ID) + th.AssertEquals(t, "new_network_name", s.Name) + th.AssertEquals(t, iFalse, s.AdminStateUp) + th.AssertEquals(t, iTrue, s.Shared) + th.AssertEquals(t, 1350, s.MTU) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/doc.go new file mode 100644 index 000000000000..910947369303 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/doc.go @@ -0,0 +1,30 @@ +/* +Package networkipavailabilities provides the ability to retrieve and manage +networkipavailabilities through the Neutron API. + +Example of Listing NetworkIPAvailabilities + + allPages, err := networkipavailabilities.List(networkClient, networkipavailabilities.ListOpts{}).AllPages() + if err != nil { + panic(err) + } + + allAvailabilities, err := subnetpools.ExtractSubnetPools(allPages) + if err != nil { + panic(err) + } + + for _, availability := range allAvailabilities { + fmt.Printf("%+v\n", availability) + } + +Example of Getting a single NetworkIPAvailability + + availability, err := networkipavailabilities.Get(networkClient, "cf11ab78-2302-49fa-870f-851a08c7afb8").Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", availability) +*/ +package networkipavailabilities diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/requests.go new file mode 100644 index 000000000000..c024d7a7b851 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/requests.go @@ -0,0 +1,61 @@ +package networkipavailabilities + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToNetworkIPAvailabilityListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the Neutron API. +type ListOpts struct { + // NetworkName allows to filter on the identifier of a network. + NetworkID string `q:"network_id"` + + // NetworkName allows to filter on the name of a network. + NetworkName string `q:"network_name"` + + // IPVersion allows to filter on the version of the IP protocol. + // You can use the well-known IP versions with the gophercloud.IPVersion type. + IPVersion string `q:"ip_version"` + + // ProjectID allows to filter on the Identity project field. + ProjectID string `q:"project_id"` + + // TenantID allows to filter on the Identity project field. + TenantID string `q:"tenant_id"` +} + +// ToNetworkIPAvailabilityListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToNetworkIPAvailabilityListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// networkipavailabilities. It accepts a ListOpts struct, which allows you to +// filter the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToNetworkIPAvailabilityListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return NetworkIPAvailabilityPage{pagination.SinglePageBase(r)} + }) +} + +// Get retrieves a specific NetworkIPAvailability based on its ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/results.go new file mode 100644 index 000000000000..db62b73ca497 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/results.go @@ -0,0 +1,139 @@ +package networkipavailabilities + +import ( + "encoding/json" + "math/big" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret it as a NetworkIPAvailability. +type GetResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts a NetworkIPAvailability. +func (r commonResult) Extract() (*NetworkIPAvailability, error) { + var s struct { + NetworkIPAvailability *NetworkIPAvailability `json:"network_ip_availability"` + } + err := r.ExtractInto(&s) + return s.NetworkIPAvailability, err +} + +// NetworkIPAvailability represents availability details for a single network. +type NetworkIPAvailability struct { + // NetworkID contains an unique identifier of the network. + NetworkID string `json:"network_id"` + + // NetworkName represents human-readable name of the network. + NetworkName string `json:"network_name"` + + // ProjectID is the ID of the Identity project. + ProjectID string `json:"project_id"` + + // TenantID is the ID of the Identity project. + TenantID string `json:"tenant_id"` + + // SubnetIPAvailabilities contains availability details for every subnet + // that is associated to the network. + SubnetIPAvailabilities []SubnetIPAvailability `json:"subnet_ip_availability"` + + // TotalIPs represents a number of IP addresses in the network. + TotalIPs string `json:"-"` + + // UsedIPs represents a number of used IP addresses in the network. + UsedIPs string `json:"-"` +} + +func (r *NetworkIPAvailability) UnmarshalJSON(b []byte) error { + type tmp NetworkIPAvailability + var s struct { + tmp + TotalIPs big.Int `json:"total_ips"` + UsedIPs big.Int `json:"used_ips"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = NetworkIPAvailability(s.tmp) + + r.TotalIPs = s.TotalIPs.String() + r.UsedIPs = s.UsedIPs.String() + + return err +} + +// SubnetIPAvailability represents availability details for a single subnet. +type SubnetIPAvailability struct { + // SubnetID contains an unique identifier of the subnet. + SubnetID string `json:"subnet_id"` + + // SubnetName represents human-readable name of the subnet. + SubnetName string `json:"subnet_name"` + + // CIDR represents prefix in the CIDR format. + CIDR string `json:"cidr"` + + // IPVersion is the IP protocol version. + IPVersion int `json:"ip_version"` + + // TotalIPs represents a number of IP addresses in the subnet. + TotalIPs string `json:"-"` + + // UsedIPs represents a number of used IP addresses in the subnet. + UsedIPs string `json:"-"` +} + +func (r *SubnetIPAvailability) UnmarshalJSON(b []byte) error { + type tmp SubnetIPAvailability + var s struct { + tmp + TotalIPs big.Int `json:"total_ips"` + UsedIPs big.Int `json:"used_ips"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = SubnetIPAvailability(s.tmp) + + r.TotalIPs = s.TotalIPs.String() + r.UsedIPs = s.UsedIPs.String() + + return err +} + +// NetworkIPAvailabilityPage stores a single page of NetworkIPAvailabilities +// from the List call. +type NetworkIPAvailabilityPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines whether or not a NetworkIPAvailability is empty. +func (r NetworkIPAvailabilityPage) IsEmpty() (bool, error) { + networkipavailabilities, err := ExtractNetworkIPAvailabilities(r) + return len(networkipavailabilities) == 0, err +} + +// ExtractNetworkIPAvailabilities interprets the results of a single page from +// a List() API call, producing a slice of NetworkIPAvailabilities structures. +func ExtractNetworkIPAvailabilities(r pagination.Page) ([]NetworkIPAvailability, error) { + var s struct { + NetworkIPAvailabilities []NetworkIPAvailability `json:"network_ip_availabilities"` + } + err := (r.(NetworkIPAvailabilityPage)).ExtractInto(&s) + if err != nil { + return nil, err + } + return s.NetworkIPAvailabilities, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/doc.go new file mode 100644 index 000000000000..baf115fc0ded --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/doc.go @@ -0,0 +1,2 @@ +// networkipavailabilities unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures.go new file mode 100644 index 000000000000..6a88f39bbe67 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures.go @@ -0,0 +1,130 @@ +package testing + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities" +) + +// NetworkIPAvailabilityListResult represents raw server response from a server to a list call. +const NetworkIPAvailabilityListResult = ` +{ + "network_ip_availabilities": [ + { + "network_id": "080ee064-036d-405a-a307-3bde4a213a1b", + "network_name": "private", + "project_id": "fb57277ef2f84a0e85b9018ec2dedbf7", + "subnet_ip_availability": [ + { + "cidr": "fdbc:bf53:567e::/64", + "ip_version": 6, + "subnet_id": "497ac4d3-0b92-42cf-82de-71302ab2b656", + "subnet_name": "ipv6-private-subnet", + "total_ips": 18446744073709552000, + "used_ips": 2 + }, + { + "cidr": "10.0.0.0/26", + "ip_version": 4, + "subnet_id": "521f47e7-c4fb-452c-b71a-851da38cc571", + "subnet_name": "private-subnet", + "total_ips": 61, + "used_ips": 2 + } + ], + "tenant_id": "fb57277ef2f84a0e85b9018ec2dedbf7", + "total_ips": 122, + "used_ips": 14 + }, + { + "network_id": "cf11ab78-2302-49fa-870f-851a08c7afb8", + "network_name": "public", + "project_id": "424e7cf0243c468ca61732ba45973b3e", + "subnet_ip_availability": [ + { + "cidr": "203.0.113.0/24", + "ip_version": 4, + "subnet_id": "4afe6e5f-9649-40db-b18f-64c7ead942bd", + "subnet_name": "public-subnet", + "total_ips": 253, + "used_ips": 3 + } + ], + "tenant_id": "424e7cf0243c468ca61732ba45973b3e", + "total_ips": 253, + "used_ips": 3 + } + ] +} +` + +// NetworkIPAvailability1 is an expected representation of a first object from the ResourceListResult. +var NetworkIPAvailability1 = networkipavailabilities.NetworkIPAvailability{ + NetworkID: "080ee064-036d-405a-a307-3bde4a213a1b", + NetworkName: "private", + ProjectID: "fb57277ef2f84a0e85b9018ec2dedbf7", + TenantID: "fb57277ef2f84a0e85b9018ec2dedbf7", + TotalIPs: "122", + UsedIPs: "14", + SubnetIPAvailabilities: []networkipavailabilities.SubnetIPAvailability{ + { + SubnetID: "497ac4d3-0b92-42cf-82de-71302ab2b656", + SubnetName: "ipv6-private-subnet", + CIDR: "fdbc:bf53:567e::/64", + IPVersion: int(gophercloud.IPv6), + TotalIPs: "18446744073709552000", + UsedIPs: "2", + }, + { + SubnetID: "521f47e7-c4fb-452c-b71a-851da38cc571", + SubnetName: "private-subnet", + CIDR: "10.0.0.0/26", + IPVersion: int(gophercloud.IPv4), + TotalIPs: "61", + UsedIPs: "2", + }, + }, +} + +// NetworkIPAvailability2 is an expected representation of a first object from the ResourceListResult. +var NetworkIPAvailability2 = networkipavailabilities.NetworkIPAvailability{ + NetworkID: "cf11ab78-2302-49fa-870f-851a08c7afb8", + NetworkName: "public", + ProjectID: "424e7cf0243c468ca61732ba45973b3e", + TenantID: "424e7cf0243c468ca61732ba45973b3e", + TotalIPs: "253", + UsedIPs: "3", + SubnetIPAvailabilities: []networkipavailabilities.SubnetIPAvailability{ + { + SubnetID: "4afe6e5f-9649-40db-b18f-64c7ead942bd", + SubnetName: "public-subnet", + CIDR: "203.0.113.0/24", + IPVersion: int(gophercloud.IPv4), + TotalIPs: "253", + UsedIPs: "3", + }, + }, +} + +// NetworkIPAvailabilityGetResult represents raw server response from a server to a get call. +const NetworkIPAvailabilityGetResult = ` +{ + "network_ip_availability": { + "network_id": "cf11ab78-2302-49fa-870f-851a08c7afb8", + "network_name": "public", + "project_id": "424e7cf0243c468ca61732ba45973b3e", + "subnet_ip_availability": [ + { + "cidr": "203.0.113.0/24", + "ip_version": 4, + "subnet_id": "4afe6e5f-9649-40db-b18f-64c7ead942bd", + "subnet_name": "public-subnet", + "total_ips": 253, + "used_ips": 3 + } + ], + "tenant_id": "424e7cf0243c468ca61732ba45973b3e", + "total_ips": 253, + "used_ips": 3 + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go new file mode 100644 index 000000000000..1b352e2a1866 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go @@ -0,0 +1,89 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud" + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/network-ip-availabilities", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, NetworkIPAvailabilityListResult) + }) + + count := 0 + + err := networkipavailabilities.List(fake.ServiceClient(), networkipavailabilities.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := networkipavailabilities.ExtractNetworkIPAvailabilities(page) + if err != nil { + t.Errorf("Failed to extract network IP availabilities: %v", err) + return false, nil + } + + expected := []networkipavailabilities.NetworkIPAvailability{ + NetworkIPAvailability1, + NetworkIPAvailability2, + } + + 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() + + th.Mux.HandleFunc("/v2.0/network-ip-availabilities/cf11ab78-2302-49fa-870f-851a08c7afb8", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, NetworkIPAvailabilityGetResult) + }) + + s, err := networkipavailabilities.Get(fake.ServiceClient(), "cf11ab78-2302-49fa-870f-851a08c7afb8").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.NetworkID, "cf11ab78-2302-49fa-870f-851a08c7afb8") + th.AssertEquals(t, s.NetworkName, "public") + th.AssertEquals(t, s.ProjectID, "424e7cf0243c468ca61732ba45973b3e") + th.AssertEquals(t, s.TenantID, "424e7cf0243c468ca61732ba45973b3e") + th.AssertEquals(t, s.TotalIPs, "253") + th.AssertEquals(t, s.UsedIPs, "3") + th.AssertDeepEquals(t, s.SubnetIPAvailabilities, []networkipavailabilities.SubnetIPAvailability{ + { + SubnetID: "4afe6e5f-9649-40db-b18f-64c7ead942bd", + SubnetName: "public-subnet", + CIDR: "203.0.113.0/24", + IPVersion: int(gophercloud.IPv4), + TotalIPs: "253", + UsedIPs: "3", + }, + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/urls.go new file mode 100644 index 000000000000..5e3228ff3a49 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/networkipavailabilities/urls.go @@ -0,0 +1,21 @@ +package networkipavailabilities + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "network-ip-availabilities" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, networkIPAvailabilityID string) string { + return c.ServiceURL(resourcePath, networkIPAvailabilityID) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, networkIPAvailabilityID string) string { + return resourceURL(c, networkIPAvailabilityID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/requests.go index 3c287463aa5b..e38000904e20 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/requests.go @@ -20,7 +20,7 @@ type CreateOptsExt struct { // A dictionary that enables the application running on the specified // host to pass and receive virtual network interface (VIF) port-specific // information to the plug-in. - Profile map[string]string `json:"binding:profile,omitempty"` + Profile map[string]interface{} `json:"binding:profile,omitempty"` } // ToPortCreateMap casts a CreateOpts struct to a map. @@ -54,7 +54,7 @@ type UpdateOptsExt struct { ports.UpdateOptsBuilder // The ID of the host where the port is allocated. - HostID string `json:"binding:host_id,omitempty"` + HostID *string `json:"binding:host_id,omitempty"` // The virtual network interface card (vNIC) type that is bound to the // neutron port. @@ -63,7 +63,7 @@ type UpdateOptsExt struct { // A dictionary that enables the application running on the specified // host to pass and receive virtual network interface (VIF) port-specific // information to the plug-in. - Profile map[string]string `json:"binding:profile,omitempty"` + Profile map[string]interface{} `json:"binding:profile,omitempty"` } // ToPortUpdateMap casts an UpdateOpts struct to a map. @@ -75,8 +75,8 @@ func (opts UpdateOptsExt) ToPortUpdateMap() (map[string]interface{}, error) { port := base["port"].(map[string]interface{}) - if opts.HostID != "" { - port["binding:host_id"] = opts.HostID + if opts.HostID != nil { + port["binding:host_id"] = *opts.HostID } if opts.VNICType != "" { @@ -84,7 +84,12 @@ func (opts UpdateOptsExt) ToPortUpdateMap() (map[string]interface{}, error) { } if opts.Profile != nil { - port["binding:profile"] = opts.Profile + if len(opts.Profile) == 0 { + // send null instead of the empty json object ("{}") + port["binding:profile"] = nil + } else { + port["binding:profile"] = opts.Profile + } } return base, nil diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/results.go index c39f7df50fcf..4d8f856a0b30 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/results.go @@ -1,11 +1,5 @@ package portsbinding -// IP is a sub-struct that represents an individual IP. -type IP struct { - SubnetID string `json:"subnet_id"` - IPAddress string `json:"ip_address"` -} - // PortsBindingExt represents a decorated form of a Port with the additional // port binding information. type PortsBindingExt struct { @@ -26,5 +20,5 @@ type PortsBindingExt struct { // A dictionary that enables the application running on the specified // host to pass and receive virtual network interface (VIF) port-specific // information to the plug-in. - Profile map[string]string `json:"binding:profile"` + Profile map[string]interface{} `json:"binding:profile"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go index 57901a6b0b55..8f3da66743bd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go @@ -156,17 +156,19 @@ func TestUpdate(t *testing.T) { portsbinding.PortsBindingExt } + name := "new_port_name" portUpdateOpts := ports.UpdateOpts{ - Name: "new_port_name", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, SecurityGroups: &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, } + hostID := "HOST1" updateOpts := portsbinding.UpdateOptsExt{ UpdateOptsBuilder: portUpdateOpts, - HostID: "HOST1", + HostID: &hostID, VNICType: "normal", } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/testing/results_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/testing/results_test.go index fa8eb8e1f369..b24210317713 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/testing/results_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/testing/results_test.go @@ -43,6 +43,9 @@ func TestList(t *testing.T) { th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", actual[1].ID) th.AssertEquals(t, "local", actual[1].NetworkType) th.AssertEquals(t, "1234567890", actual[1].SegmentationID) + th.AssertEquals(t, actual[0].Subnets[0], "54d6f61d-db07-451c-9ab3-b9609b6b6f0b") + th.AssertEquals(t, actual[1].Subnets[0], "08eae331-0402-425a-923c-34f7cfe39c1b") + } func TestGet(t *testing.T) { @@ -208,7 +211,8 @@ func TestUpdate(t *testing.T) { } iTrue := true - options := networks.UpdateOpts{Name: "new_network_name", AdminStateUp: gophercloud.Disabled, Shared: &iTrue} + name := "new_network_name" + options := networks.UpdateOpts{Name: &name, AdminStateUp: gophercloud.Disabled, Shared: &iTrue} err := networks.Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).ExtractInto(&s) th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/doc.go new file mode 100644 index 000000000000..18a3c456a9ae --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/doc.go @@ -0,0 +1,181 @@ +/* +Package policies provides information and interaction with the QoS policy extension +for the OpenStack Networking service. + +Example to Get a Port with a QoS policy + + var portWithQoS struct { + ports.Port + policies.QoSPolicyExt + } + + portID := "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2" + + err = ports.Get(client, portID).ExtractInto(&portWithQoS) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Port: %+v\n", portWithQoS) + +Example to Create a Port with a QoS policy + + var portWithQoS struct { + ports.Port + policies.QoSPolicyExt + } + + policyID := "d6ae28ce-fcb5-4180-aa62-d260a27e09ae" + networkID := "7069db8d-e817-4b39-a654-d2dd76e73d36" + + portCreateOpts := ports.CreateOpts{ + NetworkID: networkID, + } + + createOpts := policies.PortCreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + QoSPolicyID: policyID, + } + + err = ports.Create(client, createOpts).ExtractInto(&portWithQoS) + if err != nil { + panic(err) + } + + fmt.Printf("Port: %+v\n", portWithQoS) + +Example to add a QoS policy to an existing Port + + var portWithQoS struct { + ports.Port + policies.QoSPolicyExt + } + + portUpdateOpts := ports.UpdateOpts{} + + policyID := "d6ae28ce-fcb5-4180-aa62-d260a27e09ae" + + updateOpts := policies.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + QoSPolicyID: &policyID, + } + + err := ports.Update(client, "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&portWithQoS) + if err != nil { + panic(err) + } + + fmt.Printf("Port: %+v\n", portWithQoS) + +Example to delete a QoS policy from the existing Port + + var portWithQoS struct { + ports.Port + policies.QoSPolicyExt + } + + portUpdateOpts := ports.UpdateOpts{} + + policyID := "" + + updateOpts := policies.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + QoSPolicyID: &policyID, + } + + err := ports.Update(client, "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&portWithQoS) + if err != nil { + panic(err) + } + + fmt.Printf("Port: %+v\n", portWithQoS) + +Example to Get a Network with a QoS policy + + var networkWithQoS struct { + networks.Network + policies.QoSPolicyExt + } + + networkID := "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2" + + err = networks.Get(client, networkID).ExtractInto(&networkWithQoS) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Network: %+v\n", networkWithQoS) + +Example to Create a Network with a QoS policy + + var networkWithQoS struct { + networks.Network + policies.QoSPolicyExt + } + + policyID := "d6ae28ce-fcb5-4180-aa62-d260a27e09ae" + networkID := "7069db8d-e817-4b39-a654-d2dd76e73d36" + + networkCreateOpts := networks.CreateOpts{ + NetworkID: networkID, + } + + createOpts := policies.NetworkCreateOptsExt{ + CreateOptsBuilder: networkCreateOpts, + QoSPolicyID: policyID, + } + + err = networks.Create(client, createOpts).ExtractInto(&networkWithQoS) + if err != nil { + panic(err) + } + + fmt.Printf("Network: %+v\n", networkWithQoS) + +Example to add a QoS policy to an existing Network + + var networkWithQoS struct { + networks.Network + policies.QoSPolicyExt + } + + networkUpdateOpts := networks.UpdateOpts{} + + policyID := "d6ae28ce-fcb5-4180-aa62-d260a27e09ae" + + updateOpts := policies.NetworkUpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + QoSPolicyID: &policyID, + } + + err := networks.Update(client, "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&networkWithQoS) + if err != nil { + panic(err) + } + + fmt.Printf("Network: %+v\n", networkWithQoS) + +Example to delete a QoS policy from the existing Network + + var networkWithQoS struct { + networks.Network + policies.QoSPolicyExt + } + + networkUpdateOpts := networks.UpdateOpts{} + + policyID := "" + + updateOpts := policies.NetworkUpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + QoSPolicyID: &policyID, + } + + err := networks.Update(client, "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&networkWithQoS) + if err != nil { + panic(err) + } + + fmt.Printf("Network: %+v\n", networkWithQoS) +*/ +package policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/requests.go new file mode 100644 index 000000000000..20984f371748 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/requests.go @@ -0,0 +1,114 @@ +package policies + +import ( + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" +) + +// PortCreateOptsExt adds QoS options to the base ports.CreateOpts. +type PortCreateOptsExt struct { + ports.CreateOptsBuilder + + // QoSPolicyID represents an associated QoS policy. + QoSPolicyID string `json:"qos_policy_id,omitempty"` +} + +// ToPortCreateMap casts a CreateOpts struct to a map. +func (opts PortCreateOptsExt) ToPortCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToPortCreateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]interface{}) + + if opts.QoSPolicyID != "" { + port["qos_policy_id"] = opts.QoSPolicyID + } + + return base, nil +} + +// PortUpdateOptsExt adds QoS options to the base ports.UpdateOpts. +type PortUpdateOptsExt struct { + ports.UpdateOptsBuilder + + // QoSPolicyID represents an associated QoS policy. + // Setting it to a pointer of an empty string will remove associated QoS policy from port. + QoSPolicyID *string `json:"qos_policy_id,omitempty"` +} + +// ToPortUpdateMap casts a UpdateOpts struct to a map. +func (opts PortUpdateOptsExt) ToPortUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToPortUpdateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]interface{}) + + if opts.QoSPolicyID != nil { + qosPolicyID := *opts.QoSPolicyID + if qosPolicyID != "" { + port["qos_policy_id"] = qosPolicyID + } else { + port["qos_policy_id"] = nil + } + } + + return base, nil +} + +// NetworkCreateOptsExt adds QoS options to the base networks.CreateOpts. +type NetworkCreateOptsExt struct { + networks.CreateOptsBuilder + + // QoSPolicyID represents an associated QoS policy. + QoSPolicyID string `json:"qos_policy_id,omitempty"` +} + +// ToNetworkCreateMap casts a CreateOpts struct to a map. +func (opts NetworkCreateOptsExt) ToNetworkCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToNetworkCreateMap() + if err != nil { + return nil, err + } + + network := base["network"].(map[string]interface{}) + + if opts.QoSPolicyID != "" { + network["qos_policy_id"] = opts.QoSPolicyID + } + + return base, nil +} + +// NetworkUpdateOptsExt adds QoS options to the base networks.UpdateOpts. +type NetworkUpdateOptsExt struct { + networks.UpdateOptsBuilder + + // QoSPolicyID represents an associated QoS policy. + // Setting it to a pointer of an empty string will remove associated QoS policy from network. + QoSPolicyID *string `json:"qos_policy_id,omitempty"` +} + +// ToNetworkUpdateMap casts a UpdateOpts struct to a map. +func (opts NetworkUpdateOptsExt) ToNetworkUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToNetworkUpdateMap() + if err != nil { + return nil, err + } + + network := base["network"].(map[string]interface{}) + + if opts.QoSPolicyID != nil { + qosPolicyID := *opts.QoSPolicyID + if qosPolicyID != "" { + network["qos_policy_id"] = qosPolicyID + } else { + network["qos_policy_id"] = nil + } + } + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/results.go new file mode 100644 index 000000000000..d44f8a3ca6db --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/results.go @@ -0,0 +1,7 @@ +package policies + +// QoSPolicyExt represents additional resource attributes available with the QoS extension. +type QoSPolicyExt struct { + // QoSPolicyID represents an associated QoS policy. + QoSPolicyID string `json:"qos_policy_id"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/fixtures.go new file mode 100644 index 000000000000..445fce88fe15 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/fixtures.go @@ -0,0 +1,134 @@ +package testing + +const GetPortResponse = ` +{ + "port": { + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const CreatePortRequest = ` +{ + "port": { + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const CreatePortResponse = ` +{ + "port": { + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const UpdatePortWithPolicyRequest = ` +{ + "port": { + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const UpdatePortWithPolicyResponse = ` +{ + "port": { + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const UpdatePortWithoutPolicyRequest = ` +{ + "port": { + "qos_policy_id": null + } +} +` + +const UpdatePortWithoutPolicyResponse = ` +{ + "port": { + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "" + } +} +` + +const GetNetworkResponse = ` +{ + "network": { + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const CreateNetworkRequest = ` +{ + "network": { + "name": "private", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const CreateNetworkResponse = ` +{ + "network": { + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const UpdateNetworkWithPolicyRequest = ` +{ + "network": { + "name": "updated", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const UpdateNetworkWithPolicyResponse = ` +{ + "network": { + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "name": "updated", + "qos_policy_id": "591e0597-39a6-4665-8149-2111d8de9a08" + } +} +` + +const UpdateNetworkWithoutPolicyRequest = ` +{ + "network": { + "qos_policy_id": null + } +} +` + +const UpdateNetworkWithoutPolicyResponse = ` +{ + "network": { + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "qos_policy_id": "" + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go new file mode 100644 index 000000000000..9f653717586e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go @@ -0,0 +1,294 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/policies" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestGetPort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _, err := fmt.Fprintf(w, GetPortResponse) + th.AssertNoErr(t, err) + }) + + var p struct { + ports.Port + policies.QoSPolicyExt + } + err := ports.Get(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d").ExtractInto(&p) + th.AssertNoErr(t, err) + + th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, p.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") +} + +func TestCreatePort(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreatePortRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + _, err := fmt.Fprintf(w, CreatePortResponse) + th.AssertNoErr(t, err) + }) + + var p struct { + ports.Port + policies.QoSPolicyExt + } + portCreateOpts := ports.CreateOpts{ + NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7", + } + createOpts := policies.PortCreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + QoSPolicyID: "591e0597-39a6-4665-8149-2111d8de9a08", + } + err := ports.Create(fake.ServiceClient(), createOpts).ExtractInto(&p) + th.AssertNoErr(t, err) + + th.AssertEquals(t, p.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") + th.AssertEquals(t, p.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") + th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, p.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") +} + +func TestUpdatePortWithPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdatePortWithPolicyRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _, err := fmt.Fprintf(w, UpdatePortWithPolicyResponse) + th.AssertNoErr(t, err) + }) + + policyID := "591e0597-39a6-4665-8149-2111d8de9a08" + + var p struct { + ports.Port + policies.QoSPolicyExt + } + portUpdateOpts := ports.UpdateOpts{} + updateOpts := policies.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + QoSPolicyID: &policyID, + } + err := ports.Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&p) + th.AssertNoErr(t, err) + + th.AssertEquals(t, p.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") + th.AssertEquals(t, p.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") + th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, p.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") +} + +func TestUpdatePortWithoutPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdatePortWithoutPolicyRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _, err := fmt.Fprintf(w, UpdatePortWithoutPolicyResponse) + th.AssertNoErr(t, err) + }) + + policyID := "" + + var p struct { + ports.Port + policies.QoSPolicyExt + } + portUpdateOpts := ports.UpdateOpts{} + updateOpts := policies.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + QoSPolicyID: &policyID, + } + err := ports.Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&p) + th.AssertNoErr(t, err) + + th.AssertEquals(t, p.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") + th.AssertEquals(t, p.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") + th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, p.QoSPolicyID, "") +} + +func TestGetNetwork(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _, err := fmt.Fprintf(w, GetNetworkResponse) + th.AssertNoErr(t, err) + }) + + var n struct { + networks.Network + policies.QoSPolicyExt + } + err := networks.Get(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d").ExtractInto(&n) + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, n.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") +} + +func TestCreateNetwork(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreateNetworkRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + _, err := fmt.Fprintf(w, CreateNetworkResponse) + th.AssertNoErr(t, err) + }) + + var n struct { + networks.Network + policies.QoSPolicyExt + } + networkCreateOpts := networks.CreateOpts{ + Name: "private", + } + createOpts := policies.NetworkCreateOptsExt{ + CreateOptsBuilder: networkCreateOpts, + QoSPolicyID: "591e0597-39a6-4665-8149-2111d8de9a08", + } + err := networks.Create(fake.ServiceClient(), createOpts).ExtractInto(&n) + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.TenantID, "4fd44f30292945e481c7b8a0c8908869") + th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, n.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") +} + +func TestUpdateNetworkWithPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateNetworkWithPolicyRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _, err := fmt.Fprintf(w, UpdateNetworkWithPolicyResponse) + th.AssertNoErr(t, err) + }) + + policyID := "591e0597-39a6-4665-8149-2111d8de9a08" + name := "updated" + + var n struct { + networks.Network + policies.QoSPolicyExt + } + networkUpdateOpts := networks.UpdateOpts{ + Name: &name, + } + updateOpts := policies.NetworkUpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + QoSPolicyID: &policyID, + } + err := networks.Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&n) + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.TenantID, "4fd44f30292945e481c7b8a0c8908869") + th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, n.Name, "updated") + th.AssertEquals(t, n.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") +} + +func TestUpdateNetworkWithoutPolicy(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateNetworkWithoutPolicyRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _, err := fmt.Fprintf(w, UpdateNetworkWithoutPolicyResponse) + th.AssertNoErr(t, err) + }) + + policyID := "" + + var n struct { + networks.Network + policies.QoSPolicyExt + } + networkUpdateOpts := networks.UpdateOpts{} + updateOpts := policies.NetworkUpdateOptsExt{ + UpdateOptsBuilder: networkUpdateOpts, + QoSPolicyID: &policyID, + } + err := networks.Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&n) + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.TenantID, "4fd44f30292945e481c7b8a0c8908869") + th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + th.AssertEquals(t, n.QoSPolicyID, "") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/doc.go new file mode 100644 index 000000000000..5cfc6884c1d4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/doc.go @@ -0,0 +1,19 @@ +/* +Package ruletypes contains functionality for working with Neutron 'quality of service' rule-type resources. + +Example: You can list rule-types in the following way: + + page, err := ruletypes.ListRuleTypes(client).AllPages() + if err != nil { + return + } + + rules, err := ruletypes.ExtractRuleTypes(page) + if err != nil { + return + } + + fmt.Printf("%v <- Rule Types\n", rules) + +*/ +package ruletypes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/requests.go new file mode 100644 index 000000000000..e193cea5a262 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/requests.go @@ -0,0 +1,13 @@ +package ruletypes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListRuleTypes returns the list of rule types from the server +func ListRuleTypes(c *gophercloud.ServiceClient) (result pagination.Pager) { + return pagination.NewPager(c, listRuleTypesURL(c), func(r pagination.PageResult) pagination.Page { + return ListRuleTypesPage{pagination.SinglePageBase(r)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/results.go new file mode 100644 index 000000000000..f62818819ead --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/results.go @@ -0,0 +1,26 @@ +package ruletypes + +import "github.com/gophercloud/gophercloud/pagination" + +// The result of listing the qos rule types +type RuleType struct { + Type string `json:"type"` +} + +type ListRuleTypesPage struct { + pagination.SinglePageBase +} + +func (r ListRuleTypesPage) IsEmpty() (bool, error) { + v, err := ExtractRuleTypes(r) + return len(v) == 0, err +} + +func ExtractRuleTypes(r pagination.Page) ([]RuleType, error) { + var s struct { + RuleTypes []RuleType `json:"rule_types"` + } + + err := (r.(ListRuleTypesPage)).ExtractInto(&s) + return s.RuleTypes, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/doc.go new file mode 100644 index 000000000000..1e9c71e8fb77 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/doc.go @@ -0,0 +1,2 @@ +// qos unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/fixtures.go new file mode 100644 index 000000000000..63556695d6a4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/fixtures.go @@ -0,0 +1,19 @@ +package testing + +const ( + ListRuleTypesResponse = ` +{ + "rule_types": [ + { + "type": "bandwidth_limit" + }, + { + "type": "dscp_marking" + }, + { + "type": "minimum_bandwidth" + } + ] +} +` +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go new file mode 100644 index 000000000000..79559efe1ab5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go @@ -0,0 +1,41 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListRuleTypes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ListRuleTypesResponse) + }) + + page, err := ruletypes.ListRuleTypes(fake.ServiceClient()).AllPages() + if err != nil { + t.Errorf("Failed to list rule types pages: %v", err) + return + } + + rules, err := ruletypes.ExtractRuleTypes(page) + if err != nil { + t.Errorf("Failed to list rule types: %v", err) + return + } + + expected := []ruletypes.RuleType{{Type: "bandwidth_limit"}, {Type: "dscp_marking"}, {Type: "minimum_bandwidth"}} + th.AssertDeepEquals(t, expected, rules) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/urls.go new file mode 100644 index 000000000000..15795100c2cb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/qos/ruletypes/urls.go @@ -0,0 +1,7 @@ +package ruletypes + +import "github.com/gophercloud/gophercloud" + +func listRuleTypesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("qos", "rule-types") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/requests.go index 532a2f23d2c2..e388e1a168ca 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/requests.go @@ -27,6 +27,10 @@ type ListOpts struct { 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"` } // ToRBACPolicyListQuery formats a ListOpts into a query string. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/results.go index a62facc0dc7d..1327b17b990a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/rbacpolicies/results.go @@ -69,6 +69,9 @@ type RBACPolicy struct { // ProjectID is the ID of the project. ProjectID string `json:"project_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // RBACPolicyPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go index ebacc6ee3451..a22cd306e8ab 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -11,14 +11,19 @@ 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"` - 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"` + 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"` } // List returns a Pager which allows you to iterate over a collection of @@ -88,7 +93,7 @@ type UpdateOpts struct { Name string `json:"name,omitempty"` // Describes the security group. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` } // ToSecGroupUpdateMap builds a request body from UpdateOpts. @@ -128,7 +133,12 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, ListOpts{}).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go index 66915e6e559e..468952b3e4e5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go @@ -27,6 +27,9 @@ type SecGroup struct { // ProjectID is the project owner of the security group. ProjectID string `json:"project_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // SecGroupPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go index 96cce2817da2..c7741ffcd2c3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -14,6 +14,7 @@ type ListOpts struct { Direction string `q:"direction"` EtherType string `q:"ethertype"` ID string `q:"id"` + Description string `q:"description"` PortRangeMax int `q:"port_range_max"` PortRangeMin int `q:"port_range_min"` Protocol string `q:"protocol"` @@ -88,6 +89,9 @@ type CreateOpts struct { // group rule is applied. Direction RuleDirection `json:"direction" required:"true"` + // String description of each rule, optional + Description string `json:"description,omitempty"` + // Must be "IPv4" or "IPv6", and addresses represented in CIDR must match the // ingress or egress rules. EtherType RuleEtherType `json:"ethertype" required:"true"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go index 377e753140c4..3bf5501d9229 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go @@ -17,6 +17,9 @@ type SecGroupRule struct { // instance. An egress rule is applied to traffic leaving the instance. Direction string + // Descripton of the rule + Description string `json:"description"` + // Must be IPv4 or IPv6, and addresses represented in CIDR must match the // ingress or egress rules. EtherType string `json:"ethertype"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/testing/requests_test.go index 968fd04d8fdc..8d0edabb8107 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/testing/requests_test.go @@ -66,6 +66,7 @@ func TestList(t *testing.T) { expected := []rules.SecGroupRule{ { + Description: "", Direction: "egress", EtherType: "IPv6", ID: "3c0e45ff-adaf-4124-b083-bf390e5482ff", @@ -113,6 +114,7 @@ func TestCreate(t *testing.T) { th.TestJSONRequest(t, r, ` { "security_group_rule": { + "description": "test description of rule", "direction": "ingress", "port_range_min": 80, "ethertype": "IPv4", @@ -130,6 +132,7 @@ func TestCreate(t *testing.T) { fmt.Fprintf(w, ` { "security_group_rule": { + "description": "test description of rule", "direction": "ingress", "ethertype": "IPv4", "id": "2bc0accf-312e-429a-956e-e4407625eb62", @@ -146,6 +149,7 @@ func TestCreate(t *testing.T) { }) opts := rules.CreateOpts{ + Description: "test description of rule", Direction: "ingress", PortRangeMin: 80, EtherType: rules.EtherType4, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go index e7ee96aef64e..c54813b85d21 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go @@ -36,6 +36,10 @@ type ListOpts struct { 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"` } // ToSubnetPoolListQuery formats a ListOpts into a query string. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go index e761eac44d13..e97e1e60aea0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go @@ -111,6 +111,9 @@ type SubnetPool struct { // RevisionNumber is the revision number of the subnetpool. RevisionNumber int `json:"revision_number"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } func (r *SubnetPool) UnmarshalJSON(b []byte) error { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go new file mode 100644 index 000000000000..82496ffd0b24 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go @@ -0,0 +1,143 @@ +/* +Package trunks provides the ability to retrieve and manage trunks through the Neutron API. +Trunks allow you to multiplex multiple ports traffic on a single port. For example, you could +have a compute instance port be the parent port of a trunk and inside the VM run workloads +using other ports, without the need of plugging those ports. + +Example of a new empty Trunk creation + + iTrue := true + createOpts := trunks.CreateOpts{ + Name: "gophertrunk", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + PortID: "a6f0560c-b7a8-401f-bf6e-d0a5c851ae10", + } + + trunk, err := trunks.Create(networkClient, trunkOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of a new Trunk creation with 2 subports + + iTrue := true + createOpts := trunks.CreateOpts{ + Name: "gophertrunk", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + PortID: "a6f0560c-b7a8-401f-bf6e-d0a5c851ae10", + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: "bf4efcc0-b1c7-4674-81f0-31f58a33420a", + }, + { + SegmentationID: 10, + SegmentationType: "vlan", + PortID: "2cf671b9-02b3-4121-9e85-e0af3548d112", + }, + }, + } + + trunk, err := trunks.Create(client, trunkOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of deleting a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + err := trunks.Delete(networkClient, trunkID).ExtractErr() + if err != nil { + panic(err) + } + +Example of listing Trunks + + listOpts := trunks.ListOpts{} + allPages, err := trunks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + allTrunks, err := trunks.ExtractTrunks(allPages) + if err != nil { + panic(err) + } + for _, trunk := range allTrunks { + fmt.Printf("%+v\n", trunk) + } + +Example of getting a Trunk + + trunkID = "52d8d124-3dc9-4563-9fef-bad3187ecf2d" + trunk, err := trunks.Get(networkClient, trunkID).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of updating a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + subports, err := trunks.GetSubports(client, trunkID).Extract() + iFalse := false + updateOpts := trunks.UpdateOpts{ + AdminStateUp: &iFalse, + Name: "updated_gophertrunk", + Description: "trunk updated by gophercloud", + } + trunk, err = trunks.Update(networkClient, trunkID, updateOpts).Extract() + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v\n", trunk) + +Example of showing subports of a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + subports, err := trunks.GetSubports(client, trunkID).Extract() + fmt.Printf("%+v\n", subports) + +Example of adding two subports to a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + addSubportsOpts := trunks.AddSubportsOpts{ + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: "bf4efcc0-b1c7-4674-81f0-31f58a33420a", + }, + { + SegmentationID: 10, + SegmentationType: "vlan", + PortID: "2cf671b9-02b3-4121-9e85-e0af3548d112", + }, + }, + } + trunk, err := trunks.AddSubports(client, trunkID, addSubportsOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of deleting two subports from a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + removeSubportsOpts := trunks.RemoveSubportsOpts{ + Subports: []trunks.RemoveSubport{ + {PortID: "bf4efcc0-b1c7-4674-81f0-31f58a33420a"}, + {PortID: "2cf671b9-02b3-4121-9e85-e0af3548d112"}, + }, + } + trunk, err := trunks.RemoveSubports(networkClient, trunkID, removeSubportsOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) +*/ +package trunks diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go new file mode 100644 index 000000000000..447a0d4113ca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go @@ -0,0 +1,195 @@ +package trunks + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToTrunkCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents the attributes used when creating a new trunk. +type CreateOpts struct { + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` + PortID string `json:"port_id" required:"true"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Subports []Subport `json:"sub_ports"` +} + +// ToTrunkCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToTrunkCreateMap() (map[string]interface{}, error) { + if opts.Subports == nil { + opts.Subports = []Subport{} + } + return gophercloud.BuildRequestBody(opts, "trunk") +} + +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + body, err := opts.ToTrunkCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = c.Post(createURL(c), body, &r.Body, nil) + return +} + +// Delete accepts a unique ID and deletes the trunk associated with it. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToTrunkListQuery() (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 trunk attributes you want to see returned. SortKey allows you to sort +// 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"` + 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. +func (opts ListOpts) ToTrunkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// trunks. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those trunks that are owned by the tenant +// who submits the request, unless the request is submitted by a user with +// administrative rights. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToTrunkListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return TrunkPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific trunk based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} + +type UpdateOptsBuilder interface { + ToTrunkUpdateMap() (map[string]interface{}, error) +} + +type UpdateOpts struct { + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +func (opts UpdateOpts) ToTrunkUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "trunk") +} + +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + body, err := opts.ToTrunkUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +func GetSubports(c *gophercloud.ServiceClient, id string) (r GetSubportsResult) { + _, r.Err = c.Get(getSubportsURL(c, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +type AddSubportsOpts struct { + Subports []Subport `json:"sub_ports" required:"true"` +} + +type AddSubportsOptsBuilder interface { + ToTrunkAddSubportsMap() (map[string]interface{}, error) +} + +func (opts AddSubportsOpts) ToTrunkAddSubportsMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +func AddSubports(c *gophercloud.ServiceClient, id string, opts AddSubportsOptsBuilder) (r UpdateSubportsResult) { + body, err := opts.ToTrunkAddSubportsMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(addSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +type RemoveSubport struct { + PortID string `json:"port_id" required:"true"` +} + +type RemoveSubportsOpts struct { + Subports []RemoveSubport `json:"sub_ports"` +} + +type RemoveSubportsOptsBuilder interface { + ToTrunkRemoveSubportsMap() (map[string]interface{}, error) +} + +func (opts RemoveSubportsOpts) ToTrunkRemoveSubportsMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +func RemoveSubports(c *gophercloud.ServiceClient, id string, opts RemoveSubportsOptsBuilder) (r UpdateSubportsResult) { + body, err := opts.ToTrunkRemoveSubportsMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(removeSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go new file mode 100644 index 000000000000..6d979ef7a2df --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go @@ -0,0 +1,137 @@ +package trunks + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Subport struct { + SegmentationID int `json:"segmentation_id" required:"true"` + SegmentationType string `json:"segmentation_type" required:"true"` + PortID string `json:"port_id" required:"true"` +} + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Trunk. +type CreateResult struct { + commonResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Trunk. +type GetResult struct { + commonResult +} + +// UpdateResult is the result of an Update request. Call its Extract method to +// interpret it as a Trunk. +type UpdateResult struct { + commonResult +} + +// GetSubportsResult is the result of a Get request on the trunks subports +// resource. Call its Extract method to interpret it as a slice of Subport. +type GetSubportsResult struct { + commonResult +} + +// UpdateSubportsResult is the result of either an AddSubports or a RemoveSubports +// request. Call its Extract method to interpret it as a Trunk. +type UpdateSubportsResult struct { + commonResult +} + +type Trunk struct { + // Indicates whether the trunk is currently operational. Possible values include + // `ACTIVE', `DOWN', `BUILD', 'DEGRADED' or `ERROR'. + Status string `json:"status"` + + // A list of ports associated with the trunk + Subports []Subport `json:"sub_ports"` + + // Human-readable name for the trunk. Might not be unique. + Name string `json:"name,omitempty"` + + // The administrative state of the trunk. If false (down), the trunk does not + // forward packets. + AdminStateUp bool `json:"admin_state_up,omitempty"` + + // ProjectID is the project owner of the trunk. + ProjectID string `json:"project_id"` + + // TenantID is the project owner of the trunk. + TenantID string `json:"tenant_id"` + + // The date and time when the resource was created. + CreatedAt time.Time `json:"created_at"` + + // The date and time when the resource was updated, + // if the resource has not been updated, this field will show as null. + UpdatedAt time.Time `json:"updated_at"` + + RevisionNumber int `json:"revision_number"` + + // UUID of the trunk's parent port + PortID string `json:"port_id"` + + // UUID for the trunk resource + ID string `json:"id"` + + // Display description. + Description string `json:"description"` + + // A list of tags associated with the trunk + Tags []string `json:"tags,omitempty"` +} + +func (r commonResult) Extract() (*Trunk, error) { + var s struct { + Trunk *Trunk `json:"trunk"` + } + err := r.ExtractInto(&s) + return s.Trunk, err +} + +// TrunkPage is the page returned by a pager when traversing a collection of +// trunk resources. +type TrunkPage struct { + pagination.LinkedPageBase +} + +func (page TrunkPage) IsEmpty() (bool, error) { + trunks, err := ExtractTrunks(page) + return len(trunks) == 0, err +} + +func ExtractTrunks(page pagination.Page) ([]Trunk, error) { + var a struct { + Trunks []Trunk `json:"trunks"` + } + err := (page.(TrunkPage)).ExtractInto(&a) + return a.Trunks, err +} + +func (r GetSubportsResult) Extract() ([]Subport, error) { + var s struct { + Subports []Subport `json:"sub_ports"` + } + err := r.ExtractInto(&s) + return s.Subports, err +} + +func (r UpdateSubportsResult) Extract() (t *Trunk, err error) { + err = r.ExtractInto(&t) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/doc.go new file mode 100644 index 000000000000..11dd613171f3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/doc.go @@ -0,0 +1,2 @@ +// trunks unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/fixtures.go new file mode 100644 index 000000000000..b84c5a573678 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/fixtures.go @@ -0,0 +1,384 @@ +package testing + +import ( + "time" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" +) + +const CreateRequest = ` +{ + "trunk": { + "admin_state_up": true, + "description": "Trunk created by gophercloud", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ] + } +}` + +const CreateResponse = ` +{ + "trunk": { + "admin_state_up": true, + "created_at": "2018-10-03T13:57:24Z", + "description": "Trunk created by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 1, + "status": "ACTIVE", + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:26Z" + } +}` + +const CreateNoSubportsRequest = ` +{ + "trunk": { + "admin_state_up": true, + "description": "Trunk created by gophercloud", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "sub_ports": [] + } +}` + +const CreateNoSubportsResponse = ` +{ + "trunk": { + "admin_state_up": true, + "created_at": "2018-10-03T13:57:24Z", + "description": "Trunk created by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 1, + "status": "ACTIVE", + "sub_ports": [], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:26Z" + } +}` + +const ListResponse = ` +{ + "trunks": [ + { + "admin_state_up": true, + "created_at": "2018-10-01T15:29:39Z", + "description": "", + "id": "3e72aa1b-d0da-48f2-831a-fd1c5f3f99c2", + "name": "mytrunk", + "port_id": "16c425d3-d7fc-40b8-b94c-cc95da45b270", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 3, + "status": "ACTIVE", + "sub_ports": [ + { + "port_id": "424da4b7-7868-4db2-bb71-05155601c6e4", + "segmentation_id": 11, + "segmentation_type": "vlan" + } + ], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-01T15:43:04Z" + }, + { + "admin_state_up": true, + "created_at": "2018-10-03T13:57:24Z", + "description": "Trunk created by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 1, + "status": "ACTIVE", + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:26Z" + } + ] +}` + +const GetResponse = ` +{ + "trunk": { + "admin_state_up": true, + "created_at": "2018-10-03T13:57:24Z", + "description": "Trunk created by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 1, + "status": "ACTIVE", + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:26Z" + } +}` + +const UpdateRequest = ` +{ + "trunk": { + "admin_state_up": false, + "description": "gophertrunk updated by gophercloud", + "name": "updated_gophertrunk" + } +}` + +const UpdateResponse = ` +{ + "trunk": { + "admin_state_up": false, + "created_at": "2018-10-03T13:57:24Z", + "description": "gophertrunk updated by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "updated_gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 6, + "status": "ACTIVE", + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:33Z" + } +}` + +const ListSubportsResponse = ` +{ + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ] +}` + +const AddSubportsRequest = ListSubportsResponse + +const AddSubportsResponse = ` +{ + "admin_state_up": true, + "created_at": "2018-10-03T13:57:24Z", + "description": "Trunk created by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 2, + "status": "ACTIVE", + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + "segmentation_id": 1, + "segmentation_type": "vlan" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + "segmentation_id": 2, + "segmentation_type": "vlan" + } + ], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:30Z" +}` + +const RemoveSubportsRequest = ` +{ + "sub_ports": [ + { + "port_id": "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b" + }, + { + "port_id": "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab" + } + ] +}` + +const RemoveSubportsResponse = ` +{ + "admin_state_up": true, + "created_at": "2018-10-03T13:57:24Z", + "description": "Trunk created by gophercloud", + "id": "f6a9718c-5a64-43e3-944f-4deccad8e78c", + "name": "gophertrunk", + "port_id": "c373d2fa-3d3b-4492-924c-aff54dea19b6", + "project_id": "e153f3f9082240a5974f667cfe1036e3", + "revision_number": 2, + "status": "ACTIVE", + "sub_ports": [], + "tags": [], + "tenant_id": "e153f3f9082240a5974f667cfe1036e3", + "updated_at": "2018-10-03T13:57:27Z" +}` + +var ExpectedSubports = []trunks.Subport{ + { + PortID: "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + SegmentationID: 1, + SegmentationType: "vlan", + }, + { + PortID: "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + SegmentationID: 2, + SegmentationType: "vlan", + }, +} + +func ExpectedTrunkSlice() (exp []trunks.Trunk, err error) { + trunk1CreatedAt, err := time.Parse(time.RFC3339, "2018-10-01T15:29:39Z") + if err != nil { + return nil, err + } + + trunk1UpdatedAt, err := time.Parse(time.RFC3339, "2018-10-01T15:43:04Z") + if err != nil { + return nil, err + } + exp = make([]trunks.Trunk, 2) + exp[0] = trunks.Trunk{ + AdminStateUp: true, + Description: "", + ID: "3e72aa1b-d0da-48f2-831a-fd1c5f3f99c2", + Name: "mytrunk", + PortID: "16c425d3-d7fc-40b8-b94c-cc95da45b270", + ProjectID: "e153f3f9082240a5974f667cfe1036e3", + TenantID: "e153f3f9082240a5974f667cfe1036e3", + RevisionNumber: 3, + Status: "ACTIVE", + Subports: []trunks.Subport{ + { + PortID: "424da4b7-7868-4db2-bb71-05155601c6e4", + SegmentationID: 11, + SegmentationType: "vlan", + }, + }, + Tags: []string{}, + CreatedAt: trunk1CreatedAt, + UpdatedAt: trunk1UpdatedAt, + } + + trunk2CreatedAt, err := time.Parse(time.RFC3339, "2018-10-03T13:57:24Z") + if err != nil { + return nil, err + } + + trunk2UpdatedAt, err := time.Parse(time.RFC3339, "2018-10-03T13:57:26Z") + if err != nil { + return nil, err + } + exp[1] = trunks.Trunk{ + AdminStateUp: true, + Description: "Trunk created by gophercloud", + ID: "f6a9718c-5a64-43e3-944f-4deccad8e78c", + Name: "gophertrunk", + PortID: "c373d2fa-3d3b-4492-924c-aff54dea19b6", + ProjectID: "e153f3f9082240a5974f667cfe1036e3", + TenantID: "e153f3f9082240a5974f667cfe1036e3", + RevisionNumber: 1, + Status: "ACTIVE", + Subports: ExpectedSubports, + Tags: []string{}, + CreatedAt: trunk2CreatedAt, + UpdatedAt: trunk2UpdatedAt, + } + return +} + +func ExpectedSubportsAddedTrunk() (exp trunks.Trunk, err error) { + trunkUpdatedAt, err := time.Parse(time.RFC3339, "2018-10-03T13:57:30Z") + expectedTrunks, err := ExpectedTrunkSlice() + if err != nil { + return + } + exp = expectedTrunks[1] + exp.RevisionNumber += 1 + exp.UpdatedAt = trunkUpdatedAt + return +} + +func ExpectedSubportsRemovedTrunk() (exp trunks.Trunk, err error) { + trunkUpdatedAt, err := time.Parse(time.RFC3339, "2018-10-03T13:57:27Z") + expectedTrunks, err := ExpectedTrunkSlice() + if err != nil { + return + } + exp = expectedTrunks[1] + exp.RevisionNumber += 1 + exp.UpdatedAt = trunkUpdatedAt + exp.Subports = []trunks.Subport{} + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/requests_test.go new file mode 100644 index 000000000000..8f2b37785291 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/testing/requests_test.go @@ -0,0 +1,305 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreateRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, CreateResponse) + }) + + iTrue := true + options := trunks.CreateOpts{ + Name: "gophertrunk", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + }, + { + SegmentationID: 2, + SegmentationType: "vlan", + PortID: "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + }, + }, + } + _, err := trunks.Create(fake.ServiceClient(), options).Extract() + if err == nil { + t.Fatalf("Failed to detect missing parent PortID field") + } + options.PortID = "c373d2fa-3d3b-4492-924c-aff54dea19b6" + n, err := trunks.Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.Status, "ACTIVE") + expectedTrunks, err := ExpectedTrunkSlice() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, &expectedTrunks[1], n) +} + +func TestCreateNoSubports(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreateNoSubportsRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, CreateNoSubportsResponse) + }) + + iTrue := true + options := trunks.CreateOpts{ + Name: "gophertrunk", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + PortID: "c373d2fa-3d3b-4492-924c-aff54dea19b6", + } + n, err := trunks.Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.Status, "ACTIVE") + th.AssertEquals(t, 0, len(n.Subports)) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := trunks.Delete(fake.ServiceClient(), "f6a9718c-5a64-43e3-944f-4deccad8e78c") + th.AssertNoErr(t, res.Err) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ListResponse) + }) + + client := fake.ServiceClient() + count := 0 + + trunks.List(client, trunks.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + count++ + actual, err := trunks.ExtractTrunks(page) + if err != nil { + t.Errorf("Failed to extract trunks: %v", err) + return false, err + } + + expected, err := ExpectedTrunkSlice() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, GetResponse) + }) + + n, err := trunks.Get(fake.ServiceClient(), "f6a9718c-5a64-43e3-944f-4deccad8e78c").Extract() + th.AssertNoErr(t, err) + expectedTrunks, err := ExpectedTrunkSlice() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expectedTrunks[1], n) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, UpdateResponse) + }) + + iFalse := false + name := "updated_gophertrunk" + description := "gophertrunk updated by gophercloud" + options := trunks.UpdateOpts{ + Name: &name, + AdminStateUp: &iFalse, + Description: &description, + } + n, err := trunks.Update(fake.ServiceClient(), "f6a9718c-5a64-43e3-944f-4deccad8e78c", options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.Name, name) + th.AssertEquals(t, n.AdminStateUp, iFalse) + th.AssertEquals(t, n.Description, description) +} + +func TestGetSubports(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/get_subports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ListSubportsResponse) + }) + + client := fake.ServiceClient() + + subports, err := trunks.GetSubports(client, "f6a9718c-5a64-43e3-944f-4deccad8e78c").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedSubports, subports) +} + +func TestMissingFields(t *testing.T) { + iTrue := true + opts := trunks.CreateOpts{ + Name: "gophertrunk", + PortID: "c373d2fa-3d3b-4492-924c-aff54dea19b6", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b", + }, + { + SegmentationID: 2, + SegmentationType: "vlan", + PortID: "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + }, + { + PortID: "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab", + }, + }, + } + + _, err := opts.ToTrunkCreateMap() + if err == nil { + t.Fatalf("Failed to detect missing subport fields") + } +} + +func TestAddSubports(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/add_subports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddSubportsRequest) + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, AddSubportsResponse) + }) + + client := fake.ServiceClient() + + opts := trunks.AddSubportsOpts{ + Subports: ExpectedSubports, + } + + trunk, err := trunks.AddSubports(client, "f6a9718c-5a64-43e3-944f-4deccad8e78c", opts).Extract() + th.AssertNoErr(t, err) + expectedTrunk, err := ExpectedSubportsAddedTrunk() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expectedTrunk, trunk) +} + +func TestRemoveSubports(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/remove_subports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, RemoveSubportsRequest) + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, RemoveSubportsResponse) + }) + + client := fake.ServiceClient() + + opts := trunks.RemoveSubportsOpts{ + Subports: []trunks.RemoveSubport{ + {PortID: "28e452d7-4f8a-4be4-b1e6-7f3db4c0430b"}, + {PortID: "4c8b2bff-9824-4d4c-9b60-b3f6621b2bab"}, + }, + } + trunk, err := trunks.RemoveSubports(client, "f6a9718c-5a64-43e3-944f-4deccad8e78c", opts).Extract() + + th.AssertNoErr(t, err) + expectedTrunk, err := ExpectedSubportsRemovedTrunk() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expectedTrunk, trunk) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go new file mode 100644 index 000000000000..ac7dff09610a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go @@ -0,0 +1,45 @@ +package trunks + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "trunks" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func createURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func getSubportsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "get_subports") +} + +func addSubportsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "add_subports") +} + +func removeSubportsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "remove_subports") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go new file mode 100644 index 000000000000..a309a9667b21 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go @@ -0,0 +1,97 @@ +/* +Package vlantransparent provides the ability to retrieve and manage networks +with the vlan-transparent extension through the Neutron API. + +Example of Listing Networks with the vlan-transparent extension + + iTrue := true + networkListOpts := networks.ListOpts{} + listOpts := vlantransparent.ListOptsExt{ + ListOptsBuilder: networkListOpts, + VLANTransparent: &iTrue, + } + + type NetworkWithVLANTransparentExt struct { + networks.Network + vlantransparent.NetworkVLANTransparentExt + } + + var allNetworks []NetworkWithVLANTransparentExt + + allPages, err := networks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + err = networks.ExtractNetworksInto(allPages, &allNetworks) + if err != nil { + panic(err) + } + + for _, network := range allNetworks { + fmt.Println("%+v\n", network) + } + +Example of Getting a Network with the vlan-transparent extension + + var network struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Get(networkClient, "db193ab3-96e3-4cb3-8fc5-05f4296d0324").ExtractInto(&network) + if err != nil { + panic(err) + } + + fmt.Println("%+v\n", network) + +Example of Creating Network with the vlan-transparent extension + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "private", + } + + createOpts := vlantransparent.CreateOptsExt{ + CreateOptsBuilder: &networkCreateOpts, + VLANTransparent: &iTrue, + } + + var network struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Create(networkClient, createOpts).ExtractInto(&network) + if err != nil { + panic(err) + } + + fmt.Println("%+v\n", network) + +Example of Updating Network with the vlan-transparent extension + + iFalse := false + networkUpdateOpts := networks.UpdateOpts{ + Name: "new_network_name", + } + + updateOpts := vlantransparent.UpdateOptsExt{ + UpdateOptsBuilder: &networkUpdateOpts, + VLANTransparent: &iFalse, + } + + var network struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Update(networkClient, updateOpts).ExtractInto(&network) + if err != nil { + panic(err) + } + + fmt.Println("%+v\n", network) +*/ +package vlantransparent diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go new file mode 100644 index 000000000000..65504cf3ea85 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go @@ -0,0 +1,84 @@ +package vlantransparent + +import ( + "net/url" + "strconv" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" +) + +// ListOptsExt adds the vlan-transparent network options to the base ListOpts. +type ListOptsExt struct { + networks.ListOptsBuilder + VLANTransparent *bool `q:"vlan_transparent"` +} + +// ToNetworkListQuery adds the vlan_transparent option to the base network +// list options. +func (opts ListOptsExt) ToNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder) + if err != nil { + return "", err + } + + params := q.Query() + if opts.VLANTransparent != nil { + v := strconv.FormatBool(*opts.VLANTransparent) + params.Add("vlan_transparent", v) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// CreateOptsExt is the structure used when creating new vlan-transparent +// network resources. It embeds networks.CreateOpts and so inherits all of its +// required and optional fields, with the addition of the VLANTransparent field. +type CreateOptsExt struct { + networks.CreateOptsBuilder + VLANTransparent *bool `json:"vlan_transparent,omitempty"` +} + +// ToNetworkCreateMap adds the vlan_transparent option to the base network +// creation options. +func (opts CreateOptsExt) ToNetworkCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToNetworkCreateMap() + if err != nil { + return nil, err + } + + if opts.VLANTransparent == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["vlan_transparent"] = opts.VLANTransparent + + return base, nil +} + +// UpdateOptsExt is the structure used when updating existing vlan-transparent +// network resources. It embeds networks.UpdateOpts and so inherits all of its +// required and optional fields, with the addition of the VLANTransparent field. +type UpdateOptsExt struct { + networks.UpdateOptsBuilder + VLANTransparent *bool `json:"vlan_transparent,omitempty"` +} + +// ToNetworkUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOptsExt) ToNetworkUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToNetworkUpdateMap() + if err != nil { + return nil, err + } + + if opts.VLANTransparent == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["vlan_transparent"] = opts.VLANTransparent + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go new file mode 100644 index 000000000000..62eae2091a58 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go @@ -0,0 +1,8 @@ +package vlantransparent + +// TransparentExt represents a decorated form of a network with +// "vlan-transparent" extension attributes. +type TransparentExt struct { + // VLANTransparent whether the network is a VLAN transparent network or not. + VLANTransparent bool `json:"vlan_transparent"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/doc.go new file mode 100644 index 000000000000..edc6f82230be --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/doc.go @@ -0,0 +1,2 @@ +// vlantransparent extension unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/fixtures.go new file mode 100644 index 000000000000..558e1377a8da --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/fixtures.go @@ -0,0 +1,133 @@ +package testing + +// NetworksVLANTransparentListResult represents raw HTTP response for the List +// request. +const NetworksVLANTransparentListResult = ` +{ + "networks": [ + { + "status": "ACTIVE", + "subnets": [ + "08eae331-0402-425a-923c-34f7cfe39c1b" + ], + "name": "private", + "admin_state_up": true, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": 1234567890, + "provider:physical_network": null, + "provider:network_type": "local", + "router:external": false, + "port_security_enabled": false, + "vlan_transparent": true + }, + { + "status": "ACTIVE", + "subnets": [ + "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + ], + "name": "public", + "admin_state_up": true, + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "shared": true, + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "provider:segmentation_id": 9876543210, + "provider:physical_network": null, + "provider:network_type": "local", + "router:external": true, + "port_security_enabled": true + } + ] +}` + +// NetworksVLANTransparentGetResult represents raw HTTP response for the Get +// request. +const NetworksVLANTransparentGetResult = ` +{ + "network": { + "status": "ACTIVE", + "subnets": [ + "08eae331-0402-425a-923c-34f7cfe39c1b" + ], + "name": "private", + "admin_state_up": true, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": 1234567890, + "provider:physical_network": null, + "provider:network_type": "local", + "router:external": false, + "port_security_enabled": false, + "vlan_transparent": true + } +}` + +// NetworksVLANTransparentCreateRequest represents raw HTTP Create request. +const NetworksVLANTransparentCreateRequest = ` +{ + "network": { + "name": "private", + "admin_state_up": true, + "vlan_transparent": true + } +}` + +// NetworksVLANTransparentCreateResult represents raw HTTP response for the +// Create request. +const NetworksVLANTransparentCreateResult = ` +{ + "network": { + "status": "ACTIVE", + "subnets": [ + "08eae331-0402-425a-923c-34f7cfe39c1b" + ], + "name": "private", + "admin_state_up": true, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": 1234567890, + "provider:physical_network": null, + "provider:network_type": "local", + "router:external": false, + "port_security_enabled": false, + "vlan_transparent": true + } +} +` + +// NetworksVLANTransparentUpdateRequest represents raw HTTP Update request. +const NetworksVLANTransparentUpdateRequest = ` +{ + "network": { + "name": "new_network_name", + "admin_state_up": false, + "vlan_transparent": false + } +}` + +// NetworksVLANTransparentUpdateResult represents raw HTTP response for the +// Update request. +const NetworksVLANTransparentUpdateResult = ` +{ + "network": { + "status": "ACTIVE", + "subnets": [ + "08eae331-0402-425a-923c-34f7cfe39c1b" + ], + "name": "new_network_name", + "admin_state_up": false, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "shared": false, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": 1234567890, + "provider:physical_network": null, + "provider:network_type": "local", + "router:external": false, + "port_security_enabled": false, + "vlan_transparent": false + } +} +` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go new file mode 100644 index 000000000000..0632a6c9e945 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go @@ -0,0 +1,172 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, NetworksVLANTransparentListResult) + }) + + type networkVLANTransparentExt struct { + networks.Network + vlantransparent.TransparentExt + } + var actual []networkVLANTransparentExt + + allPages, err := networks.List(fake.ServiceClient(), networks.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + + err = networks.ExtractNetworksInto(allPages, &actual) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", actual[0].ID) + th.AssertEquals(t, "private", actual[0].Name) + th.AssertEquals(t, true, actual[0].AdminStateUp) + th.AssertEquals(t, "ACTIVE", actual[0].Status) + th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, actual[0].Subnets) + th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", actual[0].TenantID) + th.AssertEquals(t, false, actual[0].Shared) + th.AssertEquals(t, true, actual[0].VLANTransparent) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/db193ab3-96e3-4cb3-8fc5-05f4296d0324", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, NetworksVLANTransparentGetResult) + }) + + var s struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Get(fake.ServiceClient(), "db193ab3-96e3-4cb3-8fc5-05f4296d0324").ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) + th.AssertEquals(t, "private", s.Name) + th.AssertEquals(t, true, s.AdminStateUp) + th.AssertEquals(t, "ACTIVE", s.Status) + th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, s.Subnets) + th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", s.TenantID) + th.AssertEquals(t, false, s.Shared) + th.AssertEquals(t, true, s.VLANTransparent) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, NetworksVLANTransparentCreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, NetworksVLANTransparentCreateResult) + }) + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "private", + AdminStateUp: &iTrue, + } + vlanTransparentCreateOpts := vlantransparent.CreateOptsExt{ + CreateOptsBuilder: &networkCreateOpts, + VLANTransparent: &iTrue, + } + + var s struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Create(fake.ServiceClient(), vlanTransparentCreateOpts).ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) + th.AssertEquals(t, "private", s.Name) + th.AssertEquals(t, true, s.AdminStateUp) + th.AssertEquals(t, "ACTIVE", s.Status) + th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, s.Subnets) + th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", s.TenantID) + th.AssertEquals(t, false, s.Shared) + th.AssertEquals(t, true, s.VLANTransparent) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, NetworksVLANTransparentUpdateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, NetworksVLANTransparentUpdateResult) + }) + + iFalse := false + name := "new_network_name" + networkUpdateOpts := networks.UpdateOpts{ + Name: &name, + AdminStateUp: &iFalse, + } + + vlanTransparentUpdateOpts := vlantransparent.UpdateOptsExt{ + UpdateOptsBuilder: &networkUpdateOpts, + VLANTransparent: &iFalse, + } + + var s struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", vlanTransparentUpdateOpts).ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) + th.AssertEquals(t, "new_network_name", s.Name) + th.AssertEquals(t, false, s.AdminStateUp) + th.AssertEquals(t, "ACTIVE", s.Status) + th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, s.Subnets) + th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", s.TenantID) + th.AssertEquals(t, false, s.Shared) + th.AssertEquals(t, false, s.VLANTransparent) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go index 9c3b08f1204d..b3ec548da7a6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go @@ -74,13 +74,13 @@ func TestCreate(t *testing.T) { IKEVersion: "v2", TenantID: "9145d91459d248b1b02fdaca97c6a75d", Phase1NegotiationMode: "main", - PFS: "Group5", - EncryptionAlgorithm: "aes-128", - Description: "IKE policy", - Name: "policy", - ID: "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", - Lifetime: expectedLifetime, - ProjectID: "9145d91459d248b1b02fdaca97c6a75d", + PFS: "Group5", + EncryptionAlgorithm: "aes-128", + Description: "IKE policy", + Name: "policy", + ID: "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", + Lifetime: expectedLifetime, + ProjectID: "9145d91459d248b1b02fdaca97c6a75d", } th.AssertDeepEquals(t, expected, *actual) } @@ -130,12 +130,12 @@ func TestGet(t *testing.T) { TenantID: "9145d91459d248b1b02fdaca97c6a75d", ProjectID: "9145d91459d248b1b02fdaca97c6a75d", Phase1NegotiationMode: "main", - PFS: "Group5", - EncryptionAlgorithm: "aes-128", - Description: "IKE policy", - Name: "policy", - ID: "5c561d9d-eaea-45f6-ae3e-08d1a7080828", - Lifetime: expectedLifetime, + PFS: "Group5", + EncryptionAlgorithm: "aes-128", + Description: "IKE policy", + Name: "policy", + ID: "5c561d9d-eaea-45f6-ae3e-08d1a7080828", + Lifetime: expectedLifetime, } th.AssertDeepEquals(t, expected, *actual) } @@ -208,12 +208,12 @@ func TestList(t *testing.T) { TenantID: "9145d91459d248b1b02fdaca97c6a75d", ProjectID: "9145d91459d248b1b02fdaca97c6a75d", Phase1NegotiationMode: "main", - PFS: "Group5", - EncryptionAlgorithm: "aes-128", - Description: "IKE policy", - Name: "policy", - ID: "5c561d9d-eaea-45f6-ae3e-08d1a7080828", - Lifetime: expectedLifetime, + PFS: "Group5", + EncryptionAlgorithm: "aes-128", + Description: "IKE policy", + Name: "policy", + ID: "5c561d9d-eaea-45f6-ae3e-08d1a7080828", + Lifetime: expectedLifetime, }, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go index e768b71f8203..9d1dd5a7ea4e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go @@ -45,8 +45,9 @@ Example to Update a Network networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" + name := "new_name" updateOpts := networks.UpdateOpts{ - Name: "new_name", + Name: &name, } network, err := networks.Update(networkClient, networkID, updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go index bc4460a0652d..8006c481679d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go @@ -19,6 +19,7 @@ type ListOptsBuilder interface { 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"` @@ -28,6 +29,10 @@ type ListOpts struct { 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"` } // ToNetworkListQuery formats a ListOpts into a query string. @@ -69,6 +74,7 @@ type CreateOptsBuilder interface { type CreateOpts struct { AdminStateUp *bool `json:"admin_state_up,omitempty"` Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` Shared *bool `json:"shared,omitempty"` TenantID string `json:"tenant_id,omitempty"` ProjectID string `json:"project_id,omitempty"` @@ -105,9 +111,10 @@ type UpdateOptsBuilder interface { // UpdateOpts represents options used to update a network. type UpdateOpts struct { - AdminStateUp *bool `json:"admin_state_up,omitempty"` - Name string `json:"name,omitempty"` - Shared *bool `json:"shared,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Shared *bool `json:"shared,omitempty"` } // ToNetworkUpdateMap builds a request body from UpdateOpts. @@ -140,7 +147,12 @@ func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go index 62f4b3c3a50b..f03067415fb6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go @@ -52,6 +52,9 @@ type Network struct { // Human-readable name for the network. Might not be unique. Name string `json:"name"` + // Description for the network + Description string `json:"description"` + // The administrative state of network. If false (down), the network does not // forward packets. AdminStateUp bool `json:"admin_state_up"` @@ -76,6 +79,9 @@ type Network struct { // Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others. // Used to make network resources highly available. AvailabilityZoneHints []string `json:"availability_zone_hints"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // NetworkPage is the page returned by a pager when traversing over a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/fixtures.go index 9632d448a5f8..756fa6b18a85 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/fixtures.go @@ -21,7 +21,9 @@ const ListResponse = ` "provider:physical_network": null, "provider:network_type": "local", "router:external": true, - "port_security_enabled": true + "port_security_enabled": true, + "dns_domain": "local.", + "mtu": 1500 }, { "status": "ACTIVE", @@ -37,7 +39,9 @@ const ListResponse = ` "provider:physical_network": null, "provider:network_type": "local", "router:external": false, - "port_security_enabled": false + "port_security_enabled": false, + "dns_domain": "", + "mtu": 1500 } ] }` @@ -58,7 +62,9 @@ const GetResponse = ` "provider:physical_network": null, "provider:network_type": "local", "router:external": true, - "port_security_enabled": true + "port_security_enabled": true, + "dns_domain": "local.", + "mtu": 1500 } }` @@ -82,7 +88,8 @@ const CreateResponse = ` "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", "provider:segmentation_id": 9876543210, "provider:physical_network": null, - "provider:network_type": "local" + "provider:network_type": "local", + "dns_domain": "" } }` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/requests_test.go index 231d7f087c97..1664f3e5cba5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/testing/requests_test.go @@ -78,6 +78,8 @@ func TestListWithExtensions(t *testing.T) { th.AssertEquals(t, allNetworks[0].Status, "ACTIVE") th.AssertEquals(t, allNetworks[0].PortSecurityEnabled, true) + th.AssertEquals(t, allNetworks[0].Subnets[0], "54d6f61d-db07-451c-9ab3-b9609b6b6f0b") + th.AssertEquals(t, allNetworks[1].Subnets[0], "08eae331-0402-425a-923c-34f7cfe39c1b") } func TestGet(t *testing.T) { @@ -195,7 +197,8 @@ func TestUpdate(t *testing.T) { }) iTrue, iFalse := true, false - options := networks.UpdateOpts{Name: "new_network_name", AdminStateUp: &iFalse, Shared: &iTrue} + name := "new_network_name" + options := networks.UpdateOpts{Name: &name, AdminStateUp: &iFalse, Shared: &iTrue} n, err := networks.Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go index 90416faa19bf..f5f7d761ce85 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go @@ -19,6 +19,7 @@ type ListOptsBuilder interface { type ListOpts struct { Status string `q:"status"` Name string `q:"name"` + Description string `q:"description"` AdminStateUp *bool `q:"admin_state_up"` NetworkID string `q:"network_id"` TenantID string `q:"tenant_id"` @@ -31,6 +32,10 @@ type ListOpts struct { 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"` } // ToPortListQuery formats a ListOpts into a query string. @@ -76,6 +81,7 @@ type CreateOptsBuilder interface { type CreateOpts struct { NetworkID string `json:"network_id" required:"true"` Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` MACAddress string `json:"mac_address,omitempty"` FixedIPs interface{} `json:"fixed_ips,omitempty"` @@ -112,11 +118,12 @@ type UpdateOptsBuilder interface { // UpdateOpts represents the attributes used when updating an existing port. type UpdateOpts struct { - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` FixedIPs interface{} `json:"fixed_ips,omitempty"` - DeviceID string `json:"device_id,omitempty"` - DeviceOwner string `json:"device_owner,omitempty"` + DeviceID *string `json:"device_id,omitempty"` + DeviceOwner *string `json:"device_owner,omitempty"` SecurityGroups *[]string `json:"security_groups,omitempty"` AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"` } @@ -151,7 +158,12 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go index 66937fd9899a..3941b62300dc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go @@ -68,6 +68,9 @@ type Port struct { // Human-readable name for the port. Might not be unique. Name string `json:"name"` + // Describes the port. + Description string `json:"description"` + // Administrative state of port. If false (down), port does not forward // packets. AdminStateUp bool `json:"admin_state_up"` @@ -101,6 +104,9 @@ type Port struct { // Identifies the list of IP addresses the port will recognize/accept AllowedAddressPairs []AddressPair `json:"allowed_address_pairs"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // PortPage is the page returned by a pager when traversing over a collection diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/fixtures.go index cfce9b091295..97ad08ac2dcf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/fixtures.go @@ -21,6 +21,14 @@ const ListResponse = ` ], "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", "security_groups": [], + "dns_name": "test-port", + "dns_assignment": [ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local." + } + ], "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", "port_security_enabled": false } @@ -57,6 +65,14 @@ const GetResponse = ` ], "id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", "security_groups": [], + "dns_name": "test-port", + "dns_assignment": [ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local." + } + ], "device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e" } } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/requests_test.go index af91cf99d3ca..a674e7eaa0ab 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/testing/requests_test.go @@ -377,8 +377,9 @@ func TestUpdate(t *testing.T) { fmt.Fprintf(w, UpdateResponse) }) + name := "new_port_name" options := ports.UpdateOpts{ - Name: "new_port_name", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, @@ -418,8 +419,9 @@ func TestUpdateOmitSecurityGroups(t *testing.T) { fmt.Fprintf(w, UpdateOmitSecurityGroupsResponse) }) + name := "new_port_name" options := ports.UpdateOpts{ - Name: "new_port_name", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, @@ -495,8 +497,9 @@ func TestRemoveSecurityGroups(t *testing.T) { fmt.Fprintf(w, RemoveSecurityGroupResponse) }) + name := "new_port_name" options := ports.UpdateOpts{ - Name: "new_port_name", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, @@ -536,8 +539,9 @@ func TestRemoveAllowedAddressPairs(t *testing.T) { fmt.Fprintf(w, RemoveAllowedAddressPairsResponse) }) + name := "new_port_name" options := ports.UpdateOpts{ - Name: "new_port_name", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, @@ -573,8 +577,9 @@ func TestDontUpdateAllowedAddressPairs(t *testing.T) { fmt.Fprintf(w, DontUpdateAllowedAddressPairsResponse) }) + name := "new_port_name" options := ports.UpdateOpts{ - Name: "new_port_name", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, @@ -731,8 +736,9 @@ func TestUpdateWithExtraDHCPOpts(t *testing.T) { fmt.Fprintf(w, UpdateWithExtraDHCPOptsResponse) }) + name := "updated-port-with-dhcp-opts" portUpdateOpts := ports.UpdateOpts{ - Name: "updated-port-with-dhcp-opts", + Name: &name, FixedIPs: []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, }, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go index d0ed8dff06a0..7d3a1b9b65c3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go @@ -97,10 +97,12 @@ Example to Create a Subnet With a Default Gateway Example to Update a Subnet subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23" + dnsNameservers := []string{"8.8.8.8"} + name := "new_name" updateOpts := subnets.UpdateOpts{ - Name: "new_name", - DNSNameservers: []string{"8.8.8.8}, + Name: &name, + DNSNameservers: &dnsNameservers, } subnet, err := subnets.Update(networkClient, subnetID, updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go index 597a4e77f3e6..3e56bf389af3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go @@ -18,6 +18,7 @@ type ListOptsBuilder interface { // `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"` @@ -33,6 +34,10 @@ type ListOpts struct { 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"` } // ToSubnetListQuery formats a ListOpts into a query string. @@ -80,11 +85,14 @@ type CreateOpts struct { NetworkID string `json:"network_id" required:"true"` // CIDR is the address CIDR of the subnet. - CIDR string `json:"cidr" required:"true"` + CIDR string `json:"cidr,omitempty"` // Name is a human-readable name of the subnet. Name string `json:"name,omitempty"` + // Description of the subnet. + Description string `json:"description,omitempty"` + // The UUID of the project who owns the Subnet. Only administrative users // can specify a project UUID other than their own. TenantID string `json:"tenant_id,omitempty"` @@ -123,6 +131,10 @@ type CreateOpts struct { // SubnetPoolID is the id of the subnet pool that subnet should be associated to. SubnetPoolID string `json:"subnetpool_id,omitempty"` + + // Prefixlen is used when user creates a subnet from the subnetpool. It will + // overwrite the "default_prefixlen" value of the referenced subnetpool. + Prefixlen int `json:"prefixlen,omitempty"` } // ToSubnetCreateMap builds a request body from CreateOpts. @@ -161,7 +173,10 @@ type UpdateOptsBuilder interface { // UpdateOpts represents the attributes used when updating an existing subnet. type UpdateOpts struct { // Name is a human-readable name of the subnet. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` + + // Description of the subnet. + Description *string `json:"description,omitempty"` // AllocationPools are IP Address pools that will be available for DHCP. AllocationPools []AllocationPool `json:"allocation_pools,omitempty"` @@ -173,10 +188,10 @@ type UpdateOpts struct { GatewayIP *string `json:"gateway_ip,omitempty"` // DNSNameservers are the nameservers to be set via DHCP. - DNSNameservers []string `json:"dns_nameservers,omitempty"` + DNSNameservers *[]string `json:"dns_nameservers,omitempty"` // HostRoutes are any static host routes to be set via DHCP. - HostRoutes []HostRoute `json:"host_routes,omitempty"` + HostRoutes *[]HostRoute `json:"host_routes,omitempty"` // EnableDHCP will either enable to disable the DHCP service. EnableDHCP *bool `json:"enable_dhcp,omitempty"` @@ -221,7 +236,12 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go index 493e5c042e2a..cf0397019a21 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go @@ -68,6 +68,9 @@ type Subnet struct { // Human-readable name for the subnet. Might not be unique. Name string `json:"name"` + // Description for the subnet. + Description string `json:"description"` + // IP version, either `4' or `6'. IPVersion int `json:"ip_version"` @@ -106,6 +109,9 @@ type Subnet struct { // SubnetPoolID is the id of the subnet pool associated with the subnet. SubnetPoolID string `json:"subnetpool_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // SubnetPage is the page returned by a pager when traversing over a collection diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/fixtures.go index 619ea3e55ede..38cdbc85597f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/fixtures.go @@ -336,6 +336,31 @@ const SubnetCreateWithIPv6RaAddressModeResponse = ` } ` +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" + } +} +` + +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 + } +} +` + const SubnetUpdateRequest = ` { "subnet": { @@ -433,6 +458,79 @@ const SubnetUpdateRemoveGatewayResponse = ` } ` +const SubnetUpdateHostRoutesRequest = ` +{ + "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" + } +} +` + +const SubnetUpdateRemoveHostRoutesRequest = ` +{ + "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" + } +} +` + const SubnetUpdateAllocationPoolRequest = ` { "subnet": { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/requests_test.go index 208fc608f925..abd75319ee68 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/testing/requests_test.go @@ -286,6 +286,103 @@ func TestCreateIPv6RaAddressMode(t *testing.T) { th.AssertEquals(t, s.IPv6RAMode, "slaac") } +func TestCreateWithNoCIDR(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, SubnetCreateRequestWithNoCIDR) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, SubnetCreateResult) + }) + + opts := subnets.CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + IPVersion: 4, + DNSNameservers: []string{"foo"}, + HostRoutes: []subnets.HostRoute{ + {NextHop: "bar"}, + }, + SubnetPoolID: "b80340c7-9960-4f67-a99c-02501656284b", + } + s, err := subnets.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "") + th.AssertEquals(t, s.EnableDHCP, true) + th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") + th.AssertDeepEquals(t, s.DNSNameservers, []string{}) + th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + { + Start: "192.168.199.2", + End: "192.168.199.254", + }, + }) + th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) + th.AssertEquals(t, s.IPVersion, 4) + th.AssertEquals(t, s.GatewayIP, "192.168.199.1") + th.AssertEquals(t, s.CIDR, "192.168.199.0/24") + th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") + th.AssertEquals(t, s.SubnetPoolID, "b80340c7-9960-4f67-a99c-02501656284b") +} + +func TestCreateWithPrefixlen(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, SubnetCreateRequestWithPrefixlen) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, SubnetCreateResult) + }) + + opts := subnets.CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + IPVersion: 4, + DNSNameservers: []string{"foo"}, + HostRoutes: []subnets.HostRoute{ + {NextHop: "bar"}, + }, + SubnetPoolID: "b80340c7-9960-4f67-a99c-02501656284b", + Prefixlen: 12, + } + s, err := subnets.Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "") + th.AssertEquals(t, s.EnableDHCP, true) + th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") + th.AssertDeepEquals(t, s.DNSNameservers, []string{}) + th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + { + Start: "192.168.199.2", + End: "192.168.199.254", + }, + }) + th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) + th.AssertEquals(t, s.IPVersion, 4) + th.AssertEquals(t, s.GatewayIP, "192.168.199.1") + th.AssertEquals(t, s.CIDR, "192.168.199.0/24") + th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") + th.AssertEquals(t, s.SubnetPoolID, "b80340c7-9960-4f67-a99c-02501656284b") +} + func TestRequiredCreateOpts(t *testing.T) { res := subnets.Create(fake.ServiceClient(), subnets.CreateOpts{}) if res.Err == nil { @@ -320,10 +417,12 @@ func TestUpdate(t *testing.T) { fmt.Fprintf(w, SubnetUpdateResponse) }) + dnsNameservers := []string{"foo"} + name := "my_new_subnet" opts := subnets.UpdateOpts{ - Name: "my_new_subnet", - DNSNameservers: []string{"foo"}, - HostRoutes: []subnets.HostRoute{ + Name: &name, + DNSNameservers: &dnsNameservers, + HostRoutes: &[]subnets.HostRoute{ {NextHop: "bar"}, }, } @@ -352,8 +451,9 @@ func TestUpdateGateway(t *testing.T) { }) var gatewayIP = "10.0.0.1" + name := "my_new_subnet" opts := subnets.UpdateOpts{ - Name: "my_new_subnet", + Name: &name, GatewayIP: &gatewayIP, } s, err := subnets.Update(fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() @@ -382,8 +482,9 @@ func TestUpdateRemoveGateway(t *testing.T) { }) var noGateway = "" + name := "my_new_subnet" opts := subnets.UpdateOpts{ - Name: "my_new_subnet", + Name: &name, GatewayIP: &noGateway, } s, err := subnets.Update(fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() @@ -394,6 +495,72 @@ func TestUpdateRemoveGateway(t *testing.T) { th.AssertEquals(t, s.GatewayIP, "") } +func TestUpdateHostRoutes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, SubnetUpdateHostRoutesRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, SubnetUpdateHostRoutesResponse) + }) + + HostRoutes := []subnets.HostRoute{ + { + DestinationCIDR: "192.168.1.1/24", + NextHop: "bar", + }, + } + + name := "my_new_subnet" + opts := subnets.UpdateOpts{ + Name: &name, + HostRoutes: &HostRoutes, + } + s, err := subnets.Update(fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "my_new_subnet") + th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") + th.AssertDeepEquals(t, s.HostRoutes, HostRoutes) +} + +func TestUpdateRemoveHostRoutes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, SubnetUpdateRemoveHostRoutesRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, SubnetUpdateRemoveHostRoutesResponse) + }) + + noHostRoutes := []subnets.HostRoute{} + opts := subnets.UpdateOpts{ + HostRoutes: &noHostRoutes, + } + s, err := subnets.Update(fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "my_new_subnet") + th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") + th.AssertDeepEquals(t, s.HostRoutes, noHostRoutes) +} + func TestUpdateAllocationPool(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -411,8 +578,9 @@ func TestUpdateAllocationPool(t *testing.T) { fmt.Fprintf(w, SubnetUpdateAllocationPoolResponse) }) + name := "my_new_subnet" opts := subnets.UpdateOpts{ - Name: "my_new_subnet", + Name: &name, AllocationPools: []subnets.AllocationPool{ { Start: "10.1.0.2", diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go index df2158785346..452a331c74ca 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go @@ -35,7 +35,7 @@ func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { h[k] = v } } - resp, err := c.Request("HEAD", getURL(c), &gophercloud.RequestOpts{ + resp, err := c.Head(getURL(c), &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{204}, }) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go index bf5dc846fc3e..10661e671571 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go @@ -63,6 +63,7 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) { // GetHeader represents the headers returned in the response from a Get request. type GetHeader struct { BytesUsed int64 `json:"-"` + QuotaBytes *int64 `json:"-"` ContainerCount int64 `json:"-"` ContentLength int64 `json:"-"` ObjectCount int64 `json:"-"` @@ -78,6 +79,7 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { var s struct { tmp BytesUsed string `json:"X-Account-Bytes-Used"` + QuotaBytes string `json:"X-Account-Meta-Quota-Bytes"` ContentLength string `json:"Content-Length"` ContainerCount string `json:"X-Account-Container-Count"` ObjectCount string `json:"X-Account-Object-Count"` @@ -100,6 +102,17 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { } } + switch s.QuotaBytes { + case "": + r.QuotaBytes = nil + default: + v, err := strconv.ParseInt(s.QuotaBytes, 10, 64) + if err != nil { + return err + } + r.QuotaBytes = &v + } + switch s.ContentLength { case "": r.ContentLength = 0 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/fixtures.go index fff307147507..e1d2182699a2 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/fixtures.go @@ -11,6 +11,24 @@ import ( // HandleGetAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that // responds with a `Get` response. func HandleGetAccountSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("X-Account-Container-Count", "2") + w.Header().Set("X-Account-Object-Count", "5") + w.Header().Set("X-Account-Meta-Quota-Bytes", "42") + w.Header().Set("X-Account-Bytes-Used", "14") + w.Header().Set("X-Account-Meta-Subject", "books") + w.Header().Set("Date", "Fri, 17 Jan 2014 16:09:56 GMT") + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleGetAccountNoQuotaSuccessfully creates an HTTP handler at `/` on the +// test handler mux that responds with a `Get` response. +func HandleGetAccountNoQuotaSuccessfully(t *testing.T) { th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/requests_test.go index 97852f195799..c396227e4d37 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/testing/requests_test.go @@ -35,6 +35,32 @@ func TestGetAccount(t *testing.T) { defer th.TeardownHTTP() HandleGetAccountSuccessfully(t) + expectedMetadata := map[string]string{"Subject": "books", "Quota-Bytes": "42"} + res := accounts.Get(fake.ServiceClient(), &accounts.GetOpts{}) + th.AssertNoErr(t, res.Err) + actualMetadata, _ := res.ExtractMetadata() + th.CheckDeepEquals(t, expectedMetadata, actualMetadata) + _, err := res.Extract() + th.AssertNoErr(t, err) + + var quotaBytes int64 = 42 + expected := &accounts.GetHeader{ + QuotaBytes: "aBytes, + ContainerCount: 2, + ObjectCount: 5, + BytesUsed: 14, + Date: time.Date(2014, time.January, 17, 16, 9, 56, 0, loc), // Fri, 17 Jan 2014 16:09:56 GMT + } + actual, err := res.Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) +} + +func TestGetAccountNoQuota(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetAccountNoQuotaSuccessfully(t) + expectedMetadata := map[string]string{"Subject": "books"} res := accounts.Get(fake.ServiceClient(), &accounts.GetOpts{}) th.AssertNoErr(t, res.Err) @@ -44,6 +70,7 @@ func TestGetAccount(t *testing.T) { th.AssertNoErr(t, err) expected := &accounts.GetHeader{ + QuotaBytes: nil, ContainerCount: 2, ObjectCount: 5, BytesUsed: 14, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go index 1ac8504de72c..ffc4f05297b6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go @@ -7,6 +7,10 @@ containers represents two different objects. In addition to containing objects, you can also use the container to control access to objects by using an access control list (ACL). +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "accounts" rather than "containers". This was an intentional +design in Gophercloud to make some container actions feel more natural. + Example to List Containers listOpts := containers.ListOpts{ @@ -69,6 +73,9 @@ Example to Update a Container Metadata: map[string]string{ "bar": "baz", }, + RemoveMetadata: []string{ + "foo", + }, } container, err := containers.Update(objectStorageClient, containerName, updateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go index ecb76075b119..ca99bb2a6aae 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go @@ -74,6 +74,7 @@ type CreateOpts struct { DetectContentType bool `h:"X-Detect-Content-Type"` IfNoneMatch string `h:"If-None-Match"` VersionsLocation string `h:"X-Versions-Location"` + HistoryLocation string `h:"X-History-Location"` } // ToContainerCreateMap formats a CreateOpts into a map of headers. @@ -107,6 +108,7 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB }) if resp != nil { r.Header = resp.Header + resp.Body.Close() } r.Err = err return @@ -128,6 +130,7 @@ type UpdateOptsBuilder interface { // deleting a container's metadata. type UpdateOpts struct { Metadata map[string]string + RemoveMetadata []string ContainerRead string `h:"X-Container-Read"` ContainerSyncTo string `h:"X-Container-Sync-To"` ContainerSyncKey string `h:"X-Container-Sync-Key"` @@ -136,6 +139,8 @@ type UpdateOpts struct { DetectContentType bool `h:"X-Detect-Content-Type"` RemoveVersionsLocation string `h:"X-Remove-Versions-Location"` VersionsLocation string `h:"X-Versions-Location"` + RemoveHistoryLocation string `h:"X-Remove-History-Location"` + HistoryLocation string `h:"X-History-Location"` } // ToContainerUpdateMap formats a UpdateOpts into a map of headers. @@ -144,9 +149,15 @@ func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) { if err != nil { return nil, err } + for k, v := range opts.Metadata { h["X-Container-Meta-"+k] = v } + + for _, k := range opts.RemoveMetadata { + h["X-Remove-Container-Meta-"+k] = "remove" + } + return h, nil } @@ -176,12 +187,41 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB return } +// GetOptsBuilder allows extensions to add additional parameters to the Get +// request. +type GetOptsBuilder interface { + ToContainerGetMap() (map[string]string, error) +} + +// GetOpts is a structure that holds options for listing containers. +type GetOpts struct { + Newest bool `h:"X-Newest"` +} + +// ToContainerGetMap formats a GetOpts into a map of headers. +func (opts GetOpts) ToContainerGetMap() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + // Get is a function that retrieves the metadata of a container. To extract just // the custom metadata, pass the GetResult response to the ExtractMetadata // function. -func Get(c *gophercloud.ServiceClient, containerName string) (r GetResult) { - resp, err := c.Request("HEAD", getURL(c, containerName), &gophercloud.RequestOpts{ - OkCodes: []int{200, 204}, +func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder) (r GetResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToContainerGetMap() + if err != nil { + r.Err = err + return + } + + for k, v := range headers { + h[k] = v + } + } + resp, err := c.Head(getURL(c, containerName), &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, }) if resp != nil { r.Header = resp.Header diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go index e8c78880a4b4..cce2190ff9ff 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go @@ -100,6 +100,7 @@ type GetHeader struct { Read []string `json:"-"` TransID string `json:"X-Trans-Id"` VersionsLocation string `json:"X-Versions-Location"` + HistoryLocation string `json:"X-History-Location"` Write []string `json:"-"` StoragePolicy string `json:"X-Storage-Policy"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/testing/requests_test.go index b2c50141d45d..c5eabd29f3a2 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/testing/requests_test.go @@ -124,7 +124,10 @@ func TestGetContainer(t *testing.T) { defer th.TeardownHTTP() HandleGetContainerSuccessfully(t) - res := containers.Get(fake.ServiceClient(), "testContainer") + getOpts := containers.GetOpts{ + Newest: true, + } + res := containers.Get(fake.ServiceClient(), "testContainer", getOpts) _, err := res.ExtractMetadata() th.CheckNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go index 1e02430fb425..e9b4b8a9f321 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go @@ -4,6 +4,10 @@ object resources. An object is a resource that represents and contains data - such as documents, images, and so on. You can also store custom metadata with an object. +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "containers" rather than "objects". This was an intentional +design in Gophercloud to make some object actions feel more natural. + Example to List Objects containerName := "my_container" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go index f67bfd159040..7325cd7d0b9b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go @@ -7,6 +7,7 @@ import ( "crypto/sha1" "fmt" "io" + "io/ioutil" "strings" "time" @@ -85,6 +86,7 @@ type DownloadOpts struct { IfModifiedSince time.Time `h:"If-Modified-Since"` IfNoneMatch string `h:"If-None-Match"` IfUnmodifiedSince time.Time `h:"If-Unmodified-Since"` + Newest bool `h:"X-Newest"` Range string `h:"Range"` Expires string `q:"expires"` MultipartManifest string `q:"multipart-manifest"` @@ -125,7 +127,7 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op resp, err := c.Get(url, nil, &gophercloud.RequestOpts{ MoreHeaders: h, - OkCodes: []int{200, 304}, + OkCodes: []int{200, 206, 304}, }) if resp != nil { r.Header = resp.Header @@ -145,6 +147,7 @@ type CreateOptsBuilder interface { type CreateOpts struct { Content io.Reader Metadata map[string]string + NoETag bool CacheControl string `h:"Cache-Control"` ContentDisposition string `h:"Content-Disposition"` ContentEncoding string `h:"Content-Encoding"` @@ -179,16 +182,37 @@ func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, str h["X-Object-Meta-"+k] = v } + if opts.NoETag { + delete(h, "etag") + return opts.Content, h, q.String(), nil + } + + if h["ETag"] != "" { + return opts.Content, h, q.String(), nil + } + + // When we're dealing with big files an io.ReadSeeker allows us to efficiently calculate + // the md5 sum. An io.Reader is only readable once which means we have to copy the entire + // file content into memory first. + readSeeker, isReadSeeker := opts.Content.(io.ReadSeeker) + if !isReadSeeker { + data, err := ioutil.ReadAll(opts.Content) + if err != nil { + return nil, nil, "", err + } + readSeeker = bytes.NewReader(data) + } + hash := md5.New() - buf := bytes.NewBuffer([]byte{}) - _, err = io.Copy(io.MultiWriter(hash, buf), opts.Content) - if err != nil { + // io.Copy into md5 is very efficient as it's done in small chunks. + if _, err := io.Copy(hash, readSeeker); err != nil { return nil, nil, "", err } - localChecksum := fmt.Sprintf("%x", hash.Sum(nil)) - h["ETag"] = localChecksum + readSeeker.Seek(0, io.SeekStart) + + h["ETag"] = fmt.Sprintf("%x", hash.Sum(nil)) - return buf, h, q.String(), nil + return readSeeker, h, q.String(), nil } // Create is a function that creates a new object or replaces an existing @@ -315,20 +339,28 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts // GetOptsBuilder allows extensions to add additional parameters to the // Get request. type GetOptsBuilder interface { - ToObjectGetQuery() (string, error) + ToObjectGetParams() (map[string]string, string, error) } // GetOpts is a structure that holds parameters for getting an object's // metadata. type GetOpts struct { + Newest bool `h:"X-Newest"` Expires string `q:"expires"` Signature string `q:"signature"` } -// ToObjectGetQuery formats a GetOpts into a query string. -func (opts GetOpts) ToObjectGetQuery() (string, error) { +// ToObjectGetParams formats a GetOpts into a query string and a map of headers. +func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) { q, err := gophercloud.BuildQueryString(opts) - return q.String(), err + if err != nil { + return nil, "", err + } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, q.String(), err + } + return h, q.String(), nil } // Get is a function that retrieves the metadata of an object. To extract just @@ -336,16 +368,22 @@ func (opts GetOpts) ToObjectGetQuery() (string, error) { // function. func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { url := getURL(c, containerName, objectName) + h := make(map[string]string) if opts != nil { - query, err := opts.ToObjectGetQuery() + headers, query, err := opts.ToObjectGetParams() if err != nil { r.Err = err return } + for k, v := range headers { + h[k] = v + } url += query } - resp, err := c.Request("HEAD", url, &gophercloud.RequestOpts{ - OkCodes: []int{200, 204}, + + resp, err := c.Head(url, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, }) if resp != nil { r.Header = resp.Header diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go index a47b39343c1d..dd7c7044d0e3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go @@ -25,8 +25,7 @@ type Object struct { // Hash represents the MD5 checksum value of the object's content. Hash string `json:"hash"` - // LastModified is the time the object was last modified, represented - // as a string. + // LastModified is the time the object was last modified. LastModified time.Time `json:"-"` // Name is the unique name for the object. @@ -40,7 +39,7 @@ func (r *Object) UnmarshalJSON(b []byte) error { type tmp Object var s *struct { tmp - LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"` + LastModified string `json:"last_modified"` } err := json.Unmarshal(b, &s) @@ -50,10 +49,18 @@ func (r *Object) UnmarshalJSON(b []byte) error { *r = Object(s.tmp) - r.LastModified = time.Time(s.LastModified) + if s.LastModified != "" { + t, err := time.Parse(gophercloud.RFC3339MilliNoZ, s.LastModified) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339Milli, s.LastModified) + if err != nil { + return err + } + } + r.LastModified = t + } return nil - } // ObjectPage is a single page of objects that is returned from a call to the @@ -134,7 +141,7 @@ type DownloadHeader struct { ETag string `json:"Etag"` LastModified time.Time `json:"-"` ObjectManifest string `json:"X-Object-Manifest"` - StaticLargeObject bool `json:"X-Static-Large-Object"` + StaticLargeObject bool `json:"-"` TransID string `json:"X-Trans-Id"` } @@ -142,10 +149,11 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { type tmp DownloadHeader var s struct { tmp - ContentLength string `json:"Content-Length"` - Date gophercloud.JSONRFC1123 `json:"Date"` - DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` - LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` } err := json.Unmarshal(b, &s) if err != nil { @@ -164,6 +172,15 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { } } + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + r.Date = time.Time(s.Date) r.DeleteAt = time.Time(s.DeleteAt) r.LastModified = time.Time(s.LastModified) @@ -214,7 +231,7 @@ type GetHeader struct { ETag string `json:"Etag"` LastModified time.Time `json:"-"` ObjectManifest string `json:"X-Object-Manifest"` - StaticLargeObject bool `json:"X-Static-Large-Object"` + StaticLargeObject bool `json:"-"` TransID string `json:"X-Trans-Id"` } @@ -222,10 +239,11 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { type tmp GetHeader var s struct { tmp - ContentLength string `json:"Content-Length"` - Date gophercloud.JSONRFC1123 `json:"Date"` - DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` - LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` } err := json.Unmarshal(b, &s) if err != nil { @@ -244,6 +262,15 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { } } + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + r.Date = time.Time(s.Date) r.DeleteAt = time.Time(s.DeleteAt) r.LastModified = time.Time(s.LastModified) @@ -391,7 +418,7 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) { // DeleteHeader represents the headers returned in the response from a // Delete request. type DeleteHeader struct { - ContentLength int64 `json:"Content-Length"` + ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` Date time.Time `json:"-"` TransID string `json:"X-Trans-Id"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/fixtures.go index a6b7e571e161..0ede1d6201f5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/fixtures.go @@ -21,6 +21,7 @@ func HandleDownloadObjectSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Date", "Wed, 10 Nov 2009 23:00:00 GMT") + w.Header().Set("X-Static-Large-Object", "True") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Successful download with Gophercloud") }) @@ -243,6 +244,7 @@ func HandleGetObjectSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("X-Object-Meta-Gophercloud-Test", "objects") + w.Header().Add("X-Static-Large-Object", "true") w.WriteHeader(http.StatusNoContent) }) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/requests_test.go index 6825d3442677..c5b34a75b0da 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/testing/requests_test.go @@ -2,7 +2,10 @@ package testing import ( "bytes" + "crypto/md5" + "fmt" "io" + "io/ioutil" "strings" "testing" "time" @@ -44,9 +47,10 @@ func TestDownloadExtraction(t *testing.T) { th.CheckEquals(t, "Successful download with Gophercloud", string(bytes)) expected := &objects.DownloadHeader{ - ContentLength: 36, - ContentType: "text/plain; charset=utf-8", - Date: time.Date(2009, time.November, 10, 23, 0, 0, 0, loc), + ContentLength: 36, + ContentType: "text/plain; charset=utf-8", + Date: time.Date(2009, time.November, 10, 23, 0, 0, 0, loc), + StaticLargeObject: true, } actual, err := response.Extract() th.AssertNoErr(t, err) @@ -232,4 +236,77 @@ func TestGetObject(t *testing.T) { actual, err := objects.Get(fake.ServiceClient(), "testContainer", "testObject", nil).ExtractMetadata() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) + + getOpts := objects.GetOpts{ + Newest: true, + } + actualHeaders, err := objects.Get(fake.ServiceClient(), "testContainer", "testObject", getOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, actualHeaders.StaticLargeObject, true) +} + +func TestETag(t *testing.T) { + content := "some example object" + createOpts := objects.CreateOpts{ + Content: strings.NewReader(content), + NoETag: true, + } + + _, headers, _, err := createOpts.ToObjectCreateParams() + th.AssertNoErr(t, err) + _, ok := headers["ETag"] + th.AssertEquals(t, ok, false) + + hash := md5.New() + io.WriteString(hash, content) + localChecksum := fmt.Sprintf("%x", hash.Sum(nil)) + + createOpts = objects.CreateOpts{ + Content: strings.NewReader(content), + ETag: localChecksum, + } + + _, headers, _, err = createOpts.ToObjectCreateParams() + th.AssertNoErr(t, err) + th.AssertEquals(t, headers["ETag"], localChecksum) +} + +func TestObjectCreateParamsWithoutSeek(t *testing.T) { + content := "I do not implement Seek()" + buf := bytes.NewBuffer([]byte(content)) + + createOpts := objects.CreateOpts{Content: buf} + reader, headers, _, err := createOpts.ToObjectCreateParams() + + th.AssertNoErr(t, err) + + _, ok := reader.(io.ReadSeeker) + th.AssertEquals(t, ok, true) + + c, err := ioutil.ReadAll(reader) + th.AssertNoErr(t, err) + + th.AssertEquals(t, content, string(c)) + + _, ok = headers["ETag"] + th.AssertEquals(t, true, ok) +} + +func TestObjectCreateParamsWithSeek(t *testing.T) { + content := "I implement Seek()" + createOpts := objects.CreateOpts{Content: strings.NewReader(content)} + reader, headers, _, err := createOpts.ToObjectCreateParams() + + th.AssertNoErr(t, err) + + _, ok := reader.(io.ReadSeeker) + th.AssertEquals(t, ok, true) + + c, err := ioutil.ReadAll(reader) + th.AssertNoErr(t, err) + + th.AssertEquals(t, content, string(c)) + + _, ok = headers["ETag"] + th.AssertEquals(t, true, ok) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/testing/requests_test.go index 57b503463ff4..46571f611737 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/testing/requests_test.go @@ -25,3 +25,11 @@ func TestAuth(t *testing.T) { th.AssertNoErr(t, err) th.AssertEquals(t, swiftClient.TokenID, AuthResult.Token) } + +func TestBadAuth(t *testing.T) { + authOpts := swauth.AuthOpts{} + _, err := authOpts.ToAuthOptsMap() + if err == nil { + t.Fatalf("Expected an error due to missing auth options") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/requests.go index ff383cff6849..f0cd34e2a097 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/requests.go @@ -7,7 +7,7 @@ import ( // ListVersions lists all the Neutron API versions available to end-users func ListVersions(c *gophercloud.ServiceClient) pagination.Pager { - return pagination.NewPager(c, apiVersionsURL(c), func(r pagination.PageResult) pagination.Page { + return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page { return APIVersionPage{pagination.SinglePageBase(r)} }) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/urls.go index 0205405a0477..a6a35d4225c2 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/apiversions/urls.go @@ -1,7 +1,14 @@ package apiversions -import "github.com/gophercloud/gophercloud" +import ( + "strings" -func apiVersionsURL(c *gophercloud.ServiceClient) string { - return c.Endpoint + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" +) + +func listURL(c *gophercloud.ServiceClient) string { + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + return endpoint } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/doc.go index 51cdd97473cb..078785839114 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/doc.go @@ -1,4 +1,19 @@ -// Package stackevents provides operations for finding, listing, and retrieving -// stack events. Stack events are events that take place on stacks such as -// updating and abandoning. +/* +Package stackevents provides operations for finding, listing, and retrieving +stack events. Stack events are events that take place on stacks such as +updating and abandoning. + +Example for list events for a stack + + pages, err := stackevents.List(client, stack.Name, stack.ID, nil).AllPages() + if err != nil { + panic(err) + } + events, err := stackevents.ExtractEvents(pages) + if err != nil { + panic(err) + } + fmt.Println("Get Event List") + fmt.Println(events) +*/ package stackevents diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/results.go index 46fb0ff088c9..75f7d3f386c1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/results.go @@ -34,8 +34,9 @@ func (r *Event) UnmarshalJSON(b []byte) error { type tmp Event var s struct { tmp - Time gophercloud.JSONRFC3339NoZ `json:"event_time"` + Time string `json:"event_time"` } + err := json.Unmarshal(b, &s) if err != nil { return err @@ -43,7 +44,16 @@ func (r *Event) UnmarshalJSON(b []byte) error { *r = Event(s.tmp) - r.Time = time.Time(s.Time) + if s.Time != "" { + t, err := time.Parse(time.RFC3339, s.Time) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.Time) + if err != nil { + return err + } + } + r.Time = t + } return nil } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/fixtures.go index a40e8d4f60e7..01a64583c1b7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/fixtures.go @@ -12,11 +12,14 @@ import ( fake "github.com/gophercloud/gophercloud/testhelper/client" ) +var Timestamp1, _ = time.Parse(time.RFC3339, "2018-06-26T07:58:17Z") +var Timestamp2, _ = time.Parse(time.RFC3339, "2018-06-26T07:59:17Z") + // FindExpected represents the expected object from a Find request. var FindExpected = []stackevents.Event{ { ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), + Time: Timestamp1, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a", @@ -39,7 +42,7 @@ var FindExpected = []stackevents.Event{ }, { ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC), + Time: Timestamp2, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -68,7 +71,7 @@ const FindOutput = ` "events": [ { "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:11", + "event_time": "2018-06-26T07:58:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a", @@ -91,7 +94,7 @@ const FindOutput = ` }, { "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:27", + "event_time": "2018-06-26T07:59:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -133,7 +136,7 @@ func HandleFindSuccessfully(t *testing.T, output string) { var ListExpected = []stackevents.Event{ { ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), + Time: Timestamp1, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a", @@ -156,7 +159,7 @@ var ListExpected = []stackevents.Event{ }, { ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC), + Time: Timestamp2, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -185,7 +188,7 @@ const ListOutput = ` "events": [ { "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:11", + "event_time": "2018-06-26T07:58:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a", @@ -208,7 +211,7 @@ const ListOutput = ` }, { "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:27", + "event_time": "2018-06-26T07:59:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -258,7 +261,7 @@ func HandleListSuccessfully(t *testing.T, output string) { var ListResourceEventsExpected = []stackevents.Event{ { ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), + Time: Timestamp1, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a", @@ -281,7 +284,7 @@ var ListResourceEventsExpected = []stackevents.Event{ }, { ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC), + Time: Timestamp2, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -310,7 +313,7 @@ const ListResourceEventsOutput = ` "events": [ { "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:11", + "event_time": "2018-06-26T07:58:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a", @@ -333,7 +336,7 @@ const ListResourceEventsOutput = ` }, { "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:27", + "event_time": "2018-06-26T07:59:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -382,7 +385,7 @@ func HandleListResourceEventsSuccessfully(t *testing.T, output string) { // GetExpected represents the expected object from a Get request. var GetExpected = &stackevents.Event{ ResourceName: "hello_world", - Time: time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC), + Time: Timestamp2, Links: []gophercloud.Link{ { Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", @@ -409,7 +412,7 @@ const GetOutput = ` { "event":{ "resource_name": "hello_world", - "event_time": "2015-02-05T21:33:27", + "event_time": "2018-06-26T07:59:17Z", "links": [ { "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18", diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/requests_test.go index 0ad3fc31f621..6dabfd23ef64 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackevents/testing/requests_test.go @@ -48,7 +48,7 @@ func TestListResourceEvents(t *testing.T) { count := 0 err := stackevents.ListResourceEvents(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", nil).EachPage(func(page pagination.Page) (bool, error) { count++ - actual, err := stackevents.ExtractEvents(page) + actual, err := stackevents.ExtractResourceEvents(page) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ListResourceEventsExpected, actual) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/doc.go index e4f8b08dcc7e..ae282dc08c71 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/doc.go @@ -1,5 +1,71 @@ -// Package stackresources provides operations for working with stack resources. -// A resource is a template artifact that represents some component of your -// desired architecture (a Cloud Server, a group of scaled Cloud Servers, a load -// balancer, some configuration management system, and so forth). +/* +Package stackresources provides operations for working with stack resources. +A resource is a template artifact that represents some component of your +desired architecture (a Cloud Server, a group of scaled Cloud Servers, a load +balancer, some configuration management system, and so forth). + +Example of get resource information in stack + + rsrc_result := stackresources.Get(client, stack.Name, stack.ID, rsrc.Name) + if rsrc_result.Err != nil { + panic(rsrc_result.Err) + } + rsrc, err := rsrc_result.Extract() + if err != nil { + panic(err) + } + +Example for list stack resources + + all_stack_rsrc_pages, err := stackresources.List(client, stack.Name, stack.ID, nil).AllPages() + if err != nil { + panic(err) + } + + all_stack_rsrcs, err := stackresources.ExtractResources(all_stack_rsrc_pages) + if err != nil { + panic(err) + } + + fmt.Println("Resource List:") + for _, rsrc := range all_stack_rsrcs { + // Get information of a resource in stack + rsrc_result := stackresources.Get(client, stack.Name, stack.ID, rsrc.Name) + if rsrc_result.Err != nil { + panic(rsrc_result.Err) + } + rsrc, err := rsrc_result.Extract() + if err != nil { + panic(err) + } + fmt.Println("Resource Name: ", rsrc.Name, ", Physical ID: ", rsrc.PhysicalID, ", Status: ", rsrc.Status) + } + + +Example for get resource type schema + + schema_result := stackresources.Schema(client, "OS::Heat::Stack") + if schema_result.Err != nil { + panic(schema_result.Err) + } + schema, err := schema_result.Extract() + if err != nil { + panic(err) + } + fmt.Println("Schema for resource type OS::Heat::Stack") + fmt.Println(schema.SupportStatus) + +Example for get resource type Template + + tmp_result := stackresources.Template(client, "OS::Heat::Stack") + if tmp_result.Err != nil { + panic(tmp_result.Err) + } + tmp, err := tmp_result.Extract() + if err != nil { + panic(err) + } + fmt.Println("Template for resource type OS::Heat::Stack") + fmt.Println(string(tmp)) +*/ package stackresources diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/requests.go index f368b76c6dee..306ada629d81 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/requests.go @@ -75,3 +75,39 @@ func Template(c *gophercloud.ServiceClient, resourceType string) (r TemplateResu _, r.Err = c.Get(templateURL(c, resourceType), &r.Body, nil) return } + +// MarkUnhealthyOpts contains the common options struct used in this package's +// MarkUnhealthy operations. +type MarkUnhealthyOpts struct { + // A boolean indicating whether the target resource should be marked as unhealthy. + MarkUnhealthy bool `json:"mark_unhealthy"` + // The reason for the current stack resource state. + ResourceStatusReason string `json:"resource_status_reason,omitempty"` +} + +// MarkUnhealthyOptsBuilder is the interface options structs have to satisfy in order +// to be used in the MarkUnhealthy operation in this package +type MarkUnhealthyOptsBuilder interface { + ToMarkUnhealthyMap() (map[string]interface{}, error) +} + +// ToMarkUnhealthyMap validates that a template was supplied and calls +// the ToMarkUnhealthyMap private function. +func (opts MarkUnhealthyOpts) ToMarkUnhealthyMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return b, nil +} + +// MarkUnhealthy marks the specified resource in the stack as unhealthy. +func MarkUnhealthy(c *gophercloud.ServiceClient, stackName, stackID, resourceName string, opts MarkUnhealthyOptsBuilder) (r MarkUnhealthyResult) { + b, err := opts.ToMarkUnhealthyMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Patch(markUnhealthyURL(c, stackName, stackID, resourceName), b, nil, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/results.go index 59c02a38c14c..f86f85df3b26 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/results.go @@ -10,35 +10,57 @@ import ( // Resource represents a stack resource. type Resource struct { - Attributes map[string]interface{} `json:"attributes"` - CreationTime time.Time `json:"-"` - Description string `json:"description"` - Links []gophercloud.Link `json:"links"` - LogicalID string `json:"logical_resource_id"` - Name string `json:"resource_name"` - PhysicalID string `json:"physical_resource_id"` - RequiredBy []interface{} `json:"required_by"` - Status string `json:"resource_status"` - StatusReason string `json:"resource_status_reason"` - Type string `json:"resource_type"` - UpdatedTime time.Time `json:"-"` + Attributes map[string]interface{} `json:"attributes"` + CreationTime time.Time `json:"-"` + Description string `json:"description"` + Links []gophercloud.Link `json:"links"` + LogicalID string `json:"logical_resource_id"` + Name string `json:"resource_name"` + PhysicalID string `json:"physical_resource_id"` + RequiredBy []interface{} `json:"required_by"` + Status string `json:"resource_status"` + StatusReason string `json:"resource_status_reason"` + Type string `json:"resource_type"` + UpdatedTime time.Time `json:"-"` + ParentResource string `json:"parent_resource"` } func (r *Resource) UnmarshalJSON(b []byte) error { type tmp Resource var s struct { tmp - CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"` - UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"` + CreationTime string `json:"creation_time"` + UpdatedTime string `json:"updated_time"` } + err := json.Unmarshal(b, &s) if err != nil { return err } + *r = Resource(s.tmp) - r.CreationTime = time.Time(s.CreationTime) - r.UpdatedTime = time.Time(s.UpdatedTime) + if s.CreationTime != "" { + t, err := time.Parse(time.RFC3339, s.CreationTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime) + if err != nil { + return err + } + } + r.CreationTime = t + } + + if s.UpdatedTime != "" { + t, err := time.Parse(time.RFC3339, s.UpdatedTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime) + if err != nil { + return err + } + } + r.UpdatedTime = t + } return nil } @@ -183,3 +205,8 @@ func (r TemplateResult) Extract() ([]byte, error) { template, err := json.MarshalIndent(r.Body, "", " ") return template, err } + +// MarkUnhealthyResult represents the result of a mark unhealthy operation. +type MarkUnhealthyResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/fixtures.go index e8903374ec0c..93827364414e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/fixtures.go @@ -12,6 +12,9 @@ import ( fake "github.com/gophercloud/gophercloud/testhelper/client" ) +var Create_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:57:17Z") +var Updated_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:58:17Z") + // FindExpected represents the expected object from a Find request. var FindExpected = []stackresources.Resource{ { @@ -28,8 +31,8 @@ var FindExpected = []stackresources.Resource{ }, LogicalID: "hello_world", StatusReason: "state changed", - UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), - CreationTime: time.Date(2015, 2, 5, 21, 33, 10, 0, time.UTC), + UpdatedTime: Updated_time, + CreationTime: Create_time, RequiredBy: []interface{}{}, Status: "CREATE_IN_PROGRESS", PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf", @@ -59,8 +62,8 @@ const FindOutput = ` ], "logical_resource_id": "hello_world", "resource_status_reason": "state changed", - "updated_time": "2015-02-05T21:33:11", - "creation_time": "2015-02-05T21:33:10", + "updated_time": "2018-06-26T07:58:17Z", + "creation_time": "2018-06-26T07:57:17Z", "required_by": [], "resource_status": "CREATE_IN_PROGRESS", "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf", @@ -99,8 +102,8 @@ var ListExpected = []stackresources.Resource{ }, LogicalID: "hello_world", StatusReason: "state changed", - UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC), - CreationTime: time.Date(2015, 2, 5, 21, 33, 10, 0, time.UTC), + UpdatedTime: Updated_time, + CreationTime: Create_time, RequiredBy: []interface{}{}, Status: "CREATE_IN_PROGRESS", PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf", @@ -127,11 +130,11 @@ const ListOutput = `{ ], "logical_resource_id": "hello_world", "resource_status_reason": "state changed", - "updated_time": "2015-02-05T21:33:11", + "updated_time": "2018-06-26T07:58:17Z", + "creation_time": "2018-06-26T07:57:17Z", "required_by": [], "resource_status": "CREATE_IN_PROGRESS", "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf", - "creation_time": "2015-02-05T21:33:10", "resource_type": "OS::Nova::Server", "attributes": {"SXSW": "atx"}, "description": "Some resource" @@ -177,7 +180,7 @@ var GetExpected = &stackresources.Resource{ LogicalID: "wordpress_instance", Attributes: map[string]interface{}{"SXSW": "atx"}, StatusReason: "state changed", - UpdatedTime: time.Date(2014, 12, 10, 18, 34, 35, 0, time.UTC), + UpdatedTime: Updated_time, RequiredBy: []interface{}{}, Status: "CREATE_COMPLETE", PhysicalID: "00e3a2fe-c65d-403c-9483-4db9930dd194", @@ -204,7 +207,7 @@ const GetOutput = ` ], "logical_resource_id": "wordpress_instance", "resource_status": "CREATE_COMPLETE", - "updated_time": "2014-12-10T18:34:35", + "updated_time": "2018-06-26T07:58:17Z", "required_by": [], "resource_status_reason": "state changed", "physical_resource_id": "00e3a2fe-c65d-403c-9483-4db9930dd194", @@ -438,3 +441,16 @@ func HandleGetTemplateSuccessfully(t *testing.T, output string) { fmt.Fprintf(w, output) }) } + +// HandleMarkUnhealthySuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance` +// on the test handler mux that responds with a `MarkUnhealthy` response. +func HandleMarkUnhealthySuccessfully(t *testing.T) { + th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/requests_test.go index c714047fa317..55ca4a0be4a3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/testing/requests_test.go @@ -110,3 +110,16 @@ func TestGetResourceTemplate(t *testing.T) { expected := GetTemplateExpected th.AssertDeepEquals(t, expected, string(actual)) } + +func TestMarkUnhealthyResource(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleMarkUnhealthySuccessfully(t) + + markUnhealthyOpts := &stackresources.MarkUnhealthyOpts{ + MarkUnhealthy: true, + ResourceStatusReason: "Kubelet.Ready is Unknown more than 10 mins.", + } + err := stackresources.MarkUnhealthy(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance", markUnhealthyOpts).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/urls.go index bbddc69cbcda..6b332f17e8d0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stackresources/urls.go @@ -29,3 +29,7 @@ func schemaURL(c *gophercloud.ServiceClient, typeName string) string { func templateURL(c *gophercloud.ServiceClient, typeName string) string { return c.ServiceURL("resource_types", typeName, "template") } + +func markUnhealthyURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string { + return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/doc.go index 19231b5137e5..33fc3271c44b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/doc.go @@ -1,8 +1,239 @@ -// Package stacks provides operation for working with Heat stacks. A stack is a -// group of resources (servers, load balancers, databases, and so forth) -// combined to fulfill a useful purpose. Based on a template, Heat orchestration -// engine creates an instantiated set of resources (a stack) to run the -// application framework or component specified (in the template). A stack is a -// running instance of a template. The result of creating a stack is a deployment -// of the application framework or component. +/* +Package stacks provides operation for working with Heat stacks. A stack is a +group of resources (servers, load balancers, databases, and so forth) +combined to fulfill a useful purpose. Based on a template, Heat orchestration +engine creates an instantiated set of resources (a stack) to run the +application framework or component specified (in the template). A stack is a +running instance of a template. The result of creating a stack is a deployment +of the application framework or component. + +Prepare required import packages + +import ( + "fmt" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks" +) + +Example of Preparing Orchestration client: + + client, err := openstack.NewOrchestrationV1(provider, gophercloud.EndpointOpts{Region: "RegionOne"}) + +Example of List Stack: + all_stack_pages, err := stacks.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + all_stacks, err := stacks.ExtractStacks(all_stack_pages) + if err != nil { + panic(err) + } + + for _, stack := range all_stacks { + fmt.Printf("%+v\n", stack) + } + + +Example to Create an Stack + + // Create Template + t := make(map[string]interface{}) + f, err := ioutil.ReadFile("template.yaml") + if err != nil { + panic(err) + } + err = yaml.Unmarshal(f, t) + if err != nil { + panic(err) + } + + template := &stacks.Template{} + template.TE = stacks.TE{ + Bin: f, + } + // Create Environment if needed + t_env := make(map[string]interface{}) + f_env, err := ioutil.ReadFile("env.yaml") + if err != nil { + panic(err) + } + err = yaml.Unmarshal(f_env, t_env) + if err != nil { + panic(err) + } + + env := &stacks.Environment{} + env.TE = stacks.TE{ + Bin: f_env, + } + + // Remember, the priority of parameters you given through + // Parameters is higher than the parameters you provided in EnvironmentOpts. + params := make(map[string]string) + params["number_of_nodes"] = 1 + tags := []string{"example-stack"} + createOpts := &stacks.CreateOpts{ + // The name of the stack. It must start with an alphabetic character. + Name: "testing_group", + // A structure that contains either the template file or url. Call the + // associated methods to extract the information relevant to send in a create request. + TemplateOpts: template, + // A structure that contains details for the environment of the stack. + EnvironmentOpts: env, + // User-defined parameters to pass to the template. + Parameters: params, + // A list of tags to assosciate with the Stack + Tags: tags, + } + + r := stacks.Create(client, createOpts) + //dcreated_stack := stacks.CreatedStack() + if r.Err != nil { + panic(r.Err) + } + created_stack, err := r.Extract() + if err != nil { + panic(err) + } + fmt.Printf("Created Stack: %v", created_stack.ID) + +Example for Get Stack + + get_result := stacks.Get(client, stackName, created_stack.ID) + if get_result.Err != nil { + panic(get_result.Err) + } + stack, err := get_result.Extract() + if err != nil { + panic(err) + } + fmt.Println("Get Stack: Name: ", stack.Name, ", ID: ", stack.ID, ", Status: ", stack.Status) + +Example for Find Stack + + find_result := stacks.Find(client, stackIdentity) + if find_result.Err != nil { + panic(find_result.Err) + } + stack, err := find_result.Extract() + if err != nil { + panic(err) + } + fmt.Println("Find Stack: Name: ", stack.Name, ", ID: ", stack.ID, ", Status: ", stack.Status) + +Example for Delete Stack + + del_r := stacks.Delete(client, stackName, created_stack.ID) + if del_r.Err != nil { + panic(del_r.Err) + } + fmt.Println("Deleted Stack: ", stackName) + +Summary of Behavior Between Stack Update and UpdatePatch Methods : + +Function | Test Case | Result + +Update() | Template AND Parameters WITH Conflict | Parameter takes priority, parameters are set in raw_template.environment overlay +Update() | Template ONLY | Template updates, raw_template.environment overlay is removed +Update() | Parameters ONLY | No update, template is required + +UpdatePatch() | Template AND Parameters WITH Conflict | Parameter takes priority, parameters are set in raw_template.environment overlay +UpdatePatch() | Template ONLY | Template updates, but raw_template.environment overlay is not removed, existing parameter values will remain +UpdatePatch() | Parameters ONLY | Parameters (raw_template.environment) is updated, excluded values are unchanged + +The PUT Update() function will remove parameters from the raw_template.environment overlay +if they are excluded from the operation, whereas PATCH Update() will never be destructive to the +raw_template.environment overlay. It is not possible to expose the raw_template values with a +patch update once they have been added to the environment overlay with the PATCH verb, but +newly added values that do not have a corresponding key in the overlay will display the +raw_template value. + +Example to Update a Stack Using the Update (PUT) Method + + t := make(map[string]interface{}) + f, err := ioutil.ReadFile("template.yaml") + if err != nil { + panic(err) + } + err = yaml.Unmarshal(f, t) + if err != nil { + panic(err) + } + + template := stacks.Template{} + template.TE = stacks.TE{ + Bin: f, + } + + var params = make(map[string]interface{}) + params["number_of_nodes"] = 2 + + stackName := "my_stack" + stackId := "d68cc349-ccc5-4b44-a17d-07f068c01e5a" + + stackOpts := &stacks.UpdateOpts{ + Parameters: params, + TemplateOpts: &template, + } + + res := stacks.Update(orchestrationClient, stackName, stackId, stackOpts) + if res.Err != nil { + panic(res.Err) + } + +Example to Update a Stack Using the UpdatePatch (PATCH) Method + + var params = make(map[string]interface{}) + params["number_of_nodes"] = 2 + + stackName := "my_stack" + stackId := "d68cc349-ccc5-4b44-a17d-07f068c01e5a" + + stackOpts := &stacks.UpdateOpts{ + Parameters: params, + } + + res := stacks.UpdatePatch(orchestrationClient, stackName, stackId, stackOpts) + if res.Err != nil { + panic(res.Err) + } + +Example YAML Template Containing a Heat::ResourceGroup With Three Nodes + + heat_template_version: 2016-04-08 + + parameters: + number_of_nodes: + type: number + default: 3 + description: the number of nodes + node_flavor: + type: string + default: m1.small + description: node flavor + node_image: + type: string + default: centos7.5-latest + description: node os image + node_network: + type: string + default: my-node-network + description: node network name + + resources: + resource_group: + type: OS::Heat::ResourceGroup + properties: + count: { get_param: number_of_nodes } + resource_def: + type: OS::Nova::Server + properties: + name: my_nova_server_%index% + image: { get_param: node_image } + flavor: { get_param: node_flavor } + networks: + - network: {get_param: node_network} +*/ package stacks diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/errors.go index cd6c18f758d8..a6febe040841 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/errors.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/errors.go @@ -31,3 +31,11 @@ type ErrInvalidTemplateFormatVersion struct { func (e ErrInvalidTemplateFormatVersion) Error() string { return fmt.Sprintf("Template format version not found.") } + +type ErrTemplateRequired struct { + gophercloud.BaseError +} + +func (e ErrTemplateRequired) Error() string { + return fmt.Sprintf("Template required for this function.") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/requests.go index 91f38ee7e604..d7fd4f16fd8e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/requests.go @@ -30,7 +30,7 @@ type CreateOpts struct { // A structure that contains details for the environment of the stack. EnvironmentOpts *Environment `json:"-"` // User-defined parameters to pass to the template. - Parameters map[string]string `json:"parameters,omitempty"` + Parameters map[string]interface{} `json:"parameters,omitempty"` // The timeout for stack creation in minutes. Timeout int `json:"timeout_mins,omitempty"` // A list of tags to assosciate with the Stack @@ -127,7 +127,7 @@ type AdoptOpts struct { // A structure that contains details for the environment of the stack. EnvironmentOpts *Environment `json:"-"` // User-defined parameters to pass to the template. - Parameters map[string]string `json:"parameters,omitempty"` + Parameters map[string]interface{} `json:"parameters,omitempty"` } // ToStackAdoptMap casts a CreateOpts struct to a map. @@ -214,16 +214,54 @@ type ListOptsBuilder interface { // 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 network 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. +// the network attributes you want to see returned. type ListOpts struct { - Status string `q:"status"` - Name string `q:"name"` - Marker string `q:"marker"` - Limit int `q:"limit"` + // TenantID is the UUID of the tenant. A tenant is also known as + // a project. + TenantID string `q:"tenant_id"` + + // ID filters the stack list by a stack ID + ID string `q:"id"` + + // Status filters the stack list by a status. + Status string `q:"status"` + + // Name filters the stack list by a name. + Name string `q:"name"` + + // Marker is the ID of last-seen item. + Marker string `q:"marker"` + + // Limit is an integer value for the limit of values to return. + Limit int `q:"limit"` + + // SortKey allows you to sort by stack_name, stack_status, creation_time, or + // update_time key. SortKey SortKey `q:"sort_keys"` + + // SortDir sets the direction, and is either `asc` or `desc`. SortDir SortDir `q:"sort_dir"` + + // AllTenants is a bool to show all tenants. + AllTenants bool `q:"global_tenant"` + + // ShowDeleted set to `true` to include deleted stacks in the list. + ShowDeleted bool `q:"show_deleted"` + + // ShowNested set to `true` to include nested stacks in the list. + ShowNested bool `q:"show_nested"` + + // Tags lists stacks that contain one or more simple string tags. + Tags string `q:"tags"` + + // TagsAny lists stacks that contain one or more simple string tags. + TagsAny string `q:"tags_any"` + + // NotTags lists stacks that do not contain one or more simple string tags. + NotTags string `q:"not_tags"` + + // NotTagsAny lists stacks that do not contain one or more simple string tags. + NotTagsAny string `q:"not_tags_any"` } // ToStackListQuery formats a ListOpts into a query string. @@ -259,48 +297,78 @@ func Get(c *gophercloud.ServiceClient, stackName, stackID string) (r GetResult) return } +// Find retrieves a stack based on the stack name or stack ID. +func Find(c *gophercloud.ServiceClient, stackIdentity string) (r GetResult) { + _, r.Err = c.Get(findURL(c, stackIdentity), &r.Body, nil) + return +} + // UpdateOptsBuilder is the interface options structs have to satisfy in order // to be used in the Update operation in this package. type UpdateOptsBuilder interface { ToStackUpdateMap() (map[string]interface{}, error) } +// UpdatePatchOptsBuilder is the interface options structs have to satisfy in order +// to be used in the UpdatePatch operation in this package +type UpdatePatchOptsBuilder interface { + ToStackUpdatePatchMap() (map[string]interface{}, error) +} + // UpdateOpts contains the common options struct used in this package's Update -// operation. +// and UpdatePatch operations. type UpdateOpts struct { // A structure that contains either the template file or url. Call the // associated methods to extract the information relevant to send in a create request. - TemplateOpts *Template `json:"-" required:"true"` + TemplateOpts *Template `json:"-"` // A structure that contains details for the environment of the stack. EnvironmentOpts *Environment `json:"-"` // User-defined parameters to pass to the template. - Parameters map[string]string `json:"parameters,omitempty"` + Parameters map[string]interface{} `json:"parameters,omitempty"` // The timeout for stack creation in minutes. Timeout int `json:"timeout_mins,omitempty"` - // A list of tags to assosciate with the Stack + // A list of tags to associate with the Stack Tags []string `json:"-"` } -// ToStackUpdateMap casts a CreateOpts struct to a map. +// ToStackUpdateMap validates that a template was supplied and calls +// the toStackUpdateMap private function. func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) { - b, err := gophercloud.BuildRequestBody(opts, "") - if err != nil { - return nil, err + if opts.TemplateOpts == nil { + return nil, ErrTemplateRequired{} } + return toStackUpdateMap(opts) +} - if err := opts.TemplateOpts.Parse(); err != nil { - return nil, err - } +// ToStackUpdatePatchMap calls the private function toStackUpdateMap +// directly. +func (opts UpdateOpts) ToStackUpdatePatchMap() (map[string]interface{}, error) { + return toStackUpdateMap(opts) +} - if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil { +// ToStackUpdateMap casts a CreateOpts struct to a map. +func toStackUpdateMap(opts UpdateOpts) (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { return nil, err } - opts.TemplateOpts.fixFileRefs() - b["template"] = string(opts.TemplateOpts.Bin) files := make(map[string]string) - for k, v := range opts.TemplateOpts.Files { - files[k] = v + + if opts.TemplateOpts != nil { + if err := opts.TemplateOpts.Parse(); err != nil { + return nil, err + } + + if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil { + return nil, err + } + opts.TemplateOpts.fixFileRefs() + b["template"] = string(opts.TemplateOpts.Bin) + + for k, v := range opts.TemplateOpts.Files { + files[k] = v + } } if opts.EnvironmentOpts != nil { @@ -328,8 +396,8 @@ func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) { return b, nil } -// Update accepts an UpdateOpts struct and updates an existing stack using the values -// provided. +// Update accepts an UpdateOpts struct and updates an existing stack using the +// http PUT verb with the values provided. opts.TemplateOpts is required. func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToStackUpdateMap() if err != nil { @@ -340,6 +408,18 @@ func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts Update return } +// Update accepts an UpdateOpts struct and updates an existing stack using the +// http PATCH verb with the values provided. opts.TemplateOpts is not required. +func UpdatePatch(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdatePatchOptsBuilder) (r UpdateResult) { + b, err := opts.ToStackUpdatePatchMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Patch(updateURL(c, stackName, stackID), b, nil, nil) + return +} + // Delete deletes a stack based on the stack name and stack ID. func Delete(c *gophercloud.ServiceClient, stackName, stackID string) (r DeleteResult) { _, r.Err = c.Delete(deleteURL(c, stackName, stackID), nil) @@ -369,7 +449,7 @@ type PreviewOpts struct { // A structure that contains details for the environment of the stack. EnvironmentOpts *Environment `json:"-"` // User-defined parameters to pass to the template. - Parameters map[string]string `json:"parameters,omitempty"` + Parameters map[string]interface{} `json:"parameters,omitempty"` } // ToStackPreviewMap casts a PreviewOpts struct to a map. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/results.go index 8df541940ec0..054ab3d74b00 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/results.go @@ -63,17 +63,38 @@ func (r *ListedStack) UnmarshalJSON(b []byte) error { type tmp ListedStack var s struct { tmp - CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"` - UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"` + CreationTime string `json:"creation_time"` + UpdatedTime string `json:"updated_time"` } + err := json.Unmarshal(b, &s) if err != nil { return err } + *r = ListedStack(s.tmp) - r.CreationTime = time.Time(s.CreationTime) - r.UpdatedTime = time.Time(s.UpdatedTime) + if s.CreationTime != "" { + t, err := time.Parse(time.RFC3339, s.CreationTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime) + if err != nil { + return err + } + } + r.CreationTime = t + } + + if s.UpdatedTime != "" { + t, err := time.Parse(time.RFC3339, s.UpdatedTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime) + if err != nil { + return err + } + } + r.UpdatedTime = t + } return nil } @@ -112,17 +133,38 @@ func (r *RetrievedStack) UnmarshalJSON(b []byte) error { type tmp RetrievedStack var s struct { tmp - CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"` - UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"` + CreationTime string `json:"creation_time"` + UpdatedTime string `json:"updated_time"` } + err := json.Unmarshal(b, &s) if err != nil { return err } + *r = RetrievedStack(s.tmp) - r.CreationTime = time.Time(s.CreationTime) - r.UpdatedTime = time.Time(s.UpdatedTime) + if s.CreationTime != "" { + t, err := time.Parse(time.RFC3339, s.CreationTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime) + if err != nil { + return err + } + } + r.CreationTime = t + } + + if s.UpdatedTime != "" { + t, err := time.Parse(time.RFC3339, s.UpdatedTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime) + if err != nil { + return err + } + } + r.UpdatedTime = t + } return nil } @@ -173,17 +215,38 @@ func (r *PreviewedStack) UnmarshalJSON(b []byte) error { type tmp PreviewedStack var s struct { tmp - CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"` - UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"` + CreationTime string `json:"creation_time"` + UpdatedTime string `json:"updated_time"` } + err := json.Unmarshal(b, &s) if err != nil { return err } + *r = PreviewedStack(s.tmp) - r.CreationTime = time.Time(s.CreationTime) - r.UpdatedTime = time.Time(s.UpdatedTime) + if s.CreationTime != "" { + t, err := time.Parse(time.RFC3339, s.CreationTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime) + if err != nil { + return err + } + } + r.CreationTime = t + } + + if s.UpdatedTime != "" { + t, err := time.Parse(time.RFC3339, s.UpdatedTime) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime) + if err != nil { + return err + } + } + r.UpdatedTime = t + } return nil } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/fixtures.go index f3e3b57d1a8d..a69bfa3724d0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/fixtures.go @@ -12,6 +12,9 @@ import ( fake "github.com/gophercloud/gophercloud/testhelper/client" ) +var Create_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:58:17Z") +var Updated_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:59:17Z") + // CreateExpected represents the expected object from a Create request. var CreateExpected = &stacks.CreatedStack{ ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", @@ -61,7 +64,7 @@ var ListExpected = []stacks.ListedStack{ }, StatusReason: "Stack CREATE completed successfully", Name: "postman_stack", - CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC), + CreationTime: Create_time, Status: "CREATE_COMPLETE", ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87", Tags: []string{"rackspace", "atx"}, @@ -76,8 +79,8 @@ var ListExpected = []stacks.ListedStack{ }, StatusReason: "Stack successfully updated", Name: "gophercloud-test-stack-2", - CreationTime: time.Date(2014, 12, 11, 17, 39, 16, 0, time.UTC), - UpdatedTime: time.Date(2014, 12, 11, 17, 40, 37, 0, time.UTC), + CreationTime: Create_time, + UpdatedTime: Updated_time, Status: "UPDATE_COMPLETE", ID: "db6977b2-27aa-4775-9ae7-6213212d4ada", Tags: []string{"sfo", "satx"}, @@ -98,7 +101,7 @@ const FullListOutput = ` ], "stack_status_reason": "Stack CREATE completed successfully", "stack_name": "postman_stack", - "creation_time": "2015-02-03T20:07:39", + "creation_time": "2018-06-26T07:58:17Z", "updated_time": null, "stack_status": "CREATE_COMPLETE", "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", @@ -114,8 +117,8 @@ const FullListOutput = ` ], "stack_status_reason": "Stack successfully updated", "stack_name": "gophercloud-test-stack-2", - "creation_time": "2014-12-11T17:39:16", - "updated_time": "2014-12-11T17:40:37", + "creation_time": "2018-06-26T07:58:17Z", + "updated_time": "2018-06-26T07:59:17Z", "stack_status": "UPDATE_COMPLETE", "id": "db6977b2-27aa-4775-9ae7-6213212d4ada", "tags": ["sfo", "satx"] @@ -158,7 +161,7 @@ var GetExpected = &stacks.RetrievedStack{ StatusReason: "Stack CREATE completed successfully", Name: "postman_stack", Outputs: []map[string]interface{}{}, - CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC), + CreationTime: Create_time, Links: []gophercloud.Link{ { Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", @@ -187,7 +190,7 @@ const GetOutput = ` "stack_status_reason": "Stack CREATE completed successfully", "stack_name": "postman_stack", "outputs": [], - "creation_time": "2015-02-03T20:07:39", + "creation_time": "2018-06-26T07:58:17Z", "links": [ { "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", @@ -220,6 +223,18 @@ func HandleGetSuccessfully(t *testing.T, output string) { }) } +func HandleFindSuccessfully(t *testing.T, output string) { + th.Mux.HandleFunc("/stacks/16ef0584-4458-41eb-87c8-0dc8d5f66c87", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, output) + }) +} + // HandleUpdateSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` // on the test handler mux that responds with an `Update` response. func HandleUpdateSuccessfully(t *testing.T) { @@ -233,6 +248,19 @@ func HandleUpdateSuccessfully(t *testing.T) { }) } +// HandleUpdatePatchSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` +// on the test handler mux that responds with an `Update` response. +func HandleUpdatePatchSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + }) +} + // HandleDeleteSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` // on the test handler mux that responds with a `Delete` response. func HandleDeleteSuccessfully(t *testing.T) { @@ -256,7 +284,7 @@ var PreviewExpected = &stacks.PreviewedStack{ "OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87", }, Name: "postman_stack", - CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC), + CreationTime: Create_time, Links: []gophercloud.Link{ { Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/requests_test.go index bdc6229831b8..6f0eb3a8e9dc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/testing/requests_test.go @@ -39,6 +39,29 @@ func TestCreateStack(t *testing.T) { th.AssertDeepEquals(t, expected, actual) } +func TestCreateStackMissingRequiredInOpts(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateSuccessfully(t, CreateOutput) + template := new(stacks.Template) + template.Bin = []byte(` + { + "heat_template_version": "2013-05-23", + "description": "Simple template to test heat commands", + "parameters": { + "flavor": { + "default": "m1.tiny", + "type": "string" + } + } + }`) + createOpts := stacks.CreateOpts{ + DisableRollback: gophercloud.Disabled, + } + r := stacks.Create(fake.ServiceClient(), createOpts) + th.AssertEquals(t, "Missing input for argument [Name]", r.Err.Error()) +} + func TestAdoptStack(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -116,6 +139,18 @@ func TestGetStack(t *testing.T) { th.AssertDeepEquals(t, expected, actual) } +func TestFindStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleFindSuccessfully(t, GetOutput) + + actual, err := stacks.Find(fake.ServiceClient(), "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + th.AssertNoErr(t, err) + + expected := GetExpected + th.AssertDeepEquals(t, expected, actual) +} + func TestUpdateStack(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -133,13 +168,45 @@ func TestUpdateStack(t *testing.T) { } } }`) - updateOpts := stacks.UpdateOpts{ + updateOpts := &stacks.UpdateOpts{ TemplateOpts: template, } err := stacks.Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() th.AssertNoErr(t, err) } +func TestUpdateStackNoTemplate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateSuccessfully(t) + + parameters := make(map[string]interface{}) + parameters["flavor"] = "m1.tiny" + + updateOpts := &stacks.UpdateOpts{ + Parameters: parameters, + } + expected := stacks.ErrTemplateRequired{} + + err := stacks.Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + th.AssertEquals(t, expected, err) +} + +func TestUpdatePatchStack(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdatePatchSuccessfully(t) + + parameters := make(map[string]interface{}) + parameters["flavor"] = "m1.tiny" + + updateOpts := &stacks.UpdateOpts{ + Parameters: parameters, + } + err := stacks.UpdatePatch(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + th.AssertNoErr(t, err) +} + func TestDeleteStack(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/urls.go index b00be54e2ae4..b909caac86b7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/urls.go @@ -18,6 +18,10 @@ func getURL(c *gophercloud.ServiceClient, name, id string) string { return c.ServiceURL("stacks", name, id) } +func findURL(c *gophercloud.ServiceClient, identity string) string { + return c.ServiceURL("stacks", identity) +} + func updateURL(c *gophercloud.ServiceClient, name, id string) string { return getURL(c, name, id) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/utils.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/utils.go index 71d9e351502b..8f8d4cc4c0af 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/utils.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks/utils.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/gophercloud/gophercloud" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" ) // Client is an interface that expects a Get method similar to http.Get. This diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go index 5af0bd62a113..52fe62096bfb 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacktemplates/doc.go @@ -1,8 +1,38 @@ -// Package stacktemplates provides operations for working with Heat templates. -// A Cloud Orchestration template is a portable file, written in a user-readable -// language, that describes how a set of resources should be assembled and what -// software should be installed in order to produce a working stack. The template -// specifies what resources should be used, what attributes can be set, and other -// parameters that are critical to the successful, repeatable automation of a -// specific application stack. +/* +Package stacktemplates provides operations for working with Heat templates. +A Cloud Orchestration template is a portable file, written in a user-readable +language, that describes how a set of resources should be assembled and what +software should be installed in order to produce a working stack. The template +specifies what resources should be used, what attributes can be set, and other +parameters that are critical to the successful, repeatable automation of a +specific application stack. + +Example to get stack template + + temp, err := stacktemplates.Get(client, stack.Name, stack.ID).Extract() + if err != nil { + panic(err) + } + fmt.Println("Get Stack Template for Stack ", stack.Name) + fmt.Println(string(temp)) + +Example to validate stack template + + f2, err := ioutil.ReadFile("template.err.yaml") + if err != nil { + panic(err) + } + fmt.Println(string(f2)) + validateOpts := &stacktemplates.ValidateOpts{ + Template: string(f2), + } + validate_result, err := stacktemplates.Validate(client, validateOpts).Extract() + if err != nil { + // If validate failed, you will get error message here + fmt.Println("Validate failed: ", err.Error()) + } else { + fmt.Println(validate_result.Parameters) + } + +*/ package stacktemplates diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/apiversions/urls.go index 6a30ca91e27f..41aebdc5f2bd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/apiversions/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/apiversions/urls.go @@ -1,20 +1,20 @@ package apiversions import ( - "net/url" "strings" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" ) func getURL(c *gophercloud.ServiceClient, version string) string { - u, _ := url.Parse(c.ServiceURL("")) - u.Path = "/" + strings.TrimRight(version, "/") + "/" - return u.String() + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + strings.TrimRight(version, "/") + "/" + return endpoint } func listURL(c *gophercloud.ServiceClient) string { - u, _ := url.Parse(c.ServiceURL("")) - u.Path = "/" - return u.String() + baseEndpoint, _ := utils.BaseEndpoint(c.Endpoint) + endpoint := strings.TrimRight(baseEndpoint, "/") + "/" + return endpoint } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go new file mode 100644 index 000000000000..cd88a8ac1fd6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go @@ -0,0 +1,44 @@ +package errors + +import ( + "encoding/json" + "fmt" + + "github.com/gophercloud/gophercloud" +) + +type ManilaError struct { + Code int `json:"code"` + Message string `json:"message"` + Details string `json:"details"` +} + +type ErrorDetails map[string]ManilaError + +// error types from provider_client.go +func ExtractErrorInto(rawError error, errorDetails *ErrorDetails) (err error) { + switch e := rawError.(type) { + case gophercloud.ErrDefault400: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault401: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault403: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault404: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault405: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault408: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault429: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault500: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault503: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + default: + err = fmt.Errorf("Unable to extract detailed error message") + } + + return err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/fixtures.go new file mode 100644 index 000000000000..dc7a394fb498 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/fixtures.go @@ -0,0 +1,42 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const shareEndpoint = "/shares" + +var createRequest = `{ + "share": { + "name": "my_test_share", + "size": 1, + "share_proto": "NFS", + "snapshot_id": "70bfbebc-d3ff-4528-8bbb-58422daa280b" + } + }` + +var createResponse = `{ + "itemNotFound": { + "code": 404, + "message": "ShareSnapshotNotFound: Snapshot 70bfbebc-d3ff-4528-8bbb-58422daa280b could not be found." + } +}` + +// MockCreateResponse creates a mock response +func MockCreateResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, createRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, createResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/request_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/request_test.go new file mode 100644 index 000000000000..4d590d5e7db9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/testing/request_test.go @@ -0,0 +1,34 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockCreateResponse(t) + + options := &shares.CreateOpts{Size: 1, Name: "my_test_share", ShareProto: "NFS", SnapshotID: "70bfbebc-d3ff-4528-8bbb-58422daa280b"} + _, err := shares.Create(client.ServiceClient(), options).Extract() + + if err == nil { + t.Fatal("Expected error") + } + + detailedErr := errors.ErrorDetails{} + e := errors.ExtractErrorInto(err, &detailedErr) + th.AssertNoErr(t, e) + + for k, msg := range detailedErr { + th.AssertEquals(t, k, "itemNotFound") + th.AssertEquals(t, msg.Code, 404) + th.AssertEquals(t, msg.Message, "ShareSnapshotNotFound: Snapshot 70bfbebc-d3ff-4528-8bbb-58422daa280b could not be found.") + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go new file mode 100644 index 000000000000..44e0d94bd138 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go @@ -0,0 +1,72 @@ +package messages + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Delete will delete the existing Message with the provided ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToMessageListQuery() (string, error) +} + +// ListOpts holds options for listing Messages. It is passed to the +// messages.List function. +type ListOpts struct { + // The message ID + ID string `q:"id"` + // The ID of the action during which the message was created + ActionID string `q:"action_id"` + // The ID of the message detail + DetailID string `q:"detail_id"` + // The message level + MessageLevel string `q:"message_level"` + // The UUID of the request during which the message was created + RequestID string `q:"request_id"` + // The UUID of the resource for which the message was created + ResourceID string `q:"resource_id"` + // The type of the resource for which the message was created + ResourceType string `q:"resource_type"` + // The key to sort a list of messages + SortKey string `q:"sort_key"` + // The key to sort a list of messages + SortDir string `q:"sort_dir"` + // The maximum number of messages to return + Limit int `q:"limit"` +} + +// ToMessageListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToMessageListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Messages optionally limited by the conditions provided in ListOpts. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToMessageListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return MessagePage{pagination.SinglePageBase(r)} + }) +} + +// Get retrieves the Message with the provided ID. To extract the Message +// object from the response, call the Extract method on the GetResult. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go new file mode 100644 index 000000000000..9c48ea07b8c8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go @@ -0,0 +1,99 @@ +package messages + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Message contains all the information associated with an OpenStack +// Message. +type Message struct { + // The message ID + ID string `json:"id"` + // The UUID of the project where the message was created + ProjectID string `json:"project_id"` + // The ID of the action during which the message was created + ActionID string `json:"action_id"` + // The ID of the message detail + DetailID string `json:"detail_id"` + // The message level + MessageLevel string `json:"message_level"` + // The UUID of the request during which the message was created + RequestID string `json:"request_id"` + // The UUID of the resource for which the message was created + ResourceID string `json:"resource_id"` + // The type of the resource for which the message was created + ResourceType string `json:"resource_type"` + // The message text + UserMessage string `json:"user_message"` + // The date and time stamp when the message was created + CreatedAt time.Time `json:"-"` + // The date and time stamp when the message will expire + ExpiresAt time.Time `json:"-"` +} + +func (r *Message) UnmarshalJSON(b []byte) error { + type tmp Message + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + ExpiresAt gophercloud.JSONRFC3339MilliNoZ `json:"expires_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Message(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.ExpiresAt = time.Time(s.ExpiresAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// MessagePage is a pagination.pager that is returned from a call to the List function. +type MessagePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a ListResult contains no Messages. +func (r MessagePage) IsEmpty() (bool, error) { + messages, err := ExtractMessages(r) + return len(messages) == 0, err +} + +// ExtractMessages extracts and returns Messages. It is used while +// iterating over a messages.List call. +func ExtractMessages(r pagination.Page) ([]Message, error) { + var s struct { + Messages []Message `json:"messages"` + } + err := (r.(MessagePage)).ExtractInto(&s) + return s.Messages, err +} + +// Extract will get the Message object out of the commonResult object. +func (r commonResult) Extract() (*Message, error) { + var s struct { + Message *Message `json:"message"` + } + err := r.ExtractInto(&s) + return s.Message, err +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult contains the response body and error from a Get request. +type GetResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/fixtures.go new file mode 100644 index 000000000000..da1c98adb979 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/fixtures.go @@ -0,0 +1,115 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func MockDeleteResponse(t *testing.T) { + th.Mux.HandleFunc("/messages/messageID", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} + +func MockListResponse(t *testing.T) { + th.Mux.HandleFunc("/messages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` + { + "messages": [ + { + "resource_id": "0d0b883f-95ef-406c-b930-55612ee48a6d", + "message_level": "ERROR", + "user_message": "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + "expires_at": "2019-01-06T08:53:38.000000", + "id": "143a6cc2-1998-44d0-8356-22070b0ebdaa", + "created_at": "2018-12-07T08:53:38.000000", + "detail_id": "004", + "request_id": "req-21767eee-22ca-40a4-b6c0-ae7d35cd434f", + "project_id": "a5e9d48232dc4aa59a716b5ced963584", + "resource_type": "SHARE", + "action_id": "002" + }, + { + "resource_id": "4336d74f-3bdc-4f27-9657-c01ec63680bf", + "message_level": "ERROR", + "user_message": "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + "expires_at": "2019-01-06T08:53:34.000000", + "id": "2076373e-13a7-4b84-9e67-15ce8cceaff8", + "created_at": "2018-12-07T08:53:34.000000", + "detail_id": "004", + "request_id": "req-957792ed-f38b-42db-a86a-850f815cbbe9", + "project_id": "a5e9d48232dc4aa59a716b5ced963584", + "resource_type": "SHARE", + "action_id": "002" + } + ] + }`) + }) +} + +func MockFilteredListResponse(t *testing.T) { + th.Mux.HandleFunc("/messages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` + { + "messages": [ + { + "resource_id": "4336d74f-3bdc-4f27-9657-c01ec63680bf", + "message_level": "ERROR", + "user_message": "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + "expires_at": "2019-01-06T08:53:34.000000", + "id": "2076373e-13a7-4b84-9e67-15ce8cceaff8", + "created_at": "2018-12-07T08:53:34.000000", + "detail_id": "004", + "request_id": "req-957792ed-f38b-42db-a86a-850f815cbbe9", + "project_id": "a5e9d48232dc4aa59a716b5ced963584", + "resource_type": "SHARE", + "action_id": "002" + } + ] + }`) + }) +} + +func MockGetResponse(t *testing.T) { + th.Mux.HandleFunc("/messages/2076373e-13a7-4b84-9e67-15ce8cceaff8", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ` + { + "message": { + "resource_id": "4336d74f-3bdc-4f27-9657-c01ec63680bf", + "message_level": "ERROR", + "user_message": "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + "expires_at": "2019-01-06T08:53:34.000000", + "id": "2076373e-13a7-4b84-9e67-15ce8cceaff8", + "created_at": "2018-12-07T08:53:34.000000", + "detail_id": "004", + "request_id": "req-957792ed-f38b-42db-a86a-850f815cbbe9", + "project_id": "a5e9d48232dc4aa59a716b5ced963584", + "resource_type": "SHARE", + "action_id": "002" + } + }`) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/requests_test.go new file mode 100644 index 000000000000..c92297c6c597 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/testing/requests_test.go @@ -0,0 +1,125 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// Verifies that message deletion works +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockDeleteResponse(t) + + res := messages.Delete(client.ServiceClient(), "messageID") + th.AssertNoErr(t, res.Err) +} + +// Verifies that messages can be listed correctly +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListResponse(t) + + allPages, err := messages.List(client.ServiceClient(), &messages.ListOpts{}).AllPages() + th.AssertNoErr(t, err) + actual, err := messages.ExtractMessages(allPages) + th.AssertNoErr(t, err) + expected := []messages.Message{ + { + ResourceID: "0d0b883f-95ef-406c-b930-55612ee48a6d", + MessageLevel: "ERROR", + UserMessage: "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + ExpiresAt: time.Date(2019, 1, 6, 8, 53, 38, 0, time.UTC), + ID: "143a6cc2-1998-44d0-8356-22070b0ebdaa", + CreatedAt: time.Date(2018, 12, 7, 8, 53, 38, 0, time.UTC), + DetailID: "004", + RequestID: "req-21767eee-22ca-40a4-b6c0-ae7d35cd434f", + ProjectID: "a5e9d48232dc4aa59a716b5ced963584", + ResourceType: "SHARE", + ActionID: "002", + }, + { + ResourceID: "4336d74f-3bdc-4f27-9657-c01ec63680bf", + MessageLevel: "ERROR", + UserMessage: "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + ExpiresAt: time.Date(2019, 1, 6, 8, 53, 34, 0, time.UTC), + ID: "2076373e-13a7-4b84-9e67-15ce8cceaff8", + CreatedAt: time.Date(2018, 12, 7, 8, 53, 34, 0, time.UTC), + DetailID: "004", + RequestID: "req-957792ed-f38b-42db-a86a-850f815cbbe9", + ProjectID: "a5e9d48232dc4aa59a716b5ced963584", + ResourceType: "SHARE", + ActionID: "002", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +// Verifies that messages list can be called with query parameters +func TestFilteredList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockFilteredListResponse(t) + + options := &messages.ListOpts{ + RequestID: "req-21767eee-22ca-40a4-b6c0-ae7d35cd434f", + } + + allPages, err := messages.List(client.ServiceClient(), options).AllPages() + th.AssertNoErr(t, err) + actual, err := messages.ExtractMessages(allPages) + th.AssertNoErr(t, err) + expected := []messages.Message{ + { + ResourceID: "4336d74f-3bdc-4f27-9657-c01ec63680bf", + MessageLevel: "ERROR", + UserMessage: "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + ExpiresAt: time.Date(2019, 1, 6, 8, 53, 34, 0, time.UTC), + ID: "2076373e-13a7-4b84-9e67-15ce8cceaff8", + CreatedAt: time.Date(2018, 12, 7, 8, 53, 34, 0, time.UTC), + DetailID: "004", + RequestID: "req-957792ed-f38b-42db-a86a-850f815cbbe9", + ProjectID: "a5e9d48232dc4aa59a716b5ced963584", + ResourceType: "SHARE", + ActionID: "002", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +// Verifies that it is possible to get a message +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockGetResponse(t) + + expected := messages.Message{ + ResourceID: "4336d74f-3bdc-4f27-9657-c01ec63680bf", + MessageLevel: "ERROR", + UserMessage: "create: Could not find an existing share server or allocate one on the share network provided. You may use a different share network, or verify the network details in the share network and retry your request. If this doesn't work, contact your administrator to troubleshoot issues with your network.", + ExpiresAt: time.Date(2019, 1, 6, 8, 53, 34, 0, time.UTC), + ID: "2076373e-13a7-4b84-9e67-15ce8cceaff8", + CreatedAt: time.Date(2018, 12, 7, 8, 53, 34, 0, time.UTC), + DetailID: "004", + RequestID: "req-957792ed-f38b-42db-a86a-850f815cbbe9", + ProjectID: "a5e9d48232dc4aa59a716b5ced963584", + ResourceType: "SHARE", + ActionID: "002", + } + + n, err := messages.Get(client.ServiceClient(), "2076373e-13a7-4b84-9e67-15ce8cceaff8").Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, &expected, n) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go new file mode 100644 index 000000000000..7c2c54a87f90 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go @@ -0,0 +1,15 @@ +package messages + +import "github.com/gophercloud/gophercloud" + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("messages") +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("messages", id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return getURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go index 8ef1ba1166a5..8bfec43cf8ff 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go @@ -32,6 +32,8 @@ type CreateOpts struct { Description string `json:"description,omitempty"` // The DNS IP address that is used inside the tenant network DNSIP string `json:"dns_ip,omitempty"` + // The security service organizational unit (OU). Minimum supported microversion for OU is 2.44. + OU string `json:"ou,omitempty"` // The security service user or group name that is used by the tenant User string `json:"user,omitempty"` // The user password, if you specify a user @@ -90,6 +92,8 @@ type ListOpts struct { Name string `q:"name"` // The DNS IP address that is used inside the tenant network DNSIP string `q:"dns_ip"` + // The security service organizational unit (OU). Minimum supported microversion for OU is 2.44. + OU string `q:"ou"` // The security service user or group name that is used by the tenant User string `q:"user"` // The security service host name or IP address @@ -138,21 +142,23 @@ type UpdateOptsBuilder interface { // the SecurityService object. type UpdateOpts struct { // The security service name - Name string `json:"name"` + Name *string `json:"name"` // The security service description - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // The security service type. A valid value is ldap, kerberos, or active_directory Type string `json:"type,omitempty"` // The DNS IP address that is used inside the tenant network - DNSIP string `json:"dns_ip,omitempty"` + DNSIP *string `json:"dns_ip,omitempty"` + // The security service organizational unit (OU). Minimum supported microversion for OU is 2.44. + OU *string `json:"ou,omitempty"` // The security service user or group name that is used by the tenant - User string `json:"user,omitempty"` + User *string `json:"user,omitempty"` // The user password, if you specify a user - Password string `json:"password,omitempty"` + Password *string `json:"password,omitempty"` // The security service domain - Domain string `json:"domain,omitempty"` + Domain *string `json:"domain,omitempty"` // The security service host name or IP address - Server string `json:"server,omitempty"` + Server *string `json:"server,omitempty"` } // ToSecurityServiceUpdateMap assembles a request body based on the contents of an diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go index ce18b8f76f30..355f7c76a261 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go @@ -27,6 +27,8 @@ type SecurityService struct { Description string `json:"description"` // The DNS IP address that is used inside the tenant network DNSIP string `json:"dns_ip"` + // The security service organizational unit (OU) + OU string `json:"ou"` // The security service user or group name that is used by the tenant User string `json:"user"` // The user password, if you specify a user diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go index 06d9153d3a8b..1dcd6353e14e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go @@ -201,7 +201,8 @@ func TestUpdate(t *testing.T) { Password: "supersecret", } - options := securityservices.UpdateOpts{Name: "SecServ2"} + name := "SecServ2" + options := securityservices.UpdateOpts{Name: &name} s, err := securityservices.Update(client.ServiceClient(), "securityServiceID", options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, s) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go index cdc026c01162..15e664ec388d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go @@ -135,9 +135,9 @@ type UpdateOptsBuilder interface { // the ShareNetwork object. type UpdateOpts struct { // The share network name - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // The share network description - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // The UUID of the Neutron network to set up for share servers NeutronNetID string `json:"neutron_net_id,omitempty"` // The UUID of the Neutron subnet to set up for share servers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go index efa4691861de..61beefca212c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go @@ -191,9 +191,11 @@ func TestUpdateNeutron(t *testing.T) { ProjectID: "16e1ab15c35a457e9c2b2aa189f544e1", } + name := "net_my2" + description := "new description" options := sharenetworks.UpdateOpts{ - Name: "net_my2", - Description: "new description", + Name: &name, + Description: &description, NeutronNetID: "new-neutron-id", NeutronSubnetID: "new-neutron-subnet-id", } @@ -226,9 +228,11 @@ func TestUpdateNova(t *testing.T) { ProjectID: "16e1ab15c35a457e9c2b2aa189f544e1", } + name := "net_my2" + description := "new description" options := sharenetworks.UpdateOpts{ - Name: "net_my2", - Description: "new description", + Name: &name, + Description: &description, NovaNetID: "new-nova-id", } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go index 27bc43b56359..2028b4831e0d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go @@ -1,6 +1,9 @@ package shares -import "github.com/gophercloud/gophercloud" +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) // CreateOptsBuilder allows extensions to add additional parameters to the // Create request. @@ -24,7 +27,7 @@ type CreateOpts struct { // DisplayName is equivalent to Name. The API supports using both // This is an inherited attribute from the block storage API DisplayName string `json:"display_name,omitempty"` - // DisplayDescription is equivalent to Description. The API supports using bot + // DisplayDescription is equivalent to Description. The API supports using both // This is an inherited attribute from the block storage API DisplayDescription string `json:"display_description,omitempty"` // ShareType defines the sharetype. If omitted, a default share type is used @@ -66,6 +69,99 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } +// ListOpts holds options for listing Shares. It is passed to the +// shares.List function. +type ListOpts struct { + // (Admin only). Defines whether to list the requested resources for all projects. + AllTenants bool `q:"all_tenants"` + // The share name. + Name string `q:"name"` + // Filters by a share status. + Status string `q:"status"` + // The UUID of the share server. + ShareServerID string `q:"share_server_id"` + // One or more metadata key and value pairs as a dictionary of strings. + Metadata map[string]string `q:"metadata"` + // The extra specifications for the share type. + ExtraSpecs map[string]string `q:"extra_specs"` + // The UUID of the share type. + ShareTypeID string `q:"share_type_id"` + // The maximum number of shares to return. + Limit int `q:"limit"` + // The offset to define start point of share or share group listing. + Offset int `q:"offset"` + // The key to sort a list of shares. + SortKey string `q:"sort_key"` + // The direction to sort a list of shares. + SortDir string `q:"sort_dir"` + // The UUID of the share’s base snapshot to filter the request based on. + SnapshotID string `q:"snapshot_id"` + // The share host name. + Host string `q:"host"` + // The share network ID. + ShareNetworkID string `q:"share_network_id"` + // The UUID of the project in which the share was created. Useful with all_tenants parameter. + ProjectID string `q:"project_id"` + // The level of visibility for the share. + IsPublic *bool `q:"is_public"` + // The UUID of a share group to filter resource. + ShareGroupID string `q:"share_group_id"` + // The export location UUID that can be used to filter shares or share instances. + ExportLocationID string `q:"export_location_id"` + // The export location path that can be used to filter shares or share instances. + ExportLocationPath string `q:"export_location_path"` + // The name pattern that can be used to filter shares, share snapshots, share networks or share groups. + NamePattern string `q:"name~"` + // The description pattern that can be used to filter shares, share snapshots, share networks or share groups. + DescriptionPattern string `q:"description~"` + // Whether to show count in API response or not, default is False. + WithCount bool `q:"with_count"` + // DisplayName is equivalent to Name. The API supports using both + // This is an inherited attribute from the block storage API + DisplayName string `q:"display_name"` + // Equivalent to NamePattern. + DisplayNamePattern string `q:"display_name~"` + // VolumeTypeID is deprecated but supported. Either ShareTypeID or VolumeTypeID can be used + VolumeTypeID string `q:"volume_type_id"` + // The UUID of the share group snapshot. + ShareGroupSnapshotID string `q:"share_group_snapshot_id"` + // DisplayDescription is equivalent to Description. The API supports using both + // This is an inherited attribute from the block storage API + DisplayDescription string `q:"display_description"` + // Equivalent to DescriptionPattern + DisplayDescriptionPattern string `q:"display_description~"` +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToShareListQuery() (string, error) +} + +// ToShareListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToShareListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail returns []Share optionally limited by the conditions provided in ListOpts. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToShareListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + p := SharePage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) +} + // Delete will delete an existing Share with the given UUID. func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = client.Delete(deleteURL(client, id), nil) @@ -123,3 +219,163 @@ func GrantAccess(client *gophercloud.ServiceClient, id string, opts GrantAccessO }) return } + +// RevokeAccessOptsBuilder allows extensions to add additional parameters to the +// RevokeAccess request. +type RevokeAccessOptsBuilder interface { + ToRevokeAccessMap() (map[string]interface{}, error) +} + +// RevokeAccessOpts contains the options for creation of a RevokeAccess request. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Revoke Access documentation +type RevokeAccessOpts struct { + AccessID string `json:"access_id"` +} + +// ToRevokeAccessMap assembles a request body based on the contents of a +// RevokeAccessOpts. +func (opts RevokeAccessOpts) ToRevokeAccessMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "deny_access") +} + +// RevokeAccess will revoke an existing access to a Share based on the values in RevokeAccessOpts. +// RevokeAccessResult contains only the error. To extract it, call the ExtractErr method on +// the RevokeAccessResult. Client must have Microversion set; minimum supported microversion +// for RevokeAccess is 2.7. +func RevokeAccess(client *gophercloud.ServiceClient, id string, opts RevokeAccessOptsBuilder) (r RevokeAccessResult) { + b, err := opts.ToRevokeAccessMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(revokeAccessURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + return +} + +// ListAccessRights lists all access rules assigned to a Share based on its id. To extract +// the AccessRight slice from the response, call the Extract method on the ListAccessRightsResult. +// Client must have Microversion set; minimum supported microversion for ListAccessRights is 2.7. +func ListAccessRights(client *gophercloud.ServiceClient, id string) (r ListAccessRightsResult) { + requestBody := map[string]interface{}{"access_list": nil} + _, r.Err = client.Post(listAccessRightsURL(client, id), requestBody, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ExtendOptsBuilder allows extensions to add additional parameters to the +// Extend request. +type ExtendOptsBuilder interface { + ToShareExtendMap() (map[string]interface{}, error) +} + +// ExtendOpts contains options for extending a Share. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Extend share documentation +type ExtendOpts struct { + // New size in GBs. + NewSize int `json:"new_size"` +} + +// ToShareExtendMap assembles a request body based on the contents of a +// ExtendOpts. +func (opts ExtendOpts) ToShareExtendMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "extend") +} + +// Extend will extend the capacity of an existing share. ExtendResult contains only the error. +// To extract it, call the ExtractErr method on the ExtendResult. +// Client must have Microversion set; minimum supported microversion for Extend is 2.7. +func Extend(client *gophercloud.ServiceClient, id string, opts ExtendOptsBuilder) (r ExtendResult) { + b, err := opts.ToShareExtendMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(extendURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// ShrinkOptsBuilder allows extensions to add additional parameters to the +// Shrink request. +type ShrinkOptsBuilder interface { + ToShareShrinkMap() (map[string]interface{}, error) +} + +// ShrinkOpts contains options for shrinking a Share. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Shrink share documentation +type ShrinkOpts struct { + // New size in GBs. + NewSize int `json:"new_size"` +} + +// ToShareShrinkMap assembles a request body based on the contents of a +// ShrinkOpts. +func (opts ShrinkOpts) ToShareShrinkMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "shrink") +} + +// Shrink will shrink the capacity of an existing share. ShrinkResult contains only the error. +// To extract it, call the ExtractErr method on the ShrinkResult. +// Client must have Microversion set; minimum supported microversion for Shrink is 2.7. +func Shrink(client *gophercloud.ServiceClient, id string, opts ShrinkOptsBuilder) (r ShrinkResult) { + b, err := opts.ToShareShrinkMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(shrinkURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToShareUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Share. This object is passed +// to the share.Update function. For more information about the parameters, see +// the Share object. +type UpdateOpts struct { + // Share name. Manila share update logic doesn't have a "name" alias. + DisplayName *string `json:"display_name,omitempty"` + // Share description. Manila share update logic doesn't have a "description" alias. + DisplayDescription *string `json:"display_description,omitempty"` + // Determines whether or not the share is public + IsPublic *bool `json:"is_public,omitempty"` +} + +// ToShareUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToShareUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "share") +} + +// Update will update the Share with provided information. To extract the updated +// Share from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToShareUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go index b76f86afb0c5..0deae373b68e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go @@ -2,9 +2,16 @@ package shares import ( "encoding/json" + "net/url" + "strconv" "time" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +const ( + invalidMarker = "-1" ) // Share contains all information associated with an OpenStack Share @@ -64,6 +71,8 @@ type Share struct { SourceCgsnapshotMemberID string `json:"source_cgsnapshot_member_id"` // Timestamp when the share was created CreatedAt time.Time `json:"-"` + // Timestamp when the share was updated + UpdatedAt time.Time `json:"-"` } func (r *Share) UnmarshalJSON(b []byte) error { @@ -71,6 +80,7 @@ func (r *Share) UnmarshalJSON(b []byte) error { var s struct { tmp CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` } err := json.Unmarshal(b, &s) if err != nil { @@ -79,6 +89,7 @@ func (r *Share) UnmarshalJSON(b []byte) error { *r = Share(s.tmp) r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) return nil } @@ -101,6 +112,86 @@ type CreateResult struct { commonResult } +// SharePage is a pagination.pager that is returned from a call to the List function. +type SharePage struct { + pagination.MarkerPageBase +} + +// NextPageURL generates the URL for the page of results after this one. +func (r SharePage) NextPageURL() (string, error) { + currentURL := r.URL + mark, err := r.Owner.LastMarker() + if err != nil { + return "", err + } + if mark == invalidMarker { + return "", nil + } + + q := currentURL.Query() + q.Set("offset", mark) + currentURL.RawQuery = q.Encode() + return currentURL.String(), nil +} + +// LastMarker returns the last offset in a ListResult. +func (r SharePage) LastMarker() (string, error) { + shares, err := ExtractShares(r) + if err != nil { + return invalidMarker, err + } + if len(shares) == 0 { + return invalidMarker, nil + } + + u, err := url.Parse(r.URL.String()) + if err != nil { + return invalidMarker, err + } + queryParams := u.Query() + offset := queryParams.Get("offset") + limit := queryParams.Get("limit") + + // Limit is not present, only one page required + if limit == "" { + return invalidMarker, nil + } + + iOffset := 0 + if offset != "" { + iOffset, err = strconv.Atoi(offset) + if err != nil { + return invalidMarker, err + } + } + iLimit, err := strconv.Atoi(limit) + if err != nil { + return invalidMarker, err + } + iOffset = iOffset + iLimit + offset = strconv.Itoa(iOffset) + + return offset, nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (r SharePage) IsEmpty() (bool, error) { + shares, err := ExtractShares(r) + return len(shares) == 0, err +} + +// ExtractShares extracts and returns a Share slice. It is used while +// iterating over a shares.List call. +func ExtractShares(r pagination.Page) ([]Share, error) { + var s struct { + Shares []Share `json:"shares"` + } + + err := (r.(SharePage)).ExtractInto(&s) + + return s.Shares, err +} + // DeleteResult contains the response body and error from a Delete request. type DeleteResult struct { gophercloud.ErrResult @@ -111,6 +202,33 @@ type GetResult struct { commonResult } +// UpdateResult contains the response body and error from an Update request. +type UpdateResult struct { + commonResult +} + +// IDFromName is a convenience function that returns a share's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + r, err := ListDetail(client, &ListOpts{Name: name}).AllPages() + if err != nil { + return "", err + } + + ss, err := ExtractShares(r) + if err != nil { + return "", err + } + + switch len(ss) { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "share"} + case 1: + return ss[0].ID, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: len(ss), ResourceType: "share"} + } +} + // GetExportLocationsResult contains the result body and error from an // GetExportLocations request. type GetExportLocationsResult struct { @@ -177,3 +295,32 @@ func (r GrantAccessResult) Extract() (*AccessRight, error) { type GrantAccessResult struct { gophercloud.Result } + +// RevokeAccessResult contains the response body and error from a Revoke access request. +type RevokeAccessResult struct { + gophercloud.ErrResult +} + +// Extract will get a slice of AccessRight objects from the commonResult +func (r ListAccessRightsResult) Extract() ([]AccessRight, error) { + var s struct { + AccessRights []AccessRight `json:"access_list"` + } + err := r.ExtractInto(&s) + return s.AccessRights, err +} + +// ListAccessRightsResult contains the result body and error from a ListAccessRights request. +type ListAccessRightsResult struct { + gophercloud.Result +} + +// ExtendResult contains the response body and error from an Extend request. +type ExtendResult struct { + gophercloud.ErrResult +} + +// ShrinkResult contains the response body and error from a Shrink request. +type ShrinkResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/fixtures.go index 8ab2742df946..9048e9a9133a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/fixtures.go @@ -90,6 +90,70 @@ func MockDeleteResponse(t *testing.T) { }) } +var updateRequest = `{ + "share": { + "display_name": "my_new_test_share", + "display_description": "", + "is_public": false + } + }` + +var updateResponse = ` +{ + "share": { + "links": [ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264", + "rel": "self" + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264", + "rel": "bookmark" + } + ], + "availability_zone": "nova", + "share_network_id": "713df749-aac0-4a54-af52-10f6c991e80c", + "export_locations": [], + "share_server_id": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + "share_group_id": null, + "snapshot_id": null, + "id": "011d21e2-fbc3-4e4a-9993-9ea223f73264", + "size": 1, + "share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7", + "share_type_name": "default", + "export_location": null, + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "metadata": { + "project": "my_app", + "aim": "doc" + }, + "status": "error", + "description": "", + "host": "manila2@generic1#GENERIC1", + "task_state": null, + "is_public": false, + "snapshot_support": true, + "name": "my_new_test_share", + "created_at": "2015-09-18T10:25:24.000000", + "share_proto": "NFS", + "volume_type": "default" + } +} +` + +func MockUpdateResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, updateRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, updateResponse) + }) +} + var getResponse = `{ "share": { "links": [ @@ -142,6 +206,73 @@ func MockGetResponse(t *testing.T) { }) } +var listDetailResponse = `{ + "shares": [ + { + "links": [ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264", + "rel": "self" + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264", + "rel": "bookmark" + } + ], + "availability_zone": "nova", + "share_network_id": "713df749-aac0-4a54-af52-10f6c991e80c", + "share_server_id": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + "snapshot_id": null, + "id": "011d21e2-fbc3-4e4a-9993-9ea223f73264", + "size": 1, + "share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7", + "share_type_name": "default", + "consistency_group_id": "9397c191-8427-4661-a2e8-b23820dc01d4", + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "metadata": { + "project": "my_app", + "aim": "doc" + }, + "status": "available", + "description": "My custom share London", + "host": "manila2@generic1#GENERIC1", + "has_replicas": false, + "replication_type": null, + "task_state": null, + "is_public": true, + "snapshot_support": true, + "name": "my_test_share", + "created_at": "2015-09-18T10:25:24.000000", + "share_proto": "NFS", + "volume_type": "default", + "source_cgsnapshot_member_id": null + } + ] + }` + +var listDetailEmptyResponse = `{"shares": []}` + +// MockListDetailResponse creates a mock detailed-list response +func MockListDetailResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { + 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) + + r.ParseForm() + marker := r.Form.Get("offset") + + switch marker { + case "": + fmt.Fprint(w, listDetailResponse) + default: + fmt.Fprint(w, listDetailEmptyResponse) + } + }) +} + var getExportLocationsResponse = `{ "export_locations": [ { @@ -159,6 +290,7 @@ func MockGetExportLocationsResponse(t *testing.T) { th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/export_locations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, getExportLocationsResponse) }) @@ -197,3 +329,92 @@ func MockGrantAccessResponse(t *testing.T) { fmt.Fprintf(w, grantAccessResponse) }) } + +var revokeAccessRequest = `{ + "deny_access": { + "access_id": "a2f226a5-cee8-430b-8a03-78a59bd84ee8" + } +}` + +// MockRevokeAccessResponse creates a mock revoke access response +func MockRevokeAccessResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { + 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, revokeAccessRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + }) +} + +var listAccessRightsRequest = `{ + "access_list": null + }` + +var listAccessRightsResponse = `{ + "access_list": [ + { + "share_id": "011d21e2-fbc3-4e4a-9993-9ea223f73264", + "access_type": "ip", + "access_to": "0.0.0.0/0", + "access_key": "", + "access_level": "rw", + "state": "new", + "id": "a2f226a5-cee8-430b-8a03-78a59bd84ee8" + } + ] + }` + +// MockListAccessRightsResponse creates a mock list access response +func MockListAccessRightsResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { + 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, listAccessRightsRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, listAccessRightsResponse) + }) +} + +var extendRequest = `{ + "extend": { + "new_size": 2 + } + }` + +// MockExtendResponse creates a mock extend share response +func MockExtendResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { + 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, extendRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + }) +} + +var shrinkRequest = `{ + "shrink": { + "new_size": 1 + } + }` + +// MockShrinkResponse creates a mock shrink share response +func MockShrinkResponse(t *testing.T) { + th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { + 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, shrinkRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/request_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/request_test.go index 443dd1fa4834..75053bd9f238 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/request_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/testing/request_test.go @@ -24,6 +24,28 @@ func TestCreate(t *testing.T) { th.AssertEquals(t, n.ShareProto, "NFS") } +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockUpdateResponse(t) + + name := "my_new_test_share" + description := "" + iFalse := false + options := &shares.UpdateOpts{ + DisplayName: &name, + DisplayDescription: &description, + IsPublic: &iFalse, + } + n, err := shares.Update(client.ServiceClient(), shareID, options).Extract() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n.Name, "my_new_test_share") + th.AssertEquals(t, n.Description, "") + th.AssertEquals(t, n.IsPublic, false) +} + func TestDelete(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -83,6 +105,62 @@ func TestGet(t *testing.T) { }) } +func TestListDetail(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListDetailResponse(t) + + allPages, err := shares.ListDetail(client.ServiceClient(), &shares.ListOpts{}).AllPages() + + th.AssertNoErr(t, err) + + actual, err := shares.ExtractShares(allPages) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, actual, []shares.Share{ + shares.Share{ + AvailabilityZone: "nova", + ShareNetworkID: "713df749-aac0-4a54-af52-10f6c991e80c", + ShareServerID: "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + SnapshotID: "", + ID: shareID, + Size: 1, + ShareType: "25747776-08e5-494f-ab40-a64b9d20d8f7", + ShareTypeName: "default", + ConsistencyGroupID: "9397c191-8427-4661-a2e8-b23820dc01d4", + ProjectID: "16e1ab15c35a457e9c2b2aa189f544e1", + Metadata: map[string]string{ + "project": "my_app", + "aim": "doc", + }, + Status: "available", + Description: "My custom share London", + Host: "manila2@generic1#GENERIC1", + HasReplicas: false, + ReplicationType: "", + TaskState: "", + SnapshotSupport: true, + Name: "my_test_share", + CreatedAt: time.Date(2015, time.September, 18, 10, 25, 24, 0, time.UTC), + ShareProto: "NFS", + VolumeType: "default", + SourceCgsnapshotMemberID: "", + IsPublic: true, + Links: []map[string]string{ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264", + "rel": "self", + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264", + "rel": "bookmark", + }, + }, + }, + }) +} + func TestGetExportLocationsSuccess(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -135,3 +213,73 @@ func TestGrantAcessSuccess(t *testing.T) { ID: "a2f226a5-cee8-430b-8a03-78a59bd84ee8", }) } + +func TestRevokeAccessSuccess(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockRevokeAccessResponse(t) + + c := client.ServiceClient() + // Client c must have Microversion set; minimum supported microversion for Revoke Access is 2.7 + c.Microversion = "2.7" + + options := &shares.RevokeAccessOpts{AccessID: "a2f226a5-cee8-430b-8a03-78a59bd84ee8"} + + err := shares.RevokeAccess(c, shareID, options).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestListAccessRightsSuccess(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListAccessRightsResponse(t) + + c := client.ServiceClient() + // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 + c.Microversion = "2.7" + + s, err := shares.ListAccessRights(c, shareID).Extract() + + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, s, []shares.AccessRight{ + { + ShareID: "011d21e2-fbc3-4e4a-9993-9ea223f73264", + AccessType: "ip", + AccessTo: "0.0.0.0/0", + AccessKey: "", + AccessLevel: "rw", + State: "new", + ID: "a2f226a5-cee8-430b-8a03-78a59bd84ee8", + }, + }) +} + +func TestExtendSuccess(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockExtendResponse(t) + + c := client.ServiceClient() + // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 + c.Microversion = "2.7" + + err := shares.Extend(c, shareID, &shares.ExtendOpts{NewSize: 2}).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestShrinkSuccess(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockShrinkResponse(t) + + c := client.ServiceClient() + // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 + c.Microversion = "2.7" + + err := shares.Shrink(c, shareID, &shares.ShrinkOpts{NewSize: 1}).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go index 38e1aa431e17..02fba24d2d09 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go @@ -6,6 +6,10 @@ func createURL(c *gophercloud.ServiceClient) string { return c.ServiceURL("shares") } +func listDetailURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("shares", "detail") +} + func deleteURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL("shares", id) } @@ -14,6 +18,10 @@ func getURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL("shares", id) } +func updateURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id) +} + func getExportLocationsURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL("shares", id, "export_locations") } @@ -21,3 +29,19 @@ func getExportLocationsURL(c *gophercloud.ServiceClient, id string) string { func grantAccessURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL("shares", id, "action") } + +func revokeAccessURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func listAccessRightsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func extendURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func shrinkURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go new file mode 100644 index 000000000000..1f2f7d7cc971 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go @@ -0,0 +1,161 @@ +package snapshots + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToSnapshotCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains the options for create a Snapshot. This object is +// passed to snapshots.Create(). For more information about these parameters, +// please refer to the Snapshot object, or the shared file systems API v2 +// documentation +type CreateOpts struct { + // The UUID of the share from which to create a snapshot + ShareID string `json:"share_id" required:"true"` + // Defines the snapshot name + Name string `json:"name,omitempty"` + // Defines the snapshot description + Description string `json:"description,omitempty"` + // DisplayName is equivalent to Name. The API supports using both + // This is an inherited attribute from the block storage API + DisplayName string `json:"display_name,omitempty"` + // DisplayDescription is equivalent to Description. The API supports using both + // This is an inherited attribute from the block storage API + DisplayDescription string `json:"display_description,omitempty"` +} + +// ToSnapshotCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "snapshot") +} + +// Create will create a new Snapshot based on the values in CreateOpts. To extract +// the Snapshot object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSnapshotCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// ListOpts holds options for listing Snapshots. It is passed to the +// snapshots.List function. +type ListOpts struct { + // (Admin only). Defines whether to list the requested resources for all projects. + AllTenants bool `q:"all_tenants"` + // The snapshot name. + Name string `q:"name"` + // Filter by a snapshot description. + Description string `q:"description"` + // Filters by a share from which the snapshot was created. + ShareID string `q:"share_id"` + // Filters by a snapshot size in GB. + Size int `q:"size"` + // Filters by a snapshot status. + Status string `q:"status"` + // The maximum number of snapshots to return. + Limit int `q:"limit"` + // The offset to define start point of snapshot or snapshot group listing. + Offset int `q:"offset"` + // The key to sort a list of snapshots. + SortKey string `q:"sort_key"` + // The direction to sort a list of snapshots. + SortDir string `q:"sort_dir"` + // The UUID of the project in which the snapshot was created. Useful with all_tenants parameter. + ProjectID string `q:"project_id"` + // The name pattern that can be used to filter snapshots, snapshot snapshots, snapshot networks or snapshot groups. + NamePattern string `q:"name~"` + // The description pattern that can be used to filter snapshots, snapshot snapshots, snapshot networks or snapshot groups. + DescriptionPattern string `q:"description~"` +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToSnapshotListQuery() (string, error) +} + +// ToSnapshotListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToSnapshotListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail returns []Snapshot optionally limited by the conditions provided in ListOpts. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(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 { + p := SnapshotPage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) +} + +// Delete will delete an existing Snapshot with the given UUID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get will get a single snapshot with given UUID +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToSnapshotUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Snapshot. This object is passed +// to the snapshot.Update function. For more information about the parameters, see +// the Snapshot object. +type UpdateOpts struct { + // Snapshot name. Manila snapshot update logic doesn't have a "name" alias. + DisplayName *string `json:"display_name,omitempty"` + // Snapshot description. Manila snapshot update logic doesn't have a "description" alias. + DisplayDescription *string `json:"display_description,omitempty"` +} + +// ToSnapshotUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToSnapshotUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "snapshot") +} + +// Update will update the Snapshot with provided information. To extract the updated +// Snapshot from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSnapshotUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go new file mode 100644 index 000000000000..4952dbd34aaa --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go @@ -0,0 +1,193 @@ +package snapshots + +import ( + "encoding/json" + "net/url" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +const ( + invalidMarker = "-1" +) + +// Snapshot contains all information associated with an OpenStack Snapshot +type Snapshot struct { + // The UUID of the snapshot + ID string `json:"id"` + // The name of the snapshot + Name string `json:"name,omitempty"` + // A description of the snapshot + Description string `json:"description,omitempty"` + // UUID of the share from which the snapshot was created + ShareID string `json:"share_id"` + // The shared file system protocol + ShareProto string `json:"share_proto"` + // Size of the snapshot share in GB + ShareSize int `json:"share_size"` + // Size of the snapshot in GB + Size int `json:"size"` + // The snapshot status + Status string `json:"status"` + // The UUID of the project in which the snapshot was created + ProjectID string `json:"project_id"` + // Timestamp when the snapshot was created + CreatedAt time.Time `json:"-"` + // Snapshot links for pagination + Links []map[string]string `json:"links"` +} + +func (r *Snapshot) UnmarshalJSON(b []byte) error { + type tmp Snapshot + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Snapshot(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Snapshot object from the commonResult +func (r commonResult) Extract() (*Snapshot, error) { + var s struct { + Snapshot *Snapshot `json:"snapshot"` + } + err := r.ExtractInto(&s) + return s.Snapshot, err +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// SnapshotPage is a pagination.pager that is returned from a call to the List function. +type SnapshotPage struct { + pagination.MarkerPageBase +} + +// NextPageURL generates the URL for the page of results after this one. +func (r SnapshotPage) NextPageURL() (string, error) { + currentURL := r.URL + mark, err := r.Owner.LastMarker() + if err != nil { + return "", err + } + if mark == invalidMarker { + return "", nil + } + + q := currentURL.Query() + q.Set("offset", mark) + currentURL.RawQuery = q.Encode() + return currentURL.String(), nil +} + +// LastMarker returns the last offset in a ListResult. +func (r SnapshotPage) LastMarker() (string, error) { + snapshots, err := ExtractSnapshots(r) + if err != nil { + return invalidMarker, err + } + if len(snapshots) == 0 { + return invalidMarker, nil + } + + u, err := url.Parse(r.URL.String()) + if err != nil { + return invalidMarker, err + } + queryParams := u.Query() + offset := queryParams.Get("offset") + limit := queryParams.Get("limit") + + // Limit is not present, only one page required + if limit == "" { + return invalidMarker, nil + } + + iOffset := 0 + if offset != "" { + iOffset, err = strconv.Atoi(offset) + if err != nil { + return invalidMarker, err + } + } + iLimit, err := strconv.Atoi(limit) + if err != nil { + return invalidMarker, err + } + iOffset = iOffset + iLimit + offset = strconv.Itoa(iOffset) + + return offset, nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (r SnapshotPage) IsEmpty() (bool, error) { + snapshots, err := ExtractSnapshots(r) + return len(snapshots) == 0, err +} + +// ExtractSnapshots extracts and returns a Snapshot slice. It is used while +// iterating over a snapshots.List call. +func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) { + var s struct { + Snapshots []Snapshot `json:"snapshots"` + } + + err := (r.(SnapshotPage)).ExtractInto(&s) + + return s.Snapshots, err +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult contains the response body and error from a Get request. +type GetResult struct { + commonResult +} + +// UpdateResult contains the response body and error from an Update request. +type UpdateResult struct { + commonResult +} + +// IDFromName is a convenience function that returns a snapshot's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + r, err := ListDetail(client, &ListOpts{Name: name}).AllPages() + if err != nil { + return "", err + } + + ss, err := ExtractSnapshots(r) + if err != nil { + return "", err + } + + switch len(ss) { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} + case 1: + return ss[0].ID, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: len(ss), ResourceType: "snapshot"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go new file mode 100644 index 000000000000..c02ef10c71a4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go @@ -0,0 +1,206 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +const ( + snapshotEndpoint = "/snapshots" + snapshotID = "bc082e99-3bdb-4400-b95e-b85c7a41622c" + shareID = "19865c43-3b91-48c9-85a0-7ac4d6bb0efe" +) + +var createRequest = `{ + "snapshot": { + "share_id": "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + "name": "test snapshot", + "description": "test description" + } +}` + +var createResponse = `{ + "snapshot": { + "status": "creating", + "share_id": "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + "description": "test description", + "links": [ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/9897f5ca-2559-4a4c-b761-d3439c0c9455", + "rel": "self" + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/9897f5ca-2559-4a4c-b761-d3439c0c9455", + "rel": "bookmark" + } + ], + "id": "bc082e99-3bdb-4400-b95e-b85c7a41622c", + "size": 1, + "user_id": "619e2ad074321cf246b03a89e95afee95fb26bb0b2d1fc7ba3bd30fcca25588a", + "name": "test snapshot", + "created_at": "2019-01-09T10:22:39.613550", + "share_proto": "NFS", + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "share_size": 1 + } +}` + +// MockCreateResponse creates a mock response +func MockCreateResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, createRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + fmt.Fprintf(w, createResponse) + }) +} + +// MockDeleteResponse creates a mock delete response +func MockDeleteResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusAccepted) + }) +} + +var updateRequest = `{ + "snapshot": { + "display_name": "my_new_test_snapshot", + "display_description": "" + } +}` + +var updateResponse = `{ + "snapshot": { + "status": "available", + "share_id": "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + "description": "", + "links": [ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/9897f5ca-2559-4a4c-b761-d3439c0c9455", + "rel": "self" + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/9897f5ca-2559-4a4c-b761-d3439c0c9455", + "rel": "bookmark" + } + ], + "id": "9897f5ca-2559-4a4c-b761-d3439c0c9455", + "size": 1, + "user_id": "619e2ad074321cf246b03a89e95afee95fb26bb0b2d1fc7ba3bd30fcca25588a", + "name": "my_new_test_snapshot", + "created_at": "2019-01-09T10:22:39.613550", + "share_proto": "NFS", + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "share_size": 1 + } +}` + +func MockUpdateResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, updateRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, updateResponse) + }) +} + +var getResponse = `{ + "snapshot": { + "status": "available", + "share_id": "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + "description": null, + "links": [ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "self" + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "bookmark" + } + ], + "id": "bc082e99-3bdb-4400-b95e-b85c7a41622c", + "size": 1, + "user_id": "619e2ad074321cf246b03a89e95afee95fb26bb0b2d1fc7ba3bd30fcca25588a", + "name": "new_app_snapshot", + "created_at": "2019-01-06T11:11:02.000000", + "share_proto": "NFS", + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "share_size": 1 + } +}` + +// MockGetResponse creates a mock get response +func MockGetResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, getResponse) + }) +} + +var listDetailResponse = `{ + "snapshots": [ + { + "status": "available", + "share_id": "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + "description": null, + "links": [ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "self" + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "bookmark" + } + ], + "id": "bc082e99-3bdb-4400-b95e-b85c7a41622c", + "size": 1, + "user_id": "619e2ad074321cf246b03a89e95afee95fb26bb0b2d1fc7ba3bd30fcca25588a", + "name": "new_app_snapshot", + "created_at": "2019-01-06T11:11:02.000000", + "share_proto": "NFS", + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "share_size": 1 + } + ] +}` + +var listDetailEmptyResponse = `{"snapshots": []}` + +// MockListDetailResponse creates a mock detailed-list response +func MockListDetailResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { + 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) + + r.ParseForm() + marker := r.Form.Get("offset") + + switch marker { + case "": + fmt.Fprint(w, listDetailResponse) + default: + fmt.Fprint(w, listDetailEmptyResponse) + } + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go new file mode 100644 index 000000000000..eb588e5fd0dd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go @@ -0,0 +1,127 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockCreateResponse(t) + + options := &snapshots.CreateOpts{ShareID: shareID, Name: "test snapshot", Description: "test description"} + n, err := snapshots.Create(client.ServiceClient(), options).Extract() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n.Name, "test snapshot") + th.AssertEquals(t, n.Description, "test description") + th.AssertEquals(t, n.ShareProto, "NFS") + th.AssertEquals(t, n.ShareSize, 1) + th.AssertEquals(t, n.Size, 1) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockUpdateResponse(t) + + name := "my_new_test_snapshot" + description := "" + options := &snapshots.UpdateOpts{ + DisplayName: &name, + DisplayDescription: &description, + } + n, err := snapshots.Update(client.ServiceClient(), snapshotID, options).Extract() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n.Name, "my_new_test_snapshot") + th.AssertEquals(t, n.Description, "") +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockDeleteResponse(t) + + result := snapshots.Delete(client.ServiceClient(), snapshotID) + th.AssertNoErr(t, result.Err) +} + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockGetResponse(t) + + s, err := snapshots.Get(client.ServiceClient(), snapshotID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, s, &snapshots.Snapshot{ + ID: snapshotID, + Name: "new_app_snapshot", + Description: "", + ShareID: "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + ShareProto: "NFS", + ShareSize: 1, + Size: 1, + Status: "available", + ProjectID: "16e1ab15c35a457e9c2b2aa189f544e1", + CreatedAt: time.Date(2019, time.January, 06, 11, 11, 02, 0, time.UTC), + Links: []map[string]string{ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "self", + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "bookmark", + }, + }, + }) +} + +func TestListDetail(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListDetailResponse(t) + + allPages, err := snapshots.ListDetail(client.ServiceClient(), &snapshots.ListOpts{}).AllPages() + + th.AssertNoErr(t, err) + + actual, err := snapshots.ExtractSnapshots(allPages) + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, actual, []snapshots.Snapshot{ + snapshots.Snapshot{ + ID: snapshotID, + Name: "new_app_snapshot", + Description: "", + ShareID: "19865c43-3b91-48c9-85a0-7ac4d6bb0efe", + ShareProto: "NFS", + ShareSize: 1, + Size: 1, + Status: "available", + ProjectID: "16e1ab15c35a457e9c2b2aa189f544e1", + CreatedAt: time.Date(2019, time.January, 06, 11, 11, 02, 0, time.UTC), + Links: []map[string]string{ + { + "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "self", + }, + { + "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/snapshots/bc082e99-3bdb-4400-b95e-b85c7a41622c", + "rel": "bookmark", + }, + }, + }, + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go new file mode 100644 index 000000000000..a07e3ec8739a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go @@ -0,0 +1,23 @@ +package snapshots + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("snapshots") +} + +func listDetailURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("snapshots", "detail") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go new file mode 100644 index 000000000000..40080f7af203 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go @@ -0,0 +1,28 @@ +package utils + +import ( + "net/url" + "regexp" + "strings" +) + +// BaseEndpoint will return a URL without the /vX.Y +// portion of the URL. +func BaseEndpoint(endpoint string) (string, error) { + u, err := url.Parse(endpoint) + if err != nil { + return "", err + } + + u.RawQuery, u.Fragment = "", "" + + path := u.Path + versionRe := regexp.MustCompile("v[0-9.]+/?") + + if version := versionRe.FindString(path); version != "" { + versionIndex := strings.Index(path, version) + u.Path = path[:versionIndex] + } + + return u.String(), nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/testing/base_endpoint_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/testing/base_endpoint_test.go new file mode 100644 index 000000000000..944536b8bfc0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/testing/base_endpoint_test.go @@ -0,0 +1,88 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/utils" + th "github.com/gophercloud/gophercloud/testhelper" +) + +type endpointTestCases struct { + Endpoint string + BaseEndpoint string +} + +func TestBaseEndpoint(t *testing.T) { + tests := []endpointTestCases{ + { + Endpoint: "http://example.com:5000/v3", + BaseEndpoint: "http://example.com:5000/", + }, + { + Endpoint: "http://example.com:5000/v3.6", + BaseEndpoint: "http://example.com:5000/", + }, + { + Endpoint: "http://example.com:5000/v2.0", + BaseEndpoint: "http://example.com:5000/", + }, + { + Endpoint: "http://example.com:5000/", + BaseEndpoint: "http://example.com:5000/", + }, + { + Endpoint: "http://example.com:5000", + BaseEndpoint: "http://example.com:5000", + }, + { + Endpoint: "http://example.com/identity/v3", + BaseEndpoint: "http://example.com/identity/", + }, + { + Endpoint: "http://example.com/identity/v3.6", + BaseEndpoint: "http://example.com/identity/", + }, + { + Endpoint: "http://example.com/identity/v2.0", + BaseEndpoint: "http://example.com/identity/", + }, + { + Endpoint: "http://example.com/identity/v2.0/projects", + BaseEndpoint: "http://example.com/identity/", + }, + { + Endpoint: "http://example.com/v2.0/projects", + BaseEndpoint: "http://example.com/", + }, + { + Endpoint: "http://example.com/identity/", + BaseEndpoint: "http://example.com/identity/", + }, + { + Endpoint: "http://dev.example.com:5000/v3", + BaseEndpoint: "http://dev.example.com:5000/", + }, + { + Endpoint: "http://dev.example.com:5000/v3.6", + BaseEndpoint: "http://dev.example.com:5000/", + }, + { + Endpoint: "http://dev.example.com/identity/", + BaseEndpoint: "http://dev.example.com/identity/", + }, + { + Endpoint: "http://dev.example.com/identity/v2.0/projects", + BaseEndpoint: "http://dev.example.com/identity/", + }, + { + Endpoint: "http://dev.example.com/identity/v3.6", + BaseEndpoint: "http://dev.example.com/identity/", + }, + } + + for _, test := range tests { + actual, err := utils.BaseEndpoint(test.Endpoint) + th.AssertNoErr(t, err) + th.AssertEquals(t, test.BaseEndpoint, actual) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/doc.go new file mode 100644 index 000000000000..36e36d4a6188 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/doc.go @@ -0,0 +1,73 @@ +/* +Package crontriggers provides interaction with the cron triggers API in the OpenStack Mistral service. + +Cron trigger is an object that allows to run Mistral workflows according to a time pattern (Unix crontab patterns format). +Once a trigger is created it will run a specified workflow according to its properties: pattern, first_execution_time and remaining_executions. + +List cron triggers + +To filter cron triggers from a list request, you can use advanced filters with special FilterType to check for equality, non equality, values greater or lower, etc. +Default Filter checks equality, but you can override it with provided filter type. + + listOpts := crontriggers.ListOpts{ + WorkflowName: &executions.ListFilter{ + Value: "Workflow1,Workflow2", + Filter: executions.FilterIN, + }, + CreatedAt: &executions.ListDateFilter{ + Value: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC), + Filter: executions.FilterGTE, + }, + } + + allPages, err := crontriggers.List(mistralClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allCrontriggers, err := crontriggers.ExtractCronTriggers(allPages) + if err != nil { + panic(err) + } + + for _, ct := range allCrontriggers { + fmt.Printf("%+v\n", ct) + } + +Create a cron trigger. This example will start the workflow "echo" each day at 8am, and it will end after 10 executions. + + createOpts := &crontriggers.CreateOpts{ + Name: "daily", + Pattern: "0 8 * * *", + WorkflowName: "echo", + RemainingExecutions: 10, + WorkflowParams: map[string]interface{}{ + "msg": "hello", + }, + WorkflowInput: map[string]interface{}{ + "msg": "world", + }, + } + crontrigger, err := crontriggers.Create(mistralClient, opts).Extract() + if err != nil { + panic(err) + } + +Get a cron trigger + + crontrigger, err := crontriggers.Get(mistralClient, "0520ffd8-f7f1-4f2e-845b-55d953a1cf46").Extract() + if err != nil { + panic(err) + } + + fmt.Printf(%+v\n", crontrigger) + +Delete a cron trigger + + res := crontriggers.Delete(mistralClient, "0520ffd8-f7f1-4f2e-845b-55d953a1cf46") + if res.Err != nil { + panic(res.Err) + } + +*/ +package crontriggers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/requests.go new file mode 100644 index 000000000000..2d9ee99f5ea4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/requests.go @@ -0,0 +1,254 @@ +package crontriggers + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extension to add additional parameters to the Create request. +type CreateOptsBuilder interface { + ToCronTriggerCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters used to create a cron trigger. +type CreateOpts struct { + // Name is the cron trigger name. + Name string `json:"name"` + + // Pattern is a Unix crontab patterns format to execute the workflow. + Pattern string `json:"pattern"` + + // RemainingExecutions sets the number of executions for the trigger. + RemainingExecutions int `json:"remaining_executions,omitempty"` + + // WorkflowID is the unique id of the workflow. + WorkflowID string `json:"workflow_id,omitempty" or:"WorkflowName"` + + // WorkflowName is the name of the workflow. + // It is recommended to refer to workflow by the WorkflowID parameter instead of WorkflowName. + WorkflowName string `json:"workflow_name,omitempty" or:"WorkflowID"` + + // WorkflowParams defines workflow type specific parameters. + WorkflowParams map[string]interface{} `json:"workflow_params,omitempty"` + + // WorkflowInput defines workflow input values. + WorkflowInput map[string]interface{} `json:"workflow_input,omitempty"` + + // FirstExecutionTime defines the first execution time of the trigger. + FirstExecutionTime *time.Time `json:"-"` +} + +// ToCronTriggerCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToCronTriggerCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.FirstExecutionTime != nil { + b["first_execution_time"] = opts.FirstExecutionTime.Format("2006-01-02 15:04") + } + + return b, nil +} + +// Create requests the creation of a new cron trigger. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToCronTriggerCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), b, &r.Body, nil) + + return +} + +// Delete deletes the specified cron trigger. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get retrieves details of a single cron trigger. +// Use Extract to convert its result into an CronTrigger. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extension to add additional parameters to the List request. +type ListOptsBuilder interface { + ToCronTriggerListQuery() (string, error) +} + +// ListOpts filters the result returned by the List() function. +type ListOpts struct { + // WorkflowName allows to filter by workflow name. + WorkflowName *ListFilter `q:"-"` + // WorkflowID allows to filter by workflow id. + WorkflowID string `q:"workflow_id"` + // WorkflowInput allows to filter by specific workflow inputs. + WorkflowInput map[string]interface{} `q:"-"` + // WorkflowParams allows to filter by specific workflow parameters. + WorkflowParams map[string]interface{} `q:"-"` + // Scope filters by the trigger's scope. + // Values can be "private" or "public". + Scope string `q:"scope"` + // Name allows to filter by trigger name. + Name *ListFilter `q:"-"` + // Pattern allows to filter by pattern. + Pattern *ListFilter `q:"-"` + // RemainingExecutions allows to filter by remaining executions. + RemainingExecutions *ListIntFilter `q:"-"` + // FirstExecutionTime allows to filter by first execution time. + FirstExecutionTime *ListDateFilter `q:"-"` + // NextExecutionTime allows to filter by next execution time. + NextExecutionTime *ListDateFilter `q:"-"` + // CreatedAt allows to filter by trigger creation date. + CreatedAt *ListDateFilter `q:"-"` + // UpdatedAt allows to filter by trigger last update date. + UpdatedAt *ListDateFilter `q:"-"` + // ProjectID allows to filter by given project id. Admin required. + ProjectID string `q:"project_id"` + // AllProjects requests to get executions of all projects. Admin required. + AllProjects int `q:"all_projects"` + // SortDirs allows to select sort direction. + // It can be "asc" or "desc" (default). + SortDirs string `q:"sort_dirs"` + // SortKeys allows to sort by one of the cron trigger attributes. + SortKeys string `q:"sort_key"` + // Marker and Limit control paging. + // Marker instructs List where to start listing from. + Marker string `q:"marker"` + // Limit instructs List to refrain from sending excessively large lists of + // cron triggers. + Limit int `q:"limit"` +} + +// ListFilter allows to filter string parameters with different filters. +// Empty value for Filter checks for equality. +type ListFilter struct { + Filter FilterType + Value string +} + +func (l ListFilter) String() string { + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, l.Value) + } + return l.Value +} + +// ListDateFilter allows to filter date parameters with different filters. +// Empty value for Filter checks for equality. +type ListDateFilter struct { + Filter FilterType + Value time.Time +} + +func (l ListDateFilter) String() string { + v := l.Value.Format(gophercloud.RFC3339ZNoTNoZ) + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, v) + } + return v +} + +// ListIntFilter allows to filter integer parameters with different filters. +// Empty value for Filter checks for equality. +type ListIntFilter struct { + Filter FilterType + Value int +} + +func (l ListIntFilter) String() string { + v := fmt.Sprintf("%d", l.Value) + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, v) + } + return v +} + +// FilterType represents a valid filter to use for filtering executions. +type FilterType string + +const ( + // FilterEQ checks equality. + FilterEQ = "eq" + // FilterNEQ checks non equality. + FilterNEQ = "neq" + // FilterIN checks for belonging in a list, comma separated. + FilterIN = "in" + // FilterNIN checks for values that does not belong from a list, comma separated. + FilterNIN = "nin" + // FilterGT checks for values strictly greater. + FilterGT = "gt" + // FilterGTE checks for values greater or equal. + FilterGTE = "gte" + // FilterLT checks for values strictly lower. + FilterLT = "lt" + // FilterLTE checks for values lower or equal. + FilterLTE = "lte" + // FilterHas checks for values that contains the requested parameter. + FilterHas = "has" +) + +// ToCronTriggerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToCronTriggerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + params := q.Query() + + for queryParam, value := range map[string]map[string]interface{}{"workflow_params": opts.WorkflowParams, "workflow_input": opts.WorkflowInput} { + if value != nil { + b, err := json.Marshal(value) + if err != nil { + return "", err + } + params.Add(queryParam, string(b)) + } + } + + for queryParam, value := range map[string]fmt.Stringer{ + "workflow_name": opts.WorkflowName, + "name": opts.Name, + "pattern": opts.Pattern, + "remaining_executions": opts.RemainingExecutions, + "first_execution_time": opts.FirstExecutionTime, + "next_execution_time": opts.NextExecutionTime, + "created_at": opts.CreatedAt, + "updated_at": opts.UpdatedAt, + } { + if !reflect.ValueOf(value).IsNil() { + params.Add(queryParam, value.String()) + } + } + q = &url.URL{RawQuery: params.Encode()} + return q.String(), nil +} + +// List performs a call to list cron triggers. +// You may provide options to filter the results. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToCronTriggerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return CronTriggerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/results.go new file mode 100644 index 000000000000..5a1b4f248516 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/results.go @@ -0,0 +1,156 @@ +package crontriggers + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Post operations. Call its Extract method to interpret it as a CronTrigger. +type CreateResult struct { + commonResult +} + +// GetResult is the response of Get operations. Call its Extract method to interpret it as a CronTrigger. +type GetResult struct { + commonResult +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr method to determine the success of the call. +type DeleteResult struct { + gophercloud.ErrResult +} + +// Extract helps to get a CronTrigger struct from a Get or a Create function. +func (r commonResult) Extract() (*CronTrigger, error) { + var s CronTrigger + err := r.ExtractInto(&s) + return &s, err +} + +// CronTrigger represents a workflow cron trigger on OpenStack mistral API. +type CronTrigger struct { + // ID is the cron trigger's unique ID. + ID string `json:"id"` + + // Name is the name of the cron trigger. + Name string `json:"name"` + + // Pattern is the cron-like style pattern to execute the workflow. + // Example of value: "* * * * *" + Pattern string `json:"pattern"` + + // ProjectID is the project id owner of the cron trigger. + ProjectID string `json:"project_id"` + + // RemainingExecutions is the number of remaining executions of this trigger. + RemainingExecutions int `json:"remaining_executions"` + + // Scope is the scope of the trigger. + // Values can be "private" or "public". + Scope string `json:"scope"` + + // WorkflowID is the ID of the workflow linked to the trigger. + WorkflowID string `json:"workflow_id"` + + // WorkflowName is the name of the workflow linked to the trigger. + WorkflowName string `json:"workflow_name"` + + // WorkflowInput contains the workflow input values. + WorkflowInput map[string]interface{} `json:"-"` + + // WorkflowParams contains workflow type specific parameters. + WorkflowParams map[string]interface{} `json:"-"` + + // CreatedAt contains the cron trigger creation date. + CreatedAt time.Time `json:"-"` + + // FirstExecutionTime is the date of the first execution of the trigger. + FirstExecutionTime *time.Time `json:"-"` + + // NextExecutionTime is the date of the next execution of the trigger. + NextExecutionTime *time.Time `json:"-"` +} + +// UnmarshalJSON implements unmarshalling custom types +func (r *CronTrigger) UnmarshalJSON(b []byte) error { + type tmp CronTrigger + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + FirstExecutionTime *gophercloud.JSONRFC3339ZNoTNoZ `json:"first_execution_time"` + NextExecutionTime *gophercloud.JSONRFC3339ZNoTNoZ `json:"next_execution_time"` + WorkflowInput string `json:"workflow_input"` + WorkflowParams string `json:"workflow_params"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = CronTrigger(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + if s.FirstExecutionTime != nil { + t := time.Time(*s.FirstExecutionTime) + r.FirstExecutionTime = &t + } + + if s.NextExecutionTime != nil { + t := time.Time(*s.NextExecutionTime) + r.NextExecutionTime = &t + } + + if s.WorkflowInput != "" { + if err := json.Unmarshal([]byte(s.WorkflowInput), &r.WorkflowInput); err != nil { + return err + } + } + + if s.WorkflowParams != "" { + if err := json.Unmarshal([]byte(s.WorkflowParams), &r.WorkflowParams); err != nil { + return err + } + } + + return nil +} + +// CronTriggerPage contains a single page of all cron triggers from a List call. +type CronTriggerPage struct { + pagination.LinkedPageBase +} + +// IsEmpty checks if an CronTriggerPage contains any results. +func (r CronTriggerPage) IsEmpty() (bool, error) { + exec, err := ExtractCronTriggers(r) + return len(exec) == 0, err +} + +// NextPageURL finds the next page URL in a page in order to navigate to the next page of results. +func (r CronTriggerPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, nil +} + +// ExtractCronTriggers get the list of cron triggers from a page acquired from the List call. +func ExtractCronTriggers(r pagination.Page) ([]CronTrigger, error) { + var s struct { + CronTriggers []CronTrigger `json:"cron_triggers"` + } + err := (r.(CronTriggerPage)).ExtractInto(&s) + return s.CronTriggers, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/testing/requests_test.go new file mode 100644 index 000000000000..ef911406ebcd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/testing/requests_test.go @@ -0,0 +1,281 @@ +package testing + +import ( + "fmt" + "net/http" + "net/url" + "reflect" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateCronTrigger(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/cron_triggers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusCreated) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ` + { + "created_at": "2018-09-12 15:48:18", + "first_execution_time": "2018-09-12 17:48:00", + "id": "0520ffd8-f7f1-4f2e-845b-55d953a1cf46", + "name": "crontrigger", + "next_execution_time": "2018-09-12 17:48:00", + "pattern": "0 0 1 1 *", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "remaining_executions": 42, + "scope": "private", + "updated_at": null, + "workflow_id": "604a3a1e-94e3-4066-a34a-aa56873ef236", + "workflow_input": "{\"msg\": \"hello\"}", + "workflow_name": "workflow_echo", + "workflow_params": "{\"msg\": \"world\"}" + } + `) + }) + + firstExecution := time.Date(2018, time.September, 12, 17, 48, 0, 0, time.UTC) + opts := &crontriggers.CreateOpts{ + WorkflowID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + Name: "trigger", + FirstExecutionTime: &firstExecution, + WorkflowParams: map[string]interface{}{ + "msg": "world", + }, + WorkflowInput: map[string]interface{}{ + "msg": "hello", + }, + } + + actual, err := crontriggers.Create(fake.ServiceClient(), opts).Extract() + if err != nil { + t.Fatalf("Unable to create cron trigger: %v", err) + } + + expected := &crontriggers.CronTrigger{ + ID: "0520ffd8-f7f1-4f2e-845b-55d953a1cf46", + Name: "crontrigger", + Pattern: "0 0 1 1 *", + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + RemainingExecutions: 42, + Scope: "private", + WorkflowID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + WorkflowName: "workflow_echo", + WorkflowParams: map[string]interface{}{ + "msg": "world", + }, + WorkflowInput: map[string]interface{}{ + "msg": "hello", + }, + CreatedAt: time.Date(2018, time.September, 12, 15, 48, 18, 0, time.UTC), + FirstExecutionTime: &firstExecution, + NextExecutionTime: &firstExecution, + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + +func TestDeleteCronTrigger(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/cron_triggers/0520ffd8-f7f1-4f2e-845b-55d953a1cf46", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusAccepted) + }) + + res := crontriggers.Delete(fake.ServiceClient(), "0520ffd8-f7f1-4f2e-845b-55d953a1cf46") + th.AssertNoErr(t, res.Err) +} + +func TestGetCronTrigger(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/cron_triggers/0520ffd8-f7f1-4f2e-845b-55d953a1cf46", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ` + { + "created_at": "2018-09-12 15:48:18", + "first_execution_time": "2018-09-12 17:48:00", + "id": "0520ffd8-f7f1-4f2e-845b-55d953a1cf46", + "name": "crontrigger", + "next_execution_time": "2018-09-12 17:48:00", + "pattern": "0 0 1 1 *", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "remaining_executions": 42, + "scope": "private", + "updated_at": null, + "workflow_id": "604a3a1e-94e3-4066-a34a-aa56873ef236", + "workflow_input": "{\"msg\": \"hello\"}", + "workflow_name": "workflow_echo", + "workflow_params": "{\"msg\": \"world\"}" + } + `) + }) + actual, err := crontriggers.Get(fake.ServiceClient(), "0520ffd8-f7f1-4f2e-845b-55d953a1cf46").Extract() + if err != nil { + t.Fatalf("Unable to get cron trigger: %v", err) + } + + firstExecution := time.Date(2018, time.September, 12, 17, 48, 0, 0, time.UTC) + + expected := &crontriggers.CronTrigger{ + ID: "0520ffd8-f7f1-4f2e-845b-55d953a1cf46", + Name: "crontrigger", + Pattern: "0 0 1 1 *", + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + RemainingExecutions: 42, + Scope: "private", + WorkflowID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + WorkflowName: "workflow_echo", + WorkflowParams: map[string]interface{}{ + "msg": "world", + }, + WorkflowInput: map[string]interface{}{ + "msg": "hello", + }, + CreatedAt: time.Date(2018, time.September, 12, 15, 48, 18, 0, time.UTC), + FirstExecutionTime: &firstExecution, + NextExecutionTime: &firstExecution, + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + +func TestListCronTriggers(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/cron_triggers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, `{ + "cron_triggers": [ + { + "created_at": "2018-09-12 15:48:18", + "first_execution_time": "2018-09-12 17:48:00", + "id": "0520ffd8-f7f1-4f2e-845b-55d953a1cf46", + "name": "crontrigger", + "next_execution_time": "2018-09-12 17:48:00", + "pattern": "0 0 1 1 *", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "remaining_executions": 42, + "scope": "private", + "updated_at": null, + "workflow_id": "604a3a1e-94e3-4066-a34a-aa56873ef236", + "workflow_input": "{\"msg\": \"hello\"}", + "workflow_name": "workflow_echo", + "workflow_params": "{\"msg\": \"world\"}" + } + ], + "next": "%s/cron_triggers?marker=0520ffd8-f7f1-4f2e-845b-55d953a1cf46" + }`, th.Server.URL) + case "0520ffd8-f7f1-4f2e-845b-55d953a1cf46": + fmt.Fprintf(w, `{ "cron_triggers": [] }`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) + pages := 0 + // Get all cron triggers + err := crontriggers.List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + pages++ + actual, err := crontriggers.ExtractCronTriggers(page) + if err != nil { + return false, err + } + + firstExecution := time.Date(2018, time.September, 12, 17, 48, 0, 0, time.UTC) + + expected := []crontriggers.CronTrigger{ + crontriggers.CronTrigger{ + ID: "0520ffd8-f7f1-4f2e-845b-55d953a1cf46", + Name: "crontrigger", + Pattern: "0 0 1 1 *", + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + RemainingExecutions: 42, + Scope: "private", + WorkflowID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + WorkflowName: "workflow_echo", + WorkflowParams: map[string]interface{}{ + "msg": "world", + }, + WorkflowInput: map[string]interface{}{ + "msg": "hello", + }, + CreatedAt: time.Date(2018, time.September, 12, 15, 48, 18, 0, time.UTC), + FirstExecutionTime: &firstExecution, + NextExecutionTime: &firstExecution, + }, + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } + return true, nil + }) + if err != nil { + t.Fatal(err) + } + if pages != 1 { + t.Errorf("Expected one page, got %d", pages) + } +} + +func TestToExecutionListQuery(t *testing.T) { + for expected, opts := range map[string]*crontriggers.ListOpts{ + newValue("workflow_input", `{"msg":"Hello"}`): &crontriggers.ListOpts{ + WorkflowInput: map[string]interface{}{ + "msg": "Hello", + }, + }, + newValue("name", `neq:not_name`): &crontriggers.ListOpts{ + Name: &crontriggers.ListFilter{ + Filter: crontriggers.FilterNEQ, + Value: "not_name", + }, + }, + newValue("workflow_name", `eq:workflow`): &crontriggers.ListOpts{ + WorkflowName: &crontriggers.ListFilter{ + Filter: crontriggers.FilterEQ, + Value: "workflow", + }, + }, + newValue("created_at", `gt:2018-01-01 00:00:00`): &crontriggers.ListOpts{ + CreatedAt: &crontriggers.ListDateFilter{ + Filter: crontriggers.FilterGT, + Value: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + }, + } { + actual, _ := opts.ToCronTriggerListQuery() + th.AssertEquals(t, expected, actual) + } +} + +func newValue(param, value string) string { + v := url.Values{} + v.Add(param, value) + return "?" + v.Encode() +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/urls.go new file mode 100644 index 000000000000..853dbe30c43d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/crontriggers/urls.go @@ -0,0 +1,19 @@ +package crontriggers + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("cron_triggers") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("cron_triggers", id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("cron_triggers", id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("cron_triggers") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/doc.go new file mode 100644 index 000000000000..6e1777bd5a85 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/doc.go @@ -0,0 +1,70 @@ +/* +Package executions provides interaction with the execution API in the OpenStack Mistral service. + +An execution is a one-shot execution of a specific workflow. Each execution contains all information about workflow itself, about execution process, state, input and output data. + +An execution represents also the execution of a cron trigger. Each run of a cron trigger will generate an execution. + +List executions + +To filter executions from a list request, you can use advanced filters with special FilterType to check for equality, non equality, values greater or lower, etc. +Default Filter checks equality, but you can override it with provided filter type. + + // List all executions from a given workflow list with a creation date upper than 2018-01-01 00:00:00 + listOpts := executions.ListOpts{ + WorkflowName: &executions.ListFilter{ + Value: "Workflow1,Workflow2", + Filter: executions.FilterIN, + }, + CreatedAt: &executions.ListDateFilter{ + Value: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC), + Filter: executions.FilterGTE, + }, + } + + allPages, err := executions.List(mistralClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allExecutions, err := executions.ExtractExecutions(allPages) + if err != nil { + panic(err) + } + + for _, ex := range allExecutions { + fmt.Printf("%+v\n", ex) + } + +Create an execution + + createOpts := &executions.CreateOpts{ + WorkflowID: "6656c143-a009-4bcb-9814-cc100a20bbfa", + Input: map[string]interface{}{ + "msg": "Hello", + }, + Description: "this is a description", + } + + execution, err := executions.Create(mistralClient, opts).Extract() + if err != nil { + panic(err) + } + +Get an execution + + execution, err := executions.Get(mistralClient, "50bb59f1-eb77-4017-a77f-6d575b002667").Extract() + if err != nil { + panic(err) + } + fmt.Printf(%+v\n", execution) + +Delete an execution + + res := executions.Delete(mistralClient, "50bb59f1-eb77-4017-a77f-6d575b002667") + if res.Err != nil { + panic(res.Err) + } + +*/ +package executions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/requests.go new file mode 100644 index 000000000000..ea35499413e4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/requests.go @@ -0,0 +1,239 @@ +package executions + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extension to add additional parameters to the Create request. +type CreateOptsBuilder interface { + ToExecutionCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters used to create an execution. +type CreateOpts struct { + // ID is the unique ID of the execution. + ID string `json:"id,omitempty"` + + // SourceExecutionID can be set to create an execution based on another existing execution. + SourceExecutionID string `json:"source_execution_id,omitempty"` + + // WorkflowID is the unique id of the workflow. + WorkflowID string `json:"workflow_id,omitempty" or:"WorkflowName"` + + // WorkflowName is the name identifier of the workflow. + WorkflowName string `json:"workflow_name,omitempty" or:"WorkflowID"` + + // WorkflowNamespace is the namespace of the workflow. + WorkflowNamespace string `json:"workflow_namespace,omitempty"` + + // Input is a JSON structure containing workflow input values, serialized as string. + Input map[string]interface{} `json:"input,omitempty"` + + // Params define workflow type specific parameters. + Params map[string]interface{} `json:"params,omitempty"` + + // Description is the description of the workflow execution. + Description string `json:"description,omitempty"` +} + +// ToExecutionCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToExecutionCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create requests the creation of a new execution. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToExecutionCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), b, &r.Body, nil) + + return +} + +// Get retrieves details of a single execution. +// Use ExtractExecution to convert its result into an Execution. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// Delete deletes the specified execution. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListOptsBuilder allows extension to add additional parameters to the List request. +type ListOptsBuilder interface { + ToExecutionListQuery() (string, error) +} + +// ListOpts filters the result returned by the List() function. +type ListOpts struct { + // WorkflowName allows to filter by workflow name. + WorkflowName *ListFilter `q:"-"` + // WorkflowID allows to filter by workflow id. + WorkflowID string `q:"workflow_id"` + // Description allows to filter by execution description. + Description *ListFilter `q:"-"` + // Params allows to filter by specific parameters. + Params map[string]interface{} `q:"-"` + // TaskExecutionID allows to filter with a specific task execution id. + TaskExecutionID string `q:"task_execution_id"` + // RootExecutionID allows to filter with a specific root execution id. + RootExecutionID string `q:"root_execution_id"` + // State allows to filter by execution state. + // Possible values are IDLE, RUNNING, PAUSED, SUCCESS, ERROR, CANCELLED. + State *ListFilter `q:"-"` + // StateInfo allows to filter by state info. + StateInfo *ListFilter `q:"-"` + // Input allows to filter by specific input. + Input map[string]interface{} `q:"-"` + // Output allows to filter by specific output. + Output map[string]interface{} `q:"-"` + // CreatedAt allows to filter by execution creation date. + CreatedAt *ListDateFilter `q:"-"` + // UpdatedAt allows to filter by last execution update date. + UpdatedAt *ListDateFilter `q:"-"` + // IncludeOutput requests to include the output for all executions in the list. + IncludeOutput bool `q:"-"` + // ProjectID allows to filter by given project id. Admin required. + ProjectID string `q:"project_id"` + // AllProjects requests to get executions of all projects. Admin required. + AllProjects int `q:"all_projects"` + // SortDir allows to select sort direction. + // It can be "asc" or "desc" (default). + SortDirs string `q:"sort_dirs"` + // SortKey allows to sort by one of the execution attributes. + SortKeys string `q:"sort_keys"` + // Marker and Limit control paging. + // Marker instructs List where to start listing from. + Marker string `q:"marker"` + // Limit instructs List to refrain from sending excessively large lists of + // executions. + Limit int `q:"limit"` +} + +// ListFilter allows to filter string parameters with different filters. +// Empty value for Filter checks for equality. +type ListFilter struct { + Filter FilterType + Value string +} + +func (l ListFilter) String() string { + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, l.Value) + } + + return l.Value +} + +// ListDateFilter allows to filter date parameters with different filters. +// Empty value for Filter checks for equality. +type ListDateFilter struct { + Filter FilterType + Value time.Time +} + +func (l ListDateFilter) String() string { + v := l.Value.Format(gophercloud.RFC3339ZNoTNoZ) + + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, v) + } + + return v +} + +// FilterType represents a valid filter to use for filtering executions. +type FilterType string + +const ( + // FilterEQ checks equality. + FilterEQ = "eq" + // FilterNEQ checks non equality. + FilterNEQ = "neq" + // FilterIN checks for belonging in a list, comma separated. + FilterIN = "in" + // FilterNIN checks for values that does not belong from a list, comma separated. + FilterNIN = "nin" + // FilterGT checks for values strictly greater. + FilterGT = "gt" + // FilterGTE checks for values greater or equal. + FilterGTE = "gte" + // FilterLT checks for values strictly lower. + FilterLT = "lt" + // FilterLTE checks for values lower or equal. + FilterLTE = "lte" + // FilterHas checks for values that contains the requested parameter. + FilterHas = "has" +) + +// ToExecutionListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToExecutionListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + + if opts.IncludeOutput { + params.Add("include_output", "1") + } + + for queryParam, value := range map[string]map[string]interface{}{"params": opts.Params, "input": opts.Input, "output": opts.Output} { + if value != nil { + b, err := json.Marshal(value) + if err != nil { + return "", err + } + params.Add(queryParam, string(b)) + } + } + + for queryParam, value := range map[string]fmt.Stringer{ + "created_at": opts.CreatedAt, + "updated_at": opts.UpdatedAt, + "workflow_name": opts.WorkflowName, + "description": opts.Description, + "state": opts.State, + "state_info": opts.StateInfo, + } { + if !reflect.ValueOf(value).IsNil() { + params.Add(queryParam, value.String()) + } + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), nil +} + +// List performs a call to list executions. +// You may provide options to filter the executions. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToExecutionListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ExecutionPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/results.go new file mode 100644 index 000000000000..0c733708599f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/results.go @@ -0,0 +1,158 @@ +package executions + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Post operations. Call its Extract method to interpret it as an Execution. +type CreateResult struct { + commonResult +} + +// GetResult is the response of Get operations. Call its Extract method to interpret it as an Execution. +type GetResult struct { + commonResult +} + +// Extract helps to get an Execution struct from a Get or a Create function. +func (r commonResult) Extract() (*Execution, error) { + var s Execution + err := r.ExtractInto(&s) + return &s, err +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr method to determine the success of the call. +type DeleteResult struct { + gophercloud.ErrResult +} + +// Execution represents a workflow execution on OpenStack mistral API. +type Execution struct { + // ID is the execution's unique ID. + ID string `json:"id"` + + // CreatedAt contains the execution creation date. + CreatedAt time.Time `json:"-"` + + // UpdatedAt is the last update of the execution. + UpdatedAt time.Time `json:"-"` + + // RootExecutionID is the parent execution ID. + RootExecutionID *string `json:"root_execution_id"` + + // TaskExecutionID is the task execution ID. + TaskExecutionID *string `json:"task_execution_id"` + + // Description is the description of the execution. + Description string `json:"description"` + + // Input contains the workflow input values. + Input map[string]interface{} `json:"-"` + + // Ouput contains the workflow output values. + Output map[string]interface{} `json:"-"` + + // Params contains workflow type specific parameters. + Params map[string]interface{} `json:"-"` + + // ProjectID is the project id owner of the execution. + ProjectID string `json:"project_id"` + + // State is the current state of the execution. State can be one of: IDLE, RUNNING, SUCCESS, ERROR, PAUSED, CANCELLED. + State string `json:"state"` + + // StateInfo contains an optional state information string. + StateInfo *string `json:"state_info"` + + // WorkflowID is the ID of the workflow linked to the execution. + WorkflowID string `json:"workflow_id"` + + // WorkflowName is the name of the workflow linked to the execution. + WorkflowName string `json:"workflow_name"` + + // WorkflowNamespace is the namespace of the workflow linked to the execution. + WorkflowNamespace string `json:"workflow_namespace"` +} + +// UnmarshalJSON implements unmarshalling custom types +func (r *Execution) UnmarshalJSON(b []byte) error { + type tmp Execution + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"updated_at"` + Input string `json:"input"` + Output string `json:"output"` + Params string `json:"params"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Execution(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + if s.Input != "" { + if err := json.Unmarshal([]byte(s.Input), &r.Input); err != nil { + return err + } + } + + if s.Output != "" { + if err := json.Unmarshal([]byte(s.Output), &r.Output); err != nil { + return err + } + } + + if s.Params != "" { + if err := json.Unmarshal([]byte(s.Params), &r.Params); err != nil { + return err + } + } + + return nil +} + +// ExecutionPage contains a single page of all executions from a List call. +type ExecutionPage struct { + pagination.LinkedPageBase +} + +// IsEmpty checks if an ExecutionPage contains any results. +func (r ExecutionPage) IsEmpty() (bool, error) { + exec, err := ExtractExecutions(r) + return len(exec) == 0, err +} + +// NextPageURL finds the next page URL in a page in order to navigate to the next page of results. +func (r ExecutionPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, nil +} + +// ExtractExecutions get the list of executions from a page acquired from the List call. +func ExtractExecutions(r pagination.Page) ([]Execution, error) { + var s struct { + Executions []Execution `json:"executions"` + } + err := (r.(ExecutionPage)).ExtractInto(&s) + return s.Executions, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/testing/requests_test.go new file mode 100644 index 000000000000..dfe278294ab9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/testing/requests_test.go @@ -0,0 +1,267 @@ +package testing + +import ( + "fmt" + "net/http" + "net/url" + "reflect" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/workflow/v2/executions" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateExecution(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/executions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusCreated) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ` + { + "created_at": "2018-09-12 14:48:49", + "description": "description", + "id": "50bb59f1-eb77-4017-a77f-6d575b002667", + "input": "{\"msg\": \"Hello\"}", + "output": "{}", + "params": "{\"namespace\": \"\", \"env\": {}}", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "root_execution_id": null, + "state": "SUCCESS", + "state_info": null, + "task_execution_id": null, + "updated_at": "2018-09-12 14:48:49", + "workflow_id": "6656c143-a009-4bcb-9814-cc100a20bbfa", + "workflow_name": "echo", + "workflow_namespace": "" + } + `) + }) + + opts := &executions.CreateOpts{ + WorkflowID: "6656c143-a009-4bcb-9814-cc100a20bbfa", + Input: map[string]interface{}{ + "msg": "Hello", + }, + Description: "description", + } + + actual, err := executions.Create(fake.ServiceClient(), opts).Extract() + if err != nil { + t.Fatalf("Unable to create execution: %v", err) + } + + expected := &executions.Execution{ + ID: "50bb59f1-eb77-4017-a77f-6d575b002667", + Description: "description", + Input: map[string]interface{}{ + "msg": "Hello", + }, + Params: map[string]interface{}{ + "namespace": "", + "env": map[string]interface{}{}, + }, + Output: map[string]interface{}{}, + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + State: "SUCCESS", + WorkflowID: "6656c143-a009-4bcb-9814-cc100a20bbfa", + WorkflowName: "echo", + CreatedAt: time.Date(2018, time.September, 12, 14, 48, 49, 0, time.UTC), + UpdatedAt: time.Date(2018, time.September, 12, 14, 48, 49, 0, time.UTC), + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + +func TestGetExecution(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/executions/50bb59f1-eb77-4017-a77f-6d575b002667", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, ` + { + "created_at": "2018-09-12 14:48:49", + "description": "description", + "id": "50bb59f1-eb77-4017-a77f-6d575b002667", + "input": "{\"msg\": \"Hello\"}", + "output": "{}", + "params": "{\"namespace\": \"\", \"env\": {}}", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "root_execution_id": null, + "state": "SUCCESS", + "state_info": null, + "task_execution_id": null, + "updated_at": "2018-09-12 14:48:49", + "workflow_id": "6656c143-a009-4bcb-9814-cc100a20bbfa", + "workflow_name": "echo", + "workflow_namespace": "" + } + `) + }) + + actual, err := executions.Get(fake.ServiceClient(), "50bb59f1-eb77-4017-a77f-6d575b002667").Extract() + if err != nil { + t.Fatalf("Unable to get execution: %v", err) + } + + expected := &executions.Execution{ + ID: "50bb59f1-eb77-4017-a77f-6d575b002667", + Description: "description", + Input: map[string]interface{}{ + "msg": "Hello", + }, + Params: map[string]interface{}{ + "namespace": "", + "env": map[string]interface{}{}, + }, + Output: map[string]interface{}{}, + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + State: "SUCCESS", + WorkflowID: "6656c143-a009-4bcb-9814-cc100a20bbfa", + WorkflowName: "echo", + CreatedAt: time.Date(2018, time.September, 12, 14, 48, 49, 0, time.UTC), + UpdatedAt: time.Date(2018, time.September, 12, 14, 48, 49, 0, time.UTC), + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + +func TestDeleteExecution(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/executions/1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusAccepted) + }) + res := executions.Delete(fake.ServiceClient(), "1") + th.AssertNoErr(t, res.Err) +} + +func TestListExecutions(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/executions", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, `{ + "executions": [ + { + "created_at": "2018-09-12 14:48:49", + "description": "description", + "id": "50bb59f1-eb77-4017-a77f-6d575b002667", + "input": "{\"msg\": \"Hello\"}", + "params": "{\"namespace\": \"\", \"env\": {}}", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "root_execution_id": null, + "state": "SUCCESS", + "state_info": null, + "task_execution_id": null, + "updated_at": "2018-09-12 14:48:49", + "workflow_id": "6656c143-a009-4bcb-9814-cc100a20bbfa", + "workflow_name": "echo", + "workflow_namespace": "" + } + ], + "next": "%s/executions?marker=50bb59f1-eb77-4017-a77f-6d575b002667" + }`, th.Server.URL) + case "50bb59f1-eb77-4017-a77f-6d575b002667": + fmt.Fprintf(w, `{ "executions": [] }`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) + pages := 0 + // Get all executions + err := executions.List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + pages++ + actual, err := executions.ExtractExecutions(page) + if err != nil { + return false, err + } + + expected := []executions.Execution{ + executions.Execution{ + ID: "50bb59f1-eb77-4017-a77f-6d575b002667", + Description: "description", + Input: map[string]interface{}{ + "msg": "Hello", + }, + Params: map[string]interface{}{ + "namespace": "", + "env": map[string]interface{}{}, + }, + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + State: "SUCCESS", + WorkflowID: "6656c143-a009-4bcb-9814-cc100a20bbfa", + WorkflowName: "echo", + CreatedAt: time.Date(2018, time.September, 12, 14, 48, 49, 0, time.UTC), + UpdatedAt: time.Date(2018, time.September, 12, 14, 48, 49, 0, time.UTC), + }, + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } + return true, nil + }) + if err != nil { + t.Fatal(err) + } + if pages != 1 { + t.Errorf("Expected one page, got %d", pages) + } +} + +func TestToExecutionListQuery(t *testing.T) { + for expected, opts := range map[string]*executions.ListOpts{ + newValue("input", `{"msg":"Hello"}`): &executions.ListOpts{ + Input: map[string]interface{}{ + "msg": "Hello", + }, + }, + newValue("description", `neq:not_description`): &executions.ListOpts{ + Description: &executions.ListFilter{ + Filter: executions.FilterNEQ, + Value: "not_description", + }, + }, + newValue("created_at", `gt:2018-01-01 00:00:00`): &executions.ListOpts{ + CreatedAt: &executions.ListDateFilter{ + Filter: executions.FilterGT, + Value: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + }, + } { + actual, _ := opts.ToExecutionListQuery() + + th.AssertEquals(t, expected, actual) + } +} + +func newValue(param, value string) string { + v := url.Values{} + v.Add(param, value) + + return "?" + v.Encode() +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/urls.go new file mode 100644 index 000000000000..d593e9d28f14 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/executions/urls.go @@ -0,0 +1,19 @@ +package executions + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("executions") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("executions", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("executions", id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("executions") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/doc.go new file mode 100644 index 000000000000..4e59fff99693 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/doc.go @@ -0,0 +1,73 @@ +/* +Package workflows provides interaction with the workflows API in the OpenStack Mistral service. + +Workflow represents a process that can be described in a various number of ways and that can do some job interesting to the end user. +Each workflow consists of tasks (at least one) describing what exact steps should be made during workflow execution. + +Workflow definition is written in Mistral Workflow Language v2. You can find all specification here: https://docs.openstack.org/mistral/latest/user/wf_lang_v2.html + +List workflows + + listOpts := workflows.ListOpts{ + Namespace: "some-namespace", + } + + allPages, err := workflows.List(mistralClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allWorkflows, err := workflows.ExtractWorkflows(allPages) + if err != nil { + panic(err) + } + + for _, workflow := range allWorkflows { + fmt.Printf("%+v\n", workflow) + } + +Get a workflow + + workflow, err := workflows.Get(mistralClient, "604a3a1e-94e3-4066-a34a-aa56873ef236").Extract() + if err != nil { + t.Fatalf("Unable to get workflow %s: %v", id, err) + } + + fmt.Printf("%+v\n", workflow) + +Create a workflow + + workflowDefinition := `--- + version: '2.0' + + workflow_echo: + description: Simple workflow example + type: direct + input: + - msg + + tasks: + test: + action: std.echo output="<% $.msg %>"` + + createOpts := &workflows.CreateOpts{ + Definition: strings.NewReader(workflowDefinition), + Scope: "private", + Namespace: "some-namespace", + } + + workflow, err := workflows.Create(mistralClient, opts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", workflow) + +Delete a workflow + + res := workflows.Delete(fake.ServiceClient(), "604a3a1e-94e3-4066-a34a-aa56873ef236") + if res.Err != nil { + panic(res.Err) + } +*/ +package workflows diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/requests.go new file mode 100644 index 000000000000..d077c7da0bcd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/requests.go @@ -0,0 +1,212 @@ +package workflows + +import ( + "fmt" + "io" + "net/url" + "reflect" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extension to add additional parameters to the Create request. +type CreateOptsBuilder interface { + ToWorkflowCreateParams() (io.Reader, string, error) +} + +// CreateOpts specifies parameters used to create a cron trigger. +type CreateOpts struct { + // Scope is the scope of the workflow. + // Allowed values are "private" and "public". + Scope string `q:"scope"` + + // Namespace will define the namespace of the workflow. + Namespace string `q:"namespace"` + + // Definition is the workflow definition written in Mistral Workflow Language v2. + Definition io.Reader +} + +// ToWorkflowCreateParams constructs a request query string from CreateOpts. +func (opts CreateOpts) ToWorkflowCreateParams() (io.Reader, string, error) { + q, err := gophercloud.BuildQueryString(opts) + return opts.Definition, q.String(), err +} + +// Create requests the creation of a new execution. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + url := createURL(client) + var b io.Reader + if opts != nil { + tmpB, query, err := opts.ToWorkflowCreateParams() + if err != nil { + r.Err = err + return + } + url += query + b = tmpB + } + + _, r.Err = client.Post(url, nil, &r.Body, &gophercloud.RequestOpts{ + RawBody: b, + MoreHeaders: map[string]string{ + "Content-Type": "text/plain", + "Accept": "", // Drop default JSON Accept header + }, + }) + + return +} + +// Delete deletes the specified execution. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get retrieves details of a single execution. +// Use Extract to convert its result into an Workflow. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extension to add additional parameters to the List request. +type ListOptsBuilder interface { + ToWorkflowListQuery() (string, error) +} + +// ListOpts filters the result returned by the List() function. +type ListOpts struct { + // Scope filters by the workflow's scope. + // Values can be "private" or "public". + Scope string `q:"scope"` + // CreatedAt allows to filter by workflow creation date. + CreatedAt *ListDateFilter `q:"-"` + // UpdatedAt allows to filter by last execution update date. + UpdatedAt *ListDateFilter `q:"-"` + // Name allows to filter by workflow name. + Name *ListFilter `q:"-"` + // Tags allows to filter by tags. + Tags []string + // Definition allows to filter by workflow definition. + Definition *ListFilter `q:"-"` + // Namespace allows to filter by workflow namespace. + Namespace *ListFilter `q:"-"` + // SortDirs allows to select sort direction. + // It can be "asc" or "desc" (default). + SortDirs string `q:"sort_dirs"` + // SortKeys allows to sort by one of the cron trigger attributes. + SortKeys string `q:"sort_keys"` + // Marker and Limit control paging. + // Marker instructs List where to start listing from. + Marker string `q:"marker"` + // Limit instructs List to refrain from sending excessively large lists of + // cron triggers. + Limit int `q:"limit"` + // ProjectID allows to filter by given project id. Admin required. + ProjectID string `q:"project_id"` + // AllProjects requests to get executions of all projects. Admin required. + AllProjects int `q:"all_projects"` +} + +// ListFilter allows to filter string parameters with different filters. +// Empty value for Filter checks for equality. +type ListFilter struct { + Filter FilterType + Value string +} + +func (l ListFilter) String() string { + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, l.Value) + } + return l.Value +} + +// ListDateFilter allows to filter date parameters with different filters. +// Empty value for Filter checks for equality. +type ListDateFilter struct { + Filter FilterType + Value time.Time +} + +func (l ListDateFilter) String() string { + v := l.Value.Format(gophercloud.RFC3339ZNoTNoZ) + if l.Filter != "" { + return fmt.Sprintf("%s:%s", l.Filter, v) + } + return v +} + +// FilterType represents a valid filter to use for filtering executions. +type FilterType string + +const ( + // FilterEQ checks equality. + FilterEQ = "eq" + // FilterNEQ checks non equality. + FilterNEQ = "neq" + // FilterIN checks for belonging in a list, comma separated. + FilterIN = "in" + // FilterNIN checks for values that does not belong from a list, comma separated. + FilterNIN = "nin" + // FilterGT checks for values strictly greater. + FilterGT = "gt" + // FilterGTE checks for values greater or equal. + FilterGTE = "gte" + // FilterLT checks for values strictly lower. + FilterLT = "lt" + // FilterLTE checks for values lower or equal. + FilterLTE = "lte" + // FilterHas checks for values that contains the requested parameter. + FilterHas = "has" +) + +// ToWorkflowListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToWorkflowListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + params := q.Query() + + if opts.Tags != nil { + params.Add("tags", strings.Join(opts.Tags, ",")) + } + + for queryParam, value := range map[string]fmt.Stringer{ + "created_at": opts.CreatedAt, + "updated_at": opts.UpdatedAt, + "name": opts.Name, + "definition": opts.Definition, + "namespace": opts.Namespace, + } { + if !reflect.ValueOf(value).IsNil() { + params.Add(queryParam, value.String()) + } + } + + q = &url.URL{RawQuery: params.Encode()} + + return q.String(), nil +} + +// List performs a call to list cron triggers. +// You may provide options to filter the results. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToWorkflowListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return WorkflowPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/results.go new file mode 100644 index 000000000000..1e4803e8773e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/results.go @@ -0,0 +1,132 @@ +package workflows + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateResult is the response of a Post operations. Call its Extract method to interpret it as a list of Workflows. +type CreateResult struct { + gophercloud.Result +} + +// DeleteResult is the result from a Delete operation. Call its ExtractErr method to determine the success of the call. +type DeleteResult struct { + gophercloud.ErrResult +} + +// Extract helps to get created Workflow struct from a Create function. +func (r CreateResult) Extract() ([]Workflow, error) { + var s struct { + Workflows []Workflow `json:"workflows"` + } + err := r.ExtractInto(&s) + return s.Workflows, err +} + +// GetResult is the response of Get operations. Call its Extract method to interpret it as a Workflow. +type GetResult struct { + gophercloud.Result +} + +// Extract helps to get a Workflow struct from a Get function. +func (r GetResult) Extract() (*Workflow, error) { + var s Workflow + err := r.ExtractInto(&s) + return &s, err +} + +// Workflow represents a workflow execution on OpenStack mistral API. +type Workflow struct { + // ID is the workflow's unique ID. + ID string `json:"id"` + + // Definition is the workflow definition in Mistral v2 DSL. + Definition string `json:"definition"` + + // Name is the name of the workflow. + Name string `json:"name"` + + // Namespace is the namespace of the workflow. + Namespace string `json:"namespace"` + + // Input represents the needed input to execute the workflow. + // This parameter is a list of each input, comma separated. + Input string `json:"input"` + + // ProjectID is the project id owner of the workflow. + ProjectID string `json:"project_id"` + + // Scope is the scope of the workflow. + // Values can be "private" or "public". + Scope string `json:"scope"` + + // Tags is a list of tags associated to the workflow. + Tags []string `json:"tags"` + + // CreatedAt is the creation date of the workflow. + CreatedAt time.Time `json:"-"` + + // UpdatedAt is the last update date of the workflow. + UpdatedAt *time.Time `json:"-"` +} + +// UnmarshalJSON implements unmarshalling custom types +func (r *Workflow) UnmarshalJSON(b []byte) error { + type tmp Workflow + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + UpdatedAt *gophercloud.JSONRFC3339ZNoTNoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Workflow(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + if s.UpdatedAt != nil { + t := time.Time(*s.UpdatedAt) + r.UpdatedAt = &t + } + + return nil +} + +// WorkflowPage contains a single page of all workflows from a List call. +type WorkflowPage struct { + pagination.LinkedPageBase +} + +// IsEmpty checks if an WorkflowPage contains any results. +func (r WorkflowPage) IsEmpty() (bool, error) { + exec, err := ExtractWorkflows(r) + return len(exec) == 0, err +} + +// NextPageURL finds the next page URL in a page in order to navigate to the next page of results. +func (r WorkflowPage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Next, nil +} + +// ExtractWorkflows get the list of cron triggers from a page acquired from the List call. +func ExtractWorkflows(r pagination.Page) ([]Workflow, error) { + var s struct { + Workflows []Workflow `json:"workflows"` + } + err := (r.(WorkflowPage)).ExtractInto(&s) + return s.Workflows, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/testing/requests_test.go new file mode 100644 index 000000000000..f0c2755744ea --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/testing/requests_test.go @@ -0,0 +1,256 @@ +package testing + +import ( + "fmt" + "net/http" + "net/url" + "reflect" + "strings" + "testing" + "time" + + "github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestCreateWorkflow(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + definition := `--- +version: '2.0' + +workflow_echo: + description: Simple workflow example + type: direct + input: + - msg + + tasks: + test: + action: std.echo output="<% $.msg %>"` + + th.Mux.HandleFunc("/workflows", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "text/plain") + th.TestFormValues(t, r, map[string]string{ + "namespace": "some-namespace", + "scope": "private", + }) + th.TestBody(t, r, definition) + + w.WriteHeader(http.StatusCreated) + w.Header().Add("Content-Type", "application/json") + + fmt.Fprintf(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 %%>\"", + "id": "604a3a1e-94e3-4066-a34a-aa56873ef236", + "input": "msg", + "name": "workflow_echo", + "namespace": "some-namespace", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "scope": "private", + "tags": [], + "updated_at": "2018-09-12 15:48:17" + } + ] + }`) + }) + + opts := &workflows.CreateOpts{ + Namespace: "some-namespace", + Scope: "private", + Definition: strings.NewReader(definition), + } + + actual, err := workflows.Create(fake.ServiceClient(), opts).Extract() + if err != nil { + t.Fatalf("Unable to create workflow: %v", err) + } + + updated := time.Date(2018, time.September, 12, 15, 48, 17, 0, time.UTC) + expected := []workflows.Workflow{ + workflows.Workflow{ + ID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + 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 %>\"", + Name: "workflow_echo", + Namespace: "some-namespace", + Input: "msg", + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + Scope: "private", + Tags: []string{}, + CreatedAt: time.Date(2018, time.September, 12, 15, 48, 17, 0, time.UTC), + UpdatedAt: &updated, + }, + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + +func TestDeleteWorkflow(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/workflows/604a3a1e-94e3-4066-a34a-aa56873ef236", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusAccepted) + }) + + res := workflows.Delete(fake.ServiceClient(), "604a3a1e-94e3-4066-a34a-aa56873ef236") + th.AssertNoErr(t, res.Err) +} + +func TestGetWorkflow(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/workflows/1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(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 %%>\"", + "id": "604a3a1e-94e3-4066-a34a-aa56873ef236", + "input": "msg", + "name": "workflow_echo", + "namespace": "some-namespace", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "scope": "private", + "tags": [], + "updated_at": "2018-09-12 15:48:17" + } + `) + }) + actual, err := workflows.Get(fake.ServiceClient(), "1").Extract() + if err != nil { + t.Fatalf("Unable to get workflow: %v", err) + } + + updated := time.Date(2018, time.September, 12, 15, 48, 17, 0, time.UTC) + expected := &workflows.Workflow{ + ID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + 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 %>\"", + Name: "workflow_echo", + Namespace: "some-namespace", + Input: "msg", + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + Scope: "private", + Tags: []string{}, + CreatedAt: time.Date(2018, time.September, 12, 15, 48, 17, 0, time.UTC), + UpdatedAt: &updated, + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } +} + +func TestListWorkflows(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/workflows", 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") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, `{ + "next": "%s/workflows?marker=604a3a1e-94e3-4066-a34a-aa56873ef236", + "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 %%>\"", + "id": "604a3a1e-94e3-4066-a34a-aa56873ef236", + "input": "msg", + "name": "workflow_echo", + "namespace": "some-namespace", + "project_id": "778c0f25df0d492a9a868ee9e2fbb513", + "scope": "private", + "tags": [], + "updated_at": "2018-09-12 15:48:17" + } + ] + }`, th.Server.URL) + case "604a3a1e-94e3-4066-a34a-aa56873ef236": + fmt.Fprintf(w, `{ "workflows": [] }`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) + pages := 0 + // Get all workflows + err := workflows.List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + pages++ + actual, err := workflows.ExtractWorkflows(page) + if err != nil { + return false, err + } + + updated := time.Date(2018, time.September, 12, 15, 48, 17, 0, time.UTC) + expected := []workflows.Workflow{ + workflows.Workflow{ + ID: "604a3a1e-94e3-4066-a34a-aa56873ef236", + 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 %>\"", + Name: "workflow_echo", + Namespace: "some-namespace", + Input: "msg", + ProjectID: "778c0f25df0d492a9a868ee9e2fbb513", + Scope: "private", + Tags: []string{}, + CreatedAt: time.Date(2018, time.September, 12, 15, 48, 17, 0, time.UTC), + UpdatedAt: &updated, + }, + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %#v, but was %#v", expected, actual) + } + return true, nil + }) + if err != nil { + t.Fatal(err) + } + if pages != 1 { + t.Errorf("Expected one page, got %d", pages) + } +} + +func TestToWorkflowListQuery(t *testing.T) { + for expected, opts := range map[string]*workflows.ListOpts{ + newValue("tags", `tag1,tag2`): &workflows.ListOpts{ + Tags: []string{"tag1", "tag2"}, + }, + newValue("name", `neq:invalid_name`): &workflows.ListOpts{ + Name: &workflows.ListFilter{ + Filter: workflows.FilterNEQ, + Value: "invalid_name", + }, + }, + newValue("created_at", `gt:2018-01-01 00:00:00`): &workflows.ListOpts{ + CreatedAt: &workflows.ListDateFilter{ + Filter: workflows.FilterGT, + Value: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + }, + } { + actual, _ := opts.ToWorkflowListQuery() + th.AssertEquals(t, expected, actual) + } +} +func newValue(param, value string) string { + v := url.Values{} + v.Add(param, value) + return "?" + v.Encode() +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/urls.go new file mode 100644 index 000000000000..6c3f80d4b861 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/workflow/v2/workflows/urls.go @@ -0,0 +1,21 @@ +package workflows + +import ( + "github.com/gophercloud/gophercloud" +) + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("workflows") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("workflows", id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("workflows", id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("workflows") +} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go index 7c65926b725e..42c0b2dbe5b9 100644 --- a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go +++ b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go @@ -41,6 +41,8 @@ type Pager struct { createPage func(r PageResult) Page + firstPage Page + Err error // Headers supplies additional HTTP headers to populate on each paged request. @@ -89,9 +91,18 @@ func (p Pager) EachPage(handler func(Page) (bool, error)) error { } currentURL := p.initialURL for { - currentPage, err := p.fetchNextPage(currentURL) - if err != nil { - return err + var currentPage Page + + // if first page has already been fetched, no need to fetch it again + if p.firstPage != nil { + currentPage = p.firstPage + p.firstPage = nil + } else { + var err error + currentPage, err = p.fetchNextPage(currentURL) + if err != nil { + return err + } } empty, err := currentPage.IsEmpty() @@ -128,23 +139,26 @@ func (p Pager) AllPages() (Page, error) { // body will contain the final concatenated Page body. var body reflect.Value - // Grab a test page to ascertain the page body type. - testPage, err := p.fetchNextPage(p.initialURL) + // Grab a first page to ascertain the page body type. + firstPage, err := p.fetchNextPage(p.initialURL) if err != nil { return nil, err } // Store the page type so we can use reflection to create a new mega-page of // that type. - pageType := reflect.TypeOf(testPage) + pageType := reflect.TypeOf(firstPage) - // if it's a single page, just return the testPage (first page) + // if it's a single page, just return the firstPage (first page) if _, found := pageType.FieldByName("SinglePageBase"); found { - return testPage, nil + return firstPage, nil } + // store the first page to avoid getting it twice + p.firstPage = firstPage + // Switch on the page body type. Recognized types are `map[string]interface{}`, // `[]byte`, and `[]interface{}`. - switch pb := testPage.GetBody().(type) { + switch pb := firstPage.GetBody().(type) { case map[string]interface{}: // key is the map key for the page body if the body type is `map[string]interface{}`. var key string diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go index 28ad9068565b..b9986660cbda 100644 --- a/vendor/github.com/gophercloud/gophercloud/params.go +++ b/vendor/github.com/gophercloud/gophercloud/params.go @@ -120,6 +120,22 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, continue } + if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) { + sliceValue := v + if sliceValue.Kind() == reflect.Ptr { + sliceValue = sliceValue.Elem() + } + + for i := 0; i < sliceValue.Len(); i++ { + element := sliceValue.Index(i) + if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) { + _, err := BuildRequestBody(element.Interface(), "") + if err != nil { + return nil, err + } + } + } + } if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) { if zero { //fmt.Printf("value before change: %+v\n", optsValue.Field(i)) @@ -363,9 +379,8 @@ func BuildQueryString(opts interface{}) (*url.URL, error) { } } } else { - // Otherwise, the field is not set. - if len(tags) == 2 && tags[1] == "required" { - // And the field is required. Return an error. + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) } } @@ -439,10 +454,9 @@ func BuildHeaders(opts interface{}) (map[string]string, error) { optsMap[tags[0]] = strconv.FormatBool(v.Bool()) } } else { - // Otherwise, the field is not set. - if len(tags) == 2 && tags[1] == "required" { - // And the field is required. Return an error. - return optsMap, fmt.Errorf("Required header not set.") + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) } } } diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go index 17e451274354..fce00462fd36 100644 --- a/vendor/github.com/gophercloud/gophercloud/provider_client.go +++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go @@ -2,7 +2,9 @@ package gophercloud import ( "bytes" + "context" "encoding/json" + "errors" "io" "io/ioutil" "net/http" @@ -71,26 +73,44 @@ type ProviderClient struct { // authentication functions for different Identity service versions. ReauthFunc func() error + // Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client + // with the token and reauth func zeroed. Such client can be used to perform reauthorization. + Throwaway bool + + // Context is the context passed to the HTTP request. + Context context.Context + + // mut is a mutex for the client. It protects read and write access to client attributes such as getting + // and setting the TokenID. mut *sync.RWMutex + // reauthmut is a mutex for reauthentication it attempts to ensure that only one reauthentication + // attempt happens at one time. reauthmut *reauthlock + + authResult AuthResult } +// reauthlock represents a set of attributes used to help in the reauthentication process. type reauthlock struct { sync.RWMutex - reauthing bool + reauthing bool + reauthingErr error + done *sync.Cond } // AuthenticatedHeaders returns a map of HTTP headers that are common for all -// authenticated service requests. +// authenticated service requests. Blocks if Reauthenticate is in progress. func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) { + if client.IsThrowaway() { + return + } if client.reauthmut != nil { - client.reauthmut.RLock() - if client.reauthmut.reauthing { - client.reauthmut.RUnlock() - return + client.reauthmut.Lock() + for client.reauthmut.reauthing { + client.reauthmut.done.Wait() } - client.reauthmut.RUnlock() + client.reauthmut.Unlock() } t := client.Token() if t == "" { @@ -106,6 +126,20 @@ func (client *ProviderClient) UseTokenLock() { client.reauthmut = new(reauthlock) } +// GetAuthResult returns the result from the request that was used to obtain a +// provider client's Keystone token. +// +// The result is nil when authentication has not yet taken place, when the token +// was set manually with SetToken(), or when a ReauthFunc was used that does not +// record the AuthResult. +func (client *ProviderClient) GetAuthResult() AuthResult { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.authResult +} + // Token safely reads the value of the auth token from the ProviderClient. Applications should // call this method to access the token instead of the TokenID field func (client *ProviderClient) Token() string { @@ -117,33 +151,102 @@ func (client *ProviderClient) Token() string { } // SetToken safely sets the value of the auth token in the ProviderClient. Applications may -// use this method in a custom ReauthFunc +// use this method in a custom ReauthFunc. +// +// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead. func (client *ProviderClient) SetToken(t string) { if client.mut != nil { client.mut.Lock() defer client.mut.Unlock() } client.TokenID = t + client.authResult = nil +} + +// SetTokenAndAuthResult safely sets the value of the auth token in the +// ProviderClient and also records the AuthResult that was returned from the +// token creation request. Applications may call this in a custom ReauthFunc. +func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error { + tokenID := "" + var err error + if r != nil { + tokenID, err = r.ExtractTokenID() + if err != nil { + return err + } + } + + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + client.TokenID = tokenID + client.authResult = r + return nil } -//Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is -//called because of a 401 response, the caller may pass the previous token. In -//this case, the reauthentication can be skipped if another thread has already -//reauthenticated in the meantime. If no previous token is known, an empty -//string should be passed instead to force unconditional reauthentication. +// CopyTokenFrom safely copies the token from another ProviderClient into the +// this one. +func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) { + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + if other.mut != nil && other.mut != client.mut { + other.mut.RLock() + defer other.mut.RUnlock() + } + client.TokenID = other.TokenID + client.authResult = other.authResult +} + +// IsThrowaway safely reads the value of the client Throwaway field. +func (client *ProviderClient) IsThrowaway() bool { + if client.reauthmut != nil { + client.reauthmut.RLock() + defer client.reauthmut.RUnlock() + } + return client.Throwaway +} + +// SetThrowaway safely sets the value of the client Throwaway field. +func (client *ProviderClient) SetThrowaway(v bool) { + if client.reauthmut != nil { + client.reauthmut.Lock() + defer client.reauthmut.Unlock() + } + client.Throwaway = v +} + +// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is +// called because of a 401 response, the caller may pass the previous token. In +// this case, the reauthentication can be skipped if another thread has already +// reauthenticated in the meantime. If no previous token is known, an empty +// string should be passed instead to force unconditional reauthentication. func (client *ProviderClient) Reauthenticate(previousToken string) (err error) { if client.ReauthFunc == nil { return nil } - if client.mut == nil { + if client.reauthmut == nil { return client.ReauthFunc() } - client.mut.Lock() - defer client.mut.Unlock() + + client.reauthmut.Lock() + if client.reauthmut.reauthing { + for !client.reauthmut.reauthing { + client.reauthmut.done.Wait() + } + err = client.reauthmut.reauthingErr + client.reauthmut.Unlock() + return err + } + client.reauthmut.Unlock() client.reauthmut.Lock() client.reauthmut.reauthing = true + client.reauthmut.done = sync.NewCond(client.reauthmut) + client.reauthmut.reauthingErr = nil client.reauthmut.Unlock() if previousToken == "" || client.TokenID == previousToken { @@ -152,6 +255,8 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) { client.reauthmut.Lock() client.reauthmut.reauthing = false + client.reauthmut.reauthingErr = err + client.reauthmut.done.Broadcast() client.reauthmut.Unlock() return } @@ -192,7 +297,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) // io.ReadSeeker as-is. Default the content-type to application/json. if options.JSONBody != nil { if options.RawBody != nil { - panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().") + return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()") } rendered, err := json.Marshal(options.JSONBody) @@ -213,6 +318,9 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) if err != nil { return nil, err } + if client.Context != nil { + req = req.WithContext(client.Context) + } // Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to // modify or omit any header. @@ -251,13 +359,14 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } // Allow default OkCodes if none explicitly set - if options.OkCodes == nil { - options.OkCodes = defaultOkCodes(method) + okc := options.OkCodes + if okc == nil { + okc = defaultOkCodes(method) } // Validate the HTTP response status. var ok bool - for _, code := range options.OkCodes { + for _, code := range okc { if resp.StatusCode == code { ok = true break @@ -334,6 +443,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) if error408er, ok := errType.(Err408er); ok { err = error408er.Error408(respErr) } + case http.StatusConflict: + err = ErrDefault409{respErr} + if error409er, ok := errType.(Err409er); ok { + err = error409er.Error409(respErr) + } case 429: err = ErrDefault429{respErr} if error429er, ok := errType.(Err429er); ok { @@ -378,7 +492,7 @@ func defaultOkCodes(method string) []int { case method == "PUT": return []int{201, 202} case method == "PATCH": - return []int{200, 204} + return []int{200, 202, 204} case method == "DELETE": return []int{202, 204} } diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go index e64feee19edd..94a16bff0b4f 100644 --- a/vendor/github.com/gophercloud/gophercloud/results.go +++ b/vendor/github.com/gophercloud/gophercloud/results.go @@ -89,23 +89,47 @@ func (r Result) extractIntoPtr(to interface{}, label string) error { if typeOfV.Kind() == reflect.Struct { if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) - newType := reflect.New(typeOfV).Elem() - for _, v := range m[label].([]interface{}) { - b, err := json.Marshal(v) - if err != nil { - return err - } - - for i := 0; i < newType.NumField(); i++ { - s := newType.Field(i).Addr().Interface() - err = json.NewDecoder(bytes.NewReader(b)).Decode(s) + if mSlice, ok := m[label].([]interface{}); ok { + for _, v := range mSlice { + // For each iteration of the slice, we create a new struct. + // This is to work around a bug where elements of a slice + // are reused and not overwritten when the same copy of the + // struct is used: + // + // https://github.com/golang/go/issues/21092 + // https://github.com/golang/go/issues/24155 + // https://play.golang.org/p/NHo3ywlPZli + newType := reflect.New(typeOfV).Elem() + + b, err := json.Marshal(v) if err != nil { return err } + + // This is needed for structs with an UnmarshalJSON method. + // Technically this is just unmarshalling the response into + // a struct that is never used, but it's good enough to + // trigger the UnmarshalJSON method. + for i := 0; i < newType.NumField(); i++ { + s := newType.Field(i).Addr().Interface() + + // Unmarshal is used rather than NewDecoder to also work + // around the above-mentioned bug. + err = json.Unmarshal(b, s) + if err != nil { + return err + } + } + + newSlice = reflect.Append(newSlice, newType) } - newSlice = reflect.Append(newSlice, newType) } + + // "to" should now be properly modeled to receive the + // JSON response body and unmarshal into all the correct + // fields of the struct or composed extension struct + // at the end of this method. toValue.Set(newSlice) } } @@ -345,6 +369,48 @@ func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error { return nil } +// RFC3339ZNoT is the time format used in Zun (Containers Service). +const RFC3339ZNoT = "2006-01-02 15:04:05-07:00" + +type JSONRFC3339ZNoT time.Time + +func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoT, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoT(t) + return nil +} + +// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service). +const RFC3339ZNoTNoZ = "2006-01-02 15:04:05" + +type JSONRFC3339ZNoTNoZ time.Time + +func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoTNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoTNoZ(t) + return nil +} + /* Link is an internal type to be used in packages of collection resources that are paginated in a certain way. diff --git a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest b/vendor/github.com/gophercloud/gophercloud/script/acceptancetest index 9debd48b628b..68a5f91b0f1e 100755 --- a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest +++ b/vendor/github.com/gophercloud/gophercloud/script/acceptancetest @@ -1,5 +1,94 @@ #!/bin/bash # +set -x + +source `dirname $0`/stackenv + +timeout="60m" +failed= + # Run the acceptance tests. +# Check the error code after each suite, but do not exit early if a suite failed. + +ACCEPTANCE_TESTS=( +acceptance/openstack/blockstorage/extensions + +# snapshots_test.go:16: Unable to create a blockstorage client: CinderEndpoint is required +# acceptance/openstack/blockstorage/noauth + +# snapshots_test.go:21: Unable to retrieve snapshots: Resource not found +# acceptance/openstack/blockstorage/v1 + +acceptance/openstack/blockstorage/v2 +acceptance/openstack/blockstorage/v3 + +# No suitable endpoint could be found in the service catalog. +# acceptance/openstack/clustering/v1 + +acceptance/openstack/compute/v2 + +# No suitable endpoint could be found in the service catalog. +# acceptance/openstack/containerinfra/v1 + +acceptance/openstack/container/v1 + +# Unable to create a DB client: No suitable endpoint could be found in the service catalog. +# acceptance/openstack/db/v1 + +acceptance/openstack/dns/v2 +acceptance/openstack/identity/v2 +acceptance/openstack/identity/v3 +acceptance/openstack/imageservice/v2 +acceptance/openstack/keymanager/v1 +acceptance/openstack/loadbalancer/v2 + +# No suitable endpoint could be found in the service catalog. +# acceptance/openstack/messaging/v2 + +acceptance/openstack/networking/v2 +acceptance/openstack/networking/v2/extensions +acceptance/openstack/networking/v2/extensions/dns + +# Resource not found +# acceptance/openstack/networking/v2/extensions/fwaas + +acceptance/openstack/networking/v2/extensions/layer3 + +# Unable to create: Resource not found +# acceptance/openstack/networking/v2/extensions/lbaas + +# Resource not found +# acceptance/openstack/networking/v2/extensions/lbaas_v2 + +acceptance/openstack/networking/v2/extensions/mtu +acceptance/openstack/networking/v2/extensions/networkipavailabilities +acceptance/openstack/networking/v2/extensions/portsbinding +acceptance/openstack/networking/v2/extensions/qos/ruletypes +acceptance/openstack/networking/v2/extensions/rbacpolicies +acceptance/openstack/networking/v2/extensions/subnetpools +acceptance/openstack/networking/v2/extensions/trunks + +# Unable to create: Resource not found +# acceptance/openstack/networking/v2/extensions/vpnaas + +acceptance/openstack/objectstorage/v1 +acceptance/openstack/orchestration/v1 +acceptance/openstack/sharedfilesystems/v2 + +# No suitable endpoint could be found in the service catalog +# acceptance/openstack/workflow/v2 +) + +for acceptance_test in ${ACCEPTANCE_TESTS[@]}; do + go test -v -timeout $timeout -tags "fixtures acceptance" ./${acceptance_test} + if [[ $? != 0 ]]; then + failed=1 + fi +done + +# If any of the test suites failed, exit 1 +if [[ -n $failed ]]; then + exit 1 +fi -exec go test -p=1 github.com/gophercloud/gophercloud/acceptance/... $@ +exit 0 diff --git a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest-ironic b/vendor/github.com/gophercloud/gophercloud/script/acceptancetest-ironic new file mode 100755 index 000000000000..aa1ce2bd33ca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/script/acceptancetest-ironic @@ -0,0 +1,29 @@ +#!/bin/bash +# +set -x + +source `dirname $0`/stackenv + +timeout="60m" +failed= + +# Run the Ironic acceptance tests. +# Check the error code after each suite, but do not exit early if a suite failed. + +ACCEPTANCE_TESTS=( +acceptance/openstack/baremetal/v1 +) + +for acceptance_test in ${ACCEPTANCE_TESTS[@]}; do + go test -v -timeout $timeout -tags "fixtures acceptance" ./${acceptance_test} + if [[ $? != 0 ]]; then + failed=1 + fi +done + +# If any of the test suites failed, exit 1 +if [[ -n $failed ]]; then + exit 1 +fi + +exit 0 diff --git a/vendor/github.com/gophercloud/gophercloud/script/format b/vendor/github.com/gophercloud/gophercloud/script/format index 8ed602fde038..05645a825239 100755 --- a/vendor/github.com/gophercloud/gophercloud/script/format +++ b/vendor/github.com/gophercloud/gophercloud/script/format @@ -16,8 +16,26 @@ find_files() { \) -name '*.go' } -diff=$(find_files | xargs ${goimports} -d -e 2>&1) -if [[ -n "${diff}" ]]; then - echo "${diff}" - exit 1 +ignore_files=( + "./openstack/compute/v2/extensions/quotasets/testing/fixtures.go" + "./openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go" +) + +bad_files=$(find_files | xargs ${goimports} -l) + +final_files=() +for bad_file in $bad_files; do + found= + for ignore_file in "${ignore_files[@]}"; do + [[ "${bad_file}" == "${ignore_file}" ]] && { found=1; break; } + done + [[ -n $found ]] || final_files+=("$bad_file") +done + +if [[ "${#final_files[@]}" -gt 0 ]]; then + diff=$(echo "${final_files[@]}" | xargs ${goimports} -d -e 2>&1) + if [[ -n "${diff}" ]]; then + echo "${diff}" + exit 1 + fi fi diff --git a/vendor/github.com/gophercloud/gophercloud/script/stackenv b/vendor/github.com/gophercloud/gophercloud/script/stackenv new file mode 100644 index 000000000000..af4a5bc13e75 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/script/stackenv @@ -0,0 +1,26 @@ +# Prep the testing environment by creating the required testing resources and +# environment variables. This env is for theopenlab CI jobs, you might need +# to modify this according to your setup + +pushd /opt/stack/new/devstack +source openrc admin admin +openstack flavor create m1.acctest --id 99 --ram 512 --disk 5 --vcpu 1 --ephemeral 10 +openstack flavor create m1.resize --id 98 --ram 512 --disk 6 --vcpu 1 --ephemeral 10 +_NETWORK_ID=$(openstack network show private -c id -f value) +_SUBNET_ID=$(openstack subnet show private_subnet -c id -f value) +_EXTGW_ID=$(openstack network show public -c id -f value) +_IMAGE=$(openstack image list | grep -i cirros | head -n 1) +_IMAGE_ID=$(echo $_IMAGE | awk -F\| '{print $2}' | tr -d ' ') +_IMAGE_NAME=$(echo $_IMAGE | awk -F\| '{print $3}' | tr -d ' ') +echo export OS_IMAGE_NAME="$_IMAGE_NAME" >> openrc +echo export OS_IMAGE_ID="$_IMAGE_ID" >> openrc +echo export OS_NETWORK_ID="$_NETWORK_ID" >> openrc +echo export OS_SUBNET_ID="$_SUBNET_ID" >> openrc +echo export OS_EXTGW_ID="$_EXTGW_ID" >> openrc +echo export OS_POOL_NAME="public" >> openrc +echo export OS_FLAVOR_ID=99 >> openrc +echo export OS_FLAVOR_ID_RESIZE=98 >> openrc +echo export OS_DOMAIN_ID=default >> openrc +source openrc admin admin +popd + diff --git a/vendor/github.com/gophercloud/gophercloud/script/unittest b/vendor/github.com/gophercloud/gophercloud/script/unittest index 2c65d06034a4..609f74cd0c11 100755 --- a/vendor/github.com/gophercloud/gophercloud/script/unittest +++ b/vendor/github.com/gophercloud/gophercloud/script/unittest @@ -2,4 +2,6 @@ # # Run the unit tests. -exec go test ./testing ./.../testing $@ +# 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/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go index d1a48fea35b3..f222f05a66de 100644 --- a/vendor/github.com/gophercloud/gophercloud/service_client.go +++ b/vendor/github.com/gophercloud/gophercloud/service_client.go @@ -28,6 +28,10 @@ type ServiceClient struct { // The microversion of the service to use. Set this to use a particular microversion. Microversion string + + // MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way, + // values set in this field will be set on all the HTTP requests the service client sends. + MoreHeaders map[string]string } // ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /. @@ -108,6 +112,15 @@ func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Respon return client.Request("DELETE", url, opts) } +// Head calls `Request` with the "HEAD" HTTP verb. +func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, nil, opts) + return client.Request("HEAD", url, opts) +} + func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { switch client.Type { case "compute": @@ -116,9 +129,26 @@ func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion case "volume": opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion + case "baremetal": + opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion + case "baremetal-introspection": + opts.MoreHeaders["X-OpenStack-Ironic-Inspector-API-Version"] = client.Microversion } if client.Type != "" { opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion } } + +// Request carries out the HTTP operation for the service client +func (client *ServiceClient) Request(method, url string, options *RequestOpts) (*http.Response, error) { + if len(client.MoreHeaders) > 0 { + if options == nil { + options = new(RequestOpts) + } + for k, v := range client.MoreHeaders { + options.MoreHeaders[k] = v + } + } + return client.ProviderClient.Request(method, url, options) +} diff --git a/vendor/github.com/gophercloud/gophercloud/testing/params_test.go b/vendor/github.com/gophercloud/gophercloud/testing/params_test.go index 18d6704d9535..602536649543 100644 --- a/vendor/github.com/gophercloud/gophercloud/testing/params_test.go +++ b/vendor/github.com/gophercloud/gophercloud/testing/params_test.go @@ -39,7 +39,7 @@ func TestBuildQueryString(t *testing.T) { iFalse := false opts := struct { J int `q:"j"` - R string `q:"r,required"` + R string `q:"r" required:"true"` C bool `q:"c"` S []string `q:"s"` TS []testVar `q:"ts"` @@ -65,7 +65,7 @@ func TestBuildQueryString(t *testing.T) { opts = struct { J int `q:"j"` - R string `q:"r,required"` + R string `q:"r" required:"true"` C bool `q:"c"` S []string `q:"s"` TS []testVar `q:"ts"` @@ -91,7 +91,7 @@ func TestBuildQueryString(t *testing.T) { func TestBuildHeaders(t *testing.T) { testStruct := struct { Accept string `h:"Accept"` - Num int `h:"Number,required"` + Num int `h:"Number" required:"true"` Style bool `h:"Style"` }{ Accept: "application/json", diff --git a/vendor/github.com/gophercloud/gophercloud/testing/provider_client_test.go b/vendor/github.com/gophercloud/gophercloud/testing/provider_client_test.go index 514147e72706..81ff66f997ff 100644 --- a/vendor/github.com/gophercloud/gophercloud/testing/provider_client_test.go +++ b/vendor/github.com/gophercloud/gophercloud/testing/provider_client_test.go @@ -1,9 +1,12 @@ package testing import ( + "context" "fmt" "io/ioutil" "net/http" + "net/http/httptest" + "strings" "sync" "testing" "time" @@ -43,9 +46,11 @@ func TestUserAgent(t *testing.T) { func TestConcurrentReauth(t *testing.T) { var info = struct { - numreauths int - mut *sync.RWMutex + numreauths int + failedAuths int + mut *sync.RWMutex }{ + 0, 0, new(sync.RWMutex), } @@ -59,12 +64,14 @@ func TestConcurrentReauth(t *testing.T) { p.UseTokenLock() p.SetToken(prereauthTok) p.ReauthFunc = func() error { + p.SetThrowaway(true) time.Sleep(1 * time.Second) p.AuthenticatedHeaders() info.mut.Lock() info.numreauths++ info.mut.Unlock() p.TokenID = postreauthTok + p.SetThrowaway(false) return nil } @@ -74,6 +81,9 @@ func TestConcurrentReauth(t *testing.T) { th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Auth-Token") != postreauthTok { w.WriteHeader(http.StatusUnauthorized) + info.mut.Lock() + info.failedAuths++ + info.mut.Unlock() return } info.mut.RLock() @@ -90,6 +100,9 @@ func TestConcurrentReauth(t *testing.T) { wg := new(sync.WaitGroup) reqopts := new(gophercloud.RequestOpts) + reqopts.MoreHeaders = map[string]string{ + "X-Auth-Token": prereauthTok, + } for i := 0; i < numconc; i++ { wg.Add(1) @@ -119,3 +132,209 @@ func TestConcurrentReauth(t *testing.T) { th.AssertEquals(t, 1, info.numreauths) } + +func TestReauthEndLoop(t *testing.T) { + var info = struct { + reauthAttempts int + maxReauthReached bool + mut *sync.RWMutex + }{ + 0, + false, + new(sync.RWMutex), + } + + numconc := 20 + mut := new(sync.RWMutex) + + p := new(gophercloud.ProviderClient) + p.UseTokenLock() + p.SetToken(client.TokenID) + p.ReauthFunc = func() error { + info.mut.Lock() + defer info.mut.Unlock() + + if info.reauthAttempts > 5 { + info.maxReauthReached = true + return fmt.Errorf("Max reauthentication attempts reached") + } + p.SetThrowaway(true) + p.AuthenticatedHeaders() + p.SetThrowaway(false) + info.reauthAttempts++ + + return nil + } + + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + // route always return 401 + w.WriteHeader(http.StatusUnauthorized) + return + }) + + reqopts := new(gophercloud.RequestOpts) + + // counters for the upcoming errors + errAfter := 0 + errUnable := 0 + + wg := new(sync.WaitGroup) + for i := 0; i < numconc; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _, err := p.Request("GET", fmt.Sprintf("%s/route", th.Endpoint()), reqopts) + + mut.Lock() + defer mut.Unlock() + + // ErrErrorAfter... will happen after a successful reauthentication, + // but the service still responds with a 401. + if _, ok := err.(*gophercloud.ErrErrorAfterReauthentication); ok { + errAfter++ + } + + // ErrErrorUnable... will happen when the custom reauth func reports + // an error. + if _, ok := err.(*gophercloud.ErrUnableToReauthenticate); ok { + errUnable++ + } + }() + } + + wg.Wait() + th.AssertEquals(t, info.reauthAttempts, 6) + th.AssertEquals(t, info.maxReauthReached, true) + th.AssertEquals(t, errAfter > 1, true) + th.AssertEquals(t, errUnable < 20, true) +} + +func TestRequestThatCameDuringReauthWaitsUntilItIsCompleted(t *testing.T) { + var info = struct { + numreauths int + failedAuths int + reauthCh chan struct{} + mut *sync.RWMutex + }{ + 0, + 0, + make(chan struct{}), + new(sync.RWMutex), + } + + numconc := 20 + + prereauthTok := client.TokenID + postreauthTok := "12345678" + + p := new(gophercloud.ProviderClient) + p.UseTokenLock() + p.SetToken(prereauthTok) + p.ReauthFunc = func() error { + info.mut.RLock() + if info.numreauths == 0 { + info.mut.RUnlock() + close(info.reauthCh) + time.Sleep(1 * time.Second) + } else { + info.mut.RUnlock() + } + p.SetThrowaway(true) + p.AuthenticatedHeaders() + info.mut.Lock() + info.numreauths++ + info.mut.Unlock() + p.TokenID = postreauthTok + p.SetThrowaway(false) + return nil + } + + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("X-Auth-Token") != postreauthTok { + info.mut.Lock() + info.failedAuths++ + info.mut.Unlock() + w.WriteHeader(http.StatusUnauthorized) + return + } + info.mut.RLock() + hasReauthed := info.numreauths != 0 + info.mut.RUnlock() + + if hasReauthed { + th.CheckEquals(t, p.Token(), postreauthTok) + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, `{}`) + }) + + wg := new(sync.WaitGroup) + reqopts := new(gophercloud.RequestOpts) + reqopts.MoreHeaders = map[string]string{ + "X-Auth-Token": prereauthTok, + } + + for i := 0; i < numconc; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + if i != 0 { + <-info.reauthCh + } + resp, err := p.Request("GET", fmt.Sprintf("%s/route", th.Endpoint()), reqopts) + th.CheckNoErr(t, err) + if resp == nil { + t.Errorf("got a nil response") + return + } + if resp.Body == nil { + t.Errorf("response body was nil") + return + } + defer resp.Body.Close() + actual, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("error reading response body: %s", err) + return + } + th.CheckByteArrayEquals(t, []byte(`{}`), actual) + }(i) + } + + wg.Wait() + + th.AssertEquals(t, 1, info.numreauths) + th.AssertEquals(t, 1, info.failedAuths) +} + +func TestRequestWithContext(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "OK") + })) + defer ts.Close() + + ctx, cancel := context.WithCancel(context.Background()) + p := &gophercloud.ProviderClient{Context: ctx} + + res, err := p.Request("GET", ts.URL, &gophercloud.RequestOpts{}) + th.AssertNoErr(t, err) + _, err = ioutil.ReadAll(res.Body) + res.Body.Close() + th.AssertNoErr(t, err) + + cancel() + res, err = p.Request("GET", ts.URL, &gophercloud.RequestOpts{}) + if err == nil { + t.Fatal("expecting error, got nil") + } + if !strings.Contains(err.Error(), ctx.Err().Error()) { + t.Fatalf("expecting error to contain: %q, got %q", ctx.Err().Error(), err.Error()) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/testing/service_client_test.go b/vendor/github.com/gophercloud/gophercloud/testing/service_client_test.go index 904b303ee9e5..034fdc1d936c 100644 --- a/vendor/github.com/gophercloud/gophercloud/testing/service_client_test.go +++ b/vendor/github.com/gophercloud/gophercloud/testing/service_client_test.go @@ -1,6 +1,8 @@ package testing import ( + "fmt" + "net/http" "testing" "github.com/gophercloud/gophercloud" @@ -13,3 +15,20 @@ func TestServiceURL(t *testing.T) { actual := c.ServiceURL("more", "parts", "here") th.CheckEquals(t, expected, actual) } + +func TestMoreHeaders(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + c := new(gophercloud.ServiceClient) + c.MoreHeaders = map[string]string{ + "custom": "header", + } + c.ProviderClient = new(gophercloud.ProviderClient) + resp, err := c.Get(fmt.Sprintf("%s/route", th.Endpoint()), nil, nil) + th.AssertNoErr(t, err) + th.AssertEquals(t, resp.Request.Header.Get("custom"), "header") +} From 2da30649d390bff9a12b9361d589668979af52d5 Mon Sep 17 00:00:00 2001 From: Hemant Kumar Date: Fri, 26 Jul 2019 21:33:14 -0400 Subject: [PATCH 2/2] UPSTREAM: 73437: Update gophercloud library --- vendor/k8s.io/kubernetes/Godeps/Godeps.json | 64 +- vendor/k8s.io/kubernetes/Godeps/LICENSES | 643 ++++++++++++------ .../providers/openstack/openstack_volumes.go | 4 +- .../src/k8s.io/client-go/Godeps/Godeps.json | 14 +- 4 files changed, 464 insertions(+), 261 deletions(-) diff --git a/vendor/k8s.io/kubernetes/Godeps/Godeps.json b/vendor/k8s.io/kubernetes/Godeps/Godeps.json index 1e3f61864c79..6a5d10b73886 100644 --- a/vendor/k8s.io/kubernetes/Godeps/Godeps.json +++ b/vendor/k8s.io/kubernetes/Godeps/Godeps.json @@ -1784,123 +1784,127 @@ }, { "ImportPath": "github.com/gophercloud/gophercloud", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/images", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" + }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies", + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/networks", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/pagination", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gorilla/websocket", diff --git a/vendor/k8s.io/kubernetes/Godeps/LICENSES b/vendor/k8s.io/kubernetes/Godeps/LICENSES index 99dccec64ac5..cb008ff936ce 100644 --- a/vendor/k8s.io/kubernetes/Godeps/LICENSES +++ b/vendor/k8s.io/kubernetes/Godeps/LICENSES @@ -63528,6 +63528,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies licensed under: = + +Copyright 2012-2013 Rackspace, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners licensed under: = @@ -90675,80 +90874,80 @@ SOFTWARE. ================================================================================ = vendor/github.com/vmware/photon-controller-go-sdk/photon licensed under: = - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: -(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and -(b) You must cause any modified files to carry prominent notices stating that You changed the files; and -(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied. - - See the License for the specific language governing permissions and - limitations under the License. - - + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and +(b) You must cause any modified files to carry prominent notices stating that You changed the files; and +(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + See the License for the specific language governing permissions and + limitations under the License. + + = vendor/github.com/vmware/photon-controller-go-sdk/LICENSE 0de60303c844eac44e45012dac1987de ================================================================================ @@ -90757,80 +90956,80 @@ To apply the Apache License to your work, attach the following boilerplate notic ================================================================================ = vendor/github.com/vmware/photon-controller-go-sdk/photon/lightwave licensed under: = - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: -(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and -(b) You must cause any modified files to carry prominent notices stating that You changed the files; and -(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied. - - See the License for the specific language governing permissions and - limitations under the License. - - + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and +(b) You must cause any modified files to carry prominent notices stating that You changed the files; and +(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + See the License for the specific language governing permissions and + limitations under the License. + + = vendor/github.com/vmware/photon-controller-go-sdk/LICENSE 0de60303c844eac44e45012dac1987de ================================================================================ @@ -90839,80 +91038,80 @@ To apply the Apache License to your work, attach the following boilerplate notic ================================================================================ = vendor/github.com/vmware/photon-controller-go-sdk/SSPI licensed under: = - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: -(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and -(b) You must cause any modified files to carry prominent notices stating that You changed the files; and -(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied. - - See the License for the specific language governing permissions and - limitations under the License. - - + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and +(b) You must cause any modified files to carry prominent notices stating that You changed the files; and +(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + See the License for the specific language governing permissions and + limitations under the License. + + = vendor/github.com/vmware/photon-controller-go-sdk/LICENSE 0de60303c844eac44e45012dac1987de ================================================================================ diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_volumes.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_volumes.go index 05b4edc137e6..17b3ab945923 100644 --- a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_volumes.go +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_volumes.go @@ -255,7 +255,7 @@ func (volumes *VolumesV1) deleteVolume(volumeID string) error { func (volumes *VolumesV2) deleteVolume(volumeID string) error { startTime := time.Now() - err := volumes_v2.Delete(volumes.blockstorage, volumeID).ExtractErr() + err := volumes_v2.Delete(volumes.blockstorage, volumeID, nil).ExtractErr() timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("delete_v2_volume", timeTaken, err) return err @@ -263,7 +263,7 @@ func (volumes *VolumesV2) deleteVolume(volumeID string) error { func (volumes *VolumesV3) deleteVolume(volumeID string) error { startTime := time.Now() - err := volumes_v3.Delete(volumes.blockstorage, volumeID).ExtractErr() + err := volumes_v3.Delete(volumes.blockstorage, volumeID, nil).ExtractErr() timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("delete_v3_volume", timeTaken, err) return err diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/Godeps/Godeps.json b/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/Godeps/Godeps.json index 7ec2ab090693..39c47ab50667 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/Godeps/Godeps.json +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/Godeps/Godeps.json @@ -108,31 +108,31 @@ }, { "ImportPath": "github.com/gophercloud/gophercloud", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gophercloud/gophercloud/pagination", - "Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d" + "Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8" }, { "ImportPath": "github.com/gregjones/httpcache",