From 5b284a50b78cb7d68b12825f5c0cfa29503c1577 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Thu, 10 Nov 2022 18:22:07 +0100 Subject: [PATCH 1/2] Revert "Remove references to openstack and cinder" This reverts commit 9bbf01bae93600d3314bce0acbff1f3a0a42e74b. --- .../gophercloud/gophercloud/LICENSE | 195 ++ .../storage-class/openstack/default.yaml | 9 + cmd/cloud-controller-manager/providers.go | 1 + .../app/plugins_providers.go | 2 + cmd/kubelet/app/plugins_providers.go | 2 + go.mod | 2 + go.sum | 4 + hack/local-up-cluster.sh | 12 + pkg/apis/core/validation/validation_test.go | 14 + pkg/cloudprovider/providers/providers.go | 1 + pkg/features/kube_features.go | 17 + .../framework/plugins/names/names.go | 1 + .../plugins/nodevolumelimits/csi_test.go | 2 + .../plugins/nodevolumelimits/non_csi.go | 43 + .../plugins/nodevolumelimits/utils.go | 2 + pkg/scheduler/framework/plugins/registry.go | 1 + .../framework/plugins/volumebinding/binder.go | 2 + pkg/volume/cinder/OWNERS | 14 + pkg/volume/cinder/attacher.go | 434 +++++ pkg/volume/cinder/attacher_test.go | 758 ++++++++ pkg/volume/cinder/cinder.go | 635 +++++++ pkg/volume/cinder/cinder_block.go | 179 ++ pkg/volume/cinder/cinder_block_test.go | 151 ++ pkg/volume/cinder/cinder_test.go | 365 ++++ pkg/volume/cinder/cinder_util.go | 278 +++ pkg/volume/cinder/doc.go | 18 + pkg/volume/csi/csi_plugin.go | 3 + pkg/volume/csimigration/plugin_manager.go | 4 + pkg/volume/util/attach_limit.go | 7 + pkg/volume/util/util_test.go | 25 + .../persistentvolume/label/admission.go | 71 +- .../persistentvolume/label/admission_test.go | 67 + staging/publishing/rules.yaml | 2 + .../src/k8s.io/client-go/examples/README.md | 1 + .../create-update-delete-deployment/main.go | 1 + .../main.go | 1 + .../in-cluster-client-configuration/main.go | 1 + .../main.go | 1 + .../client/auth/openstack/openstack_stub.go | 36 + .../pkg/client/auth/plugins_providers.go | 1 + staging/src/k8s.io/cloud-provider/plugins.go | 1 + .../plugins/in_tree_volume_test.go | 42 + .../plugins/openstack_cinder.go | 184 ++ .../plugins/openstack_cinder_test.go | 80 + .../k8s.io/csi-translation-lib/translate.go | 1 + .../csi-translation-lib/translate_test.go | 29 + .../k8s.io/kubectl/pkg/describe/describe.go | 22 + .../kubectl/pkg/describe/describe_test.go | 13 + .../src/k8s.io/legacy-cloud-providers/go.mod | 6 + .../src/k8s.io/legacy-cloud-providers/go.sum | 8 + .../openstack/MAINTAINERS.md | 4 + .../legacy-cloud-providers/openstack/OWNERS | 13 + .../openstack/metadata.go | 201 +++ .../openstack/metadata_test.go | 118 ++ .../openstack/openstack.go | 949 ++++++++++ .../openstack/openstack_client.go | 101 ++ .../openstack/openstack_instances.go | 244 +++ .../openstack/openstack_loadbalancer.go | 1578 +++++++++++++++++ .../openstack/openstack_metrics.go | 64 + .../openstack/openstack_routes.go | 347 ++++ .../openstack/openstack_routes_test.go | 128 ++ .../openstack/openstack_test.go | 733 ++++++++ .../openstack/openstack_volumes.go | 769 ++++++++ .../policy/check_restrictedVolumes.go | 2 + .../policy/check_restrictedVolumes_test.go | 5 +- test/e2e/common/storage/volumes.go | 4 +- test/e2e/e2e.go | 1 + .../providers/openstack/openstack.go | 34 + test/e2e/framework/test_context.go | 2 +- test/e2e/framework/volume/fixtures.go | 4 +- test/e2e/storage/drivers/csi.go | 2 +- test/e2e/storage/drivers/in_tree.go | 176 +- test/e2e/storage/in_tree_volumes.go | 1 + test/e2e/storage/testsuites/volumelimits.go | 2 + test/e2e/storage/volume_provisioning.go | 38 +- .../upgrades/storage/persistent_volumes.go | 2 +- test/e2e/upgrades/storage/volume_mode.go | 2 +- .../gophercloud/gophercloud/.gitignore | 3 + .../gophercloud/gophercloud/.travis.yml | 25 + .../gophercloud/gophercloud/.zuul.yaml | 114 ++ .../gophercloud/gophercloud/CHANGELOG.md | 0 .../gophercloud/gophercloud/LICENSE | 191 ++ .../gophercloud/gophercloud/README.md | 159 ++ .../gophercloud/gophercloud/auth_options.go | 437 +++++ .../gophercloud/gophercloud/auth_result.go | 52 + .../github.com/gophercloud/gophercloud/doc.go | 110 ++ .../gophercloud/endpoint_search.go | 76 + .../gophercloud/gophercloud/errors.go | 471 +++++ .../gophercloud/openstack/auth_env.go | 125 ++ .../extensions/volumeactions/doc.go | 86 + .../extensions/volumeactions/requests.go | 269 +++ .../extensions/volumeactions/results.go | 191 ++ .../extensions/volumeactions/urls.go | 7 + .../openstack/blockstorage/v1/volumes/doc.go | 5 + .../blockstorage/v1/volumes/requests.go | 172 ++ .../blockstorage/v1/volumes/results.go | 109 ++ .../openstack/blockstorage/v1/volumes/urls.go | 23 + .../openstack/blockstorage/v1/volumes/util.go | 22 + .../openstack/blockstorage/v2/volumes/doc.go | 5 + .../blockstorage/v2/volumes/requests.go | 235 +++ .../blockstorage/v2/volumes/results.go | 167 ++ .../openstack/blockstorage/v2/volumes/urls.go | 23 + .../openstack/blockstorage/v2/volumes/util.go | 22 + .../openstack/blockstorage/v3/volumes/doc.go | 5 + .../blockstorage/v3/volumes/requests.go | 237 +++ .../blockstorage/v3/volumes/results.go | 172 ++ .../openstack/blockstorage/v3/volumes/urls.go | 23 + .../openstack/blockstorage/v3/volumes/util.go | 22 + .../gophercloud/openstack/client.go | 438 +++++ .../openstack/common/extensions/doc.go | 52 + .../openstack/common/extensions/requests.go | 20 + .../openstack/common/extensions/results.go | 53 + .../openstack/common/extensions/urls.go | 13 + .../v2/extensions/attachinterfaces/doc.go | 52 + .../extensions/attachinterfaces/requests.go | 72 + .../v2/extensions/attachinterfaces/results.go | 80 + .../v2/extensions/attachinterfaces/urls.go | 18 + .../compute/v2/extensions/volumeattach/doc.go | 30 + .../v2/extensions/volumeattach/requests.go | 60 + .../v2/extensions/volumeattach/results.go | 77 + .../v2/extensions/volumeattach/urls.go | 25 + .../openstack/compute/v2/flavors/doc.go | 137 ++ .../openstack/compute/v2/flavors/requests.go | 357 ++++ .../openstack/compute/v2/flavors/results.go | 252 +++ .../openstack/compute/v2/flavors/urls.go | 49 + .../openstack/compute/v2/images/doc.go | 32 + .../openstack/compute/v2/images/requests.go | 109 ++ .../openstack/compute/v2/images/results.go | 95 + .../openstack/compute/v2/images/urls.go | 15 + .../openstack/compute/v2/servers/doc.go | 115 ++ .../openstack/compute/v2/servers/errors.go | 71 + .../compute/v2/servers/microversions.go | 11 + .../openstack/compute/v2/servers/requests.go | 812 +++++++++ .../openstack/compute/v2/servers/results.go | 414 +++++ .../openstack/compute/v2/servers/urls.go | 51 + .../openstack/compute/v2/servers/util.go | 21 + .../gophercloud/gophercloud/openstack/doc.go | 14 + .../openstack/endpoint_location.go | 107 ++ .../gophercloud/openstack/errors.go | 71 + .../openstack/identity/v2/tenants/doc.go | 65 + .../openstack/identity/v2/tenants/requests.go | 116 ++ .../openstack/identity/v2/tenants/results.go | 91 + .../openstack/identity/v2/tenants/urls.go | 23 + .../openstack/identity/v2/tokens/doc.go | 46 + .../openstack/identity/v2/tokens/requests.go | 103 ++ .../openstack/identity/v2/tokens/results.go | 174 ++ .../openstack/identity/v2/tokens/urls.go | 13 + .../identity/v3/extensions/trusts/doc.go | 26 + .../identity/v3/extensions/trusts/requests.go | 39 + .../identity/v3/extensions/trusts/results.go | 27 + .../openstack/identity/v3/tokens/doc.go | 108 ++ .../openstack/identity/v3/tokens/requests.go | 162 ++ .../openstack/identity/v3/tokens/results.go | 178 ++ .../openstack/identity/v3/tokens/urls.go | 7 + .../networking/v2/extensions/delegate.go | 41 + .../networking/v2/extensions/external/doc.go | 53 + .../v2/extensions/external/requests.go | 84 + .../v2/extensions/external/results.go | 8 + .../v2/extensions/layer3/floatingips/doc.go | 71 + .../extensions/layer3/floatingips/requests.go | 182 ++ .../extensions/layer3/floatingips/results.go | 131 ++ .../v2/extensions/layer3/floatingips/urls.go | 13 + .../v2/extensions/layer3/routers/doc.go | 108 ++ .../v2/extensions/layer3/routers/requests.go | 233 +++ .../v2/extensions/layer3/routers/results.go | 181 ++ .../v2/extensions/layer3/routers/urls.go | 21 + .../v2/extensions/lbaas_v2/l7policies/doc.go | 123 ++ .../lbaas_v2/l7policies/requests.go | 376 ++++ .../extensions/lbaas_v2/l7policies/results.go | 245 +++ .../v2/extensions/lbaas_v2/l7policies/urls.go | 25 + .../v2/extensions/lbaas_v2/listeners/doc.go | 63 + .../extensions/lbaas_v2/listeners/requests.go | 212 +++ .../extensions/lbaas_v2/listeners/results.go | 141 ++ .../v2/extensions/lbaas_v2/listeners/urls.go | 16 + .../extensions/lbaas_v2/loadbalancers/doc.go | 79 + .../lbaas_v2/loadbalancers/requests.go | 204 +++ .../lbaas_v2/loadbalancers/results.go | 186 ++ .../extensions/lbaas_v2/loadbalancers/urls.go | 26 + .../v2/extensions/lbaas_v2/monitors/doc.go | 69 + .../extensions/lbaas_v2/monitors/requests.go | 257 +++ .../extensions/lbaas_v2/monitors/results.go | 153 ++ .../v2/extensions/lbaas_v2/monitors/urls.go | 16 + .../v2/extensions/lbaas_v2/pools/doc.go | 126 ++ .../v2/extensions/lbaas_v2/pools/requests.go | 356 ++++ .../v2/extensions/lbaas_v2/pools/results.go | 291 +++ .../v2/extensions/lbaas_v2/pools/urls.go | 25 + .../v2/extensions/security/groups/doc.go | 58 + .../v2/extensions/security/groups/requests.go | 166 ++ .../v2/extensions/security/groups/results.go | 108 ++ .../v2/extensions/security/groups/urls.go | 13 + .../v2/extensions/security/rules/doc.go | 50 + .../v2/extensions/security/rules/requests.go | 159 ++ .../v2/extensions/security/rules/results.go | 127 ++ .../v2/extensions/security/rules/urls.go | 13 + .../openstack/networking/v2/networks/doc.go | 66 + .../networking/v2/networks/requests.go | 180 ++ .../networking/v2/networks/results.go | 124 ++ .../openstack/networking/v2/networks/urls.go | 31 + .../openstack/networking/v2/ports/doc.go | 73 + .../openstack/networking/v2/ports/requests.go | 191 ++ .../openstack/networking/v2/ports/results.go | 149 ++ .../openstack/networking/v2/ports/urls.go | 31 + .../openstack/utils/base_endpoint.go | 28 + .../openstack/utils/choose_version.go | 111 ++ .../gophercloud/pagination/http.go | 60 + .../gophercloud/pagination/linked.go | 92 + .../gophercloud/pagination/marker.go | 58 + .../gophercloud/pagination/pager.go | 251 +++ .../gophercloud/gophercloud/pagination/pkg.go | 4 + .../gophercloud/pagination/single.go | 33 + .../gophercloud/gophercloud/params.go | 491 +++++ .../gophercloud/provider_client.go | 501 ++++++ .../gophercloud/gophercloud/results.go | 448 +++++ .../gophercloud/gophercloud/service_client.go | 154 ++ .../gophercloud/gophercloud/util.go | 102 ++ vendor/modules.txt | 36 + 216 files changed, 25733 insertions(+), 22 deletions(-) create mode 100644 LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE create mode 100644 cluster/addons/storage-class/openstack/default.yaml create mode 100644 pkg/volume/cinder/OWNERS create mode 100644 pkg/volume/cinder/attacher.go create mode 100644 pkg/volume/cinder/attacher_test.go create mode 100644 pkg/volume/cinder/cinder.go create mode 100644 pkg/volume/cinder/cinder_block.go create mode 100644 pkg/volume/cinder/cinder_block_test.go create mode 100644 pkg/volume/cinder/cinder_test.go create mode 100644 pkg/volume/cinder/cinder_util.go create mode 100644 pkg/volume/cinder/doc.go create mode 100644 staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go create mode 100644 staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go create mode 100644 staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder_test.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go create mode 100644 test/e2e/framework/providers/openstack/openstack.go create mode 100644 vendor/github.com/gophercloud/gophercloud/.gitignore create mode 100644 vendor/github.com/gophercloud/gophercloud/.travis.yml create mode 100644 vendor/github.com/gophercloud/gophercloud/.zuul.yaml create mode 100644 vendor/github.com/gophercloud/gophercloud/CHANGELOG.md create mode 100644 vendor/github.com/gophercloud/gophercloud/LICENSE create mode 100644 vendor/github.com/gophercloud/gophercloud/README.md create mode 100644 vendor/github.com/gophercloud/gophercloud/auth_options.go create mode 100644 vendor/github.com/gophercloud/gophercloud/auth_result.go create mode 100644 vendor/github.com/gophercloud/gophercloud/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/endpoint_search.go create mode 100644 vendor/github.com/gophercloud/gophercloud/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.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/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/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/choose_version.go create mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/http.go create mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/linked.go create mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/marker.go create mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/pager.go create mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/single.go create mode 100644 vendor/github.com/gophercloud/gophercloud/params.go create mode 100644 vendor/github.com/gophercloud/gophercloud/provider_client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/service_client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/util.go diff --git a/LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE b/LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE new file mode 100644 index 00000000000..19c07b7c731 --- /dev/null +++ b/LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE @@ -0,0 +1,195 @@ += vendor/github.com/gophercloud/gophercloud 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 diff --git a/cluster/addons/storage-class/openstack/default.yaml b/cluster/addons/storage-class/openstack/default.yaml new file mode 100644 index 00000000000..435b31d222b --- /dev/null +++ b/cluster/addons/storage-class/openstack/default.yaml @@ -0,0 +1,9 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: standard + annotations: + storageclass.kubernetes.io/is-default-class: "true" + labels: + addonmanager.kubernetes.io/mode: EnsureExists +provisioner: kubernetes.io/cinder diff --git a/cmd/cloud-controller-manager/providers.go b/cmd/cloud-controller-manager/providers.go index b75e7b07cbe..aa063d92844 100644 --- a/cmd/cloud-controller-manager/providers.go +++ b/cmd/cloud-controller-manager/providers.go @@ -28,5 +28,6 @@ import ( _ "k8s.io/legacy-cloud-providers/aws" _ "k8s.io/legacy-cloud-providers/azure" _ "k8s.io/legacy-cloud-providers/gce" + _ "k8s.io/legacy-cloud-providers/openstack" _ "k8s.io/legacy-cloud-providers/vsphere" ) diff --git a/cmd/kube-controller-manager/app/plugins_providers.go b/cmd/kube-controller-manager/app/plugins_providers.go index 8b7d66d2f86..99b1586d203 100644 --- a/cmd/kube-controller-manager/app/plugins_providers.go +++ b/cmd/kube-controller-manager/app/plugins_providers.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/volume/awsebs" "k8s.io/kubernetes/pkg/volume/azure_file" "k8s.io/kubernetes/pkg/volume/azuredd" + "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/gcepd" "k8s.io/kubernetes/pkg/volume/portworx" @@ -65,6 +66,7 @@ func appendAttachableLegacyProviderVolumes(allPlugins []volume.VolumePlugin, fea pluginMigrationStatus := make(map[string]pluginInfo) pluginMigrationStatus[plugins.AWSEBSInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAWS, pluginUnregisterFeature: features.InTreePluginAWSUnregister, pluginProbeFunction: awsebs.ProbeVolumePlugins} pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginUnregisterFeature: features.InTreePluginGCEUnregister, pluginProbeFunction: gcepd.ProbeVolumePlugins} + pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginUnregisterFeature: features.InTreePluginOpenStackUnregister, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins} pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} pluginMigrationStatus[plugins.PortworxVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationPortworx, pluginUnregisterFeature: features.InTreePluginPortworxUnregister, pluginProbeFunction: portworx.ProbeVolumePlugins} diff --git a/cmd/kubelet/app/plugins_providers.go b/cmd/kubelet/app/plugins_providers.go index 26e408e3dbe..9b4a6391f76 100644 --- a/cmd/kubelet/app/plugins_providers.go +++ b/cmd/kubelet/app/plugins_providers.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/volume/awsebs" "k8s.io/kubernetes/pkg/volume/azure_file" "k8s.io/kubernetes/pkg/volume/azuredd" + "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/gcepd" "k8s.io/kubernetes/pkg/volume/portworx" @@ -71,6 +72,7 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f pluginMigrationStatus := make(map[string]pluginInfo) pluginMigrationStatus[plugins.AWSEBSInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAWS, pluginUnregisterFeature: features.InTreePluginAWSUnregister, pluginProbeFunction: awsebs.ProbeVolumePlugins} pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginUnregisterFeature: features.InTreePluginGCEUnregister, pluginProbeFunction: gcepd.ProbeVolumePlugins} + pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginUnregisterFeature: features.InTreePluginOpenStackUnregister, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureFileInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureFile, pluginUnregisterFeature: features.InTreePluginAzureFileUnregister, pluginProbeFunction: azure_file.ProbeVolumePlugins} pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} diff --git a/go.mod b/go.mod index cd36227f931..72432a4ad65 100644 --- a/go.mod +++ b/go.mod @@ -175,6 +175,7 @@ require ( github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect + github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -247,6 +248,7 @@ require ( ) replace ( + github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.1.0 k8s.io/api => ./staging/src/k8s.io/api k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery diff --git a/go.sum b/go.sum index cf031607eb7..13e6a965584 100644 --- a/go.sum +++ b/go.sum @@ -409,6 +409,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -777,6 +779,7 @@ go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -912,6 +915,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 6ec7f1ded28..1c0cf514eb6 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -133,6 +133,18 @@ KUBE_CONTROLLERS="${KUBE_CONTROLLERS:-"*"}" # Audit policy AUDIT_POLICY_FILE=${AUDIT_POLICY_FILE:-""} +# sanity check for OpenStack provider +if [ "${CLOUD_PROVIDER}" == "openstack" ]; then + if [ "${CLOUD_CONFIG}" == "" ]; then + echo "Missing CLOUD_CONFIG env for OpenStack provider!" + exit 1 + fi + if [ ! -f "${CLOUD_CONFIG}" ]; then + echo "Cloud config ${CLOUD_CONFIG} doesn't exist" + exit 1 + fi +fi + # Stop right away if the build fails set -e diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 15629884d56..20d5858f953 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -4097,6 +4097,20 @@ func TestValidateVolumes(t *testing.T) { field: "rbd.image", }}, }, + // Cinder + { + name: "valid Cinder", + vol: core.Volume{ + Name: "cinder", + VolumeSource: core.VolumeSource{ + Cinder: &core.CinderVolumeSource{ + VolumeID: "29ea5088-4f60-4757-962e-dba678767887", + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, // CephFS { name: "valid CephFS", diff --git a/pkg/cloudprovider/providers/providers.go b/pkg/cloudprovider/providers/providers.go index dda8b56585f..a78450a0e41 100644 --- a/pkg/cloudprovider/providers/providers.go +++ b/pkg/cloudprovider/providers/providers.go @@ -24,5 +24,6 @@ import ( _ "k8s.io/legacy-cloud-providers/aws" _ "k8s.io/legacy-cloud-providers/azure" _ "k8s.io/legacy-cloud-providers/gce" + _ "k8s.io/legacy-cloud-providers/openstack" _ "k8s.io/legacy-cloud-providers/vsphere" ) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index b2755dfca73..436ff9d2974 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -148,6 +148,13 @@ const ( // Enables the GCE PD in-tree driver to GCE CSI Driver migration feature. CSIMigrationGCE featuregate.Feature = "CSIMigrationGCE" + // owner: @adisky + // alpha: v1.14 + // beta: v1.18 + // + // Enables the OpenStack Cinder in-tree driver to OpenStack Cinder CSI Driver migration feature. + CSIMigrationOpenStack featuregate.Feature = "CSIMigrationOpenStack" + // owner: @trierra // alpha: v1.23 // @@ -408,6 +415,12 @@ const ( // Disables the GCE PD in-tree driver. InTreePluginGCEUnregister featuregate.Feature = "InTreePluginGCEUnregister" + // owner: @adisky + // alpha: v1.21 + // + // Disables the OpenStack Cinder in-tree driver. + InTreePluginOpenStackUnregister featuregate.Feature = "InTreePluginOpenStackUnregister" + // owner: @trierra // alpha: v1.23 // @@ -921,6 +934,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CSIMigrationGCE: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.27 + CSIMigrationOpenStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26 + CSIMigrationPortworx: {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires Portworx CSI driver) CSIMigrationRBD: {Default: false, PreRelease: featuregate.Alpha}, // Off by default (requires RBD CSI driver) @@ -993,6 +1008,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS InTreePluginGCEUnregister: {Default: false, PreRelease: featuregate.Alpha}, + InTreePluginOpenStackUnregister: {Default: false, PreRelease: featuregate.Alpha}, + InTreePluginPortworxUnregister: {Default: false, PreRelease: featuregate.Alpha}, InTreePluginRBDUnregister: {Default: false, PreRelease: featuregate.Alpha}, diff --git a/pkg/scheduler/framework/plugins/names/names.go b/pkg/scheduler/framework/plugins/names/names.go index 659a5ab4073..11cc90e6392 100644 --- a/pkg/scheduler/framework/plugins/names/names.go +++ b/pkg/scheduler/framework/plugins/names/names.go @@ -30,6 +30,7 @@ const ( NodeUnschedulable = "NodeUnschedulable" NodeVolumeLimits = "NodeVolumeLimits" AzureDiskLimits = "AzureDiskLimits" + CinderLimits = "CinderLimits" EBSLimits = "EBSLimits" GCEPDLimits = "GCEPDLimits" PodTopologySpread = "PodTopologySpread" diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go b/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go index fb5fa243f1f..f26c9b8cf00 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/csi_test.go @@ -58,6 +58,8 @@ func getVolumeLimitKey(filterType string) v1.ResourceName { return v1.ResourceName(volumeutil.GCEVolumeLimitKey) case azureDiskVolumeFilterType: return v1.ResourceName(volumeutil.AzureVolumeLimitKey) + case cinderVolumeFilterType: + return v1.ResourceName(volumeutil.CinderVolumeLimitKey) default: return v1.ResourceName(volumeutil.GetCSIAttachLimitKey(filterType)) } diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/non_csi.go b/pkg/scheduler/framework/plugins/nodevolumelimits/non_csi.go index 32ba541cf76..4a2535e5f26 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/non_csi.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/non_csi.go @@ -56,6 +56,8 @@ const ( gcePDVolumeFilterType = "GCE" // azureDiskVolumeFilterType defines the filter name for azureDiskVolumeFilter. azureDiskVolumeFilterType = "AzureDisk" + // cinderVolumeFilterType defines the filter name for cinderVolumeFilter. + cinderVolumeFilterType = "Cinder" // ErrReasonMaxVolumeCountExceeded is used for MaxVolumeCount predicate error. ErrReasonMaxVolumeCountExceeded = "node(s) exceed max volume count" @@ -73,6 +75,15 @@ func NewAzureDisk(_ runtime.Object, handle framework.Handle, fts feature.Feature return newNonCSILimitsWithInformerFactory(azureDiskVolumeFilterType, informerFactory, fts), nil } +// CinderName is the name of the plugin used in the plugin registry and configurations. +const CinderName = names.CinderLimits + +// NewCinder returns function that initializes a new plugin and returns it. +func NewCinder(_ runtime.Object, handle framework.Handle, fts feature.Features) (framework.Plugin, error) { + informerFactory := handle.SharedInformerFactory() + return newNonCSILimitsWithInformerFactory(cinderVolumeFilterType, informerFactory, fts), nil +} + // EBSName is the name of the plugin used in the plugin registry and configurations. const EBSName = names.EBSLimits @@ -160,6 +171,10 @@ func newNonCSILimits( name = AzureDiskName filter = azureDiskVolumeFilter volumeLimitKey = v1.ResourceName(volumeutil.AzureVolumeLimitKey) + case cinderVolumeFilterType: + name = CinderName + filter = cinderVolumeFilter + volumeLimitKey = v1.ResourceName(volumeutil.CinderVolumeLimitKey) default: klog.ErrorS(errors.New("wrong filterName"), "Cannot create nonCSILimits plugin") return nil @@ -460,6 +475,32 @@ var azureDiskVolumeFilter = VolumeFilter{ }, } +// cinderVolumeFilter is a VolumeFilter for filtering cinder Volumes. +// It will be deprecated once Openstack cloudprovider has been removed from in-tree. +var cinderVolumeFilter = VolumeFilter{ + FilterVolume: func(vol *v1.Volume) (string, bool) { + if vol.Cinder != nil { + return vol.Cinder.VolumeID, true + } + return "", false + }, + + FilterPersistentVolume: func(pv *v1.PersistentVolume) (string, bool) { + if pv.Spec.Cinder != nil { + return pv.Spec.Cinder.VolumeID, true + } + return "", false + }, + + MatchProvisioner: func(sc *storage.StorageClass) bool { + return sc.Provisioner == csilibplugins.CinderInTreePluginName + }, + + IsMigrated: func(csiNode *storage.CSINode) bool { + return isCSIMigrationOn(csiNode, csilibplugins.CinderInTreePluginName) + }, +} + func getMaxVolumeFunc(filterName string) func(node *v1.Node) int { return func(node *v1.Node) int { maxVolumesFromEnv := getMaxVolLimitFromEnv() @@ -481,6 +522,8 @@ func getMaxVolumeFunc(filterName string) func(node *v1.Node) int { return defaultMaxGCEPDVolumes case azureDiskVolumeFilterType: return defaultMaxAzureDiskVolumes + case cinderVolumeFilterType: + return volumeutil.DefaultMaxCinderVolumes default: return -1 } diff --git a/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go b/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go index 2dde31ce1b6..79d625e17c2 100644 --- a/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go +++ b/pkg/scheduler/framework/plugins/nodevolumelimits/utils.go @@ -55,6 +55,8 @@ func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string) bool { if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureDisk) { return false } + case csilibplugins.CinderInTreePluginName: + return true case csilibplugins.RBDVolumePluginName: if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationRBD) { return false diff --git a/pkg/scheduler/framework/plugins/registry.go b/pkg/scheduler/framework/plugins/registry.go index fdd1334aaae..7605cd3c2a7 100644 --- a/pkg/scheduler/framework/plugins/registry.go +++ b/pkg/scheduler/framework/plugins/registry.go @@ -72,6 +72,7 @@ func NewInTreeRegistry() runtime.Registry { nodevolumelimits.EBSName: runtime.FactoryAdapter(fts, nodevolumelimits.NewEBS), nodevolumelimits.GCEPDName: runtime.FactoryAdapter(fts, nodevolumelimits.NewGCEPD), nodevolumelimits.AzureDiskName: runtime.FactoryAdapter(fts, nodevolumelimits.NewAzureDisk), + nodevolumelimits.CinderName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCinder), interpodaffinity.Name: interpodaffinity.New, queuesort.Name: queuesort.New, defaultbinder.Name: defaultbinder.New, diff --git a/pkg/scheduler/framework/plugins/volumebinding/binder.go b/pkg/scheduler/framework/plugins/volumebinding/binder.go index 8ee0666ec7c..a4be0899ad0 100644 --- a/pkg/scheduler/framework/plugins/volumebinding/binder.go +++ b/pkg/scheduler/framework/plugins/volumebinding/binder.go @@ -1010,6 +1010,8 @@ func isCSIMigrationOnForPlugin(pluginName string) bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationGCE) case csiplugins.AzureDiskInTreePluginName: return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureDisk) + case csiplugins.CinderInTreePluginName: + return true case csiplugins.PortworxVolumePluginName: return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) case csiplugins.RBDVolumePluginName: diff --git a/pkg/volume/cinder/OWNERS b/pkg/volume/cinder/OWNERS new file mode 100644 index 00000000000..798d555dfae --- /dev/null +++ b/pkg/volume/cinder/OWNERS @@ -0,0 +1,14 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - jsafrane + - anguslees + - dims +reviewers: + - anguslees + - saad-ali + - jsafrane + - jingxu97 + - msau42 +emeritus_approvers: + - FengyunPan2 diff --git a/pkg/volume/cinder/attacher.go b/pkg/volume/cinder/attacher.go new file mode 100644 index 00000000000..94d1ede0403 --- /dev/null +++ b/pkg/volume/cinder/attacher.go @@ -0,0 +1,434 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "context" + "fmt" + "os" + "path" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" + "k8s.io/mount-utils" + + "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" +) + +type cinderDiskAttacher struct { + host volume.VolumeHost + cinderProvider BlockStorageProvider +} + +var _ volume.Attacher = &cinderDiskAttacher{} + +var _ volume.DeviceMounter = &cinderDiskAttacher{} + +var _ volume.AttachableVolumePlugin = &cinderPlugin{} + +var _ volume.DeviceMountableVolumePlugin = &cinderPlugin{} + +const ( + probeVolumeInitDelay = 1 * time.Second + probeVolumeFactor = 2.0 + operationFinishInitDelay = 1 * time.Second + operationFinishFactor = 1.1 + operationFinishSteps = 10 + diskAttachInitDelay = 1 * time.Second + diskAttachFactor = 1.2 + diskAttachSteps = 15 + diskDetachInitDelay = 1 * time.Second + diskDetachFactor = 1.2 + diskDetachSteps = 13 +) + +func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) { + cinder, err := plugin.getCloudProvider() + if err != nil { + return nil, err + } + return &cinderDiskAttacher{ + host: plugin.host, + cinderProvider: cinder, + }, nil +} + +func (plugin *cinderPlugin) NewDeviceMounter() (volume.DeviceMounter, error) { + return plugin.NewAttacher() +} + +func (plugin *cinderPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { + mounter := plugin.host.GetMounter(plugin.GetPluginName()) + return mounter.GetMountRefs(deviceMountPath) +} + +func (attacher *cinderDiskAttacher) waitOperationFinished(volumeID string) error { + backoff := wait.Backoff{ + Duration: operationFinishInitDelay, + Factor: operationFinishFactor, + Steps: operationFinishSteps, + } + + var volumeStatus string + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + var pending bool + var err error + pending, volumeStatus, err = attacher.cinderProvider.OperationPending(volumeID) + if err != nil { + return false, err + } + return !pending, nil + }) + + if err == wait.ErrWaitTimeout { + err = fmt.Errorf("volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus) + } + + return err +} + +func (attacher *cinderDiskAttacher) waitDiskAttached(instanceID, volumeID string) error { + backoff := wait.Backoff{ + Duration: diskAttachInitDelay, + Factor: diskAttachFactor, + Steps: diskAttachSteps, + } + + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID) + if err != nil { + return false, err + } + return attached, nil + }) + + if err == wait.ErrWaitTimeout { + err = fmt.Errorf("volume %q failed to be attached within the alloted time", volumeID) + } + + return err +} + +func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { + volumeID, _, _, err := getVolumeInfo(spec) + if err != nil { + return "", err + } + + instanceID, err := attacher.nodeInstanceID(nodeName) + if err != nil { + return "", err + } + + if err := attacher.waitOperationFinished(volumeID); err != nil { + return "", err + } + + attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID) + if err != nil { + // Log error and continue with attach + klog.Warningf( + "Error checking if volume (%q) is already attached to current instance (%q). Will continue and try attach anyway. err=%v", + volumeID, instanceID, err) + } + + if err == nil && attached { + // Volume is already attached to instance. + klog.Infof("Attach operation is successful. volume %q is already attached to instance %q.", volumeID, instanceID) + } else { + _, err = attacher.cinderProvider.AttachDisk(instanceID, volumeID) + if err == nil { + if err = attacher.waitDiskAttached(instanceID, volumeID); err != nil { + klog.Errorf("Error waiting for volume %q to be attached from node %q: %v", volumeID, nodeName, err) + return "", err + } + klog.Infof("Attach operation successful: volume %q attached to instance %q.", volumeID, instanceID) + } else { + klog.Infof("Attach volume %q to instance %q failed with: %v", volumeID, instanceID, err) + return "", err + } + } + + devicePath, err := attacher.cinderProvider.GetAttachmentDiskPath(instanceID, volumeID) + if err != nil { + klog.Infof("Can not get device path of volume %q which be attached to instance %q, failed with: %v", volumeID, instanceID, err) + return "", err + } + + return devicePath, nil +} + +func (attacher *cinderDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { + volumesAttachedCheck := make(map[*volume.Spec]bool) + volumeSpecMap := make(map[string]*volume.Spec) + volumeIDList := []string{} + for _, spec := range specs { + volumeID, _, _, err := getVolumeInfo(spec) + if err != nil { + klog.Errorf("Error getting volume (%q) source : %v", spec.Name(), err) + continue + } + + volumeIDList = append(volumeIDList, volumeID) + volumesAttachedCheck[spec] = true + volumeSpecMap[volumeID] = spec + } + + attachedResult, err := attacher.cinderProvider.DisksAreAttachedByName(nodeName, volumeIDList) + if err != nil { + // Log error and continue with attach + klog.Errorf( + "Error checking if Volumes (%v) are already attached to current node (%q). Will continue and try attach anyway. err=%v", + volumeIDList, nodeName, err) + return volumesAttachedCheck, err + } + + for volumeID, attached := range attachedResult { + if !attached { + spec := volumeSpecMap[volumeID] + volumesAttachedCheck[spec] = false + klog.V(2).Infof("VolumesAreAttached: check volume %q (specName: %q) is no longer attached", volumeID, spec.Name()) + } + } + return volumesAttachedCheck, nil +} + +func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) { + // NOTE: devicePath is path as reported by Cinder, which may be incorrect and should not be used. See Issue #33128 + volumeID, _, _, err := getVolumeInfo(spec) + if err != nil { + return "", err + } + + if devicePath == "" { + return "", fmt.Errorf("WaitForAttach failed for Cinder disk %q: devicePath is empty", volumeID) + } + + ticker := time.NewTicker(probeVolumeInitDelay) + defer ticker.Stop() + timer := time.NewTimer(timeout) + defer timer.Stop() + + duration := probeVolumeInitDelay + for { + select { + case <-ticker.C: + klog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID) + probeAttachedVolume() + if !attacher.cinderProvider.ShouldTrustDevicePath() { + // Using the Cinder volume ID, find the real device path (See Issue #33128) + devicePath = attacher.cinderProvider.GetDevicePath(volumeID) + } + exists, err := mount.PathExists(devicePath) + if exists && err == nil { + klog.Infof("Successfully found attached Cinder disk %q at %v.", volumeID, devicePath) + return devicePath, nil + } + // Log an error, and continue checking periodically + klog.Errorf("Error: could not find attached Cinder disk %q (path: %q): %v", volumeID, devicePath, err) + // Using exponential backoff instead of linear + ticker.Stop() + duration = time.Duration(float64(duration) * probeVolumeFactor) + ticker = time.NewTicker(duration) + case <-timer.C: + return "", fmt.Errorf("could not find attached Cinder disk %q. Timeout waiting for mount paths to be created", volumeID) + } + } +} + +func (attacher *cinderDiskAttacher) GetDeviceMountPath( + spec *volume.Spec) (string, error) { + volumeID, _, _, err := getVolumeInfo(spec) + if err != nil { + return "", err + } + + return makeGlobalPDName(attacher.host, volumeID), nil +} + +// FIXME: this method can be further pruned. +func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error { + mounter := attacher.host.GetMounter(cinderVolumePluginName) + notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(deviceMountPath, 0750); err != nil { + return err + } + notMnt = true + } else { + return err + } + } + + _, volumeFSType, readOnly, err := getVolumeInfo(spec) + if err != nil { + return err + } + + options := []string{} + if readOnly { + options = append(options, "ro") + } + if notMnt { + diskMounter := volumeutil.NewSafeFormatAndMountFromHost(cinderVolumePluginName, attacher.host) + mountOptions := volumeutil.MountOptionFromSpec(spec, options...) + err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeFSType, mountOptions) + if err != nil { + os.Remove(deviceMountPath) + return err + } + } + return nil +} + +type cinderDiskDetacher struct { + mounter mount.Interface + cinderProvider BlockStorageProvider +} + +var _ volume.Detacher = &cinderDiskDetacher{} + +var _ volume.DeviceUnmounter = &cinderDiskDetacher{} + +func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) { + cinder, err := plugin.getCloudProvider() + if err != nil { + return nil, err + } + return &cinderDiskDetacher{ + mounter: plugin.host.GetMounter(plugin.GetPluginName()), + cinderProvider: cinder, + }, nil +} + +func (plugin *cinderPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { + return plugin.NewDetacher() +} + +func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error { + backoff := wait.Backoff{ + Duration: operationFinishInitDelay, + Factor: operationFinishFactor, + Steps: operationFinishSteps, + } + + var volumeStatus string + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + var pending bool + var err error + pending, volumeStatus, err = detacher.cinderProvider.OperationPending(volumeID) + if err != nil { + return false, err + } + return !pending, nil + }) + + if err == wait.ErrWaitTimeout { + err = fmt.Errorf("volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus) + } + + return err +} + +func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string) error { + backoff := wait.Backoff{ + Duration: diskDetachInitDelay, + Factor: diskDetachFactor, + Steps: diskDetachSteps, + } + + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID) + if err != nil { + return false, err + } + return !attached, nil + }) + + if err == wait.ErrWaitTimeout { + err = fmt.Errorf("volume %q failed to detach within the alloted time", volumeID) + } + + return err +} + +func (detacher *cinderDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error { + volumeID := path.Base(volumeName) + if err := detacher.waitOperationFinished(volumeID); err != nil { + return err + } + attached, instanceID, err := detacher.cinderProvider.DiskIsAttachedByName(nodeName, volumeID) + if err != nil { + // Log error and continue with detach + klog.Errorf( + "Error checking if volume (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v", + volumeID, nodeName, err) + } + + if err == nil && !attached { + // Volume is already detached from node. + klog.Infof("detach operation was successful. volume %q is already detached from node %q.", volumeID, nodeName) + return nil + } + + if err = detacher.cinderProvider.DetachDisk(instanceID, volumeID); err != nil { + klog.Errorf("Error detaching volume %q from node %q: %v", volumeID, nodeName, err) + return err + } + if err = detacher.waitDiskDetached(instanceID, volumeID); err != nil { + klog.Errorf("Error waiting for volume %q to detach from node %q: %v", volumeID, nodeName, err) + return err + } + klog.Infof("detached volume %q from node %q", volumeID, nodeName) + return nil +} + +func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error { + return mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false) +} + +func (plugin *cinderPlugin) CanAttach(spec *volume.Spec) (bool, error) { + return true, nil +} + +func (plugin *cinderPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { + return true, nil +} + +func (attacher *cinderDiskAttacher) nodeInstanceID(nodeName types.NodeName) (string, error) { + instances, res := attacher.cinderProvider.Instances() + if !res { + return "", fmt.Errorf("failed to list openstack instances") + } + instanceID, err := instances.InstanceID(context.TODO(), nodeName) + if err != nil { + return "", err + } + if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { + instanceID = instanceID[(ind + 1):] + } + return instanceID, nil +} diff --git a/pkg/volume/cinder/attacher_test.go b/pkg/volume/cinder/attacher_test.go new file mode 100644 index 00000000000..f5b3ab0ec5e --- /dev/null +++ b/pkg/volume/cinder/attacher_test.go @@ -0,0 +1,758 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "context" + "errors" + "os" + "path/filepath" + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + cloudprovider "k8s.io/cloud-provider" + "k8s.io/kubernetes/pkg/volume" + volumetest "k8s.io/kubernetes/pkg/volume/testing" + + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" +) + +const ( + VolumeStatusPending = "pending" + VolumeStatusDone = "done" +) + +var attachStatus = "Attach" +var detachStatus = "Detach" + +func TestGetDeviceName_Volume(t *testing.T) { + plugin := newPlugin(t) + name := "my-cinder-volume" + spec := createVolSpec(name, false) + + deviceName, err := plugin.GetVolumeName(spec) + if err != nil { + t.Errorf("GetDeviceName error: %v", err) + } + if deviceName != name { + t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName) + } +} + +func TestGetDeviceName_PersistentVolume(t *testing.T) { + plugin := newPlugin(t) + name := "my-cinder-pv" + spec := createPVSpec(name, true) + + deviceName, err := plugin.GetVolumeName(spec) + if err != nil { + t.Errorf("GetDeviceName error: %v", err) + } + if deviceName != name { + t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName) + } +} + +func TestGetDeviceMountPath(t *testing.T) { + name := "cinder-volume-id" + spec := createVolSpec(name, false) + rootDir := "/var/lib/kubelet/" + host := volumetest.NewFakeVolumeHost(t, rootDir, nil, nil) + + attacher := &cinderDiskAttacher{ + host: host, + } + + //test the path + path, err := attacher.GetDeviceMountPath(spec) + if err != nil { + t.Errorf("Get device mount path error") + } + expectedPath := filepath.Join(rootDir, "plugins/kubernetes.io/cinder/mounts", name) + if path != expectedPath { + t.Errorf("Device mount path error: expected %s, got %s ", expectedPath, path) + } +} + +// One testcase for TestAttachDetach table test below +type testcase struct { + name string + // For fake GCE: + attach attachCall + detach detachCall + operationPending operationPendingCall + diskIsAttached diskIsAttachedCall + disksAreAttached disksAreAttachedCall + diskPath diskPathCall + t *testing.T + attachOrDetach *string + + instanceID string + // Actual test to run + test func(test *testcase) (string, error) + // Expected return of the test + expectedResult string + expectedError error +} + +func TestAttachDetach(t *testing.T) { + volumeID := "disk" + instanceID := "instance" + pending := VolumeStatusPending + done := VolumeStatusDone + nodeName := types.NodeName("nodeName") + readOnly := false + spec := createVolSpec(volumeID, readOnly) + attachError := errors.New("fake attach error") + detachError := errors.New("fake detach error") + diskCheckError := errors.New("fake DiskIsAttached error") + diskPathError := errors.New("fake GetAttachmentDiskPath error") + disksCheckError := errors.New("fake DisksAreAttached error") + operationFinishTimeout := errors.New("fake waitOperationFinished error") + tests := []testcase{ + // Successful Attach call + { + name: "Attach_Positive", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, nil}, + attach: attachCall{instanceID, volumeID, "", nil}, + diskPath: diskPathCall{instanceID, volumeID, "/dev/sda", nil}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + return attacher.Attach(spec, nodeName) + }, + expectedResult: "/dev/sda", + }, + + // Disk is already attached + { + name: "Attach_Positive_AlreadyAttached", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, true, nil}, + diskPath: diskPathCall{instanceID, volumeID, "/dev/sda", nil}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + return attacher.Attach(spec, nodeName) + }, + expectedResult: "/dev/sda", + }, + + // Disk is attaching + { + name: "Attach_is_attaching", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, true, pending, operationFinishTimeout}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + return attacher.Attach(spec, nodeName) + }, + expectedError: operationFinishTimeout, + }, + + // Attach call fails + { + name: "Attach_Negative", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, diskCheckError}, + attach: attachCall{instanceID, volumeID, "/dev/sda", attachError}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + return attacher.Attach(spec, nodeName) + }, + expectedError: attachError, + }, + + // GetAttachmentDiskPath call fails + { + name: "Attach_Negative_DiskPatchFails", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, nil}, + attach: attachCall{instanceID, volumeID, "", nil}, + diskPath: diskPathCall{instanceID, volumeID, "", diskPathError}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + return attacher.Attach(spec, nodeName) + }, + expectedError: diskPathError, + }, + + // Successful VolumesAreAttached call, attached + { + name: "VolumesAreAttached_Positive", + instanceID: instanceID, + disksAreAttached: disksAreAttachedCall{instanceID, nodeName, []string{volumeID}, map[string]bool{volumeID: true}, nil}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + attachments, err := attacher.VolumesAreAttached([]*volume.Spec{spec}, nodeName) + return serializeAttachments(attachments), err + }, + expectedResult: serializeAttachments(map[*volume.Spec]bool{spec: true}), + }, + + // Successful VolumesAreAttached call, not attached + { + name: "VolumesAreAttached_Negative", + instanceID: instanceID, + disksAreAttached: disksAreAttachedCall{instanceID, nodeName, []string{volumeID}, map[string]bool{volumeID: false}, nil}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + attachments, err := attacher.VolumesAreAttached([]*volume.Spec{spec}, nodeName) + return serializeAttachments(attachments), err + }, + expectedResult: serializeAttachments(map[*volume.Spec]bool{spec: false}), + }, + + // Treat as attached when DisksAreAttached call fails + { + name: "VolumesAreAttached_CinderFailed", + instanceID: instanceID, + disksAreAttached: disksAreAttachedCall{instanceID, nodeName, []string{volumeID}, nil, disksCheckError}, + test: func(testcase *testcase) (string, error) { + attacher := newAttacher(testcase) + attachments, err := attacher.VolumesAreAttached([]*volume.Spec{spec}, nodeName) + return serializeAttachments(attachments), err + }, + expectedResult: serializeAttachments(map[*volume.Spec]bool{spec: true}), + expectedError: disksCheckError, + }, + + // Detach succeeds + { + name: "Detach_Positive", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, true, nil}, + detach: detachCall{instanceID, volumeID, nil}, + test: func(testcase *testcase) (string, error) { + detacher := newDetacher(testcase) + return "", detacher.Detach(volumeID, nodeName) + }, + }, + + // Disk is already detached + { + name: "Detach_Positive_AlreadyDetached", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, nil}, + test: func(testcase *testcase) (string, error) { + detacher := newDetacher(testcase) + return "", detacher.Detach(volumeID, nodeName) + }, + }, + + // Detach succeeds when DiskIsAttached fails + { + name: "Detach_Positive_CheckFails", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, diskCheckError}, + detach: detachCall{instanceID, volumeID, nil}, + test: func(testcase *testcase) (string, error) { + detacher := newDetacher(testcase) + return "", detacher.Detach(volumeID, nodeName) + }, + }, + + // Detach fails + { + name: "Detach_Negative", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, false, done, nil}, + diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, diskCheckError}, + detach: detachCall{instanceID, volumeID, detachError}, + test: func(testcase *testcase) (string, error) { + detacher := newDetacher(testcase) + return "", detacher.Detach(volumeID, nodeName) + }, + expectedError: detachError, + }, + + // // Disk is detaching + { + name: "Detach_Is_Detaching", + instanceID: instanceID, + operationPending: operationPendingCall{volumeID, true, pending, operationFinishTimeout}, + test: func(testcase *testcase) (string, error) { + detacher := newDetacher(testcase) + return "", detacher.Detach(volumeID, nodeName) + }, + expectedError: operationFinishTimeout, + }, + } + + for _, testcase := range tests { + testcase.t = t + attachOrDetach := "" + testcase.attachOrDetach = &attachOrDetach + result, err := testcase.test(&testcase) + if err != testcase.expectedError { + t.Errorf("%s failed: expected err=%q, got %q", testcase.name, testcase.expectedError, err) + } + if result != testcase.expectedResult { + t.Errorf("%s failed: expected result=%q, got %q", testcase.name, testcase.expectedResult, result) + } + } +} + +type volumeAttachmentFlag struct { + volumeID string + attached bool +} + +type volumeAttachmentFlags []volumeAttachmentFlag + +func (va volumeAttachmentFlags) Len() int { + return len(va) +} + +func (va volumeAttachmentFlags) Swap(i, j int) { + va[i], va[j] = va[j], va[i] +} + +func (va volumeAttachmentFlags) Less(i, j int) bool { + if va[i].volumeID < va[j].volumeID { + return true + } + if va[i].volumeID > va[j].volumeID { + return false + } + return va[j].attached +} + +func serializeAttachments(attachments map[*volume.Spec]bool) string { + var attachmentFlags volumeAttachmentFlags + for spec, attached := range attachments { + attachmentFlags = append(attachmentFlags, volumeAttachmentFlag{spec.Name(), attached}) + } + sort.Sort(attachmentFlags) + return fmt.Sprint(attachmentFlags) +} + +// newPlugin creates a new gcePersistentDiskPlugin with fake cloud, NewAttacher +// and NewDetacher won't work. +func newPlugin(t *testing.T) *cinderPlugin { + host := volumetest.NewFakeVolumeHost(t, os.TempDir(), nil, nil) + plugins := ProbeVolumePlugins() + plugin := plugins[0] + plugin.Init(host) + return plugin.(*cinderPlugin) +} + +func newAttacher(testcase *testcase) *cinderDiskAttacher { + return &cinderDiskAttacher{ + host: nil, + cinderProvider: testcase, + } +} + +func newDetacher(testcase *testcase) *cinderDiskDetacher { + return &cinderDiskDetacher{ + cinderProvider: testcase, + } +} + +func createVolSpec(name string, readOnly bool) *volume.Spec { + return &volume.Spec{ + Volume: &v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + Cinder: &v1.CinderVolumeSource{ + VolumeID: name, + ReadOnly: readOnly, + }, + }, + }, + } +} + +func createPVSpec(name string, readOnly bool) *volume.Spec { + return &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + Cinder: &v1.CinderPersistentVolumeSource{ + VolumeID: name, + ReadOnly: readOnly, + }, + }, + }, + }, + } +} + +// Fake GCE implementation + +type attachCall struct { + instanceID string + volumeID string + retDeviceName string + ret error +} + +type detachCall struct { + instanceID string + devicePath string + ret error +} + +type operationPendingCall struct { + diskName string + pending bool + volumeStatus string + ret error +} + +type diskIsAttachedCall struct { + instanceID string + nodeName types.NodeName + volumeID string + isAttached bool + ret error +} + +type diskPathCall struct { + instanceID string + volumeID string + retPath string + ret error +} + +type disksAreAttachedCall struct { + instanceID string + nodeName types.NodeName + volumeIDs []string + areAttached map[string]bool + ret error +} + +func (testcase *testcase) AttachDisk(instanceID, volumeID string) (string, error) { + expected := &testcase.attach + + if expected.volumeID == "" && expected.instanceID == "" { + // testcase.attach looks uninitialized, test did not expect to call + // AttachDisk + testcase.t.Errorf("unexpected AttachDisk call") + return "", errors.New("unexpected AttachDisk call") + } + + if expected.volumeID != volumeID { + testcase.t.Errorf("unexpected AttachDisk call: expected volumeID %s, got %s", expected.volumeID, volumeID) + return "", errors.New("unexpected AttachDisk call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("unexpected AttachDisk call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return "", errors.New("unexpected AttachDisk call: wrong instanceID") + } + + klog.V(4).Infof("AttachDisk call: %s, %s, returning %q, %v", volumeID, instanceID, expected.retDeviceName, expected.ret) + + testcase.attachOrDetach = &attachStatus + return expected.retDeviceName, expected.ret +} + +func (testcase *testcase) DetachDisk(instanceID, volumeID string) error { + expected := &testcase.detach + + if expected.devicePath == "" && expected.instanceID == "" { + // testcase.detach looks uninitialized, test did not expect to call + // DetachDisk + testcase.t.Errorf("unexpected DetachDisk call") + return errors.New("unexpected DetachDisk call") + } + + if expected.devicePath != volumeID { + testcase.t.Errorf("unexpected DetachDisk call: expected volumeID %s, got %s", expected.devicePath, volumeID) + return errors.New("unexpected DetachDisk call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("unexpected DetachDisk call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return errors.New("unexpected DetachDisk call: wrong instanceID") + } + + klog.V(4).Infof("DetachDisk call: %s, %s, returning %v", volumeID, instanceID, expected.ret) + + testcase.attachOrDetach = &detachStatus + return expected.ret +} + +func (testcase *testcase) OperationPending(diskName string) (bool, string, error) { + expected := &testcase.operationPending + + if expected.volumeStatus == VolumeStatusPending { + klog.V(4).Infof("OperationPending call: %s, returning %v, %v, %v", diskName, expected.pending, expected.volumeStatus, expected.ret) + return true, expected.volumeStatus, expected.ret + } + + klog.V(4).Infof("OperationPending call: %s, returning %v, %v, %v", diskName, expected.pending, expected.volumeStatus, expected.ret) + + return false, expected.volumeStatus, expected.ret +} + +func (testcase *testcase) DiskIsAttached(instanceID, volumeID string) (bool, error) { + expected := &testcase.diskIsAttached + // If testcase call DetachDisk*, return false + if *testcase.attachOrDetach == detachStatus { + return false, nil + } + + // If testcase call AttachDisk*, return true + if *testcase.attachOrDetach == attachStatus { + return true, nil + } + + if expected.volumeID == "" && expected.instanceID == "" { + // testcase.diskIsAttached looks uninitialized, test did not expect to + // call DiskIsAttached + testcase.t.Errorf("unexpected DiskIsAttached call") + return false, errors.New("unexpected DiskIsAttached call") + } + + if expected.volumeID != volumeID { + testcase.t.Errorf("unexpected DiskIsAttached call: expected volumeID %s, got %s", expected.volumeID, volumeID) + return false, errors.New("unexpected DiskIsAttached call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("unexpected DiskIsAttached call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return false, errors.New("unexpected DiskIsAttached call: wrong instanceID") + } + + klog.V(4).Infof("DiskIsAttached call: %s, %s, returning %v, %v", volumeID, instanceID, expected.isAttached, expected.ret) + + return expected.isAttached, expected.ret +} + +func (testcase *testcase) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) { + expected := &testcase.diskPath + if expected.volumeID == "" && expected.instanceID == "" { + // testcase.diskPath looks uninitialized, test did not expect to + // call GetAttachmentDiskPath + testcase.t.Errorf("unexpected GetAttachmentDiskPath call") + return "", errors.New("unexpected GetAttachmentDiskPath call") + } + + if expected.volumeID != volumeID { + testcase.t.Errorf("unexpected GetAttachmentDiskPath call: expected volumeID %s, got %s", expected.volumeID, volumeID) + return "", errors.New("unexpected GetAttachmentDiskPath call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("unexpected GetAttachmentDiskPath call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return "", errors.New("unexpected GetAttachmentDiskPath call: wrong instanceID") + } + + klog.V(4).Infof("GetAttachmentDiskPath call: %s, %s, returning %v, %v", volumeID, instanceID, expected.retPath, expected.ret) + + return expected.retPath, expected.ret +} + +func (testcase *testcase) ShouldTrustDevicePath() bool { + return true +} + +func (testcase *testcase) DiskIsAttachedByName(nodeName types.NodeName, volumeID string) (bool, string, error) { + expected := &testcase.diskIsAttached + instanceID := expected.instanceID + // If testcase call DetachDisk*, return false + if *testcase.attachOrDetach == detachStatus { + return false, instanceID, nil + } + + // If testcase call AttachDisk*, return true + if *testcase.attachOrDetach == attachStatus { + return true, instanceID, nil + } + + if expected.nodeName != nodeName { + testcase.t.Errorf("unexpected DiskIsAttachedByName call: expected nodename %s, got %s", expected.nodeName, nodeName) + return false, instanceID, errors.New("unexpected DiskIsAttachedByName call: wrong nodename") + } + + if expected.volumeID == "" && expected.instanceID == "" { + // testcase.diskIsAttached looks uninitialized, test did not expect to + // call DiskIsAttached + testcase.t.Errorf("unexpected DiskIsAttachedByName call") + return false, instanceID, errors.New("unexpected DiskIsAttachedByName call") + } + + if expected.volumeID != volumeID { + testcase.t.Errorf("unexpected DiskIsAttachedByName call: expected volumeID %s, got %s", expected.volumeID, volumeID) + return false, instanceID, errors.New("unexpected DiskIsAttachedByName call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("unexpected DiskIsAttachedByName call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return false, instanceID, errors.New("unexpected DiskIsAttachedByName call: wrong instanceID") + } + + klog.V(4).Infof("DiskIsAttachedByName call: %s, %s, returning %v, %v, %v", volumeID, nodeName, expected.isAttached, expected.instanceID, expected.ret) + + return expected.isAttached, expected.instanceID, expected.ret +} + +func (testcase *testcase) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, string, bool, error) { + return "", "", "", false, errors.New("not implemented") +} + +func (testcase *testcase) GetDevicePath(volumeID string) string { + return "" +} + +func (testcase *testcase) InstanceID() (string, error) { + return testcase.instanceID, nil +} + +func (testcase *testcase) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + return resource.Quantity{}, nil +} + +func (testcase *testcase) DeleteVolume(volumeID string) error { + return errors.New("not implemented") +} + +func (testcase *testcase) GetAutoLabelsForPD(name string) (map[string]string, error) { + return map[string]string{}, errors.New("not implemented") +} + +func (testcase *testcase) Instances() (cloudprovider.Instances, bool) { + return &instances{testcase.instanceID}, true +} + +func (testcase *testcase) InstancesV2() (cloudprovider.InstancesV2, bool) { + return nil, false +} + +func (testcase *testcase) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) { + expected := &testcase.disksAreAttached + + areAttached := make(map[string]bool) + + if len(expected.volumeIDs) == 0 && expected.instanceID == "" { + // testcase.volumeIDs looks uninitialized, test did not expect to call DisksAreAttached + testcase.t.Errorf("Unexpected DisksAreAttached call!") + return areAttached, errors.New("unexpected DisksAreAttached call") + } + + if !reflect.DeepEqual(expected.volumeIDs, volumeIDs) { + testcase.t.Errorf("Unexpected DisksAreAttached call: expected volumeIDs %v, got %v", expected.volumeIDs, volumeIDs) + return areAttached, errors.New("unexpected DisksAreAttached call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("Unexpected DisksAreAttached call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return areAttached, errors.New("unexpected DisksAreAttached call: wrong instanceID") + } + + klog.V(4).Infof("DisksAreAttached call: %v, %s, returning %v, %v", volumeIDs, instanceID, expected.areAttached, expected.ret) + + return expected.areAttached, expected.ret +} + +func (testcase *testcase) DisksAreAttachedByName(nodeName types.NodeName, volumeIDs []string) (map[string]bool, error) { + expected := &testcase.disksAreAttached + areAttached := make(map[string]bool) + + instanceID := expected.instanceID + if expected.nodeName != nodeName { + testcase.t.Errorf("Unexpected DisksAreAttachedByName call: expected nodeName %s, got %s", expected.nodeName, nodeName) + return areAttached, errors.New("unexpected DisksAreAttachedByName call: wrong nodename") + } + if len(expected.volumeIDs) == 0 && expected.instanceID == "" { + // testcase.volumeIDs looks uninitialized, test did not expect to call DisksAreAttached + testcase.t.Errorf("Unexpected DisksAreAttachedByName call!") + return areAttached, errors.New("unexpected DisksAreAttachedByName call") + } + + if !reflect.DeepEqual(expected.volumeIDs, volumeIDs) { + testcase.t.Errorf("Unexpected DisksAreAttachedByName call: expected volumeIDs %v, got %v", expected.volumeIDs, volumeIDs) + return areAttached, errors.New("unexpected DisksAreAttachedByName call: wrong volumeID") + } + + if expected.instanceID != instanceID { + testcase.t.Errorf("Unexpected DisksAreAttachedByName call: expected instanceID %s, got %s", expected.instanceID, instanceID) + return areAttached, errors.New("unexpected DisksAreAttachedByName call: wrong instanceID") + } + + klog.V(4).Infof("DisksAreAttachedByName call: %v, %s, returning %v, %v", volumeIDs, nodeName, expected.areAttached, expected.ret) + + return expected.areAttached, expected.ret +} + +// Implementation of fake cloudprovider.Instances +type instances struct { + instanceID string +} + +func (instances *instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) { + return []v1.NodeAddress{}, errors.New("not implemented") +} + +func (instances *instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) { + return []v1.NodeAddress{}, errors.New("not implemented") +} + +func (instances *instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) { + return instances.instanceID, nil +} + +func (instances *instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) { + return "", errors.New("not implemented") +} + +func (instances *instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) { + return "", errors.New("not implemented") +} + +func (instances *instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) { + return false, errors.New("unimplemented") +} + +func (instances *instances) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) { + return false, errors.New("unimplemented") +} + +func (instances *instances) InstanceMetadataByProviderID(ctx context.Context, providerID string) (*cloudprovider.InstanceMetadata, error) { + return nil, errors.New("unimplemented") +} + +func (instances *instances) List(filter string) ([]types.NodeName, error) { + return []types.NodeName{}, errors.New("not implemented") +} + +func (instances *instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error { + return cloudprovider.NotImplemented +} + +func (instances *instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) { + return "", errors.New("not implemented") +} diff --git a/pkg/volume/cinder/cinder.go b/pkg/volume/cinder/cinder.go new file mode 100644 index 00000000000..ef422e24c5c --- /dev/null +++ b/pkg/volume/cinder/cinder.go @@ -0,0 +1,635 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2015 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + + "k8s.io/klog/v2" + "k8s.io/mount-utils" + "k8s.io/utils/keymutex" + utilstrings "k8s.io/utils/strings" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + cloudprovider "k8s.io/cloud-provider" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/legacy-cloud-providers/openstack" +) + +const ( + // DefaultCloudConfigPath is the default path for cloud configuration + DefaultCloudConfigPath = "/etc/kubernetes/cloud-config" +) + +// ProbeVolumePlugins is the primary entrypoint for volume plugins. +func ProbeVolumePlugins() []volume.VolumePlugin { + return []volume.VolumePlugin{&cinderPlugin{}} +} + +// BlockStorageProvider is the interface for accessing cinder functionality. +type BlockStorageProvider interface { + AttachDisk(instanceID, volumeID string) (string, error) + DetachDisk(instanceID, volumeID string) error + DeleteVolume(volumeID string) error + CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, string, bool, error) + GetDevicePath(volumeID string) string + InstanceID() (string, error) + GetAttachmentDiskPath(instanceID, volumeID string) (string, error) + OperationPending(diskName string) (bool, string, error) + DiskIsAttached(instanceID, volumeID string) (bool, error) + DiskIsAttachedByName(nodeName types.NodeName, volumeID string) (bool, string, error) + DisksAreAttachedByName(nodeName types.NodeName, volumeIDs []string) (map[string]bool, error) + ShouldTrustDevicePath() bool + Instances() (cloudprovider.Instances, bool) + ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) +} + +type cinderPlugin struct { + host volume.VolumeHost + // Guarding SetUp and TearDown operations + volumeLocks keymutex.KeyMutex +} + +var _ volume.VolumePlugin = &cinderPlugin{} +var _ volume.PersistentVolumePlugin = &cinderPlugin{} +var _ volume.DeletableVolumePlugin = &cinderPlugin{} +var _ volume.ProvisionableVolumePlugin = &cinderPlugin{} + +const ( + cinderVolumePluginName = "kubernetes.io/cinder" +) + +func getPath(uid types.UID, volName string, host volume.VolumeHost) string { + return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(cinderVolumePluginName), volName) +} + +func (plugin *cinderPlugin) Init(host volume.VolumeHost) error { + plugin.host = host + plugin.volumeLocks = keymutex.NewHashed(0) + return nil +} + +func (plugin *cinderPlugin) GetPluginName() string { + return cinderVolumePluginName +} + +func (plugin *cinderPlugin) GetVolumeName(spec *volume.Spec) (string, error) { + volumeID, _, _, err := getVolumeInfo(spec) + if err != nil { + return "", err + } + + return volumeID, nil +} + +func (plugin *cinderPlugin) CanSupport(spec *volume.Spec) bool { + return (spec.Volume != nil && spec.Volume.Cinder != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Cinder != nil) +} + +func (plugin *cinderPlugin) RequiresRemount(spec *volume.Spec) bool { + return false +} + +func (plugin *cinderPlugin) SupportsMountOption() bool { + return true + +} +func (plugin *cinderPlugin) SupportsBulkVolumeVerification() bool { + return false +} + +func (plugin *cinderPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { + return false, nil +} + +var _ volume.VolumePluginWithAttachLimits = &cinderPlugin{} + +func (plugin *cinderPlugin) GetVolumeLimits() (map[string]int64, error) { + volumeLimits := map[string]int64{ + util.CinderVolumeLimitKey: util.DefaultMaxCinderVolumes, + } + cloud := plugin.host.GetCloudProvider() + + // if we can't fetch cloudprovider we return an error + // hoping external CCM or admin can set it. Returning + // default values from here will mean, no one can + // override them. + if cloud == nil { + return nil, fmt.Errorf("no cloudprovider present") + } + + if cloud.ProviderName() != openstack.ProviderName { + return nil, fmt.Errorf("expected Openstack cloud, found %s", cloud.ProviderName()) + } + + openstackCloud, ok := cloud.(*openstack.OpenStack) + if ok && openstackCloud.NodeVolumeAttachLimit() > 0 { + volumeLimits[util.CinderVolumeLimitKey] = int64(openstackCloud.NodeVolumeAttachLimit()) + } + + return volumeLimits, nil +} + +func (plugin *cinderPlugin) VolumeLimitKey(spec *volume.Spec) string { + return util.CinderVolumeLimitKey +} + +func (plugin *cinderPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { + return []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + } +} + +func (plugin *cinderPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { + return plugin.newMounterInternal(spec, pod.UID, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *cinderPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Mounter, error) { + pdName, fsType, readOnly, err := getVolumeInfo(spec) + if err != nil { + return nil, err + } + + return &cinderVolumeMounter{ + cinderVolume: &cinderVolume{ + podUID: podUID, + volName: spec.Name(), + pdName: pdName, + mounter: mounter, + manager: manager, + plugin: plugin, + MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)), + }, + fsType: fsType, + readOnly: readOnly, + blockDeviceMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), + mountOptions: util.MountOptionFromSpec(spec), + }, nil +} + +func (plugin *cinderPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { + return plugin.newUnmounterInternal(volName, podUID, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *cinderPlugin) newUnmounterInternal(volName string, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Unmounter, error) { + return &cinderVolumeUnmounter{ + &cinderVolume{ + podUID: podUID, + volName: volName, + manager: manager, + mounter: mounter, + plugin: plugin, + MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), + }}, nil +} + +func (plugin *cinderPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { + return plugin.newDeleterInternal(spec, &DiskUtil{}) +} + +func (plugin *cinderPlugin) newDeleterInternal(spec *volume.Spec, manager cdManager) (volume.Deleter, error) { + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Cinder == nil { + return nil, fmt.Errorf("spec.PersistentVolumeSource.Cinder is nil") + } + return &cinderVolumeDeleter{ + &cinderVolume{ + volName: spec.Name(), + pdName: spec.PersistentVolume.Spec.Cinder.VolumeID, + manager: manager, + plugin: plugin, + }}, nil +} + +func (plugin *cinderPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) { + return plugin.newProvisionerInternal(options, &DiskUtil{}) +} + +func (plugin *cinderPlugin) newProvisionerInternal(options volume.VolumeOptions, manager cdManager) (volume.Provisioner, error) { + return &cinderVolumeProvisioner{ + cinderVolume: &cinderVolume{ + manager: manager, + plugin: plugin, + }, + options: options, + }, nil +} + +func (plugin *cinderPlugin) getCloudProvider() (BlockStorageProvider, error) { + cloud := plugin.host.GetCloudProvider() + if cloud == nil { + if _, err := os.Stat(DefaultCloudConfigPath); err == nil { + var config *os.File + config, err = os.Open(DefaultCloudConfigPath) + if err != nil { + return nil, fmt.Errorf("unable to load OpenStack configuration from default path : %v", err) + } + defer config.Close() + cloud, err = cloudprovider.GetCloudProvider(openstack.ProviderName, config) + if err != nil { + return nil, fmt.Errorf("unable to create OpenStack cloud provider from default path : %v", err) + } + } else { + return nil, fmt.Errorf("OpenStack cloud provider was not initialized properly : %v", err) + } + } + + switch cloud := cloud.(type) { + case *openstack.OpenStack: + return cloud, nil + default: + return nil, errors.New("invalid cloud provider: expected OpenStack") + } +} + +func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { + mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() + pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) + sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) + if err != nil { + return nil, err + } + klog.V(4).Infof("Found volume %s mounted to %s", sourceName, mountPath) + cinderVolume := &v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + Cinder: &v1.CinderVolumeSource{ + VolumeID: sourceName, + }, + }, + } + return volume.NewSpecFromVolume(cinderVolume), nil +} + +var _ volume.ExpandableVolumePlugin = &cinderPlugin{} + +func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { + volumeID, _, _, err := getVolumeInfo(spec) + if err != nil { + return oldSize, err + } + cloud, err := plugin.getCloudProvider() + if err != nil { + return oldSize, err + } + + expandedSize, err := cloud.ExpandVolume(volumeID, oldSize, newSize) + if err != nil { + return oldSize, err + } + + klog.V(2).Infof("volume %s expanded to new size %d successfully", volumeID, int(newSize.Value())) + return expandedSize, nil +} + +func (plugin *cinderPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) { + fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec) + if err != nil { + return false, fmt.Errorf("error checking VolumeMode: %v", err) + } + // if volume is not a fs file system, there is nothing for us to do here. + if !fsVolume { + return true, nil + } + + _, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath) + if err != nil { + return false, err + } + return true, nil +} + +var _ volume.NodeExpandableVolumePlugin = &cinderPlugin{} + +func (plugin *cinderPlugin) RequiresFSResize() bool { + return true +} + +// Abstract interface to PD operations. +type cdManager interface { + // Attaches the disk to the kubelet's host machine. + AttachDisk(mounter *cinderVolumeMounter, globalPDPath string) error + // Detaches the disk from the kubelet's host machine. + DetachDisk(unmounter *cinderVolumeUnmounter) error + // Creates a volume + CreateVolume(provisioner *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, labels map[string]string, fstype string, err error) + // Deletes a volume + DeleteVolume(deleter *cinderVolumeDeleter) error +} + +var _ volume.Mounter = &cinderVolumeMounter{} + +type cinderVolumeMounter struct { + *cinderVolume + fsType string + readOnly bool + blockDeviceMounter *mount.SafeFormatAndMount + mountOptions []string +} + +// cinderPersistentDisk volumes are disk resources provided by C3 +// that are attached to the kubelet's host machine and exposed to the pod. +type cinderVolume struct { + volName string + podUID types.UID + // Unique identifier of the volume, used to find the disk resource in the provider. + pdName string + // Filesystem type, optional. + fsType string + // Utility interface that provides API calls to the provider to attach/detach disks. + manager cdManager + // Mounter interface that provides system calls to mount the global path to the pod local path. + mounter mount.Interface + plugin *cinderPlugin + volume.MetricsProvider +} + +func (b *cinderVolumeMounter) GetAttributes() volume.Attributes { + return volume.Attributes{ + ReadOnly: b.readOnly, + Managed: !b.readOnly, + SELinuxRelabel: true, + } +} + +func (b *cinderVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { + return b.SetUpAt(b.GetPath(), mounterArgs) +} + +// SetUp bind mounts to the volume path. +func (b *cinderVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { + klog.V(5).Infof("Cinder SetUp %s to %s", b.pdName, dir) + + b.plugin.volumeLocks.LockKey(b.pdName) + defer b.plugin.volumeLocks.UnlockKey(b.pdName) + + notmnt, err := b.mounter.IsLikelyNotMountPoint(dir) + if err != nil && !os.IsNotExist(err) { + klog.Errorf("Cannot validate mount point: %s %v", dir, err) + return err + } + if !notmnt { + klog.V(4).Infof("Something is already mounted to target %s", dir) + return nil + } + globalPDPath := makeGlobalPDName(b.plugin.host, b.pdName) + + options := []string{"bind"} + if b.readOnly { + options = append(options, "ro") + } + + if err := os.MkdirAll(dir, 0750); err != nil { + klog.V(4).Infof("Could not create directory %s: %v", dir, err) + return err + } + + mountOptions := util.JoinMountOptions(options, b.mountOptions) + // Perform a bind mount to the full path to allow duplicate mounts of the same PD. + klog.V(4).Infof("Attempting to mount cinder volume %s to %s with options %v", b.pdName, dir, mountOptions) + err = b.mounter.MountSensitiveWithoutSystemd(globalPDPath, dir, "", options, nil) + if err != nil { + klog.V(4).Infof("Mount failed: %v", err) + notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) + if mntErr != nil { + klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) + return err + } + if !notmnt { + if mntErr = b.mounter.Unmount(dir); mntErr != nil { + klog.Errorf("Failed to unmount: %v", mntErr) + return err + } + notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) + if mntErr != nil { + klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) + return err + } + if !notmnt { + // This is very odd, we don't expect it. We'll try again next sync loop. + klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", b.GetPath()) + return err + } + } + os.Remove(dir) + klog.Errorf("Failed to mount %s: %v", dir, err) + return err + } + + if !b.readOnly { + volume.SetVolumeOwnership(b, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil)) + } + klog.V(3).Infof("Cinder volume %s mounted to %s", b.pdName, dir) + + return nil +} + +func makeGlobalPDName(host volume.VolumeHost, devName string) string { + return filepath.Join(host.GetPluginDir(cinderVolumePluginName), util.MountsInGlobalPDPath, devName) +} + +func (cd *cinderVolume) GetPath() string { + return getPath(cd.podUID, cd.volName, cd.plugin.host) +} + +type cinderVolumeUnmounter struct { + *cinderVolume +} + +var _ volume.Unmounter = &cinderVolumeUnmounter{} + +func (c *cinderVolumeUnmounter) TearDown() error { + return c.TearDownAt(c.GetPath()) +} + +// Unmounts the bind mount, and detaches the disk only if the PD +// resource was the last reference to that disk on the kubelet. +func (c *cinderVolumeUnmounter) TearDownAt(dir string) error { + if pathExists, pathErr := mount.PathExists(dir); pathErr != nil { + return fmt.Errorf("error checking if path exists: %v", pathErr) + } else if !pathExists { + klog.Warningf("Warning: Unmount skipped because path does not exist: %w", dir) + return nil + } + + klog.V(5).Infof("Cinder TearDown of %s", dir) + notmnt, err := c.mounter.IsLikelyNotMountPoint(dir) + if err != nil { + klog.V(4).Infof("IsLikelyNotMountPoint check failed: %v", err) + return err + } + if notmnt { + klog.V(4).Infof("Nothing is mounted to %s, ignoring", dir) + return os.Remove(dir) + } + + // Find Cinder volumeID to lock the right volume + // TODO: refactor VolumePlugin.NewUnmounter to get full volume.Spec just like + // NewMounter. We could then find volumeID there without probing MountRefs. + refs, err := c.mounter.GetMountRefs(dir) + if err != nil { + klog.V(4).Infof("GetMountRefs failed: %v", err) + return err + } + if len(refs) == 0 { + klog.V(4).Infof("Directory %s is not mounted", dir) + return fmt.Errorf("directory %s is not mounted", dir) + } + c.pdName = path.Base(refs[0]) + klog.V(4).Infof("Found volume %s mounted to %s", c.pdName, dir) + + // lock the volume (and thus wait for any concurrent SetUpAt to finish) + c.plugin.volumeLocks.LockKey(c.pdName) + defer c.plugin.volumeLocks.UnlockKey(c.pdName) + + // Reload list of references, there might be SetUpAt finished in the meantime + _, err = c.mounter.GetMountRefs(dir) + if err != nil { + klog.V(4).Infof("GetMountRefs failed: %v", err) + return err + } + if err := c.mounter.Unmount(dir); err != nil { + klog.V(4).Infof("Unmount failed: %v", err) + return err + } + klog.V(3).Infof("Successfully unmounted: %s\n", dir) + + notmnt, mntErr := c.mounter.IsLikelyNotMountPoint(dir) + if mntErr != nil { + klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) + return err + } + if notmnt { + if err := os.Remove(dir); err != nil { + klog.V(4).Infof("Failed to remove directory after unmount: %v", err) + return err + } + } + return nil +} + +type cinderVolumeDeleter struct { + *cinderVolume +} + +var _ volume.Deleter = &cinderVolumeDeleter{} + +func (r *cinderVolumeDeleter) GetPath() string { + return getPath(r.podUID, r.volName, r.plugin.host) +} + +func (r *cinderVolumeDeleter) Delete() error { + return r.manager.DeleteVolume(r) +} + +type cinderVolumeProvisioner struct { + *cinderVolume + options volume.VolumeOptions +} + +var _ volume.Provisioner = &cinderVolumeProvisioner{} + +func (c *cinderVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { + if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) { + return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) + } + + volumeID, sizeGB, labels, fstype, err := c.manager.CreateVolume(c, selectedNode, allowedTopologies) + if err != nil { + return nil, err + } + + if fstype == "" { + fstype = "ext4" + } + + volumeMode := c.options.PVC.Spec.VolumeMode + if volumeMode != nil && *volumeMode == v1.PersistentVolumeBlock { + // Block volumes should not have any FSType + fstype = "" + } + + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: c.options.PVName, + Labels: labels, + Annotations: map[string]string{ + util.VolumeDynamicallyCreatedByKey: "cinder-dynamic-provisioner", + }, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy, + AccessModes: c.options.PVC.Spec.AccessModes, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)), + }, + VolumeMode: volumeMode, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Cinder: &v1.CinderPersistentVolumeSource{ + VolumeID: volumeID, + FSType: fstype, + ReadOnly: false, + }, + }, + MountOptions: c.options.MountOptions, + }, + } + if len(c.options.PVC.Spec.AccessModes) == 0 { + pv.Spec.AccessModes = c.plugin.GetAccessModes() + } + + requirements := make([]v1.NodeSelectorRequirement, 0) + for k, v := range labels { + if v != "" { + requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: []string{v}}) + } + } + if len(requirements) > 0 { + pv.Spec.NodeAffinity = new(v1.VolumeNodeAffinity) + pv.Spec.NodeAffinity.Required = new(v1.NodeSelector) + pv.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1) + pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = requirements + } + + return pv, nil +} + +func getVolumeInfo(spec *volume.Spec) (string, string, bool, error) { + if spec.Volume != nil && spec.Volume.Cinder != nil { + return spec.Volume.Cinder.VolumeID, spec.Volume.Cinder.FSType, spec.Volume.Cinder.ReadOnly, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.Cinder != nil { + return spec.PersistentVolume.Spec.Cinder.VolumeID, spec.PersistentVolume.Spec.Cinder.FSType, spec.ReadOnly, nil + } + + return "", "", false, fmt.Errorf("Spec does not reference a Cinder volume type") +} diff --git a/pkg/volume/cinder/cinder_block.go b/pkg/volume/cinder/cinder_block.go new file mode 100644 index 00000000000..618115770e3 --- /dev/null +++ b/pkg/volume/cinder/cinder_block.go @@ -0,0 +1,179 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2018 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "fmt" + "path/filepath" + + "k8s.io/klog/v2" + "k8s.io/mount-utils" + utilstrings "k8s.io/utils/strings" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" +) + +var _ volume.VolumePlugin = &cinderPlugin{} +var _ volume.PersistentVolumePlugin = &cinderPlugin{} +var _ volume.BlockVolumePlugin = &cinderPlugin{} +var _ volume.DeletableVolumePlugin = &cinderPlugin{} +var _ volume.ProvisionableVolumePlugin = &cinderPlugin{} +var _ volume.ExpandableVolumePlugin = &cinderPlugin{} + +func (plugin *cinderPlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) { + pluginDir := plugin.host.GetVolumeDevicePluginDir(cinderVolumePluginName) + blkutil := volumepathhandler.NewBlockVolumePathHandler() + globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID) + if err != nil { + return nil, err + } + klog.V(5).Infof("globalMapPathUUID: %v, err: %v", globalMapPathUUID, err) + + globalMapPath := filepath.Dir(globalMapPathUUID) + if len(globalMapPath) <= 1 { + return nil, fmt.Errorf("failed to get volume plugin information from globalMapPathUUID: %v", globalMapPathUUID) + } + + return getVolumeSpecFromGlobalMapPath(volumeName, globalMapPath) +} + +func getVolumeSpecFromGlobalMapPath(volumeName, globalMapPath string) (*volume.Spec, error) { + // Get volume spec information from globalMapPath + // globalMapPath example: + // plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumeID} + // plugins/kubernetes.io/cinder/volumeDevices/vol-XXXXXX + vID := filepath.Base(globalMapPath) + if len(vID) <= 1 { + return nil, fmt.Errorf("failed to get volumeID from global path=%s", globalMapPath) + } + block := v1.PersistentVolumeBlock + cinderVolume := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: volumeName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + Cinder: &v1.CinderPersistentVolumeSource{ + VolumeID: vID, + }, + }, + VolumeMode: &block, + }, + } + return volume.NewSpecFromPersistentVolume(cinderVolume, true), nil +} + +// NewBlockVolumeMapper creates a new volume.BlockVolumeMapper from an API specification. +func (plugin *cinderPlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) { + // If this is called via GenerateUnmapDeviceFunc(), pod is nil. + // Pass empty string as dummy uid since uid isn't used in the case. + var uid types.UID + if pod != nil { + uid = pod.UID + } + + return plugin.newBlockVolumeMapperInternal(spec, uid, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *cinderPlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.BlockVolumeMapper, error) { + pdName, fsType, readOnly, err := getVolumeInfo(spec) + if err != nil { + return nil, err + } + + mapper := &cinderVolumeMapper{ + cinderVolume: &cinderVolume{ + podUID: podUID, + volName: spec.Name(), + pdName: pdName, + fsType: fsType, + manager: manager, + mounter: mounter, + plugin: plugin, + }, + readOnly: readOnly, + } + + blockPath, err := mapper.GetGlobalMapPath(spec) + if err != nil { + return nil, fmt.Errorf("failed to get device path: %v", err) + } + mapper.MetricsProvider = volume.NewMetricsBlock(filepath.Join(blockPath, string(podUID))) + + return mapper, nil +} + +func (plugin *cinderPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) { + return plugin.newUnmapperInternal(volName, podUID, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *cinderPlugin) newUnmapperInternal(volName string, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.BlockVolumeUnmapper, error) { + return &cinderPluginUnmapper{ + cinderVolume: &cinderVolume{ + podUID: podUID, + volName: volName, + manager: manager, + mounter: mounter, + plugin: plugin, + }}, nil +} + +type cinderPluginUnmapper struct { + *cinderVolume + volume.MetricsNil +} + +var _ volume.BlockVolumeUnmapper = &cinderPluginUnmapper{} + +type cinderVolumeMapper struct { + *cinderVolume + readOnly bool +} + +var _ volume.BlockVolumeMapper = &cinderVolumeMapper{} + +// GetGlobalMapPath returns global map path and error +// path: plugins/kubernetes.io/{PluginName}/volumeDevices/volumeID +// +// plugins/kubernetes.io/cinder/volumeDevices/vol-XXXXXX +func (cd *cinderVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) { + pdName, _, _, err := getVolumeInfo(spec) + if err != nil { + return "", err + } + return filepath.Join(cd.plugin.host.GetVolumeDevicePluginDir(cinderVolumePluginName), pdName), nil +} + +// GetPodDeviceMapPath returns pod device map path and volume name +// path: pods/{podUid}/volumeDevices/kubernetes.io~cinder +func (cd *cinderVolume) GetPodDeviceMapPath() (string, string) { + name := cinderVolumePluginName + return cd.plugin.host.GetPodVolumeDeviceDir(cd.podUID, utilstrings.EscapeQualifiedName(name)), cd.volName +} + +// SupportsMetrics returns true for cinderVolumeMapper as it initializes the +// MetricsProvider. +func (cvm *cinderVolumeMapper) SupportsMetrics() bool { + return true +} diff --git a/pkg/volume/cinder/cinder_block_test.go b/pkg/volume/cinder/cinder_block_test.go new file mode 100644 index 00000000000..0b0aa20321d --- /dev/null +++ b/pkg/volume/cinder/cinder_block_test.go @@ -0,0 +1,151 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2018 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "os" + "path/filepath" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utiltesting "k8s.io/client-go/util/testing" + "k8s.io/kubernetes/pkg/volume" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +const ( + testVolName = "vol-1234" + testPVName = "pv1" + testGlobalPath = "plugins/kubernetes.io/cinder/volumeDevices/vol-1234" + testPodPath = "pods/poduid/volumeDevices/kubernetes.io~cinder" +) + +func TestGetVolumeSpecFromGlobalMapPath(t *testing.T) { + // make our test path for fake GlobalMapPath + // /tmp symbolized our pluginDir + // /tmp/testGlobalPathXXXXX/plugins/kubernetes.io/cinder/volumeDevices/pdVol1 + tmpVDir, err := utiltesting.MkTmpdir("cinderBlockTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + //deferred clean up + defer os.RemoveAll(tmpVDir) + + expectedGlobalPath := filepath.Join(tmpVDir, testGlobalPath) + + //Bad Path + badspec, err := getVolumeSpecFromGlobalMapPath("", "") + if badspec != nil || err == nil { + t.Errorf("Expected not to get spec from GlobalMapPath but did") + } + + // Good Path + spec, err := getVolumeSpecFromGlobalMapPath("myVolume", expectedGlobalPath) + if spec == nil || err != nil { + t.Fatalf("Failed to get spec from GlobalMapPath: %v", err) + } + if spec.PersistentVolume.Name != "myVolume" { + t.Errorf("Invalid PV name from GlobalMapPath spec: %s", spec.PersistentVolume.Name) + } + if spec.PersistentVolume.Spec.Cinder.VolumeID != testVolName { + t.Errorf("Invalid volumeID from GlobalMapPath spec: %s", spec.PersistentVolume.Spec.Cinder.VolumeID) + } + block := v1.PersistentVolumeBlock + specMode := spec.PersistentVolume.Spec.VolumeMode + if specMode == nil { + t.Fatalf("Failed to get volumeMode from PersistentVolumeBlock") + } + if *specMode != block { + t.Errorf("Invalid volumeMode from GlobalMapPath spec: %v expected: %v", *specMode, block) + } +} + +func getTestVolume(readOnly bool, isBlock bool) *volume.Spec { + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: testPVName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + Cinder: &v1.CinderPersistentVolumeSource{ + VolumeID: testVolName, + }, + }, + }, + } + + if isBlock { + blockMode := v1.PersistentVolumeBlock + pv.Spec.VolumeMode = &blockMode + } + return volume.NewSpecFromPersistentVolume(pv, readOnly) +} + +func TestGetPodAndPluginMapPaths(t *testing.T) { + tmpVDir, err := utiltesting.MkTmpdir("cinderBlockTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + //deferred clean up + defer os.RemoveAll(tmpVDir) + + expectedGlobalPath := filepath.Join(tmpVDir, testGlobalPath) + expectedPodPath := filepath.Join(tmpVDir, testPodPath) + + spec := getTestVolume(false, true /*isBlock*/) + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpVDir, nil, nil)) + plug, err := plugMgr.FindMapperPluginByName(cinderVolumePluginName) + if err != nil { + os.RemoveAll(tmpVDir) + t.Fatalf("Can't find the plugin by name: %q", cinderVolumePluginName) + } + if plug.GetPluginName() != cinderVolumePluginName { + t.Fatalf("Wrong name: %s", plug.GetPluginName()) + } + pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} + mapper, err := plug.NewBlockVolumeMapper(spec, pod, volume.VolumeOptions{}) + if err != nil { + t.Fatalf("Failed to make a new Mounter: %v", err) + } + if mapper == nil { + t.Fatalf("Got a nil Mounter") + } + + //GetGlobalMapPath + gMapPath, err := mapper.GetGlobalMapPath(spec) + if err != nil || len(gMapPath) == 0 { + t.Fatalf("Invalid GlobalMapPath from spec: %s", spec.PersistentVolume.Spec.Cinder.VolumeID) + } + if gMapPath != expectedGlobalPath { + t.Errorf("Failed to get GlobalMapPath: %s %s", gMapPath, expectedGlobalPath) + } + + //GetPodDeviceMapPath + gDevicePath, gVolName := mapper.GetPodDeviceMapPath() + if gDevicePath != expectedPodPath { + t.Errorf("Got unexpected pod path: %s, expected %s", gDevicePath, expectedPodPath) + } + if gVolName != testPVName { + t.Errorf("Got unexpected volNamne: %s, expected %s", gVolName, testPVName) + } +} diff --git a/pkg/volume/cinder/cinder_test.go b/pkg/volume/cinder/cinder_test.go new file mode 100644 index 00000000000..61c9bc27ac9 --- /dev/null +++ b/pkg/volume/cinder/cinder_test.go @@ -0,0 +1,365 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2015 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + utiltesting "k8s.io/client-go/util/testing" + "k8s.io/mount-utils" + + "k8s.io/kubernetes/pkg/volume" + volumetest "k8s.io/kubernetes/pkg/volume/testing" + "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/legacy-cloud-providers/openstack" +) + +func TestCanSupport(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("cinderTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil)) + + plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") + if err != nil { + t.Fatal("Can't find the plugin by name") + } + if plug.GetPluginName() != "kubernetes.io/cinder" { + t.Errorf("Wrong name: %s", plug.GetPluginName()) + } + if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{Cinder: &v1.CinderVolumeSource{}}}}) { + t.Errorf("Expected true") + } + + if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{Cinder: &v1.CinderPersistentVolumeSource{}}}}}) { + t.Errorf("Expected true") + } +} + +type fakePDManager struct { + // How long should AttachDisk/DetachDisk take - we need slower AttachDisk in a test. + attachDetachDuration time.Duration +} + +func getFakeDeviceName(host volume.VolumeHost, pdName string) string { + return filepath.Join(host.GetPluginDir(cinderVolumePluginName), "device", pdName) +} + +// Real Cinder AttachDisk attaches a cinder volume. If it is not yet mounted, +// it mounts it to globalPDPath. +// We create a dummy directory (="device") and bind-mount it to globalPDPath +func (fake *fakePDManager) AttachDisk(b *cinderVolumeMounter, globalPDPath string) error { + globalPath := makeGlobalPDName(b.plugin.host, b.pdName) + fakeDeviceName := getFakeDeviceName(b.plugin.host, b.pdName) + err := os.MkdirAll(fakeDeviceName, 0750) + if err != nil { + return err + } + // Attaching a Cinder volume can be slow... + time.Sleep(fake.attachDetachDuration) + + // The volume is "attached", bind-mount it if it's not mounted yet. + notmnt, err := b.mounter.IsLikelyNotMountPoint(globalPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(globalPath, 0750); err != nil { + return err + } + notmnt = true + } else { + return err + } + } + if notmnt { + err = b.mounter.MountSensitiveWithoutSystemd(fakeDeviceName, globalPath, "", []string{"bind"}, nil) + if err != nil { + return err + } + } + return nil +} + +func (fake *fakePDManager) DetachDisk(c *cinderVolumeUnmounter) error { + globalPath := makeGlobalPDName(c.plugin.host, c.pdName) + fakeDeviceName := getFakeDeviceName(c.plugin.host, c.pdName) + // unmount the bind-mount - should be fast + err := c.mounter.Unmount(globalPath) + if err != nil { + return err + } + + // "Detach" the fake "device" + err = os.RemoveAll(fakeDeviceName) + if err != nil { + return err + } + return nil +} + +func (fake *fakePDManager) CreateVolume(c *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, labels map[string]string, fstype string, err error) { + labels = make(map[string]string) + labels[v1.LabelTopologyZone] = "nova" + return "test-volume-name", 1, labels, "", nil +} + +func (fake *fakePDManager) DeleteVolume(cd *cinderVolumeDeleter) error { + if cd.pdName != "test-volume-name" { + return fmt.Errorf("Deleter got unexpected volume name: %s", cd.pdName) + } + return nil +} + +func TestPlugin(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("cinderTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil)) + + plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") + if err != nil { + t.Errorf("Can't find the plugin by name") + } + spec := &v1.Volume{ + Name: "vol1", + VolumeSource: v1.VolumeSource{ + Cinder: &v1.CinderVolumeSource{ + VolumeID: "pd", + FSType: "ext4", + }, + }, + } + mounter, err := plug.(*cinderPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{0}, mount.NewFakeMounter(nil)) + if err != nil { + t.Errorf("Failed to make a new Mounter: %v", err) + } + if mounter == nil { + t.Errorf("Got a nil Mounter") + } + volPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~cinder/vol1") + path := mounter.GetPath() + if path != volPath { + t.Errorf("Got unexpected path: %s", path) + } + + if err := mounter.SetUp(volume.MounterArgs{}); err != nil { + t.Errorf("Expected success, got: %v", err) + } + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + t.Errorf("SetUp() failed, volume path not created: %s", path) + } else { + t.Errorf("SetUp() failed: %v", err) + } + } + + unmounter, err := plug.(*cinderPlugin).newUnmounterInternal("vol1", types.UID("poduid"), &fakePDManager{0}, mount.NewFakeMounter(nil)) + if err != nil { + t.Errorf("Failed to make a new Unmounter: %v", err) + } + if unmounter == nil { + t.Errorf("Got a nil Unmounter") + } + + if err := unmounter.TearDown(); err != nil { + t.Errorf("Expected success, got: %v", err) + } + if _, err := os.Stat(path); err == nil { + t.Errorf("TearDown() failed, volume path still exists: %s", path) + } else if !os.IsNotExist(err) { + t.Errorf("TearDown() failed: %v", err) + } + + // Test Provisioner + options := volume.VolumeOptions{ + PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}), + PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete, + } + provisioner, err := plug.(*cinderPlugin).newProvisionerInternal(options, &fakePDManager{0}) + if err != nil { + t.Errorf("ProvisionerInternal() failed: %v", err) + } + persistentSpec, err := provisioner.Provision(nil, nil) + if err != nil { + t.Errorf("Provision() failed: %v", err) + } + + if persistentSpec.Spec.PersistentVolumeSource.Cinder.VolumeID != "test-volume-name" { + t.Errorf("Provision() returned unexpected volume ID: %s", persistentSpec.Spec.PersistentVolumeSource.Cinder.VolumeID) + } + cap := persistentSpec.Spec.Capacity[v1.ResourceStorage] + size := cap.Value() + if size != 1024*1024*1024 { + t.Errorf("Provision() returned unexpected volume size: %v", size) + } + + // check nodeaffinity members + if persistentSpec.Spec.NodeAffinity == nil { + t.Errorf("Provision() returned unexpected nil NodeAffinity") + } + + if persistentSpec.Spec.NodeAffinity.Required == nil { + t.Errorf("Provision() returned unexpected nil NodeAffinity.Required") + } + + n := len(persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms) + if n != 1 { + t.Errorf("Provision() returned unexpected number of NodeSelectorTerms %d. Expected %d", n, 1) + } + + n = len(persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions) + if n != 1 { + t.Errorf("Provision() returned unexpected number of MatchExpressions %d. Expected %d", n, 1) + } + + req := persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions[0] + + if req.Key != v1.LabelTopologyZone { + t.Errorf("Provision() returned unexpected requirement key in NodeAffinity %v", req.Key) + } + + if req.Operator != v1.NodeSelectorOpIn { + t.Errorf("Provision() returned unexpected requirement operator in NodeAffinity %v", req.Operator) + } + + if len(req.Values) != 1 || req.Values[0] != "nova" { + t.Errorf("Provision() returned unexpected requirement value in NodeAffinity %v", req.Values) + } + + // Test Deleter + volSpec := &volume.Spec{ + PersistentVolume: persistentSpec, + } + deleter, err := plug.(*cinderPlugin).newDeleterInternal(volSpec, &fakePDManager{0}) + if err != nil { + t.Errorf("DeleterInternal() failed: %v", err) + } + err = deleter.Delete() + if err != nil { + t.Errorf("Deleter() failed: %v", err) + } +} + +func TestGetVolumeLimit(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("cinderTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + + cloud, err := getOpenstackCloudProvider() + if err != nil { + t.Fatalf("can not instantiate openstack cloudprovider : %v", err) + } + + defer os.RemoveAll(tmpDir) + plugMgr := volume.VolumePluginMgr{} + volumeHost := volumetest.NewFakeKubeletVolumeHostWithCloudProvider(t, tmpDir, nil, nil, cloud) + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumeHost) + + plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") + if err != nil { + t.Fatalf("Can't find the plugin by name") + } + attachablePlugin, ok := plug.(volume.VolumePluginWithAttachLimits) + if !ok { + t.Fatalf("plugin %s is not of attachable type", plug.GetPluginName()) + } + + limits, err := attachablePlugin.GetVolumeLimits() + if err != nil { + t.Errorf("error fetching limits : %v", err) + } + if len(limits) == 0 { + t.Fatalf("expecting limit from openstack got none") + } + limit, _ := limits[util.CinderVolumeLimitKey] + if limit != 10 { + t.Fatalf("expected volume limit to be 10 got %d", limit) + } +} + +func getOpenstackCloudProvider() (*openstack.OpenStack, error) { + cfg := getOpenstackConfig() + return openstack.NewFakeOpenStackCloud(cfg) +} + +func getOpenstackConfig() openstack.Config { + cfg := openstack.Config{ + Global: struct { + AuthURL string `gcfg:"auth-url"` + Username string + UserID string `gcfg:"user-id"` + Password string `datapolicy:"password"` + TenantID string `gcfg:"tenant-id"` + TenantName string `gcfg:"tenant-name"` + TrustID string `gcfg:"trust-id"` + DomainID string `gcfg:"domain-id"` + DomainName string `gcfg:"domain-name"` + Region string + CAFile string `gcfg:"ca-file"` + SecretName string `gcfg:"secret-name"` + SecretNamespace string `gcfg:"secret-namespace"` + KubeconfigPath string `gcfg:"kubeconfig-path"` + }{ + Username: "user", + Password: "pass", + TenantID: "foobar", + DomainID: "2a73b8f597c04551a0fdc8e95544be8a", + DomainName: "local", + AuthURL: "http://auth.url", + UserID: "user", + }, + BlockStorage: openstack.BlockStorageOpts{ + NodeVolumeAttachLimit: 10, + }, + } + return cfg +} + +func TestUnsupportedVolumeHost(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("cinderTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) + + plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") + if err != nil { + t.Fatal("Can't find the plugin by name") + } + + _, err = plug.ConstructVolumeSpec("", "") + if err == nil { + t.Errorf("Expected failure constructing volume spec with unsupported VolumeHost") + } +} diff --git a/pkg/volume/cinder/cinder_util.go b/pkg/volume/cinder/cinder_util.go new file mode 100644 index 00000000000..3d0dc45796b --- /dev/null +++ b/pkg/volume/cinder/cinder_util.go @@ -0,0 +1,278 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2015 The Kubernetes Authors. + +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. +*/ + +package cinder + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" + clientset "k8s.io/client-go/kubernetes" + volumehelpers "k8s.io/cloud-provider/volume/helpers" + "k8s.io/kubernetes/pkg/volume" + volutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/utils/exec" +) + +// DiskUtil has utility/helper methods +type DiskUtil struct{} + +// AttachDisk attaches a disk specified by a volume.CinderPersistenDisk to the current kubelet. +// Mounts the disk to its global path. +func (util *DiskUtil) AttachDisk(b *cinderVolumeMounter, globalPDPath string) error { + options := []string{} + if b.readOnly { + options = append(options, "ro") + } + cloud, err := b.plugin.getCloudProvider() + if err != nil { + return err + } + instanceid, err := cloud.InstanceID() + if err != nil { + return err + } + diskid, err := cloud.AttachDisk(instanceid, b.pdName) + if err != nil { + return err + } + + var devicePath string + numTries := 0 + for { + devicePath = cloud.GetDevicePath(diskid) + probeAttachedVolume() + + _, err := os.Stat(devicePath) + if err == nil { + break + } + if err != nil && !os.IsNotExist(err) { + return err + } + numTries++ + if numTries == 10 { + return errors.New("could not attach disk: Timeout after 60s") + } + time.Sleep(time.Second * 6) + } + notmnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(globalPDPath, 0750); err != nil { + return err + } + notmnt = true + } else { + return err + } + } + if notmnt { + err = b.blockDeviceMounter.FormatAndMount(devicePath, globalPDPath, b.fsType, options) + if err != nil { + os.Remove(globalPDPath) + return err + } + klog.V(2).Infof("Safe mount successful: %q\n", devicePath) + } + return nil +} + +// DetachDisk unmounts the device and detaches the disk from the kubelet's host machine. +func (util *DiskUtil) DetachDisk(cd *cinderVolumeUnmounter) error { + globalPDPath := makeGlobalPDName(cd.plugin.host, cd.pdName) + if err := cd.mounter.Unmount(globalPDPath); err != nil { + return err + } + if err := os.Remove(globalPDPath); err != nil { + return err + } + klog.V(2).Infof("Successfully unmounted main device: %s\n", globalPDPath) + + cloud, err := cd.plugin.getCloudProvider() + if err != nil { + return err + } + instanceid, err := cloud.InstanceID() + if err != nil { + return err + } + if err = cloud.DetachDisk(instanceid, cd.pdName); err != nil { + return err + } + klog.V(2).Infof("Successfully detached cinder volume %s", cd.pdName) + return nil +} + +// DeleteVolume uses the cloud entrypoint to delete specified volume +func (util *DiskUtil) DeleteVolume(cd *cinderVolumeDeleter) error { + cloud, err := cd.plugin.getCloudProvider() + if err != nil { + return err + } + + if err = cloud.DeleteVolume(cd.pdName); err != nil { + // OpenStack cloud provider returns volume.tryAgainError when necessary, + // no handling needed here. + klog.V(2).Infof("Error deleting cinder volume %s: %v", cd.pdName, err) + return err + } + klog.V(2).Infof("Successfully deleted cinder volume %s", cd.pdName) + return nil +} + +func getZonesFromNodes(kubeClient clientset.Interface) (sets.String, error) { + // TODO: caching, currently it is overkill because it calls this function + // only when it creates dynamic PV + zones := make(sets.String) + nodes, err := kubeClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + klog.V(2).Infof("Error listing nodes") + return zones, err + } + for _, node := range nodes.Items { + if zone, ok := node.Labels[v1.LabelTopologyZone]; ok { + zones.Insert(zone) + } + } + klog.V(4).Infof("zones found: %v", zones) + return zones, nil +} + +// CreateVolume uses the cloud provider entrypoint for creating a volume +func (util *DiskUtil) CreateVolume(c *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, volumeLabels map[string]string, fstype string, err error) { + cloud, err := c.plugin.getCloudProvider() + if err != nil { + return "", 0, nil, "", err + } + + capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] + // Cinder works with gigabytes, convert to GiB with rounding up + volSizeGiB, err := volumehelpers.RoundUpToGiBInt(capacity) + if err != nil { + return "", 0, nil, "", err + } + + name := volutil.GenerateVolumeName(c.options.ClusterName, c.options.PVName, 255) // Cinder volume name can have up to 255 characters + vtype := "" + availability := "" + // Apply ProvisionerParameters (case-insensitive). We leave validation of + // the values to the cloud provider. + for k, v := range c.options.Parameters { + switch strings.ToLower(k) { + case "type": + vtype = v + case "availability": + availability = v + case volume.VolumeParameterFSType: + fstype = v + default: + return "", 0, nil, "", fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName()) + } + } + // TODO: implement PVC.Selector parsing + if c.options.PVC.Spec.Selector != nil { + return "", 0, nil, "", fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on Cinder") + } + + if availability == "" { + // No zone specified, choose one randomly in the same region + zones, err := getZonesFromNodes(c.plugin.host.GetKubeClient()) + if err != nil { + klog.V(2).Infof("error getting zone information: %v", err) + return "", 0, nil, "", err + } + // if we did not get any zones, lets leave it blank and gophercloud will + // use zone "nova" as default + if len(zones) > 0 { + availability, err = volumehelpers.SelectZoneForVolume(false, false, "", nil, zones, node, allowedTopologies, c.options.PVC.Name) + if err != nil { + klog.V(2).Infof("error selecting zone for volume: %v", err) + return "", 0, nil, "", err + } + } + } + + volumeID, volumeAZ, volumeRegion, IgnoreVolumeAZ, err := cloud.CreateVolume(name, volSizeGiB, vtype, availability, c.options.CloudTags) + if err != nil { + klog.V(2).Infof("Error creating cinder volume: %v", err) + return "", 0, nil, "", err + } + klog.V(2).Infof("Successfully created cinder volume %s", volumeID) + + // these are needed that pod is spawning to same AZ + volumeLabels = make(map[string]string) + if IgnoreVolumeAZ == false { + if volumeAZ != "" { + volumeLabels[v1.LabelTopologyZone] = volumeAZ + } + if volumeRegion != "" { + volumeLabels[v1.LabelTopologyRegion] = volumeRegion + } + } + return volumeID, volSizeGiB, volumeLabels, fstype, nil +} + +func probeAttachedVolume() error { + // rescan scsi bus + scsiHostRescan() + + executor := exec.New() + + // udevadm settle waits for udevd to process the device creation + // events for all hardware devices, thus ensuring that any device + // nodes have been created successfully before proceeding. + argsSettle := []string{"settle"} + cmdSettle := executor.Command("udevadm", argsSettle...) + _, errSettle := cmdSettle.CombinedOutput() + if errSettle != nil { + klog.Errorf("error running udevadm settle %v\n", errSettle) + } + + args := []string{"trigger"} + cmd := executor.Command("udevadm", args...) + _, err := cmd.CombinedOutput() + if err != nil { + klog.Errorf("error running udevadm trigger %v\n", err) + return err + } + klog.V(4).Infof("Successfully probed all attachments") + return nil +} + +func scsiHostRescan() { + scsiPath := "/sys/class/scsi_host/" + if dirs, err := ioutil.ReadDir(scsiPath); err == nil { + for _, f := range dirs { + name := scsiPath + f.Name() + "/scan" + data := []byte("- - -") + ioutil.WriteFile(name, data, 0666) + } + } +} diff --git a/pkg/volume/cinder/doc.go b/pkg/volume/cinder/doc.go new file mode 100644 index 00000000000..08e6fa8ab1b --- /dev/null +++ b/pkg/volume/cinder/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors. + +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. +*/ + +// Package cinder contains the internal representation of cinder volumes. +package cinder // import "k8s.io/kubernetes/pkg/volume/cinder" diff --git a/pkg/volume/csi/csi_plugin.go b/pkg/volume/csi/csi_plugin.go index e3c1076ec3a..e00a4d6d097 100644 --- a/pkg/volume/csi/csi_plugin.go +++ b/pkg/volume/csi/csi_plugin.go @@ -222,6 +222,9 @@ func (p *csiPlugin) Init(host volume.VolumeHost) error { csitranslationplugins.AWSEBSInTreePluginName: func() bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAWS) }, + csitranslationplugins.CinderInTreePluginName: func() bool { + return true + }, csitranslationplugins.AzureDiskInTreePluginName: func() bool { return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureDisk) }, diff --git a/pkg/volume/csimigration/plugin_manager.go b/pkg/volume/csimigration/plugin_manager.go index 2e09fc70cef..b568e85ac15 100644 --- a/pkg/volume/csimigration/plugin_manager.go +++ b/pkg/volume/csimigration/plugin_manager.go @@ -68,6 +68,8 @@ func (pm PluginManager) IsMigrationCompleteForPlugin(pluginName string) bool { return pm.featureGate.Enabled(features.InTreePluginAzureFileUnregister) case csilibplugins.AzureDiskInTreePluginName: return pm.featureGate.Enabled(features.InTreePluginAzureDiskUnregister) + case csilibplugins.CinderInTreePluginName: + return pm.featureGate.Enabled(features.InTreePluginOpenStackUnregister) case csilibplugins.VSphereInTreePluginName: return pm.featureGate.Enabled(features.InTreePluginvSphereUnregister) case csilibplugins.PortworxVolumePluginName: @@ -94,6 +96,8 @@ func (pm PluginManager) IsMigrationEnabledForPlugin(pluginName string) bool { return pm.featureGate.Enabled(features.CSIMigrationAzureFile) case csilibplugins.AzureDiskInTreePluginName: return pm.featureGate.Enabled(features.CSIMigrationAzureDisk) + case csilibplugins.CinderInTreePluginName: + return true case csilibplugins.VSphereInTreePluginName: return pm.featureGate.Enabled(features.CSIMigrationvSphere) case csilibplugins.PortworxVolumePluginName: diff --git a/pkg/volume/util/attach_limit.go b/pkg/volume/util/attach_limit.go index 8325dbf755b..943357b6538 100644 --- a/pkg/volume/util/attach_limit.go +++ b/pkg/volume/util/attach_limit.go @@ -40,6 +40,13 @@ const ( // GCEVolumeLimitKey stores resource name that will store volume limits for GCE node GCEVolumeLimitKey = "attachable-volumes-gce-pd" + // CinderVolumeLimitKey contains Volume limit key for Cinder + CinderVolumeLimitKey = "attachable-volumes-cinder" + // DefaultMaxCinderVolumes defines the maximum number of PD Volumes for Cinder + // For Openstack we are keeping this to a high enough value so as depending on backend + // cluster admins can configure it. + DefaultMaxCinderVolumes = 256 + // CSIAttachLimitPrefix defines prefix used for CSI volumes CSIAttachLimitPrefix = "attachable-volumes-csi-" diff --git a/pkg/volume/util/util_test.go b/pkg/volume/util/util_test.go index 0f3d99c837e..9c3814383a7 100644 --- a/pkg/volume/util/util_test.go +++ b/pkg/volume/util/util_test.go @@ -21,6 +21,7 @@ import ( "os" "reflect" "runtime" + "strings" "testing" v1 "k8s.io/api/core/v1" @@ -260,6 +261,30 @@ func TestFsUserFrom(t *testing.T) { } } +func TestGenerateVolumeName(t *testing.T) { + + // Normal operation, no truncate + v1 := GenerateVolumeName("kubernetes", "pv-cinder-abcde", 255) + if v1 != "kubernetes-dynamic-pv-cinder-abcde" { + t.Errorf("Expected kubernetes-dynamic-pv-cinder-abcde, got %s", v1) + } + + // Truncate trailing "6789-dynamic" + prefix := strings.Repeat("0123456789", 9) // 90 characters prefix + 8 chars. of "-dynamic" + v2 := GenerateVolumeName(prefix, "pv-cinder-abcde", 100) + expect := prefix[:84] + "-pv-cinder-abcde" + if v2 != expect { + t.Errorf("Expected %s, got %s", expect, v2) + } + + // Truncate really long cluster name + prefix = strings.Repeat("0123456789", 1000) // 10000 characters prefix + v3 := GenerateVolumeName(prefix, "pv-cinder-abcde", 100) + if v3 != expect { + t.Errorf("Expected %s, got %s", expect, v3) + } +} + func TestHasMountRefs(t *testing.T) { testCases := map[string]struct { mountPath string diff --git a/plugin/pkg/admission/storage/persistentvolume/label/admission.go b/plugin/pkg/admission/storage/persistentvolume/label/admission.go index a874fac2768..a7ae3ba5dfd 100644 --- a/plugin/pkg/admission/storage/persistentvolume/label/admission.go +++ b/plugin/pkg/admission/storage/persistentvolume/label/admission.go @@ -55,12 +55,13 @@ var _ = admission.Interface(&persistentVolumeLabel{}) type persistentVolumeLabel struct { *admission.Handler - mutex sync.Mutex - cloudConfig []byte - awsPVLabeler cloudprovider.PVLabeler - gcePVLabeler cloudprovider.PVLabeler - azurePVLabeler cloudprovider.PVLabeler - vspherePVLabeler cloudprovider.PVLabeler + mutex sync.Mutex + cloudConfig []byte + awsPVLabeler cloudprovider.PVLabeler + gcePVLabeler cloudprovider.PVLabeler + azurePVLabeler cloudprovider.PVLabeler + openStackPVLabeler cloudprovider.PVLabeler + vspherePVLabeler cloudprovider.PVLabeler } var _ admission.MutationInterface = &persistentVolumeLabel{} @@ -72,7 +73,7 @@ var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{} // As a side effect, the cloud provider may block invalid or non-existent volumes. func newPersistentVolumeLabel() *persistentVolumeLabel { // DEPRECATED: in a future release, we will use mutating admission webhooks to apply PV labels. - // Once the mutating admission webhook is used for AWS, Azure and GCE, + // Once the mutating admission webhook is used for AWS, Azure, GCE, and OpenStack, // this admission controller will be removed. klog.Warning("PersistentVolumeLabel admission controller is deprecated. " + "Please remove this controller from your configuration files and scripts.") @@ -218,6 +219,12 @@ func (l *persistentVolumeLabel) findVolumeLabels(volume *api.PersistentVolume) ( return nil, fmt.Errorf("error querying AzureDisk volume %s: %v", volume.Spec.AzureDisk.DiskName, err) } return labels, nil + case volume.Spec.Cinder != nil: + labels, err := l.findCinderDiskLabels(volume) + if err != nil { + return nil, fmt.Errorf("error querying Cinder volume %s: %v", volume.Spec.Cinder.VolumeID, err) + } + return labels, nil case volume.Spec.VsphereVolume != nil: labels, err := l.findVsphereVolumeLabels(volume) if err != nil { @@ -374,6 +381,56 @@ func (l *persistentVolumeLabel) findAzureDiskLabels(volume *api.PersistentVolume return pvlabler.GetLabelsForVolume(context.TODO(), pv) } +func (l *persistentVolumeLabel) getOpenStackPVLabeler() (cloudprovider.PVLabeler, error) { + l.mutex.Lock() + defer l.mutex.Unlock() + + if l.openStackPVLabeler == nil { + var cloudConfigReader io.Reader + if len(l.cloudConfig) > 0 { + cloudConfigReader = bytes.NewReader(l.cloudConfig) + } + + cloudProvider, err := cloudprovider.GetCloudProvider("openstack", cloudConfigReader) + if err != nil || cloudProvider == nil { + return nil, err + } + + openStackPVLabeler, ok := cloudProvider.(cloudprovider.PVLabeler) + if !ok { + return nil, errors.New("OpenStack cloud provider does not implement PV labeling") + } + + l.openStackPVLabeler = openStackPVLabeler + } + + return l.openStackPVLabeler, nil + +} + +func (l *persistentVolumeLabel) findCinderDiskLabels(volume *api.PersistentVolume) (map[string]string, error) { + // Ignore any volumes that are being provisioned + if volume.Spec.Cinder.VolumeID == cloudvolume.ProvisionedVolumeName { + return nil, nil + } + + pvlabler, err := l.getOpenStackPVLabeler() + if err != nil { + return nil, err + } + if pvlabler == nil { + return nil, fmt.Errorf("unable to build OpenStack cloud provider for Cinder disk") + } + + pv := &v1.PersistentVolume{} + err = k8s_api_v1.Convert_core_PersistentVolume_To_v1_PersistentVolume(volume, pv, nil) + if err != nil { + return nil, fmt.Errorf("failed to convert PersistentVolume to core/v1: %q", err) + } + return pvlabler.GetLabelsForVolume(context.TODO(), pv) + +} + func (l *persistentVolumeLabel) findVsphereVolumeLabels(volume *api.PersistentVolume) (map[string]string, error) { pvlabler, err := l.getVspherePVLabeler() if err != nil { diff --git a/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go b/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go index eb3d2d90689..84694431994 100644 --- a/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go +++ b/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go @@ -560,6 +560,72 @@ func Test_PVLAdmission(t *testing.T) { }, err: nil, }, + { + name: "Cinder Disk PV labeled correctly", + handler: newPersistentVolumeLabel(), + pvlabeler: mockVolumeLabels(map[string]string{ + "a": "1", + "b": "2", + v1.LabelFailureDomainBetaZone: "1__2__3", + }), + preAdmissionPV: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azurepd", + Namespace: "myns", + }, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + Cinder: &api.CinderPersistentVolumeSource{ + VolumeID: "123", + }, + }, + }, + }, + postAdmissionPV: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azurepd", + Namespace: "myns", + Labels: map[string]string{ + "a": "1", + "b": "2", + v1.LabelFailureDomainBetaZone: "1__2__3", + }, + }, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + Cinder: &api.CinderPersistentVolumeSource{ + VolumeID: "123", + }, + }, + NodeAffinity: &api.VolumeNodeAffinity{ + Required: &api.NodeSelector{ + NodeSelectorTerms: []api.NodeSelectorTerm{ + { + MatchExpressions: []api.NodeSelectorRequirement{ + { + Key: "a", + Operator: api.NodeSelectorOpIn, + Values: []string{"1"}, + }, + { + Key: "b", + Operator: api.NodeSelectorOpIn, + Values: []string{"2"}, + }, + { + Key: v1.LabelFailureDomainBetaZone, + Operator: api.NodeSelectorOpIn, + Values: []string{"1", "2", "3"}, + }, + }, + }, + }, + }, + }, + }, + }, + err: nil, + }, { name: "AWS EBS PV overrides user applied labels", handler: newPersistentVolumeLabel(), @@ -917,6 +983,7 @@ func setPVLabeler(handler *persistentVolumeLabel, pvlabeler cloudprovider.PVLabe handler.awsPVLabeler = pvlabeler handler.gcePVLabeler = pvlabeler handler.azurePVLabeler = pvlabeler + handler.openStackPVLabeler = pvlabeler handler.vspherePVLabeler = pvlabeler } diff --git a/staging/publishing/rules.yaml b/staging/publishing/rules.yaml index ceaa9706ebf..7e528783ed8 100644 --- a/staging/publishing/rules.yaml +++ b/staging/publishing/rules.yaml @@ -1655,6 +1655,8 @@ rules: branch: master - repository: controller-manager branch: master + - repository: mount-utils + branch: master - repository: component-helpers branch: master - repository: kms diff --git a/staging/src/k8s.io/client-go/examples/README.md b/staging/src/k8s.io/client-go/examples/README.md index 0ec0e13d6aa..984aba00b5f 100644 --- a/staging/src/k8s.io/client-go/examples/README.md +++ b/staging/src/k8s.io/client-go/examples/README.md @@ -19,6 +19,7 @@ Or you can load specific auth plugins: import _ "k8s.io/client-go/plugin/pkg/client/auth/azure" import _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" import _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" +import _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ``` ### Configuration diff --git a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go index dae3bc95f18..224dbc12519 100644 --- a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go +++ b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go @@ -40,6 +40,7 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go b/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go index cc6b1226706..b7439e482d6 100644 --- a/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go +++ b/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go @@ -41,6 +41,7 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go b/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go index b583e337338..a8c71612e31 100644 --- a/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go +++ b/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go @@ -34,6 +34,7 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go b/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go index 7698e132181..cf00d48094d 100644 --- a/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go +++ b/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go @@ -37,6 +37,7 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go new file mode 100644 index 00000000000..6e404beda20 --- /dev/null +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "errors" + + "k8s.io/client-go/rest" + "k8s.io/klog/v2" +) + +func init() { + if err := rest.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil { + klog.Fatalf("Failed to register openstack auth plugin: %s", err) + } +} + +func newOpenstackAuthProvider(_ string, _ map[string]string, _ rest.AuthProviderConfigPersister) (rest.AuthProvider, error) { + return nil, errors.New(`The openstack auth plugin has been removed. +Please use the "client-keystone-auth" kubectl/client-go credential plugin instead. +See https://github.com/kubernetes/cloud-provider-openstack/blob/master/docs/using-client-keystone-auth.md for further details`) +} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go index 3f0688774ee..ebfbd715c0c 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go @@ -23,4 +23,5 @@ import ( // Initialize client auth plugins for cloud providers. _ "k8s.io/client-go/plugin/pkg/client/auth/azure" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) diff --git a/staging/src/k8s.io/cloud-provider/plugins.go b/staging/src/k8s.io/cloud-provider/plugins.go index 5300abdb4c0..bfd73163149 100644 --- a/staging/src/k8s.io/cloud-provider/plugins.go +++ b/staging/src/k8s.io/cloud-provider/plugins.go @@ -43,6 +43,7 @@ var ( {"aws", false, "The AWS provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-aws"}, {"azure", false, "The Azure provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes-sigs/cloud-provider-azure"}, {"gce", false, "The GCE provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-gcp"}, + {"openstack", true, "https://github.com/kubernetes/cloud-provider-openstack"}, {"vsphere", false, "The vSphere provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-vsphere"}, } ) diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go index cbe50b21d75..3cab797ee65 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go @@ -421,6 +421,48 @@ func TestTranslateTopologyFromCSIToInTree(t *testing.T) { v1.LabelTopologyRegion: "us-east1", }, }, + { + name: "cinder translation", + key: CinderTopologyKey, + expErr: false, + regionParser: nil, + pv: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cinder", Namespace: "myns", + }, + Spec: v1.PersistentVolumeSpec{ + NodeAffinity: &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: CinderTopologyKey, + Operator: v1.NodeSelectorOpIn, + Values: []string{"nova"}, + }, + }, + }, + }, + }, + }, + }, + }, + expectedNodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: v1.LabelTopologyZone, + Operator: v1.NodeSelectorOpIn, + Values: []string{"nova"}, + }, + }, + }, + }, + expectedLabels: map[string]string{ + v1.LabelTopologyZone: "nova", + }, + }, } for _, tc := range testCases { diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go b/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go new file mode 100644 index 00000000000..92c5a133837 --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go @@ -0,0 +1,184 @@ +/* +Copyright 2019 The Kubernetes Authors. + +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. +*/ + +package plugins + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // CinderDriverName is the name of the CSI driver for Cinder + CinderDriverName = "cinder.csi.openstack.org" + // CinderTopologyKey is the zonal topology key for Cinder CSI Driver + CinderTopologyKey = "topology.cinder.csi.openstack.org/zone" + // CinderInTreePluginName is the name of the intree plugin for Cinder + CinderInTreePluginName = "kubernetes.io/cinder" +) + +var _ InTreePlugin = (*osCinderCSITranslator)(nil) + +// osCinderCSITranslator handles translation of PV spec from In-tree Cinder to CSI Cinder and vice versa +type osCinderCSITranslator struct{} + +// NewOpenStackCinderCSITranslator returns a new instance of osCinderCSITranslator +func NewOpenStackCinderCSITranslator() InTreePlugin { + return &osCinderCSITranslator{} +} + +// TranslateInTreeStorageClassToCSI translates InTree Cinder storage class parameters to CSI storage class +func (t *osCinderCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) { + var ( + params = map[string]string{} + ) + for k, v := range sc.Parameters { + switch strings.ToLower(k) { + case fsTypeKey: + params[csiFsTypeKey] = v + default: + // All other parameters are supported by the CSI driver. + // This includes also "availability", therefore do not translate it to sc.AllowedTopologies + params[k] = v + } + } + + if len(sc.AllowedTopologies) > 0 { + newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, CinderTopologyKey) + if err != nil { + return nil, fmt.Errorf("failed translating allowed topologies: %v", err) + } + sc.AllowedTopologies = newTopologies + } + + sc.Parameters = params + + return sc, nil +} + +// TranslateInTreeInlineVolumeToCSI takes a Volume with Cinder set from in-tree +// and converts the Cinder source to a CSIPersistentVolumeSource +func (t *osCinderCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) { + if volume == nil || volume.Cinder == nil { + return nil, fmt.Errorf("volume is nil or Cinder not defined on volume") + } + + cinderSource := volume.Cinder + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + // Must be unique per disk as it is used as the unique part of the + // staging path + Name: fmt.Sprintf("%s-%s", CinderDriverName, cinderSource.VolumeID), + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: CinderDriverName, + VolumeHandle: cinderSource.VolumeID, + ReadOnly: cinderSource.ReadOnly, + FSType: cinderSource.FSType, + VolumeAttributes: map[string]string{}, + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + }, + } + return pv, nil +} + +// TranslateInTreePVToCSI takes a PV with Cinder set from in-tree +// and converts the Cinder source to a CSIPersistentVolumeSource +func (t *osCinderCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil || pv.Spec.Cinder == nil { + return nil, fmt.Errorf("pv is nil or Cinder not defined on pv") + } + + cinderSource := pv.Spec.Cinder + + csiSource := &v1.CSIPersistentVolumeSource{ + Driver: CinderDriverName, + VolumeHandle: cinderSource.VolumeID, + ReadOnly: cinderSource.ReadOnly, + FSType: cinderSource.FSType, + VolumeAttributes: map[string]string{}, + } + + if err := translateTopologyFromInTreeToCSI(pv, CinderTopologyKey); err != nil { + return nil, fmt.Errorf("failed to translate topology: %v", err) + } + + pv.Spec.Cinder = nil + pv.Spec.CSI = csiSource + return pv, nil +} + +// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and +// translates the Cinder CSI source to a Cinder In-tree source. +func (t *osCinderCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { + if pv == nil || pv.Spec.CSI == nil { + return nil, fmt.Errorf("pv is nil or CSI source not defined on pv") + } + + csiSource := pv.Spec.CSI + + cinderSource := &v1.CinderPersistentVolumeSource{ + VolumeID: csiSource.VolumeHandle, + FSType: csiSource.FSType, + ReadOnly: csiSource.ReadOnly, + } + + // translate CSI topology to In-tree topology for rollback compatibility. + // It is not possible to guess Cinder Region from the Zone, therefore leave it empty. + if err := translateTopologyFromCSIToInTree(pv, CinderTopologyKey, nil); err != nil { + return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err) + } + + pv.Spec.CSI = nil + pv.Spec.Cinder = cinderSource + return pv, nil +} + +// CanSupport tests whether the plugin supports a given persistent volume +// specification from the API. The spec pointer should be considered +// const. +func (t *osCinderCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { + return pv != nil && pv.Spec.Cinder != nil +} + +// CanSupportInline tests whether the plugin supports a given inline volume +// specification from the API. The spec pointer should be considered +// const. +func (t *osCinderCSITranslator) CanSupportInline(volume *v1.Volume) bool { + return volume != nil && volume.Cinder != nil +} + +// GetInTreePluginName returns the name of the intree plugin driver +func (t *osCinderCSITranslator) GetInTreePluginName() string { + return CinderInTreePluginName +} + +// GetCSIPluginName returns the name of the CSI plugin +func (t *osCinderCSITranslator) GetCSIPluginName() string { + return CinderDriverName +} + +func (t *osCinderCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) { + return volumeHandle, nil +} diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder_test.go new file mode 100644 index 00000000000..e4a95da8083 --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2021 The Kubernetes Authors. + +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. +*/ + +package plugins + +import ( + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" +) + +func TestTranslateCinderInTreeStorageClassToCSI(t *testing.T) { + translator := NewOpenStackCinderCSITranslator() + + cases := []struct { + name string + sc *storage.StorageClass + expSc *storage.StorageClass + expErr bool + }{ + { + name: "translate normal", + sc: NewStorageClass(map[string]string{"foo": "bar"}, nil), + expSc: NewStorageClass(map[string]string{"foo": "bar"}, nil), + }, + { + name: "translate empty map", + sc: NewStorageClass(map[string]string{}, nil), + expSc: NewStorageClass(map[string]string{}, nil), + }, + + { + name: "translate with fstype", + sc: NewStorageClass(map[string]string{"fstype": "ext3"}, nil), + expSc: NewStorageClass(map[string]string{"csi.storage.k8s.io/fstype": "ext3"}, nil), + }, + { + name: "translate with topology in parameters (no translation expected)", + sc: NewStorageClass(map[string]string{"availability": "nova"}, nil), + expSc: NewStorageClass(map[string]string{"availability": "nova"}, nil), + }, + { + name: "translate with topology", + sc: NewStorageClass(map[string]string{}, generateToplogySelectors(v1.LabelFailureDomainBetaZone, []string{"nova"})), + expSc: NewStorageClass(map[string]string{}, generateToplogySelectors(CinderTopologyKey, []string{"nova"})), + }, + } + + for _, tc := range cases { + t.Logf("Testing %v", tc.name) + got, err := translator.TranslateInTreeStorageClassToCSI(tc.sc) + if err != nil && !tc.expErr { + t.Errorf("Did not expect error but got: %v", err) + } + + if err == nil && tc.expErr { + t.Errorf("Expected error, but did not get one.") + } + + if !reflect.DeepEqual(got, tc.expSc) { + t.Errorf("Got parameters: %v, expected: %v", got, tc.expSc) + } + + } +} diff --git a/staging/src/k8s.io/csi-translation-lib/translate.go b/staging/src/k8s.io/csi-translation-lib/translate.go index 96f2d609ce4..9dde216299b 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate.go +++ b/staging/src/k8s.io/csi-translation-lib/translate.go @@ -29,6 +29,7 @@ var ( inTreePlugins = map[string]plugins.InTreePlugin{ plugins.GCEPDDriverName: plugins.NewGCEPersistentDiskCSITranslator(), plugins.AWSEBSDriverName: plugins.NewAWSElasticBlockStoreCSITranslator(), + plugins.CinderDriverName: plugins.NewOpenStackCinderCSITranslator(), plugins.AzureDiskDriverName: plugins.NewAzureDiskCSITranslator(), plugins.AzureFileDriverName: plugins.NewAzureFileCSITranslator(), plugins.VSphereDriverName: plugins.NewvSphereCSITranslator(), diff --git a/staging/src/k8s.io/csi-translation-lib/translate_test.go b/staging/src/k8s.io/csi-translation-lib/translate_test.go index 592edda9e2b..8a08c6e6ed3 100644 --- a/staging/src/k8s.io/csi-translation-lib/translate_test.go +++ b/staging/src/k8s.io/csi-translation-lib/translate_test.go @@ -189,6 +189,17 @@ func TestTopologyTranslation(t *testing.T) { pv: makeAWSEBSPV(kubernetesGATopologyLabels, makeTopology(v1.LabelTopologyZone, "us-east-2a")), expectedNodeAffinity: makeNodeAffinity(false /*multiTerms*/, plugins.AWSEBSTopologyKey, "us-east-2a"), }, + // Cinder test cases: test mosty topology key, i.e., don't repeat testing done with GCE + { + name: "OpenStack Cinder with zone labels", + pv: makeCinderPV(kubernetesBetaTopologyLabels, nil /*topology*/), + expectedNodeAffinity: makeNodeAffinity(false /*multiTerms*/, plugins.CinderTopologyKey, "us-east-1a"), + }, + { + name: "OpenStack Cinder with zone labels and topology", + pv: makeCinderPV(kubernetesBetaTopologyLabels, makeTopology(v1.LabelFailureDomainBetaZone, "us-east-2a")), + expectedNodeAffinity: makeNodeAffinity(false /*multiTerms*/, plugins.CinderTopologyKey, "us-east-2a"), + }, } for _, test := range testCases { @@ -291,6 +302,18 @@ func makeAWSEBSPV(labels map[string]string, topology *v1.NodeSelectorRequirement return pv } +func makeCinderPV(labels map[string]string, topology *v1.NodeSelectorRequirement) *v1.PersistentVolume { + pv := makePV(labels, topology) + pv.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{ + Cinder: &v1.CinderPersistentVolumeSource{ + VolumeID: "vol1", + FSType: "ext4", + ReadOnly: false, + }, + } + return pv +} + func makeNodeAffinity(multiTerms bool, key string, values ...string) *v1.VolumeNodeAffinity { nodeAffinity := &v1.VolumeNodeAffinity{ Required: &v1.NodeSelector{ @@ -389,6 +412,12 @@ func generateUniqueVolumeSource(driverName string) (v1.VolumeSource, error) { }, }, nil + case plugins.CinderDriverName: + return v1.VolumeSource{ + Cinder: &v1.CinderVolumeSource{ + VolumeID: string(uuid.NewUUID()), + }, + }, nil case plugins.AzureDiskDriverName: return v1.VolumeSource{ AzureDisk: &v1.AzureDiskVolumeSource{ diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/describe.go index d9f4bc13638..18fe58e7ac5 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe.go @@ -950,6 +950,8 @@ func describeVolumes(volumes []corev1.Volume, w PrefixWriter, space string) { printAzureDiskVolumeSource(volume.VolumeSource.AzureDisk, w) case volume.VolumeSource.VsphereVolume != nil: printVsphereVolumeSource(volume.VolumeSource.VsphereVolume, w) + case volume.VolumeSource.Cinder != nil: + printCinderVolumeSource(volume.VolumeSource.Cinder, w) case volume.VolumeSource.PhotonPersistentDisk != nil: printPhotonPersistentDiskVolumeSource(volume.VolumeSource.PhotonPersistentDisk, w) case volume.VolumeSource.PortworxVolume != nil: @@ -1228,6 +1230,24 @@ func printPhotonPersistentDiskVolumeSource(photon *corev1.PhotonPersistentDiskVo photon.PdID, photon.FSType) } +func printCinderVolumeSource(cinder *corev1.CinderVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+ + " VolumeID:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " SecretRef:\t%v\n", + cinder.VolumeID, cinder.FSType, cinder.ReadOnly, cinder.SecretRef) +} + +func printCinderPersistentVolumeSource(cinder *corev1.CinderPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+ + " VolumeID:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " SecretRef:\t%v\n", + cinder.VolumeID, cinder.FSType, cinder.ReadOnly, cinder.SecretRef) +} + func printScaleIOVolumeSource(sio *corev1.ScaleIOVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ " Gateway:\t%v\n"+ @@ -1545,6 +1565,8 @@ func describePersistentVolume(pv *corev1.PersistentVolume, events *corev1.EventL printQuobyteVolumeSource(pv.Spec.Quobyte, w) case pv.Spec.VsphereVolume != nil: printVsphereVolumeSource(pv.Spec.VsphereVolume, w) + case pv.Spec.Cinder != nil: + printCinderPersistentVolumeSource(pv.Spec.Cinder, w) case pv.Spec.AzureDisk != nil: printAzureDiskVolumeSource(pv.Spec.AzureDisk, w) case pv.Spec.PhotonPersistentDisk != nil: diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go index afcb2262c4c..12dfa77e74b 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go @@ -1483,6 +1483,19 @@ func TestPersistentVolumeDescriber(t *testing.T) { }, unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, + { + name: "test8", + plugin: "cinder", + pv: &corev1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Cinder: &corev1.CinderPersistentVolumeSource{}, + }, + }, + }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, + }, { name: "test9", plugin: "fc", diff --git a/staging/src/k8s.io/legacy-cloud-providers/go.mod b/staging/src/k8s.io/legacy-cloud-providers/go.mod index 524dcea5868..8fc86c5c8e0 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/go.mod +++ b/staging/src/k8s.io/legacy-cloud-providers/go.mod @@ -15,6 +15,8 @@ require ( github.com/aws/aws-sdk-go v1.44.116 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 + github.com/gophercloud/gophercloud v0.1.0 + github.com/mitchellh/mapstructure v1.4.1 github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021 github.com/stretchr/testify v1.8.0 github.com/vmware/govmomi v0.20.3 @@ -29,6 +31,7 @@ require ( k8s.io/component-base v0.0.0 k8s.io/csi-translation-lib v0.0.0 k8s.io/klog/v2 v2.80.1 + k8s.io/mount-utils v0.0.0 k8s.io/utils v0.0.0-20221107191617-1a15be271d1d sigs.k8s.io/yaml v1.3.0 ) @@ -59,11 +62,13 @@ require ( github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/moby/sys/mountinfo v0.6.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -106,4 +111,5 @@ replace ( k8s.io/csi-translation-lib => ../csi-translation-lib k8s.io/kms => ../kms k8s.io/legacy-cloud-providers => ../legacy-cloud-providers + k8s.io/mount-utils => ../mount-utils ) diff --git a/staging/src/k8s.io/legacy-cloud-providers/go.sum b/staging/src/k8s.io/legacy-cloud-providers/go.sum index 2050f48714a..9d0e3f33a75 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/go.sum +++ b/staging/src/k8s.io/legacy-cloud-providers/go.sum @@ -237,11 +237,15 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -276,6 +280,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/moby/sys/mountinfo v0.6.0 h1:gUDhXQx58YNrpHlK4nSL+7y2pxFZkUcXqzFDKWdC0Oo= +github.com/moby/sys/mountinfo v0.6.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -365,6 +371,7 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -484,6 +491,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md b/staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md new file mode 100644 index 00000000000..0802490bcd1 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md @@ -0,0 +1,4 @@ +# Maintainers + +* [Angus Lees](https://github.com/anguslees) + diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS b/staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS new file mode 100644 index 00000000000..6b5183953e2 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS @@ -0,0 +1,13 @@ +# See the OWNERS docs at https://go.k8s.io/owners +# We are no longer accepting features into k8s.io/legacy-cloud-providers. +# Any kind/feature PRs must be approved by SIG Cloud Provider going forward. + +emeritus_approvers: + - anguslees + - NickrenREN + - dims + - FengyunPan2 +reviewers: + - anguslees + - NickrenREN + - dims diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go new file mode 100644 index 00000000000..948b32a5b67 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go @@ -0,0 +1,201 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + + "k8s.io/klog/v2" + "k8s.io/mount-utils" + "k8s.io/utils/exec" +) + +const ( + // metadataURLTemplate allows building an OpenStack Metadata service URL. + // It's a hardcoded IPv4 link-local address as documented in "OpenStack Cloud + // Administrator Guide", chapter Compute - Networking with nova-network. + //https://docs.openstack.org/nova/latest/admin/networking-nova.html#metadata-service + defaultMetadataVersion = "2012-08-10" + metadataURLTemplate = "http://169.254.169.254/openstack/%s/meta_data.json" + + // metadataID is used as an identifier on the metadata search order configuration. + metadataID = "metadataService" + + // Config drive is defined as an iso9660 or vfat (deprecated) drive + // with the "config-2" label. + //https://docs.openstack.org/nova/latest/user/config-drive.html + configDriveLabel = "config-2" + configDrivePathTemplate = "openstack/%s/meta_data.json" + + // configDriveID is used as an identifier on the metadata search order configuration. + configDriveID = "configDrive" +) + +// ErrBadMetadata is used to indicate a problem parsing data from metadata server +var ErrBadMetadata = errors.New("invalid OpenStack metadata, got empty uuid") + +// DeviceMetadata is a single/simplified data structure for all kinds of device metadata types. +type DeviceMetadata struct { + Type string `json:"type"` + Bus string `json:"bus,omitempty"` + Serial string `json:"serial,omitempty"` + Address string `json:"address,omitempty"` + // .. and other fields. +} + +// Metadata has the information fetched from OpenStack metadata service or +// config drives. Assumes the "2012-08-10" meta_data.json format. +// See http://docs.openstack.org/user-guide/cli_config_drive.html +type Metadata struct { + UUID string `json:"uuid"` + Name string `json:"name"` + AvailabilityZone string `json:"availability_zone"` + Devices []DeviceMetadata `json:"devices,omitempty"` + // .. and other fields we don't care about. Expand as necessary. +} + +// parseMetadata reads JSON from OpenStack metadata server and parses +// instance ID out of it. +func parseMetadata(r io.Reader) (*Metadata, error) { + var metadata Metadata + json := json.NewDecoder(r) + if err := json.Decode(&metadata); err != nil { + return nil, err + } + + if metadata.UUID == "" { + return nil, ErrBadMetadata + } + + return &metadata, nil +} + +func getMetadataURL(metadataVersion string) string { + return fmt.Sprintf(metadataURLTemplate, metadataVersion) +} + +func getConfigDrivePath(metadataVersion string) string { + return fmt.Sprintf(configDrivePathTemplate, metadataVersion) +} + +func getMetadataFromConfigDrive(metadataVersion string) (*Metadata, error) { + // Try to read instance UUID from config drive. + dev := "/dev/disk/by-label/" + configDriveLabel + if _, err := os.Stat(dev); os.IsNotExist(err) { + out, err := exec.New().Command( + "blkid", "-l", + "-t", "LABEL="+configDriveLabel, + "-o", "device", + ).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("unable to run blkid: %v", err) + } + dev = strings.TrimSpace(string(out)) + } + + mntdir, err := ioutil.TempDir("", "configdrive") + if err != nil { + return nil, err + } + defer os.Remove(mntdir) + + klog.V(4).Infof("Attempting to mount configdrive %s on %s", dev, mntdir) + + mounter := mount.New("" /* default mount path */) + err = mounter.Mount(dev, mntdir, "iso9660", []string{"ro"}) + if err != nil { + err = mounter.Mount(dev, mntdir, "vfat", []string{"ro"}) + } + if err != nil { + return nil, fmt.Errorf("error mounting configdrive %s: %v", dev, err) + } + defer mounter.Unmount(mntdir) + + klog.V(4).Infof("Configdrive mounted on %s", mntdir) + + configDrivePath := getConfigDrivePath(metadataVersion) + f, err := os.Open( + filepath.Join(mntdir, configDrivePath)) + if err != nil { + return nil, fmt.Errorf("error reading %s on config drive: %v", configDrivePath, err) + } + defer f.Close() + + return parseMetadata(f) +} + +func getMetadataFromMetadataService(metadataVersion string) (*Metadata, error) { + // Try to get JSON from metadata server. + metadataURL := getMetadataURL(metadataVersion) + klog.V(4).Infof("Attempting to fetch metadata from %s", metadataURL) + resp, err := http.Get(metadataURL) + if err != nil { + return nil, fmt.Errorf("error fetching %s: %v", metadataURL, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("unexpected status code when reading metadata from %s: %s", metadataURL, resp.Status) + return nil, err + } + + return parseMetadata(resp.Body) +} + +// Metadata is fixed for the current host, so cache the value process-wide +var metadataCache *Metadata + +func getMetadata(order string) (*Metadata, error) { + if metadataCache == nil { + var md *Metadata + var err error + + elements := strings.Split(order, ",") + for _, id := range elements { + id = strings.TrimSpace(id) + switch id { + case configDriveID: + md, err = getMetadataFromConfigDrive(defaultMetadataVersion) + case metadataID: + md, err = getMetadataFromMetadataService(defaultMetadataVersion) + default: + err = fmt.Errorf("%s is not a valid metadata search order option. Supported options are %s and %s", id, configDriveID, metadataID) + } + + if err == nil { + break + } + } + + if err != nil { + return nil, err + } + metadataCache = md + } + return metadataCache, nil +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go new file mode 100644 index 00000000000..d0eb67d54c6 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go @@ -0,0 +1,118 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "strings" + "testing" +) + +var FakeMetadata = Metadata{ + UUID: "83679162-1378-4288-a2d4-70e13ec132aa", + Name: "test", + AvailabilityZone: "nova", +} + +func SetMetadataFixture(value *Metadata) { + metadataCache = value +} + +func ClearMetadata() { + metadataCache = nil +} + +func TestParseMetadata(t *testing.T) { + _, err := parseMetadata(strings.NewReader("bogus")) + if err == nil { + t.Errorf("Should fail when bad data is provided: %s", err) + } + + data := strings.NewReader(` +{ + "availability_zone": "nova", + "files": [ + { + "content_path": "/content/0000", + "path": "/etc/network/interfaces" + }, + { + "content_path": "/content/0001", + "path": "known_hosts" + } + ], + "hostname": "test.novalocal", + "launch_index": 0, + "name": "test", + "meta": { + "role": "webservers", + "essential": "false" + }, + "public_keys": { + "mykey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDBqUfVvCSez0/Wfpd8dLLgZXV9GtXQ7hnMN+Z0OWQUyebVEHey1CXuin0uY1cAJMhUq8j98SiW+cU0sU4J3x5l2+xi1bodDm1BtFWVeLIOQINpfV1n8fKjHB+ynPpe1F6tMDvrFGUlJs44t30BrujMXBe8Rq44cCk6wqyjATA3rQ== Generated by Nova\n" + }, + "uuid": "83679162-1378-4288-a2d4-70e13ec132aa", + "devices": [ + { + "bus": "scsi", + "serial": "6df1888b-f373-41cf-b960-3786e60a28ef", + "tags": ["fake_tag"], + "type": "disk", + "address": "0:0:0:0" + } + ] +} +`) + md, err := parseMetadata(data) + if err != nil { + t.Fatalf("Should succeed when provided with valid data: %s", err) + } + + if md.Name != "test" { + t.Errorf("incorrect name: %s", md.Name) + } + + if md.UUID != "83679162-1378-4288-a2d4-70e13ec132aa" { + t.Errorf("incorrect uuid: %s", md.UUID) + } + + if md.AvailabilityZone != "nova" { + t.Errorf("incorrect az: %s", md.AvailabilityZone) + } + + if len(md.Devices) != 1 { + t.Errorf("expecting to find 1 device, found %d", len(md.Devices)) + } + + if md.Devices[0].Bus != "scsi" { + t.Errorf("incorrect disk bus: %s", md.Devices[0].Bus) + } + + if md.Devices[0].Address != "0:0:0:0" { + t.Errorf("incorrect disk address: %s", md.Devices[0].Address) + } + + if md.Devices[0].Type != "disk" { + t.Errorf("incorrect device type: %s", md.Devices[0].Type) + } + + if md.Devices[0].Serial != "6df1888b-f373-41cf-b960-3786e60a28ef" { + t.Errorf("incorrect device serial: %s", md.Devices[0].Serial) + } +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go new file mode 100644 index 00000000000..668caaecc07 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go @@ -0,0 +1,949 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2014 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "reflect" + "regexp" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts" + tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/pagination" + "github.com/mitchellh/mapstructure" + "gopkg.in/gcfg.v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + netutil "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + certutil "k8s.io/client-go/util/cert" + cloudprovider "k8s.io/cloud-provider" + nodehelpers "k8s.io/cloud-provider/node/helpers" + "k8s.io/klog/v2" + netutils "k8s.io/utils/net" +) + +const ( + // ProviderName is the name of the openstack provider + ProviderName = "openstack" + + // TypeHostName is the name type of openstack instance + TypeHostName = "hostname" + availabilityZone = "availability_zone" + defaultTimeOut = 60 * time.Second +) + +// ErrNotFound is used to inform that the object is missing +var ErrNotFound = errors.New("failed to find object") + +// ErrMultipleResults is used when we unexpectedly get back multiple results +var ErrMultipleResults = errors.New("multiple results where only one expected") + +// ErrNoAddressFound is used when we cannot find an ip address for the host +var ErrNoAddressFound = errors.New("no address found for host") + +// MyDuration is the encoding.TextUnmarshaler interface for time.Duration +type MyDuration struct { + time.Duration +} + +// UnmarshalText is used to convert from text to Duration +func (d *MyDuration) UnmarshalText(text []byte) error { + res, err := time.ParseDuration(string(text)) + if err != nil { + return err + } + d.Duration = res + return nil +} + +// LoadBalancer is used for creating and maintaining load balancers +type LoadBalancer struct { + network *gophercloud.ServiceClient + compute *gophercloud.ServiceClient + lb *gophercloud.ServiceClient + opts LoadBalancerOpts +} + +// LoadBalancerOpts have the options to talk to Neutron LBaaSV2 or Octavia +type LoadBalancerOpts struct { + LBVersion string `gcfg:"lb-version"` // overrides autodetection. Only support v2. + UseOctavia bool `gcfg:"use-octavia"` // uses Octavia V2 service catalog endpoint + SubnetID string `gcfg:"subnet-id"` // overrides autodetection. + FloatingNetworkID string `gcfg:"floating-network-id"` // If specified, will create floating ip for loadbalancer, or do not create floating ip. + LBMethod string `gcfg:"lb-method"` // default to ROUND_ROBIN. + LBProvider string `gcfg:"lb-provider"` + CreateMonitor bool `gcfg:"create-monitor"` + MonitorDelay MyDuration `gcfg:"monitor-delay"` + MonitorTimeout MyDuration `gcfg:"monitor-timeout"` + MonitorMaxRetries uint `gcfg:"monitor-max-retries"` + ManageSecurityGroups bool `gcfg:"manage-security-groups"` + NodeSecurityGroupIDs []string // Do not specify, get it automatically when enable manage-security-groups. TODO(FengyunPan): move it into cache +} + +// BlockStorageOpts is used to talk to Cinder service +type BlockStorageOpts struct { + BSVersion string `gcfg:"bs-version"` // overrides autodetection. v1 or v2. Defaults to auto + TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128 + IgnoreVolumeAZ bool `gcfg:"ignore-volume-az"` + NodeVolumeAttachLimit int `gcfg:"node-volume-attach-limit"` // override volume attach limit for Cinder. Default is : 256 +} + +// RouterOpts is used for Neutron routes +type RouterOpts struct { + RouterID string `gcfg:"router-id"` // required +} + +// MetadataOpts is used for configuring how to talk to metadata service or config drive +type MetadataOpts struct { + SearchOrder string `gcfg:"search-order"` + RequestTimeout MyDuration `gcfg:"request-timeout"` +} + +var _ cloudprovider.Interface = (*OpenStack)(nil) +var _ cloudprovider.Zones = (*OpenStack)(nil) + +// OpenStack is an implementation of cloud provider Interface for OpenStack. +type OpenStack struct { + provider *gophercloud.ProviderClient + region string + lbOpts LoadBalancerOpts + bsOpts BlockStorageOpts + routeOpts RouterOpts + metadataOpts MetadataOpts + // InstanceID of the server where this OpenStack object is instantiated. + localInstanceID string +} + +// Config is used to read and store information from the cloud configuration file +// NOTE: Cloud config files should follow the same Kubernetes deprecation policy as +// flags or CLIs. Config fields should not change behavior in incompatible ways and +// should be deprecated for at least 2 release prior to removing. +// See https://kubernetes.io/docs/reference/using-api/deprecation-policy/#deprecating-a-flag-or-cli +// for more details. +type Config struct { + Global struct { + AuthURL string `gcfg:"auth-url"` + Username string + UserID string `gcfg:"user-id"` + Password string `datapolicy:"password"` + TenantID string `gcfg:"tenant-id"` + TenantName string `gcfg:"tenant-name"` + TrustID string `gcfg:"trust-id"` + DomainID string `gcfg:"domain-id"` + DomainName string `gcfg:"domain-name"` + Region string + CAFile string `gcfg:"ca-file"` + SecretName string `gcfg:"secret-name"` + SecretNamespace string `gcfg:"secret-namespace"` + KubeconfigPath string `gcfg:"kubeconfig-path"` + } + LoadBalancer LoadBalancerOpts + BlockStorage BlockStorageOpts + Route RouterOpts + Metadata MetadataOpts +} + +func init() { + registerMetrics() + + cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { + cfg, err := readConfig(config) + if err != nil { + return nil, err + } + return newOpenStack(cfg) + }) +} + +func (cfg Config) toAuthOptions() gophercloud.AuthOptions { + return gophercloud.AuthOptions{ + IdentityEndpoint: cfg.Global.AuthURL, + Username: cfg.Global.Username, + UserID: cfg.Global.UserID, + Password: cfg.Global.Password, + TenantID: cfg.Global.TenantID, + TenantName: cfg.Global.TenantName, + DomainID: cfg.Global.DomainID, + DomainName: cfg.Global.DomainName, + + // Persistent service, so we need to be able to renew tokens. + AllowReauth: true, + } +} + +func (cfg Config) toAuth3Options() tokens3.AuthOptions { + return tokens3.AuthOptions{ + IdentityEndpoint: cfg.Global.AuthURL, + Username: cfg.Global.Username, + UserID: cfg.Global.UserID, + Password: cfg.Global.Password, + DomainID: cfg.Global.DomainID, + DomainName: cfg.Global.DomainName, + AllowReauth: true, + } +} + +// configFromEnv allows setting up credentials etc using the +// standard OS_* OpenStack client environment variables. +func configFromEnv() (cfg Config, ok bool) { + cfg.Global.AuthURL = os.Getenv("OS_AUTH_URL") + cfg.Global.Username = os.Getenv("OS_USERNAME") + cfg.Global.Region = os.Getenv("OS_REGION_NAME") + cfg.Global.UserID = os.Getenv("OS_USER_ID") + cfg.Global.TrustID = os.Getenv("OS_TRUST_ID") + + cfg.Global.TenantID = os.Getenv("OS_TENANT_ID") + if cfg.Global.TenantID == "" { + cfg.Global.TenantID = os.Getenv("OS_PROJECT_ID") + } + cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME") + if cfg.Global.TenantName == "" { + cfg.Global.TenantName = os.Getenv("OS_PROJECT_NAME") + } + + cfg.Global.DomainID = os.Getenv("OS_DOMAIN_ID") + if cfg.Global.DomainID == "" { + cfg.Global.DomainID = os.Getenv("OS_USER_DOMAIN_ID") + } + cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME") + if cfg.Global.DomainName == "" { + cfg.Global.DomainName = os.Getenv("OS_USER_DOMAIN_NAME") + } + + cfg.Global.SecretName = os.Getenv("SECRET_NAME") + cfg.Global.SecretNamespace = os.Getenv("SECRET_NAMESPACE") + cfg.Global.KubeconfigPath = os.Getenv("KUBECONFIG_PATH") + + ok = cfg.Global.AuthURL != "" && + cfg.Global.Username != "" && + cfg.Global.Password != "" && + (cfg.Global.TenantID != "" || cfg.Global.TenantName != "" || + cfg.Global.DomainID != "" || cfg.Global.DomainName != "" || + cfg.Global.Region != "" || cfg.Global.UserID != "" || + cfg.Global.TrustID != "") + + cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID) + cfg.BlockStorage.BSVersion = "auto" + + return +} + +func createKubernetesClient(kubeconfigPath string) (*kubernetes.Clientset, error) { + klog.Info("Creating kubernetes API client.") + + cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, err + } + cfg.DisableCompression = true + + client, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, err + } + + v, err := client.Discovery().ServerVersion() + if err != nil { + return nil, err + } + + klog.Infof("Kubernetes API client created, server version %s", fmt.Sprintf("v%v.%v", v.Major, v.Minor)) + return client, nil +} + +// setConfigFromSecret allows setting up the config from k8s secret +func setConfigFromSecret(cfg *Config) error { + secretName := cfg.Global.SecretName + secretNamespace := cfg.Global.SecretNamespace + kubeconfigPath := cfg.Global.KubeconfigPath + + k8sClient, err := createKubernetesClient(kubeconfigPath) + if err != nil { + return fmt.Errorf("failed to get kubernetes client: %v", err) + } + + secret, err := k8sClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + if err != nil { + klog.Warningf("Cannot get secret %s in namespace %s. error: %q", secretName, secretNamespace, err) + return err + } + + if content, ok := secret.Data["clouds.conf"]; ok { + err = gcfg.ReadStringInto(cfg, string(content)) + if err != nil { + klog.Error("Cannot parse data from the secret.") + return fmt.Errorf("cannot parse data from the secret") + } + return nil + } + + klog.Error("Cannot find \"clouds.conf\" key in the secret.") + return fmt.Errorf("cannot find \"clouds.conf\" key in the secret") +} + +func readConfig(config io.Reader) (Config, error) { + if config == nil { + return Config{}, fmt.Errorf("no OpenStack cloud provider config file given") + } + + cfg, _ := configFromEnv() + + // Set default values for config params + cfg.BlockStorage.BSVersion = "auto" + cfg.BlockStorage.TrustDevicePath = false + cfg.BlockStorage.IgnoreVolumeAZ = false + cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID) + + err := gcfg.ReadInto(&cfg, config) + if err != nil { + // Warn instead of failing on non-fatal config parsing errors. + // This is important during the transition to external CCM we + // may be sharing user-managed configuration KCM, using legacy + // cloud provider, and CCM using external cloud provider. + // We do not want to prevent KCM from starting if the user adds + // new configuration which is only present in OpenStack CCM. + if gcfg.FatalOnly(err) == nil { + klog.Warningf("Non-fatal error parsing OpenStack cloud config. "+ + "This may happen when passing config directives exclusive to OpenStack CCM to the legacy cloud provider. "+ + "Legacy cloud provider has correctly parsed all directives it knows about: %s", err) + } else { + return cfg, err + } + } + + if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" { + klog.Infof("Set credentials from secret %s in namespace %s", cfg.Global.SecretName, cfg.Global.SecretNamespace) + err = setConfigFromSecret(&cfg) + if err != nil { + return cfg, err + } + } + + return cfg, nil +} + +// caller is a tiny helper for conditional unwind logic +type caller bool + +func newCaller() caller { return caller(true) } +func (c *caller) disarm() { *c = false } + +func (c *caller) call(f func()) { + if *c { + f() + } +} + +func readInstanceID(searchOrder string) (string, error) { + // Try to find instance ID on the local filesystem (created by cloud-init) + const instanceIDFile = "/var/lib/cloud/data/instance-id" + idBytes, err := ioutil.ReadFile(instanceIDFile) + if err == nil { + instanceID := string(idBytes) + instanceID = strings.TrimSpace(instanceID) + klog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID) + if instanceID != "" { + return instanceID, nil + } + // Fall through to metadata server lookup + } + + md, err := getMetadata(searchOrder) + if err != nil { + return "", err + } + + return md.UUID, nil +} + +// check opts for OpenStack +func checkOpenStackOpts(openstackOpts *OpenStack) error { + lbOpts := openstackOpts.lbOpts + + // if need to create health monitor for Neutron LB, + // monitor-delay, monitor-timeout and monitor-max-retries should be set. + emptyDuration := MyDuration{} + if lbOpts.CreateMonitor { + if lbOpts.MonitorDelay == emptyDuration { + return fmt.Errorf("monitor-delay not set in cloud provider config") + } + if lbOpts.MonitorTimeout == emptyDuration { + return fmt.Errorf("monitor-timeout not set in cloud provider config") + } + if lbOpts.MonitorMaxRetries == uint(0) { + return fmt.Errorf("monitor-max-retries not set in cloud provider config") + } + } + return checkMetadataSearchOrder(openstackOpts.metadataOpts.SearchOrder) +} + +func newOpenStack(cfg Config) (*OpenStack, error) { + provider, err := openstack.NewClient(cfg.Global.AuthURL) + if err != nil { + return nil, err + } + if cfg.Global.CAFile != "" { + roots, err := certutil.NewPool(cfg.Global.CAFile) + if err != nil { + return nil, err + } + config := &tls.Config{} + config.RootCAs = roots + provider.HTTPClient.Transport = netutil.SetOldTransportDefaults(&http.Transport{TLSClientConfig: config}) + + } + if cfg.Global.TrustID != "" { + opts := cfg.toAuth3Options() + authOptsExt := trusts.AuthOptsExt{ + TrustID: cfg.Global.TrustID, + AuthOptionsBuilder: &opts, + } + err = openstack.AuthenticateV3(provider, authOptsExt, gophercloud.EndpointOpts{}) + } else { + err = openstack.Authenticate(provider, cfg.toAuthOptions()) + } + + if err != nil { + return nil, err + } + + emptyDuration := MyDuration{} + if cfg.Metadata.RequestTimeout == emptyDuration { + cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut) + } + provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration + + os := OpenStack{ + provider: provider, + region: cfg.Global.Region, + lbOpts: cfg.LoadBalancer, + bsOpts: cfg.BlockStorage, + routeOpts: cfg.Route, + metadataOpts: cfg.Metadata, + } + + err = checkOpenStackOpts(&os) + if err != nil { + return nil, err + } + + return &os, nil +} + +// NewFakeOpenStackCloud creates and returns an instance of Openstack cloudprovider. +// Mainly for use in tests that require instantiating Openstack without having +// to go through cloudprovider interface. +func NewFakeOpenStackCloud(cfg Config) (*OpenStack, error) { + provider, err := openstack.NewClient(cfg.Global.AuthURL) + if err != nil { + return nil, err + } + emptyDuration := MyDuration{} + if cfg.Metadata.RequestTimeout == emptyDuration { + cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut) + } + provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration + + os := OpenStack{ + provider: provider, + region: cfg.Global.Region, + lbOpts: cfg.LoadBalancer, + bsOpts: cfg.BlockStorage, + routeOpts: cfg.Route, + metadataOpts: cfg.Metadata, + } + + return &os, nil +} + +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (os *OpenStack) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) { +} + +// mapNodeNameToServerName maps a k8s NodeName to an OpenStack Server Name +// This is a simple string cast. +func mapNodeNameToServerName(nodeName types.NodeName) string { + return string(nodeName) +} + +// GetNodeNameByID maps instanceid to types.NodeName +func (os *OpenStack) GetNodeNameByID(instanceID string) (types.NodeName, error) { + client, err := os.NewComputeV2() + var nodeName types.NodeName + if err != nil { + return nodeName, err + } + + server, err := servers.Get(client, instanceID).Extract() + if err != nil { + return nodeName, err + } + nodeName = mapServerToNodeName(server) + return nodeName, nil +} + +// mapServerToNodeName maps an OpenStack Server to a k8s NodeName +func mapServerToNodeName(server *servers.Server) types.NodeName { + // Node names are always lowercase, and (at least) + // routecontroller does case-sensitive string comparisons + // assuming this + return types.NodeName(strings.ToLower(server.Name)) +} + +func foreachServer(client *gophercloud.ServiceClient, opts servers.ListOptsBuilder, handler func(*servers.Server) (bool, error)) error { + pager := servers.List(client, opts) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + for _, server := range s { + ok, err := handler(&server) + if !ok || err != nil { + return false, err + } + } + return true, nil + }) + return err +} + +func getServerByName(client *gophercloud.ServiceClient, name types.NodeName) (*servers.Server, error) { + opts := servers.ListOpts{ + Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(mapNodeNameToServerName(name))), + } + + pager := servers.List(client, opts) + + serverList := make([]servers.Server, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + serverList = append(serverList, s...) + if len(serverList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + return nil, err + } + + if len(serverList) == 0 { + return nil, ErrNotFound + } + + return &serverList[0], nil +} + +func nodeAddresses(srv *servers.Server) ([]v1.NodeAddress, error) { + addrs := []v1.NodeAddress{} + + type Address struct { + IPType string `mapstructure:"OS-EXT-IPS:type"` + Addr string + } + + var addresses map[string][]Address + err := mapstructure.Decode(srv.Addresses, &addresses) + if err != nil { + return nil, err + } + + for network, addrList := range addresses { + for _, props := range addrList { + var addressType v1.NodeAddressType + if props.IPType == "floating" || network == "public" { + addressType = v1.NodeExternalIP + } else { + addressType = v1.NodeInternalIP + } + + nodehelpers.AddToNodeAddresses(&addrs, + v1.NodeAddress{ + Type: addressType, + Address: props.Addr, + }, + ) + } + } + + // AccessIPs are usually duplicates of "public" addresses. + if srv.AccessIPv4 != "" { + nodehelpers.AddToNodeAddresses(&addrs, + v1.NodeAddress{ + Type: v1.NodeExternalIP, + Address: srv.AccessIPv4, + }, + ) + } + + if srv.AccessIPv6 != "" { + nodehelpers.AddToNodeAddresses(&addrs, + v1.NodeAddress{ + Type: v1.NodeExternalIP, + Address: srv.AccessIPv6, + }, + ) + } + + if srv.Metadata[TypeHostName] != "" { + nodehelpers.AddToNodeAddresses(&addrs, + v1.NodeAddress{ + Type: v1.NodeHostName, + Address: srv.Metadata[TypeHostName], + }, + ) + } + + return addrs, nil +} + +func getAddressesByName(client *gophercloud.ServiceClient, name types.NodeName) ([]v1.NodeAddress, error) { + srv, err := getServerByName(client, name) + if err != nil { + return nil, err + } + + return nodeAddresses(srv) +} + +func getAddressByName(client *gophercloud.ServiceClient, name types.NodeName, needIPv6 bool) (string, error) { + addrs, err := getAddressesByName(client, name) + if err != nil { + return "", err + } else if len(addrs) == 0 { + return "", ErrNoAddressFound + } + + for _, addr := range addrs { + isIPv6 := netutils.ParseIPSloppy(addr.Address).To4() == nil + if (addr.Type == v1.NodeInternalIP) && (isIPv6 == needIPv6) { + return addr.Address, nil + } + } + + for _, addr := range addrs { + isIPv6 := netutils.ParseIPSloppy(addr.Address).To4() == nil + if (addr.Type == v1.NodeExternalIP) && (isIPv6 == needIPv6) { + return addr.Address, nil + } + } + // It should never return an address from a different IP Address family than the one needed + return "", ErrNoAddressFound +} + +// getAttachedInterfacesByID returns the node interfaces of the specified instance. +func getAttachedInterfacesByID(client *gophercloud.ServiceClient, serviceID string) ([]attachinterfaces.Interface, error) { + var interfaces []attachinterfaces.Interface + + pager := attachinterfaces.List(client, serviceID) + err := pager.EachPage(func(page pagination.Page) (bool, error) { + s, err := attachinterfaces.ExtractInterfaces(page) + if err != nil { + return false, err + } + interfaces = append(interfaces, s...) + return true, nil + }) + if err != nil { + return interfaces, err + } + + return interfaces, nil +} + +// Clusters is a no-op +func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) { + return nil, false +} + +// ProviderName returns the cloud provider ID. +func (os *OpenStack) ProviderName() string { + return ProviderName +} + +// HasClusterID returns true if the cluster has a clusterID +func (os *OpenStack) HasClusterID() bool { + return true +} + +// LoadBalancer initializes a LbaasV2 object +func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + klog.V(4).Info("openstack.LoadBalancer() called") + + if reflect.DeepEqual(os.lbOpts, LoadBalancerOpts{}) { + klog.V(4).Info("LoadBalancer section is empty/not defined in cloud-config") + return nil, false + } + + network, err := os.NewNetworkV2() + if err != nil { + return nil, false + } + + compute, err := os.NewComputeV2() + if err != nil { + return nil, false + } + + lb, err := os.NewLoadBalancerV2() + if err != nil { + return nil, false + } + + // LBaaS v1 is deprecated in the OpenStack Liberty release. + // Currently kubernetes OpenStack cloud provider just support LBaaS v2. + lbVersion := os.lbOpts.LBVersion + if lbVersion != "" && lbVersion != "v2" { + klog.Warningf("Config error: currently only support LBaaS v2, unrecognised lb-version \"%v\"", lbVersion) + return nil, false + } + + klog.V(1).Info("Claiming to support LoadBalancer") + + return &LbaasV2{LoadBalancer{network, compute, lb, os.lbOpts}}, true +} + +func isNotFound(err error) bool { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return true + } + + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == http.StatusNotFound { + return true + } + } + + return false +} + +// Zones indicates that we support zones +func (os *OpenStack) Zones() (cloudprovider.Zones, bool) { + klog.V(1).Info("Claiming to support Zones") + return os, true +} + +// GetZone returns the current zone +func (os *OpenStack) GetZone(ctx context.Context) (cloudprovider.Zone, error) { + md, err := getMetadata(os.metadataOpts.SearchOrder) + if err != nil { + return cloudprovider.Zone{}, err + } + + zone := cloudprovider.Zone{ + FailureDomain: md.AvailabilityZone, + Region: os.region, + } + klog.V(4).Infof("Current zone is %v", zone) + return zone, nil +} + +// GetZoneByProviderID implements Zones.GetZoneByProviderID +// This is particularly useful in external cloud providers where the kubelet +// does not initialize node data. +func (os *OpenStack) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) { + instanceID, err := instanceIDFromProviderID(providerID) + if err != nil { + return cloudprovider.Zone{}, err + } + + compute, err := os.NewComputeV2() + if err != nil { + return cloudprovider.Zone{}, err + } + + srv, err := servers.Get(compute, instanceID).Extract() + if err != nil { + return cloudprovider.Zone{}, err + } + + zone := cloudprovider.Zone{ + FailureDomain: srv.Metadata[availabilityZone], + Region: os.region, + } + klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone) + return zone, nil +} + +// GetZoneByNodeName implements Zones.GetZoneByNodeName +// This is particularly useful in external cloud providers where the kubelet +// does not initialize node data. +func (os *OpenStack) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) { + compute, err := os.NewComputeV2() + if err != nil { + return cloudprovider.Zone{}, err + } + + srv, err := getServerByName(compute, nodeName) + if err != nil { + if err == ErrNotFound { + return cloudprovider.Zone{}, cloudprovider.InstanceNotFound + } + return cloudprovider.Zone{}, err + } + + zone := cloudprovider.Zone{ + FailureDomain: srv.Metadata[availabilityZone], + Region: os.region, + } + klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone) + return zone, nil +} + +// Routes initializes routes support +func (os *OpenStack) Routes() (cloudprovider.Routes, bool) { + klog.V(4).Info("openstack.Routes() called") + + network, err := os.NewNetworkV2() + if err != nil { + return nil, false + } + + netExts, err := networkExtensions(network) + if err != nil { + klog.Warningf("Failed to list neutron extensions: %v", err) + return nil, false + } + + if !netExts["extraroute"] { + klog.V(3).Info("Neutron extraroute extension not found, required for Routes support") + return nil, false + } + + compute, err := os.NewComputeV2() + if err != nil { + return nil, false + } + + r, err := NewRoutes(compute, network, os.routeOpts) + if err != nil { + klog.Warningf("Error initialising Routes support: %v", err) + return nil, false + } + + klog.V(1).Info("Claiming to support Routes") + return r, true +} + +func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) { + bsVersion := "" + if forceVersion == "" { + bsVersion = os.bsOpts.BSVersion + } else { + bsVersion = forceVersion + } + + switch bsVersion { + case "v1": + sClient, err := os.NewBlockStorageV1() + if err != nil { + return nil, err + } + klog.V(3).Info("Using Blockstorage API V1") + return &VolumesV1{sClient, os.bsOpts}, nil + case "v2": + sClient, err := os.NewBlockStorageV2() + if err != nil { + return nil, err + } + klog.V(3).Info("Using Blockstorage API V2") + return &VolumesV2{sClient, os.bsOpts}, nil + case "v3": + sClient, err := os.NewBlockStorageV3() + if err != nil { + return nil, err + } + klog.V(3).Info("Using Blockstorage API V3") + return &VolumesV3{sClient, os.bsOpts}, nil + case "auto": + // Currently kubernetes support Cinder v1 / Cinder v2 / Cinder v3. + // Choose Cinder v3 firstly, if kubernetes can't initialize cinder v3 client, try to initialize cinder v2 client. + // If kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client. + // Return appropriate message when kubernetes can't initialize them. + if sClient, err := os.NewBlockStorageV3(); err == nil { + klog.V(3).Info("Using Blockstorage API V3") + return &VolumesV3{sClient, os.bsOpts}, nil + } + + if sClient, err := os.NewBlockStorageV2(); err == nil { + klog.V(3).Info("Using Blockstorage API V2") + return &VolumesV2{sClient, os.bsOpts}, nil + } + + if sClient, err := os.NewBlockStorageV1(); err == nil { + klog.V(3).Info("Using Blockstorage API V1") + return &VolumesV1{sClient, os.bsOpts}, nil + } + + errTxt := "BlockStorage API version autodetection failed. " + + "Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`" + return nil, errors.New(errTxt) + default: + errTxt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion) + return nil, errors.New(errTxt) + } +} + +func checkMetadataSearchOrder(order string) error { + if order == "" { + return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot be empty") + } + + elements := strings.Split(order, ",") + if len(elements) > 2 { + return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements") + } + + for _, id := range elements { + id = strings.TrimSpace(id) + switch id { + case configDriveID: + case metadataID: + default: + return fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+ + "Supported elements include %q and %q", id, configDriveID, metadataID) + } + } + + return nil +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go new file mode 100644 index 00000000000..305af56284a --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go @@ -0,0 +1,101 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" +) + +// NewNetworkV2 creates a ServiceClient that may be used with the neutron v2 API +func (os *OpenStack) NewNetworkV2() (*gophercloud.ServiceClient, error) { + network, err := openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + return nil, fmt.Errorf("failed to find network v2 endpoint for region %s: %v", os.region, err) + } + return network, nil +} + +// NewComputeV2 creates a ServiceClient that may be used with the nova v2 API +func (os *OpenStack) NewComputeV2() (*gophercloud.ServiceClient, error) { + compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + return nil, fmt.Errorf("failed to find compute v2 endpoint for region %s: %v", os.region, err) + } + return compute, nil +} + +// NewBlockStorageV1 creates a ServiceClient that may be used with the Cinder v1 API +func (os *OpenStack) NewBlockStorageV1() (*gophercloud.ServiceClient, error) { + storage, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + return nil, fmt.Errorf("unable to initialize cinder v1 client for region %s: %v", os.region, err) + } + return storage, nil +} + +// NewBlockStorageV2 creates a ServiceClient that may be used with the Cinder v2 API +func (os *OpenStack) NewBlockStorageV2() (*gophercloud.ServiceClient, error) { + storage, err := openstack.NewBlockStorageV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + return nil, fmt.Errorf("unable to initialize cinder v2 client for region %s: %v", os.region, err) + } + return storage, nil +} + +// NewBlockStorageV3 creates a ServiceClient that may be used with the Cinder v3 API +func (os *OpenStack) NewBlockStorageV3() (*gophercloud.ServiceClient, error) { + storage, err := openstack.NewBlockStorageV3(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + return nil, fmt.Errorf("unable to initialize cinder v3 client for region %s: %v", os.region, err) + } + return storage, nil +} + +// NewLoadBalancerV2 creates a ServiceClient that may be used with the Neutron LBaaS v2 API +func (os *OpenStack) NewLoadBalancerV2() (*gophercloud.ServiceClient, error) { + var lb *gophercloud.ServiceClient + var err error + if os.lbOpts.UseOctavia { + lb, err = openstack.NewLoadBalancerV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + } else { + lb, err = openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + } + if err != nil { + return nil, fmt.Errorf("failed to find load-balancer v2 endpoint for region %s: %v", os.region, err) + } + return lb, nil +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go new file mode 100644 index 00000000000..37bc9383ad9 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go @@ -0,0 +1,244 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "fmt" + "regexp" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + v1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/types" + cloudprovider "k8s.io/cloud-provider" +) + +var _ cloudprovider.Instances = (*Instances)(nil) + +// Instances encapsulates an implementation of Instances for OpenStack. +type Instances struct { + compute *gophercloud.ServiceClient + opts MetadataOpts +} + +const ( + instanceShutoff = "SHUTOFF" +) + +// Instances returns an implementation of Instances for OpenStack. +func (os *OpenStack) Instances() (cloudprovider.Instances, bool) { + klog.V(4).Info("openstack.Instances() called") + + compute, err := os.NewComputeV2() + if err != nil { + klog.Errorf("unable to access compute v2 API : %v", err) + return nil, false + } + + klog.V(4).Info("Claiming to support Instances") + + return &Instances{ + compute: compute, + opts: os.metadataOpts, + }, true +} + +// InstancesV2 returns an implementation of InstancesV2 for OpenStack. +// TODO: implement ONLY for external cloud provider +func (os *OpenStack) InstancesV2() (cloudprovider.InstancesV2, bool) { + return nil, false +} + +// CurrentNodeName implements Instances.CurrentNodeName +// Note this is *not* necessarily the same as hostname. +func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) { + md, err := getMetadata(i.opts.SearchOrder) + if err != nil { + return "", err + } + return types.NodeName(md.Name), nil +} + +// AddSSHKeyToAllInstances is not implemented for OpenStack +func (i *Instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error { + return cloudprovider.NotImplemented +} + +// NodeAddresses implements Instances.NodeAddresses +func (i *Instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) { + klog.V(4).Infof("NodeAddresses(%v) called", name) + + addrs, err := getAddressesByName(i.compute, name) + if err != nil { + return nil, err + } + + klog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs) + return addrs, nil +} + +// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID +// This method will not be called from the node that is requesting this ID. i.e. metadata service +// and other local methods cannot be used here +func (i *Instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) { + instanceID, err := instanceIDFromProviderID(providerID) + + if err != nil { + return []v1.NodeAddress{}, err + } + + server, err := servers.Get(i.compute, instanceID).Extract() + + if err != nil { + return []v1.NodeAddress{}, err + } + + addresses, err := nodeAddresses(server) + if err != nil { + return []v1.NodeAddress{}, err + } + + return addresses, nil +} + +// InstanceExistsByProviderID returns true if the instance with the given provider id still exist. +// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. +func (i *Instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) { + instanceID, err := instanceIDFromProviderID(providerID) + if err != nil { + return false, err + } + + _, err = servers.Get(i.compute, instanceID).Extract() + if err != nil { + if isNotFound(err) { + return false, nil + } + return false, err + } + + return true, nil +} + +// InstanceShutdownByProviderID returns true if the instances is in safe state to detach volumes +func (i *Instances) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) { + instanceID, err := instanceIDFromProviderID(providerID) + if err != nil { + return false, err + } + + server, err := servers.Get(i.compute, instanceID).Extract() + if err != nil { + return false, err + } + + // SHUTOFF is the only state where we can detach volumes immediately + if server.Status == instanceShutoff { + return true, nil + } + return false, nil +} + +// InstanceID returns the kubelet's cloud provider ID. +func (os *OpenStack) InstanceID() (string, error) { + if len(os.localInstanceID) == 0 { + id, err := readInstanceID(os.metadataOpts.SearchOrder) + if err != nil { + return "", err + } + os.localInstanceID = id + } + return os.localInstanceID, nil +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) { + srv, err := getServerByName(i.compute, name) + if err != nil { + if err == ErrNotFound { + return "", cloudprovider.InstanceNotFound + } + return "", err + } + // In the future it is possible to also return an endpoint as: + // / + return "/" + srv.ID, nil +} + +// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID +// This method will not be called from the node that is requesting this ID. i.e. metadata service +// and other local methods cannot be used here +func (i *Instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) { + instanceID, err := instanceIDFromProviderID(providerID) + + if err != nil { + return "", err + } + + server, err := servers.Get(i.compute, instanceID).Extract() + + if err != nil { + return "", err + } + + return srvInstanceType(server) +} + +// InstanceType returns the type of the specified instance. +func (i *Instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) { + srv, err := getServerByName(i.compute, name) + + if err != nil { + return "", err + } + + return srvInstanceType(srv) +} + +func srvInstanceType(srv *servers.Server) (string, error) { + keys := []string{"name", "id", "original_name"} + for _, key := range keys { + val, found := srv.Flavor[key] + if found { + flavor, ok := val.(string) + if ok { + return flavor, nil + } + } + } + return "", fmt.Errorf("flavor name/id not found") +} + +// instanceIDFromProviderID splits a provider's id and return instanceID. +// A providerID is build out of '${ProviderName}:///${instance-id}'which contains ':///'. +// See cloudprovider.GetInstanceProviderID and Instances.InstanceID. +func instanceIDFromProviderID(providerID string) (instanceID string, err error) { + // If Instances.InstanceID or cloudprovider.GetInstanceProviderID is changed, the regexp should be changed too. + var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `:///([^/]+)$`) + + matches := providerIDRegexp.FindStringSubmatch(providerID) + if len(matches) != 2 { + return "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"openstack:///InstanceID\"", providerID) + } + return matches[1], nil +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go new file mode 100644 index 00000000000..c735fbc0929 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go @@ -0,0 +1,1578 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "fmt" + "reflect" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" + v2monitors "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" + v2pools "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/gophercloud/gophercloud/pagination" + "k8s.io/klog/v2" + netutils "k8s.io/utils/net" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + cloudprovider "k8s.io/cloud-provider" + servicehelpers "k8s.io/cloud-provider/service/helpers" +) + +// Note: when creating a new Loadbalancer (VM), it can take some time before it is ready for use, +// this timeout is used for waiting until the Loadbalancer provisioning status goes to ACTIVE state. +const ( + // loadbalancerActive* is configuration of exponential backoff for + // going into ACTIVE loadbalancer provisioning status. Starting with 1 + // seconds, multiplying by 1.2 with each step and taking 19 steps at maximum + // it will time out after 128s, which roughly corresponds to 120s + loadbalancerActiveInitDelay = 1 * time.Second + loadbalancerActiveFactor = 1.2 + loadbalancerActiveSteps = 19 + + // loadbalancerDelete* is configuration of exponential backoff for + // waiting for delete operation to complete. Starting with 1 + // seconds, multiplying by 1.2 with each step and taking 13 steps at maximum + // it will time out after 32s, which roughly corresponds to 30s + loadbalancerDeleteInitDelay = 1 * time.Second + loadbalancerDeleteFactor = 1.2 + loadbalancerDeleteSteps = 13 + + activeStatus = "ACTIVE" + errorStatus = "ERROR" + + ServiceAnnotationLoadBalancerFloatingNetworkID = "loadbalancer.openstack.org/floating-network-id" + ServiceAnnotationLoadBalancerSubnetID = "loadbalancer.openstack.org/subnet-id" + + // ServiceAnnotationLoadBalancerInternal is the annotation used on the service + // to indicate that we want an internal loadbalancer service. + // If the value of ServiceAnnotationLoadBalancerInternal is false, it indicates that we want an external loadbalancer service. Default to false. + ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/openstack-internal-load-balancer" +) + +var _ cloudprovider.LoadBalancer = (*LbaasV2)(nil) + +// LbaasV2 is a LoadBalancer implementation for Neutron LBaaS v2 API +type LbaasV2 struct { + LoadBalancer +} + +func networkExtensions(client *gophercloud.ServiceClient) (map[string]bool, error) { + seen := make(map[string]bool) + + pager := extensions.List(client) + err := pager.EachPage(func(page pagination.Page) (bool, error) { + exts, err := extensions.ExtractExtensions(page) + if err != nil { + return false, err + } + for _, ext := range exts { + seen[ext.Alias] = true + } + return true, nil + }) + + return seen, err +} + +func getFloatingIPByPortID(client *gophercloud.ServiceClient, portID string) (*floatingips.FloatingIP, error) { + opts := floatingips.ListOpts{ + PortID: portID, + } + pager := floatingips.List(client, opts) + + floatingIPList := make([]floatingips.FloatingIP, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + f, err := floatingips.ExtractFloatingIPs(page) + if err != nil { + return false, err + } + floatingIPList = append(floatingIPList, f...) + if len(floatingIPList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return nil, ErrNotFound + } + return nil, err + } + + if len(floatingIPList) == 0 { + return nil, ErrNotFound + } else if len(floatingIPList) > 1 { + return nil, ErrMultipleResults + } + + return &floatingIPList[0], nil +} + +func getLoadbalancerByName(client *gophercloud.ServiceClient, name string) (*loadbalancers.LoadBalancer, error) { + opts := loadbalancers.ListOpts{ + Name: name, + } + pager := loadbalancers.List(client, opts) + + loadbalancerList := make([]loadbalancers.LoadBalancer, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + v, err := loadbalancers.ExtractLoadBalancers(page) + if err != nil { + return false, err + } + loadbalancerList = append(loadbalancerList, v...) + if len(loadbalancerList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return nil, ErrNotFound + } + return nil, err + } + + if len(loadbalancerList) == 0 { + return nil, ErrNotFound + } else if len(loadbalancerList) > 1 { + return nil, ErrMultipleResults + } + + return &loadbalancerList[0], nil +} + +func getListenersByLoadBalancerID(client *gophercloud.ServiceClient, id string) ([]listeners.Listener, error) { + var existingListeners []listeners.Listener + err := listeners.List(client, listeners.ListOpts{LoadbalancerID: id}).EachPage(func(page pagination.Page) (bool, error) { + listenerList, err := listeners.ExtractListeners(page) + if err != nil { + return false, err + } + for _, l := range listenerList { + for _, lb := range l.Loadbalancers { + if lb.ID == id { + existingListeners = append(existingListeners, l) + break + } + } + } + + return true, nil + }) + if err != nil { + return nil, err + } + + return existingListeners, nil +} + +// get listener for a port or nil if does not exist +func getListenerForPort(existingListeners []listeners.Listener, port v1.ServicePort) *listeners.Listener { + for _, l := range existingListeners { + if listeners.Protocol(l.Protocol) == toListenersProtocol(port.Protocol) && l.ProtocolPort == int(port.Port) { + return &l + } + } + + return nil +} + +// Get pool for a listener. A listener always has exactly one pool. +func getPoolByListenerID(client *gophercloud.ServiceClient, loadbalancerID string, listenerID string) (*v2pools.Pool, error) { + listenerPools := make([]v2pools.Pool, 0, 1) + err := v2pools.List(client, v2pools.ListOpts{LoadbalancerID: loadbalancerID}).EachPage(func(page pagination.Page) (bool, error) { + poolsList, err := v2pools.ExtractPools(page) + if err != nil { + return false, err + } + for _, p := range poolsList { + for _, l := range p.Listeners { + if l.ID == listenerID { + listenerPools = append(listenerPools, p) + } + } + } + if len(listenerPools) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return nil, ErrNotFound + } + return nil, err + } + + if len(listenerPools) == 0 { + return nil, ErrNotFound + } else if len(listenerPools) > 1 { + return nil, ErrMultipleResults + } + + return &listenerPools[0], nil +} + +func getMembersByPoolID(client *gophercloud.ServiceClient, id string) ([]v2pools.Member, error) { + var members []v2pools.Member + err := v2pools.ListMembers(client, id, v2pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) { + membersList, err := v2pools.ExtractMembers(page) + if err != nil { + return false, err + } + members = append(members, membersList...) + + return true, nil + }) + if err != nil { + return nil, err + } + + return members, nil +} + +// Check if a member exists for node +func memberExists(members []v2pools.Member, addr string, port int) bool { + for _, member := range members { + if member.Address == addr && member.ProtocolPort == port { + return true + } + } + + return false +} + +func popListener(existingListeners []listeners.Listener, id string) []listeners.Listener { + for i, existingListener := range existingListeners { + if existingListener.ID == id { + existingListeners[i] = existingListeners[len(existingListeners)-1] + existingListeners = existingListeners[:len(existingListeners)-1] + break + } + } + + return existingListeners +} + +func popMember(members []v2pools.Member, addr string, port int) []v2pools.Member { + for i, member := range members { + if member.Address == addr && member.ProtocolPort == port { + members[i] = members[len(members)-1] + members = members[:len(members)-1] + } + } + + return members +} + +func getSecurityGroupName(service *v1.Service) string { + securityGroupName := fmt.Sprintf("lb-sg-%s-%s-%s", service.UID, service.Namespace, service.Name) + //OpenStack requires that the name of a security group is shorter than 255 bytes. + if len(securityGroupName) > 255 { + securityGroupName = securityGroupName[:255] + } + + return securityGroupName +} + +func getSecurityGroupRules(client *gophercloud.ServiceClient, opts rules.ListOpts) ([]rules.SecGroupRule, error) { + + pager := rules.List(client, opts) + + var securityRules []rules.SecGroupRule + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + ruleList, err := rules.ExtractRules(page) + if err != nil { + return false, err + } + securityRules = append(securityRules, ruleList...) + return true, nil + }) + + if err != nil { + return nil, err + } + + return securityRules, nil +} + +func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) (string, error) { + backoff := wait.Backoff{ + Duration: loadbalancerActiveInitDelay, + Factor: loadbalancerActiveFactor, + Steps: loadbalancerActiveSteps, + } + + var provisioningStatus string + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + loadbalancer, err := loadbalancers.Get(client, loadbalancerID).Extract() + if err != nil { + return false, err + } + provisioningStatus = loadbalancer.ProvisioningStatus + if loadbalancer.ProvisioningStatus == activeStatus { + return true, nil + } else if loadbalancer.ProvisioningStatus == errorStatus { + return true, fmt.Errorf("loadbalancer has gone into ERROR state") + } else { + return false, nil + } + + }) + + if err == wait.ErrWaitTimeout { + err = fmt.Errorf("loadbalancer failed to go into ACTIVE provisioning status within alloted time") + } + return provisioningStatus, err +} + +func waitLoadbalancerDeleted(client *gophercloud.ServiceClient, loadbalancerID string) error { + backoff := wait.Backoff{ + Duration: loadbalancerDeleteInitDelay, + Factor: loadbalancerDeleteFactor, + Steps: loadbalancerDeleteSteps, + } + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + _, err := loadbalancers.Get(client, loadbalancerID).Extract() + if err != nil { + if isNotFound(err) { + return true, nil + } + return false, err + } + return false, nil + }) + + if err == wait.ErrWaitTimeout { + err = fmt.Errorf("loadbalancer failed to delete within the alloted time") + } + + return err +} + +func toRuleProtocol(protocol v1.Protocol) rules.RuleProtocol { + switch protocol { + case v1.ProtocolTCP: + return rules.ProtocolTCP + case v1.ProtocolUDP: + return rules.ProtocolUDP + default: + return rules.RuleProtocol(strings.ToLower(string(protocol))) + } +} + +func toListenersProtocol(protocol v1.Protocol) listeners.Protocol { + switch protocol { + case v1.ProtocolTCP: + return listeners.ProtocolTCP + default: + return listeners.Protocol(string(protocol)) + } +} + +func createNodeSecurityGroup(client *gophercloud.ServiceClient, nodeSecurityGroupID string, port int, protocol v1.Protocol, lbSecGroup string) error { + v4NodeSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: rules.DirIngress, + PortRangeMax: port, + PortRangeMin: port, + Protocol: toRuleProtocol(protocol), + RemoteGroupID: lbSecGroup, + SecGroupID: nodeSecurityGroupID, + EtherType: rules.EtherType4, + } + + v6NodeSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: rules.DirIngress, + PortRangeMax: port, + PortRangeMin: port, + Protocol: toRuleProtocol(protocol), + RemoteGroupID: lbSecGroup, + SecGroupID: nodeSecurityGroupID, + EtherType: rules.EtherType6, + } + + _, err := rules.Create(client, v4NodeSecGroupRuleCreateOpts).Extract() + + if err != nil { + return err + } + + _, err = rules.Create(client, v6NodeSecGroupRuleCreateOpts).Extract() + + if err != nil { + return err + } + return nil +} + +func (lbaas *LbaasV2) createLoadBalancer(service *v1.Service, name string, internalAnnotation bool) (*loadbalancers.LoadBalancer, error) { + createOpts := loadbalancers.CreateOpts{ + Name: name, + Description: fmt.Sprintf("Kubernetes external service %s", name), + VipSubnetID: lbaas.opts.SubnetID, + Provider: lbaas.opts.LBProvider, + } + + loadBalancerIP := service.Spec.LoadBalancerIP + if loadBalancerIP != "" && internalAnnotation { + createOpts.VipAddress = loadBalancerIP + } + + loadbalancer, err := loadbalancers.Create(lbaas.lb, createOpts).Extract() + if err != nil { + return nil, fmt.Errorf("error creating loadbalancer %v: %v", createOpts, err) + } + return loadbalancer, nil +} + +// GetLoadBalancer returns whether the specified load balancer exists and its status +func (lbaas *LbaasV2) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (*v1.LoadBalancerStatus, bool, error) { + loadBalancerName := lbaas.GetLoadBalancerName(ctx, clusterName, service) + loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) + if err == ErrNotFound { + return nil, false, nil + } + if loadbalancer == nil { + return nil, false, err + } + + status := &v1.LoadBalancerStatus{} + + portID := loadbalancer.VipPortID + if portID != "" { + floatIP, err := getFloatingIPByPortID(lbaas.network, portID) + if err != nil && err != ErrNotFound { + return nil, false, fmt.Errorf("error getting floating ip for port %s: %v", portID, err) + } + + if floatIP != nil { + status.Ingress = []v1.LoadBalancerIngress{{IP: floatIP.FloatingIP}} + } + } else { + status.Ingress = []v1.LoadBalancerIngress{{IP: loadbalancer.VipAddress}} + } + + return status, true, err +} + +// GetLoadBalancerName is an implementation of LoadBalancer.GetLoadBalancerName. +func (lbaas *LbaasV2) GetLoadBalancerName(ctx context.Context, clusterName string, service *v1.Service) string { + // TODO: replace DefaultLoadBalancerName to generate more meaningful loadbalancer names. + return cloudprovider.DefaultLoadBalancerName(service) +} + +// The LB needs to be configured with instance addresses on the same +// subnet as the LB (aka opts.SubnetID). Currently we're just +// guessing that the node's InternalIP is the right address. +// In case no InternalIP can be found, ExternalIP is tried. +// If neither InternalIP nor ExternalIP can be found an error is +// returned. +func nodeAddressForLB(node *v1.Node) (string, error) { + addrs := node.Status.Addresses + if len(addrs) == 0 { + return "", ErrNoAddressFound + } + + allowedAddrTypes := []v1.NodeAddressType{v1.NodeInternalIP, v1.NodeExternalIP} + + for _, allowedAddrType := range allowedAddrTypes { + for _, addr := range addrs { + if addr.Type == allowedAddrType { + return addr.Address, nil + } + } + } + + return "", ErrNoAddressFound +} + +// getStringFromServiceAnnotation searches a given v1.Service for a specific annotationKey and either returns the annotation's value or a specified defaultSetting +func getStringFromServiceAnnotation(service *v1.Service, annotationKey string, defaultSetting string) string { + klog.V(4).Infof("getStringFromServiceAnnotation(%v, %v, %v)", service, annotationKey, defaultSetting) + if annotationValue, ok := service.Annotations[annotationKey]; ok { + //if there is an annotation for this setting, set the "setting" var to it + // annotationValue can be empty, it is working as designed + // it makes possible for instance provisioning loadbalancer without floatingip + klog.V(4).Infof("Found a Service Annotation: %v = %v", annotationKey, annotationValue) + return annotationValue + } + //if there is no annotation, set "settings" var to the value from cloud config + klog.V(4).Infof("Could not find a Service Annotation; falling back on cloud-config setting: %v = %v", annotationKey, defaultSetting) + return defaultSetting +} + +// getSubnetIDForLB returns subnet-id for a specific node +func getSubnetIDForLB(compute *gophercloud.ServiceClient, node v1.Node) (string, error) { + ipAddress, err := nodeAddressForLB(&node) + if err != nil { + return "", err + } + + instanceID := node.Spec.ProviderID + if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { + instanceID = instanceID[(ind + 1):] + } + + interfaces, err := getAttachedInterfacesByID(compute, instanceID) + if err != nil { + return "", err + } + + for _, intf := range interfaces { + for _, fixedIP := range intf.FixedIPs { + if fixedIP.IPAddress == ipAddress { + return fixedIP.SubnetID, nil + } + } + } + + return "", ErrNotFound +} + +// getNodeSecurityGroupIDForLB lists node-security-groups for specific nodes +func getNodeSecurityGroupIDForLB(compute *gophercloud.ServiceClient, network *gophercloud.ServiceClient, nodes []*v1.Node) ([]string, error) { + secGroupNames := sets.NewString() + + for _, node := range nodes { + nodeName := types.NodeName(node.Name) + srv, err := getServerByName(compute, nodeName) + if err != nil { + return []string{}, err + } + + // use the first node-security-groups + // case 0: node1:SG1 node2:SG1 return SG1 + // case 1: node1:SG1 node2:SG2 return SG1,SG2 + // case 2: node1:SG1,SG2 node2:SG3,SG4 return SG1,SG3 + // case 3: node1:SG1,SG2 node2:SG2,SG3 return SG1,SG2 + secGroupNames.Insert(srv.SecurityGroups[0]["name"].(string)) + } + + secGroupIDs := make([]string, secGroupNames.Len()) + for i, name := range secGroupNames.List() { + secGroupID, err := groups.IDFromName(network, name) + if err != nil { + return []string{}, err + } + secGroupIDs[i] = secGroupID + } + + return secGroupIDs, nil +} + +// isSecurityGroupNotFound return true while 'err' is object of gophercloud.ErrResourceNotFound +func isSecurityGroupNotFound(err error) bool { + errType := reflect.TypeOf(err).String() + errTypeSlice := strings.Split(errType, ".") + errTypeValue := "" + if len(errTypeSlice) != 0 { + errTypeValue = errTypeSlice[len(errTypeSlice)-1] + } + if errTypeValue == "ErrResourceNotFound" { + return true + } + + return false +} + +// getFloatingNetworkIDForLB returns a floating-network-id for cluster. +func getFloatingNetworkIDForLB(client *gophercloud.ServiceClient) (string, error) { + var floatingNetworkIds []string + + type NetworkWithExternalExt struct { + networks.Network + external.NetworkExternalExt + } + + err := networks.List(client, networks.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + var externalNetwork []NetworkWithExternalExt + err := networks.ExtractNetworksInto(page, &externalNetwork) + if err != nil { + return false, err + } + + for _, externalNet := range externalNetwork { + if externalNet.External { + floatingNetworkIds = append(floatingNetworkIds, externalNet.ID) + } + } + + if len(floatingNetworkIds) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return "", ErrNotFound + } + + if err == ErrMultipleResults { + klog.V(4).Infof("find multiple external networks, pick the first one when there are no explicit configuration.") + return floatingNetworkIds[0], nil + } + return "", err + } + + if len(floatingNetworkIds) == 0 { + return "", ErrNotFound + } + + return floatingNetworkIds[0], nil +} + +// TODO: This code currently ignores 'region' and always creates a +// loadbalancer in only the current OpenStack region. We should take +// a list of regions (from config) and query/create loadbalancers in +// each region. + +// EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. +func (lbaas *LbaasV2) EnsureLoadBalancer(ctx context.Context, clusterName string, apiService *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { + klog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", clusterName, apiService.Namespace, apiService.Name, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, nodes, apiService.Annotations) + + if len(nodes) == 0 { + return nil, fmt.Errorf("there are no available nodes for LoadBalancer service %s/%s", apiService.Namespace, apiService.Name) + } + + lbaas.opts.SubnetID = getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerSubnetID, lbaas.opts.SubnetID) + if len(lbaas.opts.SubnetID) == 0 { + // Get SubnetID automatically. + // The LB needs to be configured with instance addresses on the same subnet, so get SubnetID by one node. + subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0]) + if err != nil { + klog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return nil, fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ + "and failed to find subnet-id from OpenStack: %v", apiService.Namespace, apiService.Name, err) + } + lbaas.opts.SubnetID = subnetID + } + + ports := apiService.Spec.Ports + if len(ports) == 0 { + return nil, fmt.Errorf("no ports provided to openstack load balancer") + } + + floatingPool := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerFloatingNetworkID, lbaas.opts.FloatingNetworkID) + if len(floatingPool) == 0 { + var err error + floatingPool, err = getFloatingNetworkIDForLB(lbaas.network) + if err != nil { + klog.Warningf("Failed to find floating-network-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + } + + var internalAnnotation bool + internal := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerInternal, "false") + switch internal { + case "true": + klog.V(4).Info("Ensure an internal loadbalancer service.") + internalAnnotation = true + case "false": + if len(floatingPool) != 0 { + klog.V(4).Infof("Ensure an external loadbalancer service, using floatingPool: %v", floatingPool) + internalAnnotation = false + } else { + return nil, fmt.Errorf("floating-network-id or loadbalancer.openstack.org/floating-network-id should be specified when ensuring an external loadbalancer service") + } + default: + return nil, fmt.Errorf("unknown service.beta.kubernetes.io/openstack-internal-load-balancer annotation: %v, specify \"true\" or \"false\" ", + internal) + } + + // Check for TCP protocol on each port + // TODO: Convert all error messages to use an event recorder + for _, port := range ports { + if port.Protocol != v1.ProtocolTCP { + return nil, fmt.Errorf("only TCP LoadBalancer is supported for openstack load balancers") + } + } + + sourceRanges, err := servicehelpers.GetLoadBalancerSourceRanges(apiService) + if err != nil { + return nil, fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + + if !servicehelpers.IsAllowAll(sourceRanges) && !lbaas.opts.ManageSecurityGroups { + return nil, fmt.Errorf("source range restrictions are not supported for openstack load balancers without managing security groups") + } + + affinity := apiService.Spec.SessionAffinity + var persistence *v2pools.SessionPersistence + switch affinity { + case v1.ServiceAffinityNone: + persistence = nil + case v1.ServiceAffinityClientIP: + persistence = &v2pools.SessionPersistence{Type: "SOURCE_IP"} + default: + return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity) + } + + name := lbaas.GetLoadBalancerName(ctx, clusterName, apiService) + loadbalancer, err := getLoadbalancerByName(lbaas.lb, name) + if err != nil { + if err != ErrNotFound { + return nil, fmt.Errorf("error getting loadbalancer %s: %v", name, err) + } + klog.V(2).Infof("Creating loadbalancer %s", name) + loadbalancer, err = lbaas.createLoadBalancer(apiService, name, internalAnnotation) + if err != nil { + // Unknown error, retry later + return nil, fmt.Errorf("error creating loadbalancer %s: %v", name, err) + } + } else { + klog.V(2).Infof("LoadBalancer %s already exists", name) + } + + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + + lbmethod := v2pools.LBMethod(lbaas.opts.LBMethod) + if lbmethod == "" { + lbmethod = v2pools.LBMethodRoundRobin + } + + oldListeners, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("error getting LB %s listeners: %v", name, err) + } + for portIndex, port := range ports { + listener := getListenerForPort(oldListeners, port) + if listener == nil { + klog.V(4).Infof("Creating listener for port %d", int(port.Port)) + listener, err = listeners.Create(lbaas.lb, listeners.CreateOpts{ + Name: fmt.Sprintf("listener_%s_%d", name, portIndex), + Protocol: listeners.Protocol(port.Protocol), + ProtocolPort: int(port.Port), + LoadbalancerID: loadbalancer.ID, + }).Extract() + if err != nil { + // Unknown error, retry later + return nil, fmt.Errorf("error creating LB listener: %v", err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + klog.V(4).Infof("Listener for %s port %d: %s", string(port.Protocol), int(port.Port), listener.ID) + + // After all ports have been processed, remaining listeners are removed as obsolete. + // Pop valid listeners. + oldListeners = popListener(oldListeners, listener.ID) + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) + if err != nil && err != ErrNotFound { + // Unknown error, retry later + return nil, fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err) + } + if pool == nil { + klog.V(4).Infof("Creating pool for listener %s", listener.ID) + pool, err = v2pools.Create(lbaas.lb, v2pools.CreateOpts{ + Name: fmt.Sprintf("pool_%s_%d", name, portIndex), + Protocol: v2pools.Protocol(port.Protocol), + LBMethod: lbmethod, + ListenerID: listener.ID, + Persistence: persistence, + }).Extract() + if err != nil { + // Unknown error, retry later + return nil, fmt.Errorf("error creating pool for listener %s: %v", listener.ID, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + + } + + klog.V(4).Infof("Pool for listener %s: %s", listener.ID, pool.ID) + members, err := getMembersByPoolID(lbaas.lb, pool.ID) + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error getting pool members %s: %v", pool.ID, err) + } + for _, node := range nodes { + addr, err := nodeAddressForLB(node) + if err != nil { + if err == ErrNotFound { + // Node failure, do not create member + klog.Warningf("Failed to create LB pool member for node %s: %v", node.Name, err) + continue + } else { + return nil, fmt.Errorf("error getting address for node %s: %v", node.Name, err) + } + } + + if !memberExists(members, addr, int(port.NodePort)) { + klog.V(4).Infof("Creating member for pool %s", pool.ID) + _, err := v2pools.CreateMember(lbaas.lb, pool.ID, v2pools.CreateMemberOpts{ + Name: fmt.Sprintf("member_%s_%d_%s", name, portIndex, node.Name), + ProtocolPort: int(port.NodePort), + Address: addr, + SubnetID: lbaas.opts.SubnetID, + }).Extract() + if err != nil { + return nil, fmt.Errorf("error creating LB pool member for node: %s, %v", node.Name, err) + } + + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } else { + // After all members have been processed, remaining members are deleted as obsolete. + members = popMember(members, addr, int(port.NodePort)) + } + + klog.V(4).Infof("Ensured pool %s has member for %s at %s", pool.ID, node.Name, addr) + } + + // Delete obsolete members for this pool + for _, member := range members { + klog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID, pool.ID, member.Address) + err := v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + monitorID := pool.MonitorID + if monitorID == "" && lbaas.opts.CreateMonitor { + klog.V(4).Infof("Creating monitor for pool %s", pool.ID) + monitor, err := v2monitors.Create(lbaas.lb, v2monitors.CreateOpts{ + Name: fmt.Sprintf("monitor_%s_%d", name, portIndex), + PoolID: pool.ID, + Type: string(port.Protocol), + Delay: int(lbaas.opts.MonitorDelay.Duration.Seconds()), + Timeout: int(lbaas.opts.MonitorTimeout.Duration.Seconds()), + MaxRetries: int(lbaas.opts.MonitorMaxRetries), + }).Extract() + if err != nil { + return nil, fmt.Errorf("error creating LB pool healthmonitor: %v", err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + monitorID = monitor.ID + } else if !lbaas.opts.CreateMonitor { + klog.V(4).Infof("Do not create monitor for pool %s when create-monitor is false", pool.ID) + } + + if monitorID != "" { + klog.V(4).Infof("Monitor for pool %s: %s", pool.ID, monitorID) + } + } + + // All remaining listeners are obsolete, delete + for _, listener := range oldListeners { + klog.V(4).Infof("Deleting obsolete listener %s:", listener.ID) + // get pool for listener + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) + if err != nil && err != ErrNotFound { + return nil, fmt.Errorf("error getting pool for obsolete listener %s: %v", listener.ID, err) + } + if pool != nil { + // get and delete monitor + monitorID := pool.MonitorID + if monitorID != "" { + klog.V(4).Infof("Deleting obsolete monitor %s for pool %s", monitorID, pool.ID) + err = v2monitors.Delete(lbaas.lb, monitorID).ExtractErr() + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error deleting obsolete monitor %s for pool %s: %v", monitorID, pool.ID, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + // get and delete pool members + members, err := getMembersByPoolID(lbaas.lb, pool.ID) + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error getting members for pool %s: %v", pool.ID, err) + } + for _, member := range members { + klog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID, pool.ID, member.Address) + err := v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + klog.V(4).Infof("Deleting obsolete pool %s for listener %s", pool.ID, listener.ID) + // delete pool + err = v2pools.Delete(lbaas.lb, pool.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error deleting obsolete pool %s for listener %s: %v", pool.ID, listener.ID, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + // delete listener + err = listeners.Delete(lbaas.lb, listener.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return nil, fmt.Errorf("error deleteting obsolete listener: %v", err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + klog.V(2).Infof("Deleted obsolete listener: %s", listener.ID) + } + + portID := loadbalancer.VipPortID + floatIP, err := getFloatingIPByPortID(lbaas.network, portID) + if err != nil && err != ErrNotFound { + return nil, fmt.Errorf("error getting floating ip for port %s: %v", portID, err) + } + if floatIP == nil && floatingPool != "" && !internalAnnotation { + klog.V(4).Infof("Creating floating ip for loadbalancer %s port %s", loadbalancer.ID, portID) + floatIPOpts := floatingips.CreateOpts{ + FloatingNetworkID: floatingPool, + PortID: portID, + } + + loadBalancerIP := apiService.Spec.LoadBalancerIP + if loadBalancerIP != "" { + floatIPOpts.FloatingIP = loadBalancerIP + } + + floatIP, err = floatingips.Create(lbaas.network, floatIPOpts).Extract() + if err != nil { + return nil, fmt.Errorf("error creating LB floatingip %+v: %v", floatIPOpts, err) + } + } + + status := &v1.LoadBalancerStatus{} + + if floatIP != nil { + status.Ingress = []v1.LoadBalancerIngress{{IP: floatIP.FloatingIP}} + } else { + status.Ingress = []v1.LoadBalancerIngress{{IP: loadbalancer.VipAddress}} + } + + if lbaas.opts.ManageSecurityGroups { + err := lbaas.ensureSecurityGroup(clusterName, apiService, nodes, loadbalancer) + if err != nil { + return status, fmt.Errorf("Error reconciling security groups for LB service %v/%v: %v", apiService.Namespace, apiService.Name, err) + } + } + + return status, nil +} + +// ensureSecurityGroup ensures security group exist for specific loadbalancer service. +// Creating security group for specific loadbalancer service when it does not exist. +func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Service, nodes []*v1.Node, loadbalancer *loadbalancers.LoadBalancer) error { + // find node-security-group for service + var err error + if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { + lbaas.opts.NodeSecurityGroupIDs, err = getNodeSecurityGroupIDForLB(lbaas.compute, lbaas.network, nodes) + if err != nil { + return fmt.Errorf("failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + } + klog.V(4).Infof("find node-security-group %v for loadbalancer service %s/%s", lbaas.opts.NodeSecurityGroupIDs, apiService.Namespace, apiService.Name) + + // get service ports + ports := apiService.Spec.Ports + if len(ports) == 0 { + return fmt.Errorf("no ports provided to openstack load balancer") + } + + // get service source ranges + sourceRanges, err := servicehelpers.GetLoadBalancerSourceRanges(apiService) + if err != nil { + return fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + + // ensure security group for LB + lbSecGroupName := getSecurityGroupName(apiService) + lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + if err != nil { + // If the security group of LB not exist, create it later + if isSecurityGroupNotFound(err) { + lbSecGroupID = "" + } else { + return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) + } + } + if len(lbSecGroupID) == 0 { + // create security group + lbSecGroupCreateOpts := groups.CreateOpts{ + Name: getSecurityGroupName(apiService), + Description: fmt.Sprintf("Security Group for %s/%s Service LoadBalancer in cluster %s", apiService.Namespace, apiService.Name, clusterName), + } + + lbSecGroup, err := groups.Create(lbaas.network, lbSecGroupCreateOpts).Extract() + if err != nil { + return fmt.Errorf("failed to create Security Group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + lbSecGroupID = lbSecGroup.ID + + //add rule in security group + for _, port := range ports { + for _, sourceRange := range sourceRanges.StringSlice() { + ethertype := rules.EtherType4 + network, _, err := netutils.ParseCIDRSloppy(sourceRange) + + if err != nil { + return fmt.Errorf("error parsing source range %s as a CIDR: %v", sourceRange, err) + } + + if network.To4() == nil { + ethertype = rules.EtherType6 + } + + lbSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: rules.DirIngress, + PortRangeMax: int(port.Port), + PortRangeMin: int(port.Port), + Protocol: toRuleProtocol(port.Protocol), + RemoteIPPrefix: sourceRange, + SecGroupID: lbSecGroup.ID, + EtherType: ethertype, + } + + _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() + + if err != nil { + return fmt.Errorf("error occurred creating rule for SecGroup %s: %v", lbSecGroup.ID, err) + } + } + } + + lbSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: rules.DirIngress, + PortRangeMax: 4, // ICMP: Code - Values for ICMP "Destination Unreachable: Fragmentation Needed and Don't Fragment was Set" + PortRangeMin: 3, // ICMP: Type + Protocol: rules.ProtocolICMP, + RemoteIPPrefix: "0.0.0.0/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all + SecGroupID: lbSecGroup.ID, + EtherType: rules.EtherType4, + } + + _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() + + if err != nil { + return fmt.Errorf("error occurred creating rule for SecGroup %s: %v", lbSecGroup.ID, err) + } + + lbSecGroupRuleCreateOpts = rules.CreateOpts{ + Direction: rules.DirIngress, + PortRangeMax: 0, // ICMP: Code - Values for ICMP "Packet Too Big" + PortRangeMin: 2, // ICMP: Type + Protocol: rules.ProtocolICMP, + RemoteIPPrefix: "::/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all + SecGroupID: lbSecGroup.ID, + EtherType: rules.EtherType6, + } + + _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() + if err != nil { + return fmt.Errorf("error occurred creating rule for SecGroup %s: %v", lbSecGroup.ID, err) + } + + // get security groups of port + portID := loadbalancer.VipPortID + port, err := getPortByID(lbaas.network, portID) + if err != nil { + return err + } + + // ensure the vip port has the security groups + found := false + for _, portSecurityGroups := range port.SecurityGroups { + if portSecurityGroups == lbSecGroup.ID { + found = true + break + } + } + + // update loadbalancer vip port + if !found { + port.SecurityGroups = append(port.SecurityGroups, lbSecGroup.ID) + updateOpts := neutronports.UpdateOpts{SecurityGroups: &port.SecurityGroups} + res := neutronports.Update(lbaas.network, portID, updateOpts) + if res.Err != nil { + msg := fmt.Sprintf("Error occurred updating port %s for loadbalancer service %s/%s: %v", portID, apiService.Namespace, apiService.Name, res.Err) + return fmt.Errorf(msg) + } + } + } + + // ensure rules for every node security group + for _, port := range ports { + for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { + opts := rules.ListOpts{ + Direction: string(rules.DirIngress), + SecGroupID: nodeSecurityGroupID, + RemoteGroupID: lbSecGroupID, + PortRangeMax: int(port.NodePort), + PortRangeMin: int(port.NodePort), + Protocol: string(port.Protocol), + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + if err != nil && !isNotFound(err) { + msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) + return fmt.Errorf(msg) + } + if len(secGroupRules) != 0 { + // Do not add rule when find rules for remote group in the Node Security Group + continue + } + + // Add the rules in the Node Security Group + err = createNodeSecurityGroup(lbaas.network, nodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroupID) + if err != nil { + return fmt.Errorf("error occurred creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + } + } + + return nil +} + +// UpdateLoadBalancer updates hosts under the specified load balancer. +func (lbaas *LbaasV2) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error { + loadBalancerName := lbaas.GetLoadBalancerName(ctx, clusterName, service) + klog.V(4).Infof("UpdateLoadBalancer(%v, %v, %v)", clusterName, loadBalancerName, nodes) + + lbaas.opts.SubnetID = getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerSubnetID, lbaas.opts.SubnetID) + if len(lbaas.opts.SubnetID) == 0 && len(nodes) > 0 { + // Get SubnetID automatically. + // The LB needs to be configured with instance addresses on the same subnet, so get SubnetID by one node. + subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0]) + if err != nil { + klog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) + return fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ + "and failed to find subnet-id from OpenStack: %v", service.Namespace, service.Name, err) + } + lbaas.opts.SubnetID = subnetID + } + + ports := service.Spec.Ports + if len(ports) == 0 { + return fmt.Errorf("no ports provided to openstack load balancer") + } + + loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) + if err != nil { + return err + } + if loadbalancer == nil { + return fmt.Errorf("loadbalancer %s does not exist", loadBalancerName) + } + + // Get all listeners for this loadbalancer, by "port key". + type portKey struct { + Protocol listeners.Protocol + Port int + } + var listenerIDs []string + lbListeners := make(map[portKey]listeners.Listener) + allListeners, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("error getting listeners for LB %s: %v", loadBalancerName, err) + } + for _, l := range allListeners { + key := portKey{Protocol: listeners.Protocol(l.Protocol), Port: l.ProtocolPort} + lbListeners[key] = l + listenerIDs = append(listenerIDs, l.ID) + } + + // Get all pools for this loadbalancer, by listener ID. + lbPools := make(map[string]v2pools.Pool) + for _, listenerID := range listenerIDs { + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listenerID) + if err != nil { + return fmt.Errorf("error getting pool for listener %s: %v", listenerID, err) + } + lbPools[listenerID] = *pool + } + + // Compose Set of member (addresses) that _should_ exist + addrs := make(map[string]*v1.Node) + for _, node := range nodes { + addr, err := nodeAddressForLB(node) + if err != nil { + return err + } + addrs[addr] = node + } + + // Check for adding/removing members associated with each port + for portIndex, port := range ports { + // Get listener associated with this port + listener, ok := lbListeners[portKey{ + Protocol: toListenersProtocol(port.Protocol), + Port: int(port.Port), + }] + if !ok { + return fmt.Errorf("loadbalancer %s does not contain required listener for port %d and protocol %s", loadBalancerName, port.Port, port.Protocol) + } + + // Get pool associated with this listener + pool, ok := lbPools[listener.ID] + if !ok { + return fmt.Errorf("loadbalancer %s does not contain required pool for listener %s", loadBalancerName, listener.ID) + } + + // Find existing pool members (by address) for this port + getMembers, err := getMembersByPoolID(lbaas.lb, pool.ID) + if err != nil { + return fmt.Errorf("error getting pool members %s: %v", pool.ID, err) + } + members := make(map[string]v2pools.Member) + for _, member := range getMembers { + members[member.Address] = member + } + + // Add any new members for this port + for addr, node := range addrs { + if _, ok := members[addr]; ok && members[addr].ProtocolPort == int(port.NodePort) { + // Already exists, do not create member + continue + } + _, err := v2pools.CreateMember(lbaas.lb, pool.ID, v2pools.CreateMemberOpts{ + Name: fmt.Sprintf("member_%s_%d_%s", loadbalancer.Name, portIndex, node.Name), + Address: addr, + ProtocolPort: int(port.NodePort), + SubnetID: lbaas.opts.SubnetID, + }).Extract() + if err != nil { + return err + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + // Remove any old members for this port + for _, member := range members { + if _, ok := addrs[member.Address]; ok && member.ProtocolPort == int(port.NodePort) { + // Still present, do not delete member + continue + } + err = v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + } + + if lbaas.opts.ManageSecurityGroups { + err := lbaas.updateSecurityGroup(clusterName, service, nodes, loadbalancer) + if err != nil { + return fmt.Errorf("failed to update Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) + } + } + + return nil +} + +// updateSecurityGroup updating security group for specific loadbalancer service. +func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Service, nodes []*v1.Node, loadbalancer *loadbalancers.LoadBalancer) error { + originalNodeSecurityGroupIDs := lbaas.opts.NodeSecurityGroupIDs + + var err error + lbaas.opts.NodeSecurityGroupIDs, err = getNodeSecurityGroupIDForLB(lbaas.compute, lbaas.network, nodes) + if err != nil { + return fmt.Errorf("failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + klog.V(4).Infof("find node-security-group %v for loadbalancer service %s/%s", lbaas.opts.NodeSecurityGroupIDs, apiService.Namespace, apiService.Name) + + original := sets.NewString(originalNodeSecurityGroupIDs...) + current := sets.NewString(lbaas.opts.NodeSecurityGroupIDs...) + removals := original.Difference(current) + + // Generate Name + lbSecGroupName := getSecurityGroupName(apiService) + lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + if err != nil { + return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) + } + + ports := apiService.Spec.Ports + if len(ports) == 0 { + return fmt.Errorf("no ports provided to openstack load balancer") + } + + for _, port := range ports { + for removal := range removals { + // Delete the rules in the Node Security Group + opts := rules.ListOpts{ + Direction: string(rules.DirIngress), + SecGroupID: removal, + RemoteGroupID: lbSecGroupID, + PortRangeMax: int(port.NodePort), + PortRangeMin: int(port.NodePort), + Protocol: string(port.Protocol), + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + if err != nil && !isNotFound(err) { + return fmt.Errorf("error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, removal, err) + } + + for _, rule := range secGroupRules { + res := rules.Delete(lbaas.network, rule.ID) + if res.Err != nil && !isNotFound(res.Err) { + return fmt.Errorf("error occurred deleting security group rule: %s: %v", rule.ID, res.Err) + } + } + } + + for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { + opts := rules.ListOpts{ + Direction: string(rules.DirIngress), + SecGroupID: nodeSecurityGroupID, + RemoteGroupID: lbSecGroupID, + PortRangeMax: int(port.NodePort), + PortRangeMin: int(port.NodePort), + Protocol: string(port.Protocol), + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + if err != nil && !isNotFound(err) { + return fmt.Errorf("error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) + } + if len(secGroupRules) != 0 { + // Do not add rule when find rules for remote group in the Node Security Group + continue + } + + // Add the rules in the Node Security Group + err = createNodeSecurityGroup(lbaas.network, nodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroupID) + if err != nil { + return fmt.Errorf("error occurred creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + } + } + + return nil +} + +// EnsureLoadBalancerDeleted deletes the specified load balancer +func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error { + loadBalancerName := lbaas.GetLoadBalancerName(ctx, clusterName, service) + klog.V(4).Infof("EnsureLoadBalancerDeleted(%v, %v)", clusterName, loadBalancerName) + + loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) + if err != nil && err != ErrNotFound { + return err + } + if loadbalancer == nil { + return nil + } + + if loadbalancer.VipPortID != "" { + portID := loadbalancer.VipPortID + floatingIP, err := getFloatingIPByPortID(lbaas.network, portID) + if err != nil && err != ErrNotFound { + return err + } + if floatingIP != nil { + err = floatingips.Delete(lbaas.network, floatingIP.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + } + } + + // get all listeners associated with this loadbalancer + listenerList, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("error getting LB %s listeners: %v", loadbalancer.ID, err) + } + + // get all pools (and health monitors) associated with this loadbalancer + var poolIDs []string + var monitorIDs []string + for _, listener := range listenerList { + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) + if err != nil && err != ErrNotFound { + return fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err) + } + if pool != nil { + poolIDs = append(poolIDs, pool.ID) + // If create-monitor of cloud-config is false, pool has not monitor. + if pool.MonitorID != "" { + monitorIDs = append(monitorIDs, pool.MonitorID) + } + } + } + + // delete all monitors + for _, monitorID := range monitorIDs { + err := v2monitors.Delete(lbaas.lb, monitorID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + // delete all members and pools + for _, poolID := range poolIDs { + // get members for current pool + membersList, err := getMembersByPoolID(lbaas.lb, poolID) + if err != nil && !isNotFound(err) { + return fmt.Errorf("error getting pool members %s: %v", poolID, err) + } + // delete all members for this pool + for _, member := range membersList { + err := v2pools.DeleteMember(lbaas.lb, poolID, member.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + // delete pool + err = v2pools.Delete(lbaas.lb, poolID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + // delete all listeners + for _, listener := range listenerList { + err := listeners.Delete(lbaas.lb, listener.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } + + // delete loadbalancer + err = loadbalancers.Delete(lbaas.lb, loadbalancer.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + err = waitLoadbalancerDeleted(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to delete loadbalancer: %v", err) + } + + // Delete the Security Group + if lbaas.opts.ManageSecurityGroups { + err := lbaas.EnsureSecurityGroupDeleted(clusterName, service) + if err != nil { + return fmt.Errorf("failed to delete Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) + } + } + + return nil +} + +// EnsureSecurityGroupDeleted deleting security group for specific loadbalancer service. +func (lbaas *LbaasV2) EnsureSecurityGroupDeleted(clusterName string, service *v1.Service) error { + // Generate Name + lbSecGroupName := getSecurityGroupName(service) + lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + if err != nil { + if isSecurityGroupNotFound(err) { + // It is OK when the security group has been deleted by others. + return nil + } + return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) + } + + lbSecGroup := groups.Delete(lbaas.network, lbSecGroupID) + if lbSecGroup.Err != nil && !isNotFound(lbSecGroup.Err) { + return lbSecGroup.Err + } + + if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { + // Just happen when nodes have not Security Group, or should not happen + // UpdateLoadBalancer and EnsureLoadBalancer can set lbaas.opts.NodeSecurityGroupIDs when it is empty + // And service controller call UpdateLoadBalancer to set lbaas.opts.NodeSecurityGroupIDs when controller manager service is restarted. + klog.Warningf("Can not find node-security-group from all the nodes of this cluster when delete loadbalancer service %s/%s", + service.Namespace, service.Name) + } else { + // Delete the rules in the Node Security Group + for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { + opts := rules.ListOpts{ + SecGroupID: nodeSecurityGroupID, + RemoteGroupID: lbSecGroupID, + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + + if err != nil && !isNotFound(err) { + msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) + return fmt.Errorf(msg) + } + + for _, rule := range secGroupRules { + res := rules.Delete(lbaas.network, rule.ID) + if res.Err != nil && !isNotFound(res.Err) { + return fmt.Errorf("error occurred deleting security group rule: %s: %v", rule.ID, res.Err) + } + } + } + } + + return nil +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go new file mode 100644 index 00000000000..8d84a7758b6 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go @@ -0,0 +1,64 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "sync" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const ( + openstackSubsystem = "openstack" + openstackOperationKey = "cloudprovider_openstack_api_request_duration_seconds" + openstackOperationErrorKey = "cloudprovider_openstack_api_request_errors" +) + +var ( + openstackOperationsLatency = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Subsystem: openstackSubsystem, + Name: openstackOperationKey, + Help: "Latency of openstack api call", + StabilityLevel: metrics.ALPHA, + }, + []string{"request"}, + ) + + openstackAPIRequestErrors = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: openstackSubsystem, + Name: openstackOperationErrorKey, + Help: "Cumulative number of openstack Api call errors", + StabilityLevel: metrics.ALPHA, + }, + []string{"request"}, + ) +) + +var registerOnce sync.Once + +func registerMetrics() { + registerOnce.Do(func() { + legacyregistry.MustRegister(openstackOperationsLatency) + legacyregistry.MustRegister(openstackAPIRequestErrors) + }) +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go new file mode 100644 index 00000000000..03163fcf048 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go @@ -0,0 +1,347 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "errors" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + + "k8s.io/apimachinery/pkg/types" + cloudprovider "k8s.io/cloud-provider" + "k8s.io/klog/v2" + netutils "k8s.io/utils/net" +) + +var errNoRouterID = errors.New("router-id not set in cloud provider config") + +var _ cloudprovider.Routes = (*Routes)(nil) + +// Routes implements the cloudprovider.Routes for OpenStack clouds +type Routes struct { + compute *gophercloud.ServiceClient + network *gophercloud.ServiceClient + opts RouterOpts +} + +// NewRoutes creates a new instance of Routes +func NewRoutes(compute *gophercloud.ServiceClient, network *gophercloud.ServiceClient, opts RouterOpts) (cloudprovider.Routes, error) { + if opts.RouterID == "" { + return nil, errNoRouterID + } + + return &Routes{ + compute: compute, + network: network, + opts: opts, + }, nil +} + +// ListRoutes lists all managed routes that belong to the specified clusterName +func (r *Routes) ListRoutes(ctx context.Context, clusterName string) ([]*cloudprovider.Route, error) { + klog.V(4).Infof("ListRoutes(%v)", clusterName) + + nodeNamesByAddr := make(map[string]types.NodeName) + err := foreachServer(r.compute, servers.ListOpts{}, func(srv *servers.Server) (bool, error) { + addrs, err := nodeAddresses(srv) + if err != nil { + return false, err + } + + name := mapServerToNodeName(srv) + for _, addr := range addrs { + nodeNamesByAddr[addr.Address] = name + } + + return true, nil + }) + if err != nil { + return nil, err + } + + router, err := routers.Get(r.network, r.opts.RouterID).Extract() + if err != nil { + return nil, err + } + + var routes []*cloudprovider.Route + for _, item := range router.Routes { + nodeName, foundNode := nodeNamesByAddr[item.NextHop] + if !foundNode { + nodeName = types.NodeName(item.NextHop) + } + route := cloudprovider.Route{ + Name: item.DestinationCIDR, + TargetNode: nodeName, //contains the nexthop address if node was not found + Blackhole: !foundNode, + DestinationCIDR: item.DestinationCIDR, + } + routes = append(routes, &route) + } + + return routes, nil +} + +func updateRoutes(network *gophercloud.ServiceClient, router *routers.Router, newRoutes []routers.Route) (func(), error) { + origRoutes := router.Routes // shallow copy + + _, err := routers.Update(network, router.ID, routers.UpdateOpts{ + Routes: newRoutes, + }).Extract() + if err != nil { + return nil, err + } + + unwinder := func() { + klog.V(4).Infof("Reverting routes change to router %v", router.ID) + _, err := routers.Update(network, router.ID, routers.UpdateOpts{ + Routes: origRoutes, + }).Extract() + if err != nil { + klog.Warningf("Unable to reset routes during error unwind: %v", err) + } + } + + return unwinder, nil +} + +func updateAllowedAddressPairs(network *gophercloud.ServiceClient, port *neutronports.Port, newPairs []neutronports.AddressPair) (func(), error) { + origPairs := port.AllowedAddressPairs // shallow copy + + _, err := neutronports.Update(network, port.ID, neutronports.UpdateOpts{ + AllowedAddressPairs: &newPairs, + }).Extract() + if err != nil { + return nil, err + } + + unwinder := func() { + klog.V(4).Infof("Reverting allowed-address-pairs change to port %v", port.ID) + _, err := neutronports.Update(network, port.ID, neutronports.UpdateOpts{ + AllowedAddressPairs: &origPairs, + }).Extract() + if err != nil { + klog.Warningf("Unable to reset allowed-address-pairs during error unwind: %v", err) + } + } + + return unwinder, nil +} + +// CreateRoute creates the described managed route +func (r *Routes) CreateRoute(ctx context.Context, clusterName string, nameHint string, route *cloudprovider.Route) error { + klog.V(4).Infof("CreateRoute(%v, %v, %v)", clusterName, nameHint, route) + + onFailure := newCaller() + + ip, _, _ := netutils.ParseCIDRSloppy(route.DestinationCIDR) + isCIDRv6 := ip.To4() == nil + addr, err := getAddressByName(r.compute, route.TargetNode, isCIDRv6) + + if err != nil { + return err + } + + klog.V(4).Infof("Using nexthop %v for node %v", addr, route.TargetNode) + + router, err := routers.Get(r.network, r.opts.RouterID).Extract() + if err != nil { + return err + } + + routes := router.Routes + + for _, item := range routes { + if item.DestinationCIDR == route.DestinationCIDR && item.NextHop == addr { + klog.V(4).Infof("Skipping existing route: %v", route) + return nil + } + } + + routes = append(routes, routers.Route{ + DestinationCIDR: route.DestinationCIDR, + NextHop: addr, + }) + + unwind, err := updateRoutes(r.network, router, routes) + if err != nil { + return err + } + defer onFailure.call(unwind) + + // get the port of addr on target node. + portID, err := getPortIDByIP(r.compute, route.TargetNode, addr) + if err != nil { + return err + } + port, err := getPortByID(r.network, portID) + if err != nil { + return err + } + + found := false + for _, item := range port.AllowedAddressPairs { + if item.IPAddress == route.DestinationCIDR { + klog.V(4).Infof("Found existing allowed-address-pair: %v", item) + found = true + break + } + } + + if !found { + newPairs := append(port.AllowedAddressPairs, neutronports.AddressPair{ + IPAddress: route.DestinationCIDR, + }) + unwind, err := updateAllowedAddressPairs(r.network, port, newPairs) + if err != nil { + return err + } + defer onFailure.call(unwind) + } + + klog.V(4).Infof("Route created: %v", route) + onFailure.disarm() + return nil +} + +// DeleteRoute deletes the specified managed route +func (r *Routes) DeleteRoute(ctx context.Context, clusterName string, route *cloudprovider.Route) error { + klog.V(4).Infof("DeleteRoute(%v, %v)", clusterName, route) + + onFailure := newCaller() + + ip, _, _ := netutils.ParseCIDRSloppy(route.DestinationCIDR) + isCIDRv6 := ip.To4() == nil + + var addr string + + // Blackhole routes are orphaned and have no counterpart in OpenStack + if !route.Blackhole { + var err error + addr, err = getAddressByName(r.compute, route.TargetNode, isCIDRv6) + if err != nil { + return err + } + } + + router, err := routers.Get(r.network, r.opts.RouterID).Extract() + if err != nil { + return err + } + + routes := router.Routes + index := -1 + for i, item := range routes { + if item.DestinationCIDR == route.DestinationCIDR && (item.NextHop == addr || route.Blackhole && item.NextHop == string(route.TargetNode)) { + index = i + break + } + } + + if index == -1 { + klog.V(4).Infof("Skipping non-existent route: %v", route) + return nil + } + + // Delete element `index` + routes[index] = routes[len(routes)-1] + routes = routes[:len(routes)-1] + + unwind, err := updateRoutes(r.network, router, routes) + // If this was a blackhole route we are done, there are no ports to update + if err != nil || route.Blackhole { + return err + } + defer onFailure.call(unwind) + + // get the port of addr on target node. + portID, err := getPortIDByIP(r.compute, route.TargetNode, addr) + if err != nil { + return err + } + port, err := getPortByID(r.network, portID) + if err != nil { + return err + } + + addrPairs := port.AllowedAddressPairs + index = -1 + for i, item := range addrPairs { + if item.IPAddress == route.DestinationCIDR { + index = i + break + } + } + + if index != -1 { + // Delete element `index` + addrPairs[index] = addrPairs[len(addrPairs)-1] + addrPairs = addrPairs[:len(addrPairs)-1] + + unwind, err := updateAllowedAddressPairs(r.network, port, addrPairs) + if err != nil { + return err + } + defer onFailure.call(unwind) + } + + klog.V(4).Infof("Route deleted: %v", route) + onFailure.disarm() + return nil +} + +func getPortIDByIP(compute *gophercloud.ServiceClient, targetNode types.NodeName, ipAddress string) (string, error) { + srv, err := getServerByName(compute, targetNode) + if err != nil { + return "", err + } + + interfaces, err := getAttachedInterfacesByID(compute, srv.ID) + if err != nil { + return "", err + } + + for _, intf := range interfaces { + for _, fixedIP := range intf.FixedIPs { + if fixedIP.IPAddress == ipAddress { + return intf.PortID, nil + } + } + } + + return "", ErrNotFound +} + +func getPortByID(client *gophercloud.ServiceClient, portID string) (*neutronports.Port, error) { + targetPort, err := neutronports.Get(client, portID).Extract() + if err != nil { + return nil, err + } + + if targetPort == nil { + return nil, ErrNotFound + } + + return targetPort, nil +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go new file mode 100644 index 00000000000..56752b5b6cf --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go @@ -0,0 +1,128 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + "k8s.io/apimachinery/pkg/types" + cloudprovider "k8s.io/cloud-provider" + netutils "k8s.io/utils/net" +) + +func TestRoutes(t *testing.T) { + const clusterName = "ignored" + + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + os, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } + + vms := getServers(os) + _, err = os.InstanceID() + if err != nil || len(vms) == 0 { + t.Skipf("Please run this test in an OpenStack vm or create at least one VM in OpenStack before you run this test.") + } + + // We know we have at least one vm. + servername := vms[0].Name + + // Pick the first router and server to try a test with + os.routeOpts.RouterID = getRouters(os)[0].ID + + r, ok := os.Routes() + if !ok { + t.Skip("Routes() returned false - perhaps your stack does not support Neutron extraroute extension?") + } + + newroute := cloudprovider.Route{ + DestinationCIDR: "10.164.2.0/24", + TargetNode: types.NodeName(servername), + } + err = r.CreateRoute(context.TODO(), clusterName, "myhint", &newroute) + if err != nil { + t.Fatalf("CreateRoute error: %v", err) + } + + routelist, err := r.ListRoutes(context.TODO(), clusterName) + if err != nil { + t.Fatalf("ListRoutes() error: %v", err) + } + for _, route := range routelist { + _, cidr, err := netutils.ParseCIDRSloppy(route.DestinationCIDR) + if err != nil { + t.Logf("Ignoring route %s, unparsable CIDR: %v", route.Name, err) + continue + } + t.Logf("%s via %s", cidr, route.TargetNode) + } + + err = r.DeleteRoute(context.TODO(), clusterName, &newroute) + if err != nil { + t.Fatalf("DeleteRoute error: %v", err) + } +} + +func getServers(os *OpenStack) []servers.Server { + c, err := os.NewComputeV2() + if err != nil { + panic(err) + } + allPages, err := servers.List(c, servers.ListOpts{}).AllPages() + if err != nil { + panic(err) + } + allServers, err := servers.ExtractServers(allPages) + if err != nil { + panic(err) + } + if len(allServers) == 0 { + panic("No servers to test with") + } + return allServers +} + +func getRouters(os *OpenStack) []routers.Router { + listOpts := routers.ListOpts{} + n, err := os.NewNetworkV2() + if err != nil { + panic(err) + } + allPages, err := routers.List(n, listOpts).AllPages() + if err != nil { + panic(err) + } + allRouters, err := routers.ExtractRouters(allPages) + if err != nil { + panic(err) + } + if len(allRouters) == 0 { + panic("No routers to test with") + } + return allRouters +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go new file mode 100644 index 00000000000..40fdf5bc2d9 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go @@ -0,0 +1,733 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2014 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "fmt" + "os" + "reflect" + "regexp" + "sort" + "strings" + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + v1 "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/apimachinery/pkg/util/wait" +) + +const ( + testClusterName = "testCluster" + + volumeStatusTimeoutSeconds = 30 + // volumeStatus* is configuration of exponential backoff for + // waiting for specified volume status. Starting with 1 + // seconds, multiplying by 1.2 with each step and taking 13 steps at maximum + // it will time out after 32s, which roughly corresponds to 30s + volumeStatusInitDelay = 1 * time.Second + volumeStatusFactor = 1.2 + volumeStatusSteps = 13 +) + +func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string) { + backoff := wait.Backoff{ + Duration: volumeStatusInitDelay, + Factor: volumeStatusFactor, + Steps: volumeStatusSteps, + } + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + getVol, err := os.getVolume(volumeName) + if err != nil { + return false, err + } + if getVol.Status == status { + t.Logf("Volume (%s) status changed to %s after %v seconds\n", + volumeName, + status, + volumeStatusTimeoutSeconds) + return true, nil + } + return false, nil + }) + if err == wait.ErrWaitTimeout { + t.Logf("Volume (%s) status did not change to %s after %v seconds\n", + volumeName, + status, + volumeStatusTimeoutSeconds) + return + } + if err != nil { + t.Fatalf("Cannot get existing Cinder volume (%s): %v", volumeName, err) + } +} + +func TestReadConfig(t *testing.T) { + _, err := readConfig(nil) + if err == nil { + t.Errorf("Should fail when no config is provided: %s", err) + } + + // Since we are setting env vars, we need to reset old + // values for other tests to succeed. + env := clearEnviron(t) + defer resetEnviron(t, env) + + os.Setenv("OS_PASSWORD", "mypass") // Fake value for testing. + defer os.Unsetenv("OS_PASSWORD") + + os.Setenv("OS_TENANT_NAME", "admin") + defer os.Unsetenv("OS_TENANT_NAME") + + cfg, err := readConfig(strings.NewReader(` + [Global] + auth-url = http://auth.url + user-id = user + tenant-name = demo + region = RegionOne + [LoadBalancer] + create-monitor = yes + monitor-delay = 1m + monitor-timeout = 30s + monitor-max-retries = 3 + [BlockStorage] + bs-version = auto + trust-device-path = yes + ignore-volume-az = yes + [Metadata] + search-order = configDrive, metadataService + `)) + cfg.Global.Password = os.Getenv("OS_PASSWORD") + + if err != nil { + t.Fatalf("Should succeed when a valid config is provided: %s", err) + } + if cfg.Global.AuthURL != "http://auth.url" { + t.Errorf("incorrect authurl: %s", cfg.Global.AuthURL) + } + + if cfg.Global.UserID != "user" { + t.Errorf("incorrect userid: %s", cfg.Global.UserID) + } + + if cfg.Global.Password != "mypass" { + t.Errorf("incorrect password: %s", cfg.Global.Password) + } + + // config file wins over environment variable + if cfg.Global.TenantName != "demo" { + t.Errorf("incorrect tenant name: %s", cfg.Global.TenantName) + } + + if cfg.Global.Region != "RegionOne" { + t.Errorf("incorrect region: %s", cfg.Global.Region) + } + + if !cfg.LoadBalancer.CreateMonitor { + t.Errorf("incorrect lb.createmonitor: %t", cfg.LoadBalancer.CreateMonitor) + } + if cfg.LoadBalancer.MonitorDelay.Duration != 1*time.Minute { + t.Errorf("incorrect lb.monitordelay: %s", cfg.LoadBalancer.MonitorDelay) + } + if cfg.LoadBalancer.MonitorTimeout.Duration != 30*time.Second { + t.Errorf("incorrect lb.monitortimeout: %s", cfg.LoadBalancer.MonitorTimeout) + } + if cfg.LoadBalancer.MonitorMaxRetries != 3 { + t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries) + } + if cfg.BlockStorage.TrustDevicePath != true { + t.Errorf("incorrect bs.trustdevicepath: %v", cfg.BlockStorage.TrustDevicePath) + } + if cfg.BlockStorage.BSVersion != "auto" { + t.Errorf("incorrect bs.bs-version: %v", cfg.BlockStorage.BSVersion) + } + if cfg.BlockStorage.IgnoreVolumeAZ != true { + t.Errorf("incorrect bs.IgnoreVolumeAZ: %v", cfg.BlockStorage.IgnoreVolumeAZ) + } + if cfg.Metadata.SearchOrder != "configDrive, metadataService" { + t.Errorf("incorrect md.search-order: %v", cfg.Metadata.SearchOrder) + } +} + +func TestToAuthOptions(t *testing.T) { + cfg := Config{} + cfg.Global.Username = "user" + cfg.Global.Password = "pass" // Fake value for testing. + cfg.Global.DomainID = "2a73b8f597c04551a0fdc8e95544be8a" + cfg.Global.DomainName = "local" + cfg.Global.AuthURL = "http://auth.url" + cfg.Global.UserID = "user" + + ao := cfg.toAuthOptions() + + if !ao.AllowReauth { + t.Errorf("Will need to be able to reauthenticate") + } + if ao.Username != cfg.Global.Username { + t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username) + } + if ao.Password != cfg.Global.Password { + t.Errorf("Password %s != %s", ao.Password, cfg.Global.Password) + } + if ao.DomainID != cfg.Global.DomainID { + t.Errorf("DomainID %s != %s", ao.DomainID, cfg.Global.DomainID) + } + if ao.IdentityEndpoint != cfg.Global.AuthURL { + t.Errorf("IdentityEndpoint %s != %s", ao.IdentityEndpoint, cfg.Global.AuthURL) + } + if ao.UserID != cfg.Global.UserID { + t.Errorf("UserID %s != %s", ao.UserID, cfg.Global.UserID) + } + if ao.DomainName != cfg.Global.DomainName { + t.Errorf("DomainName %s != %s", ao.DomainName, cfg.Global.DomainName) + } + if ao.TenantID != cfg.Global.TenantID { + t.Errorf("TenantID %s != %s", ao.TenantID, cfg.Global.TenantID) + } +} + +func TestCheckOpenStackOpts(t *testing.T) { + delay := MyDuration{60 * time.Second} + timeout := MyDuration{30 * time.Second} + tests := []struct { + name string + openstackOpts *OpenStack + expectedError error + }{ + { + name: "test1", + openstackOpts: &OpenStack{ + provider: nil, + lbOpts: LoadBalancerOpts{ + LBVersion: "v2", + SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", + FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", + LBMethod: "ROUND_ROBIN", + LBProvider: "haproxy", + CreateMonitor: true, + MonitorDelay: delay, + MonitorTimeout: timeout, + MonitorMaxRetries: uint(3), + ManageSecurityGroups: true, + }, + metadataOpts: MetadataOpts{ + SearchOrder: configDriveID, + }, + }, + expectedError: nil, + }, + { + name: "test2", + openstackOpts: &OpenStack{ + provider: nil, + lbOpts: LoadBalancerOpts{ + LBVersion: "v2", + FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", + LBMethod: "ROUND_ROBIN", + CreateMonitor: true, + MonitorDelay: delay, + MonitorTimeout: timeout, + MonitorMaxRetries: uint(3), + ManageSecurityGroups: true, + }, + metadataOpts: MetadataOpts{ + SearchOrder: configDriveID, + }, + }, + expectedError: nil, + }, + { + name: "test3", + openstackOpts: &OpenStack{ + provider: nil, + lbOpts: LoadBalancerOpts{ + LBVersion: "v2", + SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", + FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", + LBMethod: "ROUND_ROBIN", + CreateMonitor: true, + MonitorTimeout: timeout, + MonitorMaxRetries: uint(3), + ManageSecurityGroups: true, + }, + metadataOpts: MetadataOpts{ + SearchOrder: configDriveID, + }, + }, + expectedError: fmt.Errorf("monitor-delay not set in cloud provider config"), + }, + { + name: "test4", + openstackOpts: &OpenStack{ + provider: nil, + metadataOpts: MetadataOpts{ + SearchOrder: "", + }, + }, + expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot be empty"), + }, + { + name: "test5", + openstackOpts: &OpenStack{ + provider: nil, + metadataOpts: MetadataOpts{ + SearchOrder: "value1,value2,value3", + }, + }, + expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements"), + }, + { + name: "test6", + openstackOpts: &OpenStack{ + provider: nil, + metadataOpts: MetadataOpts{ + SearchOrder: "value1", + }, + }, + expectedError: fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+ + "Supported elements include %q and %q", "value1", configDriveID, metadataID), + }, + { + name: "test7", + openstackOpts: &OpenStack{ + provider: nil, + lbOpts: LoadBalancerOpts{ + LBVersion: "v2", + SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", + FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", + LBMethod: "ROUND_ROBIN", + CreateMonitor: true, + MonitorDelay: delay, + MonitorTimeout: timeout, + ManageSecurityGroups: true, + }, + metadataOpts: MetadataOpts{ + SearchOrder: configDriveID, + }, + }, + expectedError: fmt.Errorf("monitor-max-retries not set in cloud provider config"), + }, + { + name: "test8", + openstackOpts: &OpenStack{ + provider: nil, + lbOpts: LoadBalancerOpts{ + LBVersion: "v2", + SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", + FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", + LBMethod: "ROUND_ROBIN", + CreateMonitor: true, + MonitorDelay: delay, + MonitorMaxRetries: uint(3), + ManageSecurityGroups: true, + }, + metadataOpts: MetadataOpts{ + SearchOrder: configDriveID, + }, + }, + expectedError: fmt.Errorf("monitor-timeout not set in cloud provider config"), + }, + } + + for _, testcase := range tests { + err := checkOpenStackOpts(testcase.openstackOpts) + + if err == nil && testcase.expectedError == nil { + continue + } + if (err != nil && testcase.expectedError == nil) || (err == nil && testcase.expectedError != nil) || err.Error() != testcase.expectedError.Error() { + t.Errorf("%s failed: expected err=%q, got %q", + testcase.name, testcase.expectedError, err) + } + } +} + +func TestCaller(t *testing.T) { + called := false + myFunc := func() { called = true } + + c := newCaller() + c.call(myFunc) + + if !called { + t.Errorf("caller failed to call function in default case") + } + + c.disarm() + called = false + c.call(myFunc) + + if called { + t.Error("caller still called function when disarmed") + } + + // Confirm the "usual" deferred caller pattern works as expected + + called = false + successCase := func() { + c := newCaller() + defer c.call(func() { called = true }) + c.disarm() + } + if successCase(); called { + t.Error("Deferred success case still invoked unwind") + } + + called = false + failureCase := func() { + c := newCaller() + defer c.call(func() { called = true }) + } + if failureCase(); !called { + t.Error("Deferred failure case failed to invoke unwind") + } +} + +// An arbitrary sort.Interface, just for easier comparison +type AddressSlice []v1.NodeAddress + +func (a AddressSlice) Len() int { return len(a) } +func (a AddressSlice) Less(i, j int) bool { return a[i].Address < a[j].Address } +func (a AddressSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func TestNodeAddresses(t *testing.T) { + srv := servers.Server{ + Status: "ACTIVE", + HostID: "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", + AccessIPv4: "50.56.176.99", + AccessIPv6: "2001:4800:790e:510:be76:4eff:fe04:82a8", + Addresses: map[string]interface{}{ + "private": []interface{}{ + map[string]interface{}{ + "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b", + "version": float64(4), + "addr": "10.0.0.32", + "OS-EXT-IPS:type": "fixed", + }, + map[string]interface{}{ + "version": float64(4), + "addr": "50.56.176.36", + "OS-EXT-IPS:type": "floating", + }, + map[string]interface{}{ + "version": float64(4), + "addr": "10.0.0.31", + // No OS-EXT-IPS:type + }, + }, + "public": []interface{}{ + map[string]interface{}{ + "version": float64(4), + "addr": "50.56.176.35", + }, + map[string]interface{}{ + "version": float64(6), + "addr": "2001:4800:780e:510:be76:4eff:fe04:84a8", + }, + }, + }, + Metadata: map[string]string{ + "name": "a1-yinvcez57-0-bvynoyawrhcg-kube-minion-fg5i4jwcc2yy", + TypeHostName: "a1-yinvcez57-0-bvynoyawrhcg-kube-minion-fg5i4jwcc2yy.novalocal", + }, + } + + addrs, err := nodeAddresses(&srv) + if err != nil { + t.Fatalf("nodeAddresses returned error: %v", err) + } + + sort.Sort(AddressSlice(addrs)) + t.Logf("addresses is %v", addrs) + + want := []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.0.0.31"}, + {Type: v1.NodeInternalIP, Address: "10.0.0.32"}, + {Type: v1.NodeExternalIP, Address: "2001:4800:780e:510:be76:4eff:fe04:84a8"}, + {Type: v1.NodeExternalIP, Address: "2001:4800:790e:510:be76:4eff:fe04:82a8"}, + {Type: v1.NodeExternalIP, Address: "50.56.176.35"}, + {Type: v1.NodeExternalIP, Address: "50.56.176.36"}, + {Type: v1.NodeExternalIP, Address: "50.56.176.99"}, + {Type: v1.NodeHostName, Address: "a1-yinvcez57-0-bvynoyawrhcg-kube-minion-fg5i4jwcc2yy.novalocal"}, + } + + if !reflect.DeepEqual(want, addrs) { + t.Errorf("nodeAddresses returned incorrect value %v", addrs) + } +} + +func configFromEnvWithPasswd() (cfg Config, ok bool) { + cfg, ok = configFromEnv() + if !ok { + return cfg, ok + } + cfg.Global.Password = os.Getenv("OS_PASSWORD") + return cfg, ok +} + +func TestNewOpenStack(t *testing.T) { + cfg, ok := configFromEnvWithPasswd() + if !ok { + t.Skip("No config found in environment") + } + + _, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } +} + +func TestLoadBalancer(t *testing.T) { + cfg, ok := configFromEnvWithPasswd() + if !ok { + t.Skip("No config found in environment") + } + + versions := []string{"v2", ""} + + for _, v := range versions { + t.Logf("Trying LBVersion = '%s'\n", v) + cfg.LoadBalancer.LBVersion = v + + os, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } + + lb, ok := os.LoadBalancer() + if !ok { + t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?") + } + + _, exists, err := lb.GetLoadBalancer(context.TODO(), testClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "noexist"}}) + if err != nil { + t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err) + } + if exists { + t.Fatalf("GetLoadBalancer(\"noexist\") returned exists") + } + } +} + +func TestZones(t *testing.T) { + SetMetadataFixture(&FakeMetadata) + defer ClearMetadata() + + os := OpenStack{ + provider: &gophercloud.ProviderClient{ + IdentityBase: "http://auth.url/", + }, + region: "myRegion", + } + + z, ok := os.Zones() + if !ok { + t.Fatalf("Zones() returned false") + } + + zone, err := z.GetZone(context.TODO()) + if err != nil { + t.Fatalf("GetZone() returned error: %s", err) + } + + if zone.Region != "myRegion" { + t.Fatalf("GetZone() returned wrong region (%s)", zone.Region) + } + + if zone.FailureDomain != "nova" { + t.Fatalf("GetZone() returned wrong failure domain (%s)", zone.FailureDomain) + } +} + +var diskPathRegexp = regexp.MustCompile("/dev/disk/(?:by-id|by-path)/") + +func TestVolumes(t *testing.T) { + cfg, ok := configFromEnvWithPasswd() + if !ok { + t.Skip("No config found in environment") + } + + os, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } + + tags := map[string]string{ + "test": "value", + } + vol, _, _, _, err := os.CreateVolume("kubernetes-test-volume-"+rand.String(10), 1, "", "", &tags) + if err != nil { + t.Fatalf("Cannot create a new Cinder volume: %v", err) + } + t.Logf("Volume (%s) created\n", vol) + + WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) + + id, err := os.InstanceID() + if err != nil { + t.Logf("Cannot find instance id: %v - perhaps you are running this test outside a VM launched by OpenStack", err) + } else { + diskID, err := os.AttachDisk(id, vol) + if err != nil { + t.Fatalf("Cannot AttachDisk Cinder volume %s: %v", vol, err) + } + t.Logf("Volume (%s) attached, disk ID: %s\n", vol, diskID) + + WaitForVolumeStatus(t, os, vol, volumeInUseStatus) + + devicePath := os.GetDevicePath(diskID) + if diskPathRegexp.FindString(devicePath) == "" { + t.Fatalf("GetDevicePath returned and unexpected path for Cinder volume %s, returned %s", vol, devicePath) + } + t.Logf("Volume (%s) found at path: %s\n", vol, devicePath) + + err = os.DetachDisk(id, vol) + if err != nil { + t.Fatalf("Cannot DetachDisk Cinder volume %s: %v", vol, err) + } + t.Logf("Volume (%s) detached\n", vol) + + WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) + } + + expectedVolSize := resource.MustParse("2Gi") + newVolSize, err := os.ExpandVolume(vol, resource.MustParse("1Gi"), expectedVolSize) + if err != nil { + t.Fatalf("Cannot expand a Cinder volume: %v", err) + } + if newVolSize != expectedVolSize { + t.Logf("Expected: %v but got: %v ", expectedVolSize, newVolSize) + } + t.Logf("Volume expanded to (%v) \n", newVolSize) + + WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) + + err = os.DeleteVolume(vol) + if err != nil { + t.Fatalf("Cannot delete Cinder volume %s: %v", vol, err) + } + t.Logf("Volume (%s) deleted\n", vol) + +} + +func TestInstanceIDFromProviderID(t *testing.T) { + testCases := []struct { + providerID string + instanceID string + fail bool + }{ + { + providerID: ProviderName + "://" + "/" + "7b9cf879-7146-417c-abfd-cb4272f0c935", + instanceID: "7b9cf879-7146-417c-abfd-cb4272f0c935", + fail: false, + }, + { + providerID: "openstack://7b9cf879-7146-417c-abfd-cb4272f0c935", + instanceID: "", + fail: true, + }, + { + providerID: "7b9cf879-7146-417c-abfd-cb4272f0c935", + instanceID: "", + fail: true, + }, + { + providerID: "other-provider:///7b9cf879-7146-417c-abfd-cb4272f0c935", + instanceID: "", + fail: true, + }, + } + + for _, test := range testCases { + instanceID, err := instanceIDFromProviderID(test.providerID) + if (err != nil) != test.fail { + t.Errorf("%s yielded `err != nil` as %t. expected %t", test.providerID, (err != nil), test.fail) + } + + if test.fail { + continue + } + + if instanceID != test.instanceID { + t.Errorf("%s yielded %s. expected %s", test.providerID, instanceID, test.instanceID) + } + } +} + +func TestToAuth3Options(t *testing.T) { + cfg := Config{} + cfg.Global.Username = "user" + cfg.Global.Password = "pass" // Fake value for testing. + cfg.Global.DomainID = "2a73b8f597c04551a0fdc8e95544be8a" + cfg.Global.DomainName = "local" + cfg.Global.AuthURL = "http://auth.url" + cfg.Global.UserID = "user" + + ao := cfg.toAuth3Options() + + if !ao.AllowReauth { + t.Errorf("Will need to be able to reauthenticate") + } + if ao.Username != cfg.Global.Username { + t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username) + } + if ao.Password != cfg.Global.Password { + t.Errorf("Password %s != %s", ao.Password, cfg.Global.Password) + } + if ao.DomainID != cfg.Global.DomainID { + t.Errorf("DomainID %s != %s", ao.DomainID, cfg.Global.DomainID) + } + if ao.IdentityEndpoint != cfg.Global.AuthURL { + t.Errorf("IdentityEndpoint %s != %s", ao.IdentityEndpoint, cfg.Global.AuthURL) + } + if ao.UserID != cfg.Global.UserID { + t.Errorf("UserID %s != %s", ao.UserID, cfg.Global.UserID) + } + if ao.DomainName != cfg.Global.DomainName { + t.Errorf("DomainName %s != %s", ao.DomainName, cfg.Global.DomainName) + } +} + +func clearEnviron(t *testing.T) []string { + env := os.Environ() + for _, pair := range env { + if strings.HasPrefix(pair, "OS_") { + i := strings.Index(pair, "=") + 1 + os.Unsetenv(pair[:i-1]) + } + } + return env +} +func resetEnviron(t *testing.T, items []string) { + for _, pair := range items { + if strings.HasPrefix(pair, "OS_") { + i := strings.Index(pair, "=") + 1 + if err := os.Setenv(pair[:i-1], pair[i:]); err != nil { + t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i-1], pair[i:], err) + } + } + } +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go new file mode 100644 index 00000000000..3c4dda06811 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go @@ -0,0 +1,769 @@ +//go:build !providerless +// +build !providerless + +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "path" + "path/filepath" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" + cloudprovider "k8s.io/cloud-provider" + cloudvolume "k8s.io/cloud-provider/volume" + volerr "k8s.io/cloud-provider/volume/errors" + volumehelpers "k8s.io/cloud-provider/volume/helpers" + "k8s.io/component-base/metrics" + + "github.com/gophercloud/gophercloud" + volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" + volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" + volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + volumes_v3 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" + "k8s.io/klog/v2" +) + +type volumeService interface { + createVolume(opts volumeCreateOpts) (string, string, error) + getVolume(volumeID string) (Volume, error) + deleteVolume(volumeName string) error + expandVolume(volumeID string, newSize int) error +} + +// VolumesV1 is a Volumes implementation for cinder v1 +type VolumesV1 struct { + blockstorage *gophercloud.ServiceClient + opts BlockStorageOpts +} + +// VolumesV2 is a Volumes implementation for cinder v2 +type VolumesV2 struct { + blockstorage *gophercloud.ServiceClient + opts BlockStorageOpts +} + +// VolumesV3 is a Volumes implementation for cinder v3 +type VolumesV3 struct { + blockstorage *gophercloud.ServiceClient + opts BlockStorageOpts +} + +// Volume stores information about a single volume +type Volume struct { + // ID of the instance, to which this volume is attached. "" if not attached + AttachedServerID string + // Device file path + AttachedDevice string + // availabilityZone is which availability zone the volume is in + AvailabilityZone string + // Unique identifier for the volume. + ID string + // Human-readable display name for the volume. + Name string + // Current status of the volume. + Status string + // Volume size in GB + Size int +} + +type volumeCreateOpts struct { + Size int + Availability string + Name string + VolumeType string + Metadata map[string]string +} + +// implements PVLabeler. +var _ cloudprovider.PVLabeler = (*OpenStack)(nil) + +const ( + volumeAvailableStatus = "available" + volumeInUseStatus = "in-use" + volumeDeletedStatus = "deleted" + volumeErrorStatus = "error" + + // On some environments, we need to query the metadata service in order + // to locate disks. We'll use the Newton version, which includes device + // metadata. + newtonMetadataVersion = "2016-06-30" +) + +func (volumes *VolumesV1) createVolume(opts volumeCreateOpts) (string, string, error) { + startTime := time.Now() + + createOpts := volumes_v1.CreateOpts{ + Name: opts.Name, + Size: opts.Size, + VolumeType: opts.VolumeType, + AvailabilityZone: opts.Availability, + Metadata: opts.Metadata, + } + + vol, err := volumes_v1.Create(volumes.blockstorage, createOpts).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("create_v1_volume", timeTaken, err) + if err != nil { + return "", "", err + } + return vol.ID, vol.AvailabilityZone, nil +} + +func (volumes *VolumesV2) createVolume(opts volumeCreateOpts) (string, string, error) { + startTime := time.Now() + + createOpts := volumes_v2.CreateOpts{ + Name: opts.Name, + Size: opts.Size, + VolumeType: opts.VolumeType, + AvailabilityZone: opts.Availability, + Metadata: opts.Metadata, + } + + vol, err := volumes_v2.Create(volumes.blockstorage, createOpts).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("create_v2_volume", timeTaken, err) + if err != nil { + return "", "", err + } + return vol.ID, vol.AvailabilityZone, nil +} + +func (volumes *VolumesV3) createVolume(opts volumeCreateOpts) (string, string, error) { + startTime := time.Now() + + createOpts := volumes_v3.CreateOpts{ + Name: opts.Name, + Size: opts.Size, + VolumeType: opts.VolumeType, + AvailabilityZone: opts.Availability, + Metadata: opts.Metadata, + } + + vol, err := volumes_v3.Create(volumes.blockstorage, createOpts).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("create_v3_volume", timeTaken, err) + if err != nil { + return "", "", err + } + return vol.ID, vol.AvailabilityZone, nil +} + +func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) { + startTime := time.Now() + volumeV1, err := volumes_v1.Get(volumes.blockstorage, volumeID).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("get_v1_volume", timeTaken, err) + if err != nil { + if isNotFound(err) { + return Volume{}, ErrNotFound + } + return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) + } + + volume := Volume{ + AvailabilityZone: volumeV1.AvailabilityZone, + ID: volumeV1.ID, + Name: volumeV1.Name, + Status: volumeV1.Status, + Size: volumeV1.Size, + } + + if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil { + volume.AttachedServerID = volumeV1.Attachments[0]["server_id"].(string) + volume.AttachedDevice = volumeV1.Attachments[0]["device"].(string) + } + + return volume, nil +} + +func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) { + startTime := time.Now() + volumeV2, err := volumes_v2.Get(volumes.blockstorage, volumeID).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("get_v2_volume", timeTaken, err) + if err != nil { + if isNotFound(err) { + return Volume{}, ErrNotFound + } + return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) + } + + volume := Volume{ + AvailabilityZone: volumeV2.AvailabilityZone, + ID: volumeV2.ID, + Name: volumeV2.Name, + Status: volumeV2.Status, + Size: volumeV2.Size, + } + + if len(volumeV2.Attachments) > 0 { + volume.AttachedServerID = volumeV2.Attachments[0].ServerID + volume.AttachedDevice = volumeV2.Attachments[0].Device + } + + return volume, nil +} + +func (volumes *VolumesV3) getVolume(volumeID string) (Volume, error) { + startTime := time.Now() + volumeV3, err := volumes_v3.Get(volumes.blockstorage, volumeID).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("get_v3_volume", timeTaken, err) + if err != nil { + if isNotFound(err) { + return Volume{}, ErrNotFound + } + return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) + } + + volume := Volume{ + AvailabilityZone: volumeV3.AvailabilityZone, + ID: volumeV3.ID, + Name: volumeV3.Name, + Status: volumeV3.Status, + Size: volumeV3.Size, + } + + if len(volumeV3.Attachments) > 0 { + volume.AttachedServerID = volumeV3.Attachments[0].ServerID + volume.AttachedDevice = volumeV3.Attachments[0].Device + } + + return volume, nil +} + +func (volumes *VolumesV1) deleteVolume(volumeID string) error { + startTime := time.Now() + err := volumes_v1.Delete(volumes.blockstorage, volumeID).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("delete_v1_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV2) deleteVolume(volumeID string) error { + startTime := time.Now() + err := volumes_v2.Delete(volumes.blockstorage, volumeID, nil).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("delete_v2_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV3) deleteVolume(volumeID string) error { + startTime := time.Now() + err := volumes_v3.Delete(volumes.blockstorage, volumeID, nil).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("delete_v3_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV1) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + createOpts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, createOpts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV2) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + createOpts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, createOpts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV3) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + createOpts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, createOpts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + return err +} + +// OperationPending checks if there is an operation pending on a volume +func (os *OpenStack) OperationPending(diskName string) (bool, string, error) { + volume, err := os.getVolume(diskName) + if err != nil { + return false, "", err + } + volumeStatus := volume.Status + if volumeStatus == volumeErrorStatus { + err = fmt.Errorf("status of volume %s is %s", diskName, volumeStatus) + return false, volumeStatus, err + } + if volumeStatus == volumeAvailableStatus || volumeStatus == volumeInUseStatus || volumeStatus == volumeDeletedStatus { + return false, volume.Status, nil + } + return true, volumeStatus, nil +} + +// AttachDisk attaches given cinder volume to the compute running kubelet +func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) { + volume, err := os.getVolume(volumeID) + if err != nil { + return "", err + } + + cClient, err := os.NewComputeV2() + if err != nil { + return "", err + } + + if volume.AttachedServerID != "" { + if instanceID == volume.AttachedServerID { + klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID) + return volume.ID, nil + } + nodeName, err := os.GetNodeNameByID(volume.AttachedServerID) + attachErr := fmt.Sprintf("disk %s path %s is attached to a different instance (%s)", volumeID, volume.AttachedDevice, volume.AttachedServerID) + if err != nil { + klog.Error(attachErr) + return "", errors.New(attachErr) + } + // using volume.AttachedDevice may cause problems because cinder does not report device path correctly see issue #33128 + devicePath := volume.AttachedDevice + danglingErr := volerr.NewDanglingError(attachErr, nodeName, devicePath) + klog.V(2).Infof("Found dangling volume %s attached to node %s", volumeID, nodeName) + return "", danglingErr + } + + startTime := time.Now() + // add read only flag here if possible spothanis + _, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{ + VolumeID: volume.ID, + }).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("attach_disk", timeTaken, err) + if err != nil { + return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err) + } + klog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID) + return volume.ID, nil +} + +// DetachDisk detaches given cinder volume from the compute running kubelet +func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { + volume, err := os.getVolume(volumeID) + if err != nil { + return err + } + if volume.Status == volumeAvailableStatus { + // "available" is fine since that means the volume is detached from instance already. + klog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID) + return nil + } + + if volume.Status != volumeInUseStatus { + return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status) + } + cClient, err := os.NewComputeV2() + if err != nil { + return err + } + if volume.AttachedServerID != instanceID { + return fmt.Errorf("disk: %s has no attachments or is not attached to compute: %s", volume.Name, instanceID) + } + + startTime := time.Now() + // This is a blocking call and effects kubelet's performance directly. + // We should consider kicking it out into a separate routine, if it is bad. + err = volumeattach.Delete(cClient, instanceID, volume.ID).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("detach_disk", timeTaken, err) + if err != nil { + return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err) + } + klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID) + + return nil +} + +// ExpandVolume expands the size of specific cinder volume (in GiB) +func (os *OpenStack) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + volume, err := os.getVolume(volumeID) + if err != nil { + return oldSize, err + } + if volume.Status != volumeAvailableStatus { + // cinder volume can not be expanded if its status is not available + if volume.Status == volumeInUseStatus { + // Send a nice event when the volume is used + return oldSize, fmt.Errorf("PVC used by a Pod can not be expanded, please ensure the PVC is not used by any Pod and is fully detached from a node") + } + // Send not so nice event when the volume is in any other state (deleted, error) + return oldSize, fmt.Errorf("volume in state %q can not be expanded, it must be \"available\"", volume.Status) + } + + // Cinder works with gigabytes, convert to GiB with rounding up + volSizeGiB, err := volumehelpers.RoundUpToGiBInt(newSize) + if err != nil { + return oldSize, err + } + newSizeQuant := resource.MustParse(fmt.Sprintf("%dGi", volSizeGiB)) + + // if volume size equals to or greater than the newSize, return nil + if volume.Size >= volSizeGiB { + return newSizeQuant, nil + } + + volumes, err := os.volumeService("") + if err != nil { + return oldSize, err + } + + err = volumes.expandVolume(volumeID, volSizeGiB) + if err != nil { + return oldSize, err + } + return newSizeQuant, nil +} + +// getVolume retrieves Volume by its ID. +func (os *OpenStack) getVolume(volumeID string) (Volume, error) { + volumes, err := os.volumeService("") + if err != nil { + return Volume{}, fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) + } + return volumes.getVolume(volumeID) +} + +// CreateVolume creates a volume of given size (in GiB) +func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, string, bool, error) { + volumes, err := os.volumeService("") + if err != nil { + return "", "", "", os.bsOpts.IgnoreVolumeAZ, fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) + } + + opts := volumeCreateOpts{ + Name: name, + Size: size, + VolumeType: vtype, + Availability: availability, + } + if tags != nil { + opts.Metadata = *tags + } + + volumeID, volumeAZ, err := volumes.createVolume(opts) + + if err != nil { + return "", "", "", os.bsOpts.IgnoreVolumeAZ, fmt.Errorf("failed to create a %d GB volume: %v", size, err) + } + + klog.Infof("Created volume %v in Availability Zone: %v Region: %v Ignore volume AZ: %v", volumeID, volumeAZ, os.region, os.bsOpts.IgnoreVolumeAZ) + return volumeID, volumeAZ, os.region, os.bsOpts.IgnoreVolumeAZ, nil +} + +// GetDevicePathBySerialID returns the path of an attached block storage volume, specified by its id. +func (os *OpenStack) GetDevicePathBySerialID(volumeID string) string { + // Build a list of candidate device paths. + // Certain Nova drivers will set the disk serial ID, including the Cinder volume id. + // Newer OpenStacks may not truncate the volumeID to 20 chars. + candidateDeviceNodes := []string{ + // KVM + fmt.Sprintf("virtio-%s", volumeID[:20]), + fmt.Sprintf("virtio-%s", volumeID), + // KVM virtio-scsi + fmt.Sprintf("scsi-0QEMU_QEMU_HARDDISK_%s", volumeID[:20]), + fmt.Sprintf("scsi-0QEMU_QEMU_HARDDISK_%s", volumeID), + // ESXi + fmt.Sprintf("wwn-0x%s", strings.Replace(volumeID, "-", "", -1)), + } + + files, _ := ioutil.ReadDir("/dev/disk/by-id/") + + for _, f := range files { + for _, c := range candidateDeviceNodes { + if c == f.Name() { + klog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name())) + return path.Join("/dev/disk/by-id/", f.Name()) + } + } + } + + klog.V(4).Infof("Failed to find device for the volumeID: %q by serial ID", volumeID) + return "" +} + +func (os *OpenStack) getDevicePathFromInstanceMetadata(volumeID string) string { + // Nova Hyper-V hosts cannot override disk SCSI IDs. In order to locate + // volumes, we're querying the metadata service. Note that the Hyper-V + // driver will include device metadata for untagged volumes as well. + // + // We're avoiding using cached metadata (or the configdrive), + // relying on the metadata service. + instanceMetadata, err := getMetadataFromMetadataService( + newtonMetadataVersion) + + if err != nil { + klog.V(4).Infof( + "Could not retrieve instance metadata. Error: %v", err) + return "" + } + + for _, device := range instanceMetadata.Devices { + if device.Type == "disk" && device.Serial == volumeID { + klog.V(4).Infof( + "Found disk metadata for volumeID %q. Bus: %q, Address: %q", + volumeID, device.Bus, device.Address) + + diskPattern := fmt.Sprintf( + "/dev/disk/by-path/*-%s-%s", + device.Bus, device.Address) + diskPaths, err := filepath.Glob(diskPattern) + if err != nil { + klog.Errorf( + "could not retrieve disk path for volumeID: %q. Error filepath.Glob(%q): %v", + volumeID, diskPattern, err) + return "" + } + + if len(diskPaths) == 1 { + return diskPaths[0] + } + + klog.Errorf( + "expecting to find one disk path for volumeID %q, found %d: %v", + volumeID, len(diskPaths), diskPaths) + return "" + } + } + + klog.V(4).Infof( + "Could not retrieve device metadata for volumeID: %q", volumeID) + return "" +} + +// GetDevicePath returns the path of an attached block storage volume, specified by its id. +func (os *OpenStack) GetDevicePath(volumeID string) string { + devicePath := os.GetDevicePathBySerialID(volumeID) + + if devicePath == "" { + devicePath = os.getDevicePathFromInstanceMetadata(volumeID) + } + + if devicePath == "" { + klog.Warningf("Failed to find device for the volumeID: %q", volumeID) + } + + return devicePath +} + +// DeleteVolume deletes a volume given volume name. +func (os *OpenStack) DeleteVolume(volumeID string) error { + used, err := os.diskIsUsed(volumeID) + if err != nil { + return err + } + if used { + msg := fmt.Sprintf("Cannot delete the volume %q, it's still attached to a node", volumeID) + return volerr.NewDeletedVolumeInUseError(msg) + } + + volumes, err := os.volumeService("") + if err != nil { + return fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) + } + + err = volumes.deleteVolume(volumeID) + return err + +} + +// GetAttachmentDiskPath gets device path of attached volume to the compute running kubelet, as known by cinder +func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) { + // See issue #33128 - Cinder does not always tell you the right device path, as such + // we must only use this value as a last resort. + volume, err := os.getVolume(volumeID) + if err != nil { + return "", err + } + if volume.Status != volumeInUseStatus { + return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status) + } + if volume.AttachedServerID != "" { + if instanceID == volume.AttachedServerID { + // Attachment[0]["device"] points to the device path + // see http://developer.openstack.org/api-ref-blockstorage-v1.html + return volume.AttachedDevice, nil + } + return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.AttachedServerID) + } + return "", fmt.Errorf("volume %s has no ServerId", volumeID) +} + +// DiskIsAttached queries if a volume is attached to a compute instance +func (os *OpenStack) DiskIsAttached(instanceID, volumeID string) (bool, error) { + if instanceID == "" { + klog.Warningf("calling DiskIsAttached with empty instanceid: %s %s", instanceID, volumeID) + } + volume, err := os.getVolume(volumeID) + if err != nil { + if err == ErrNotFound { + // Volume does not exists, it can't be attached. + return false, nil + } + return false, err + } + + return instanceID == volume.AttachedServerID, nil +} + +// DiskIsAttachedByName queries if a volume is attached to a compute instance by name +func (os *OpenStack) DiskIsAttachedByName(nodeName types.NodeName, volumeID string) (bool, string, error) { + cClient, err := os.NewComputeV2() + if err != nil { + return false, "", err + } + srv, err := getServerByName(cClient, nodeName) + if err != nil { + if err == ErrNotFound { + // instance not found anymore in cloudprovider, assume that cinder is detached + return false, "", nil + } + return false, "", err + } + instanceID := "/" + srv.ID + if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { + instanceID = instanceID[(ind + 1):] + } + attached, err := os.DiskIsAttached(instanceID, volumeID) + return attached, instanceID, err +} + +// DisksAreAttached queries if a list of volumes are attached to a compute instance +func (os *OpenStack) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) { + attached := make(map[string]bool) + for _, volumeID := range volumeIDs { + isAttached, err := os.DiskIsAttached(instanceID, volumeID) + if err != nil && err != ErrNotFound { + attached[volumeID] = true + continue + } + attached[volumeID] = isAttached + } + return attached, nil +} + +// DisksAreAttachedByName queries if a list of volumes are attached to a compute instance by name +func (os *OpenStack) DisksAreAttachedByName(nodeName types.NodeName, volumeIDs []string) (map[string]bool, error) { + attached := make(map[string]bool) + cClient, err := os.NewComputeV2() + if err != nil { + return attached, err + } + srv, err := getServerByName(cClient, nodeName) + if err != nil { + if err == ErrNotFound { + // instance not found anymore, mark all volumes as detached + for _, volumeID := range volumeIDs { + attached[volumeID] = false + } + return attached, nil + } + return attached, err + } + instanceID := "/" + srv.ID + if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { + instanceID = instanceID[(ind + 1):] + } + return os.DisksAreAttached(instanceID, volumeIDs) +} + +// diskIsUsed returns true a disk is attached to any node. +func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) { + volume, err := os.getVolume(volumeID) + if err != nil { + return false, err + } + return volume.AttachedServerID != "", nil +} + +// ShouldTrustDevicePath queries if we should trust the cinder provide deviceName, See issue #33128 +func (os *OpenStack) ShouldTrustDevicePath() bool { + return os.bsOpts.TrustDevicePath +} + +// NodeVolumeAttachLimit specifies number of cinder volumes that can be attached to this node. +func (os *OpenStack) NodeVolumeAttachLimit() int { + return os.bsOpts.NodeVolumeAttachLimit +} + +// GetLabelsForVolume implements PVLabeler.GetLabelsForVolume +func (os *OpenStack) GetLabelsForVolume(ctx context.Context, pv *v1.PersistentVolume) (map[string]string, error) { + // Ignore if not Cinder. + if pv.Spec.Cinder == nil { + return nil, nil + } + + // Ignore any volumes that are being provisioned + if pv.Spec.Cinder.VolumeID == cloudvolume.ProvisionedVolumeName { + return nil, nil + } + + // if volume az is to be ignored we should return nil from here + if os.bsOpts.IgnoreVolumeAZ { + return nil, nil + } + + // Get Volume + volume, err := os.getVolume(pv.Spec.Cinder.VolumeID) + if err != nil { + return nil, err + } + + // Construct Volume Labels + labels := make(map[string]string) + if volume.AvailabilityZone != "" { + labels[v1.LabelTopologyZone] = volume.AvailabilityZone + } + if os.region != "" { + labels[v1.LabelTopologyRegion] = os.region + } + klog.V(4).Infof("The Volume %s has labels %v", pv.Spec.Cinder.VolumeID, labels) + + return labels, nil +} + +// recordOpenstackOperationMetric records openstack operation metrics +func recordOpenstackOperationMetric(operation string, timeTaken float64, err error) { + if err != nil { + openstackAPIRequestErrors.With(metrics.Labels{"request": operation}).Inc() + } else { + openstackOperationsLatency.With(metrics.Labels{"request": operation}).Observe(timeTaken) + } +} diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes.go b/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes.go index f33cf4c7b69..e171cdd60f1 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes.go +++ b/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes.go @@ -122,6 +122,8 @@ func restrictedVolumes_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSp badVolumeTypes.Insert("rbd") case volume.FlexVolume != nil: badVolumeTypes.Insert("flexVolume") + case volume.Cinder != nil: + badVolumeTypes.Insert("cinder") case volume.CephFS != nil: badVolumeTypes.Insert("cephfs") case volume.Flocker != nil: diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes_test.go b/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes_test.go index b7fdda4338f..45b08235bdb 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes_test.go +++ b/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes_test.go @@ -53,6 +53,7 @@ func TestRestrictedVolumes(t *testing.T) { {Name: "b7", VolumeSource: corev1.VolumeSource{Glusterfs: &corev1.GlusterfsVolumeSource{}}}, {Name: "b8", VolumeSource: corev1.VolumeSource{RBD: &corev1.RBDVolumeSource{}}}, {Name: "b9", VolumeSource: corev1.VolumeSource{FlexVolume: &corev1.FlexVolumeSource{}}}, + {Name: "b10", VolumeSource: corev1.VolumeSource{Cinder: &corev1.CinderVolumeSource{}}}, {Name: "b11", VolumeSource: corev1.VolumeSource{CephFS: &corev1.CephFSVolumeSource{}}}, {Name: "b12", VolumeSource: corev1.VolumeSource{Flocker: &corev1.FlockerVolumeSource{}}}, {Name: "b13", VolumeSource: corev1.VolumeSource{FC: &corev1.FCVolumeSource{}}}, @@ -71,9 +72,9 @@ func TestRestrictedVolumes(t *testing.T) { }}, expectReason: `restricted volume types`, expectDetail: `volumes ` + - `"b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "b11", "b12", "b13", "b14", "b15", "b16", "b17", "b18", "b19", "b20", "b21", "c1"` + + `"b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "b10", "b11", "b12", "b13", "b14", "b15", "b16", "b17", "b18", "b19", "b20", "b21", "c1"` + ` use restricted volume types ` + - `"awsElasticBlockStore", "azureDisk", "azureFile", "cephfs", "fc", "flexVolume", "flocker", "gcePersistentDisk", "gitRepo", "glusterfs", ` + + `"awsElasticBlockStore", "azureDisk", "azureFile", "cephfs", "cinder", "fc", "flexVolume", "flocker", "gcePersistentDisk", "gitRepo", "glusterfs", ` + `"hostPath", "iscsi", "nfs", "photonPersistentDisk", "portworxVolume", "quobyte", "rbd", "scaleIO", "storageos", "unknown", "vsphereVolume"`, }, } diff --git a/test/e2e/common/storage/volumes.go b/test/e2e/common/storage/volumes.go index ecb005e9663..79204cf4a42 100644 --- a/test/e2e/common/storage/volumes.go +++ b/test/e2e/common/storage/volumes.go @@ -31,8 +31,8 @@ limitations under the License. * Note that the server containers are for testing purposes only and should not * be used in production. * - * 2) With server outside of Kubernetes - * Appropriate server exist somewhere outside + * 2) With server outside of Kubernetes (Cinder, ...) + * Appropriate server (e.g. OpenStack Cinder) must exist somewhere outside * the tested Kubernetes cluster. The test itself creates a new volume, * and checks, that Kubernetes can use it as a volume. */ diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index afa7d9e7e9c..336892341c8 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -57,6 +57,7 @@ import ( _ "k8s.io/kubernetes/test/e2e/framework/providers/azure" _ "k8s.io/kubernetes/test/e2e/framework/providers/gce" _ "k8s.io/kubernetes/test/e2e/framework/providers/kubemark" + _ "k8s.io/kubernetes/test/e2e/framework/providers/openstack" _ "k8s.io/kubernetes/test/e2e/framework/providers/vsphere" // Ensure that logging flags are part of the command line. diff --git a/test/e2e/framework/providers/openstack/openstack.go b/test/e2e/framework/providers/openstack/openstack.go new file mode 100644 index 00000000000..784c2f709f6 --- /dev/null +++ b/test/e2e/framework/providers/openstack/openstack.go @@ -0,0 +1,34 @@ +/* +Copyright 2019 The Kubernetes Authors. + +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. +*/ + +package openstack + +import ( + "k8s.io/kubernetes/test/e2e/framework" +) + +func init() { + framework.RegisterProvider("openstack", newProvider) +} + +func newProvider() (framework.ProviderInterface, error) { + return &Provider{}, nil +} + +// Provider is a structure to handle OpenStack clouds for e2e testing +type Provider struct { + framework.NullProvider +} diff --git a/test/e2e/framework/test_context.go b/test/e2e/framework/test_context.go index 6108cddf740..6600c953e2a 100644 --- a/test/e2e/framework/test_context.go +++ b/test/e2e/framework/test_context.go @@ -260,7 +260,7 @@ type CloudConfig struct { ClusterIPRange string ClusterTag string Network string - ConfigFile string // for azure + ConfigFile string // for azure and openstack NodeTag string MasterTag string diff --git a/test/e2e/framework/volume/fixtures.go b/test/e2e/framework/volume/fixtures.go index 8afc4128dd6..2ed2341994c 100644 --- a/test/e2e/framework/volume/fixtures.go +++ b/test/e2e/framework/volume/fixtures.go @@ -31,8 +31,8 @@ limitations under the License. * Note that the server containers are for testing purposes only and should not * be used in production. * - * 2) With server outside of Kubernetes - * Appropriate server must exist somewhere outside + * 2) With server outside of Kubernetes (Cinder, ...) + * Appropriate server (e.g. OpenStack Cinder) must exist somewhere outside * the tested Kubernetes cluster. The test itself creates a new volume, * and checks, that Kubernetes can use it as a volume. */ diff --git a/test/e2e/storage/drivers/csi.go b/test/e2e/storage/drivers/csi.go index 37d2c5a2ca2..1c954d35d05 100644 --- a/test/e2e/storage/drivers/csi.go +++ b/test/e2e/storage/drivers/csi.go @@ -27,7 +27,7 @@ limitations under the License. * Note that the server containers are for testing purposes only and should not * be used in production. * - * 2) With server or cloud provider outside of Kubernetes (GCE, AWS, Azure, ...) + * 2) With server or cloud provider outside of Kubernetes (Cinder, GCE, AWS, Azure, ...) * Appropriate server or cloud provider must exist somewhere outside * the tested Kubernetes cluster. CreateVolume will create a new volume to be * used in the TestSuites for inlineVolume or DynamicPV tests. diff --git a/test/e2e/storage/drivers/in_tree.go b/test/e2e/storage/drivers/in_tree.go index aaaa3d37bf1..f11becafd71 100644 --- a/test/e2e/storage/drivers/in_tree.go +++ b/test/e2e/storage/drivers/in_tree.go @@ -27,7 +27,7 @@ limitations under the License. * Note that the server containers are for testing purposes only and should not * be used in production. * - * 2) With server or cloud provider outside of Kubernetes (GCE, AWS, Azure, ...) + * 2) With server or cloud provider outside of Kubernetes (Cinder, GCE, AWS, Azure, ...) * Appropriate server or cloud provider must exist somewhere outside * the tested Kubernetes cluster. CreateVolume will create a new volume to be * used in the TestSuites for inlineVolume or DynamicPV tests. @@ -38,6 +38,7 @@ package drivers import ( "context" "fmt" + "os/exec" "strconv" "strings" "time" @@ -928,6 +929,179 @@ func (e *emptydirDriver) PrepareTest(f *framework.Framework) *storageframework.P } } +// Cinder +// This driver assumes that OpenStack client tools are installed +// (/usr/bin/nova, /usr/bin/cinder and /usr/bin/keystone) +// and that the usual OpenStack authentication env. variables are set +// (OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME at least). +type cinderDriver struct { + driverInfo storageframework.DriverInfo +} + +type cinderVolume struct { + volumeName string + volumeID string +} + +var _ storageframework.TestDriver = &cinderDriver{} +var _ storageframework.PreprovisionedVolumeTestDriver = &cinderDriver{} +var _ storageframework.InlineVolumeTestDriver = &cinderDriver{} +var _ storageframework.PreprovisionedPVTestDriver = &cinderDriver{} +var _ storageframework.DynamicPVTestDriver = &cinderDriver{} + +// InitCinderDriver returns cinderDriver that implements TestDriver interface +func InitCinderDriver() storageframework.TestDriver { + return &cinderDriver{ + driverInfo: storageframework.DriverInfo{ + Name: "cinder", + InTreePluginName: "kubernetes.io/cinder", + MaxFileSize: storageframework.FileSizeMedium, + SupportedSizeRange: e2evolume.SizeRange{ + Min: "1Gi", + }, + SupportedFsType: sets.NewString( + "", // Default fsType + ), + TopologyKeys: []string{v1.LabelFailureDomainBetaZone}, + Capabilities: map[storageframework.Capability]bool{ + storageframework.CapPersistence: true, + storageframework.CapFsGroup: true, + storageframework.CapExec: true, + storageframework.CapBlock: true, + // Cinder supports volume limits, but the test creates large + // number of volumes and times out test suites. + storageframework.CapVolumeLimits: false, + storageframework.CapTopology: true, + }, + }, + } +} + +func (c *cinderDriver) GetDriverInfo() *storageframework.DriverInfo { + return &c.driverInfo +} + +func (c *cinderDriver) SkipUnsupportedTest(pattern storageframework.TestPattern) { + e2eskipper.SkipUnlessProviderIs("openstack") +} + +func (c *cinderDriver) GetVolumeSource(readOnly bool, fsType string, e2evolume storageframework.TestVolume) *v1.VolumeSource { + cv, ok := e2evolume.(*cinderVolume) + framework.ExpectEqual(ok, true, "Failed to cast test volume to Cinder test volume") + + volSource := v1.VolumeSource{ + Cinder: &v1.CinderVolumeSource{ + VolumeID: cv.volumeID, + ReadOnly: readOnly, + }, + } + if fsType != "" { + volSource.Cinder.FSType = fsType + } + return &volSource +} + +func (c *cinderDriver) GetPersistentVolumeSource(readOnly bool, fsType string, e2evolume storageframework.TestVolume) (*v1.PersistentVolumeSource, *v1.VolumeNodeAffinity) { + cv, ok := e2evolume.(*cinderVolume) + framework.ExpectEqual(ok, true, "Failed to cast test volume to Cinder test volume") + + pvSource := v1.PersistentVolumeSource{ + Cinder: &v1.CinderPersistentVolumeSource{ + VolumeID: cv.volumeID, + ReadOnly: readOnly, + }, + } + if fsType != "" { + pvSource.Cinder.FSType = fsType + } + return &pvSource, nil +} + +func (c *cinderDriver) GetDynamicProvisionStorageClass(config *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass { + provisioner := "kubernetes.io/cinder" + parameters := map[string]string{} + if fsType != "" { + parameters["fsType"] = fsType + } + ns := config.Framework.Namespace.Name + + return storageframework.GetStorageClass(provisioner, parameters, nil, ns) +} + +func (c *cinderDriver) PrepareTest(f *framework.Framework) (*storageframework.PerTestConfig, func()) { + return &storageframework.PerTestConfig{ + Driver: c, + Prefix: "cinder", + Framework: f, + }, func() {} +} + +func (c *cinderDriver) CreateVolume(config *storageframework.PerTestConfig, volType storageframework.TestVolType) storageframework.TestVolume { + f := config.Framework + ns := f.Namespace + + // We assume that namespace.Name is a random string + volumeName := ns.Name + ginkgo.By("creating a test Cinder volume") + output, err := exec.Command("cinder", "create", "--display-name="+volumeName, "1").CombinedOutput() + outputString := string(output[:]) + framework.Logf("cinder output:\n%s", outputString) + framework.ExpectNoError(err) + + // Parse 'id'' from stdout. Expected format: + // | attachments | [] | + // | availability_zone | nova | + // ... + // | id | 1d6ff08f-5d1c-41a4-ad72-4ef872cae685 | + volumeID := "" + for _, line := range strings.Split(outputString, "\n") { + fields := strings.Fields(line) + if len(fields) != 5 { + continue + } + if fields[1] != "id" { + continue + } + volumeID = fields[3] + break + } + framework.Logf("Volume ID: %s", volumeID) + framework.ExpectNotEqual(volumeID, "") + return &cinderVolume{ + volumeName: volumeName, + volumeID: volumeID, + } +} + +func (v *cinderVolume) DeleteVolume() { + id := v.volumeID + name := v.volumeName + + // Try to delete the volume for several seconds - it takes + // a while for the plugin to detach it. + var output []byte + var err error + timeout := time.Second * 120 + + framework.Logf("Waiting up to %v for removal of cinder volume %s / %s", timeout, id, name) + for start := time.Now(); time.Since(start) < timeout; time.Sleep(5 * time.Second) { + output, err = exec.Command("cinder", "delete", id).CombinedOutput() + if err == nil { + framework.Logf("Cinder volume %s deleted", id) + return + } + framework.Logf("Failed to delete volume %s / %s: %v\n%s", id, name, err, string(output)) + } + // Timed out, try to get "cinder show " output for easier debugging + showOutput, showErr := exec.Command("cinder", "show", id).CombinedOutput() + if showErr != nil { + framework.Logf("Failed to show volume %s / %s: %v\n%s", id, name, showErr, string(showOutput)) + } else { + framework.Logf("Volume %s / %s:\n%s", id, name, string(showOutput)) + } + framework.Failf("Failed to delete pre-provisioned volume %s / %s: %v\n%s", id, name, err, string(output[:])) +} + // GCE type gcePdDriver struct { driverInfo storageframework.DriverInfo diff --git a/test/e2e/storage/in_tree_volumes.go b/test/e2e/storage/in_tree_volumes.go index d9334969ed4..53764bcbc5c 100644 --- a/test/e2e/storage/in_tree_volumes.go +++ b/test/e2e/storage/in_tree_volumes.go @@ -36,6 +36,7 @@ var testDrivers = []func() storageframework.TestDriver{ drivers.InitHostPathDriver, drivers.InitHostPathSymlinkDriver, drivers.InitEmptydirDriver, + drivers.InitCinderDriver, drivers.InitVSphereDriver, drivers.InitAzureDiskDriver, drivers.InitAzureFileDriver, diff --git a/test/e2e/storage/testsuites/volumelimits.go b/test/e2e/storage/testsuites/volumelimits.go index 84692be70b4..c1edad5dd30 100644 --- a/test/e2e/storage/testsuites/volumelimits.go +++ b/test/e2e/storage/testsuites/volumelimits.go @@ -371,6 +371,8 @@ func getInTreeNodeLimits(cs clientset.Interface, nodeName string, driverInfo *st allocatableKey = volumeutil.EBSVolumeLimitKey case migrationplugins.GCEPDInTreePluginName: allocatableKey = volumeutil.GCEVolumeLimitKey + case migrationplugins.CinderInTreePluginName: + allocatableKey = volumeutil.CinderVolumeLimitKey case migrationplugins.AzureDiskInTreePluginName: allocatableKey = volumeutil.AzureVolumeLimitKey default: diff --git a/test/e2e/storage/volume_provisioning.go b/test/e2e/storage/volume_provisioning.go index 1a676251ff6..892a035a4a3 100644 --- a/test/e2e/storage/volume_provisioning.go +++ b/test/e2e/storage/volume_provisioning.go @@ -284,6 +284,34 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { framework.ExpectNoError(err, "checkAWSEBS gp2 encrypted") }, }, + // OpenStack generic tests (works on all OpenStack deployments) + { + Name: "generic Cinder volume on OpenStack", + CloudProviders: []string{"openstack"}, + Timeouts: f.Timeouts, + Provisioner: "kubernetes.io/cinder", + Parameters: map[string]string{}, + ClaimSize: "1.5Gi", + ExpectedSize: "2Gi", + PvCheck: func(claim *v1.PersistentVolumeClaim) { + testsuites.PVWriteReadSingleNodeCheck(c, f.Timeouts, claim, e2epod.NodeSelection{}) + }, + }, + { + Name: "Cinder volume with empty volume type and zone on OpenStack", + CloudProviders: []string{"openstack"}, + Timeouts: f.Timeouts, + Provisioner: "kubernetes.io/cinder", + Parameters: map[string]string{ + "type": "", + "availability": "", + }, + ClaimSize: "1.5Gi", + ExpectedSize: "2Gi", + PvCheck: func(claim *v1.PersistentVolumeClaim) { + testsuites.PVWriteReadSingleNodeCheck(c, f.Timeouts, claim, e2epod.NodeSelection{}) + }, + }, // vSphere generic test { Name: "generic vSphere volume", @@ -397,7 +425,7 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { // not being deleted. // NOTE: Polls until no PVs are detected, times out at 5 minutes. - e2eskipper.SkipUnlessProviderIs("gce", "aws", "gke", "vsphere", "azure") + e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") const raceAttempts int = 100 var residualPVs []*v1.PersistentVolume @@ -572,7 +600,7 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { ginkgo.Describe("DynamicProvisioner Default", func() { ginkgo.It("should create and delete default persistent volumes [Slow]", func(ctx context.Context) { - e2eskipper.SkipUnlessProviderIs("gce", "aws", "gke", "vsphere", "azure") + e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") e2epv.SkipIfNoDefaultStorageClass(c) ginkgo.By("creating a claim with no annotation") @@ -596,7 +624,7 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { // Modifying the default storage class can be disruptive to other tests that depend on it ginkgo.It("should be disabled by changing the default annotation [Serial] [Disruptive]", func() { - e2eskipper.SkipUnlessProviderIs("gce", "aws", "gke", "vsphere", "azure") + e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") e2epv.SkipIfNoDefaultStorageClass(c) scName, scErr := e2epv.GetDefaultStorageClassName(c) @@ -635,7 +663,7 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { // Modifying the default storage class can be disruptive to other tests that depend on it ginkgo.It("should be disabled by removing the default annotation [Serial] [Disruptive]", func() { - e2eskipper.SkipUnlessProviderIs("gce", "aws", "gke", "vsphere", "azure") + e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") e2epv.SkipIfNoDefaultStorageClass(c) scName, scErr := e2epv.GetDefaultStorageClassName(c) @@ -776,6 +804,8 @@ func getDefaultPluginName() string { return "kubernetes.io/gce-pd" case framework.ProviderIs("aws"): return "kubernetes.io/aws-ebs" + case framework.ProviderIs("openstack"): + return "kubernetes.io/cinder" case framework.ProviderIs("vsphere"): return "kubernetes.io/vsphere-volume" case framework.ProviderIs("azure"): diff --git a/test/e2e/upgrades/storage/persistent_volumes.go b/test/e2e/upgrades/storage/persistent_volumes.go index e907726bad5..9f60c3097ff 100644 --- a/test/e2e/upgrades/storage/persistent_volumes.go +++ b/test/e2e/upgrades/storage/persistent_volumes.go @@ -48,7 +48,7 @@ const ( func (t *PersistentVolumeUpgradeTest) Setup(f *framework.Framework) { var err error - e2eskipper.SkipUnlessProviderIs("gce", "gke", "aws", "vsphere", "azure") + e2eskipper.SkipUnlessProviderIs("gce", "gke", "openstack", "aws", "vsphere", "azure") ns := f.Namespace.Name diff --git a/test/e2e/upgrades/storage/volume_mode.go b/test/e2e/upgrades/storage/volume_mode.go index 52a068cb308..4cde7f6f0a3 100644 --- a/test/e2e/upgrades/storage/volume_mode.go +++ b/test/e2e/upgrades/storage/volume_mode.go @@ -52,7 +52,7 @@ func (VolumeModeDowngradeTest) Name() string { // Skip returns true when this test can be skipped. func (t *VolumeModeDowngradeTest) Skip(upgCtx upgrades.UpgradeContext) bool { - if !framework.ProviderIs("gce", "aws", "gke", "vsphere", "azure") { + if !framework.ProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") { return true } diff --git a/vendor/github.com/gophercloud/gophercloud/.gitignore b/vendor/github.com/gophercloud/gophercloud/.gitignore new file mode 100644 index 00000000000..dd91ed20559 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.gitignore @@ -0,0 +1,3 @@ +**/*.swp +.idea +.vscode diff --git a/vendor/github.com/gophercloud/gophercloud/.travis.yml b/vendor/github.com/gophercloud/gophercloud/.travis.yml new file mode 100644 index 00000000000..9153a00fc55 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.travis.yml @@ -0,0 +1,25 @@ +language: go +sudo: false +install: +- 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.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 new file mode 100644 index 00000000000..135e3b203a8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml @@ -0,0 +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/CHANGELOG.md b/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/vendor/github.com/gophercloud/gophercloud/LICENSE b/vendor/github.com/gophercloud/gophercloud/LICENSE new file mode 100644 index 00000000000..fbbbc9e4cba --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/LICENSE @@ -0,0 +1,191 @@ +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 diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md new file mode 100644 index 00000000000..ad29041d9bf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/README.md @@ -0,0 +1,159 @@ +# Gophercloud: an OpenStack SDK for Go +[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud) +[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master) + +Gophercloud is an OpenStack Go SDK. + +## Useful links + +* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud) +* [Effective Go](https://golang.org/doc/effective_go.html) + +## How to install + +Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH) +is pointing to an appropriate directory where you want to install Gophercloud: + +```bash +mkdir $HOME/go +export GOPATH=$HOME/go +``` + +To protect yourself against changes in your dependencies, we highly recommend choosing a +[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for +your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install +Gophercloud as a dependency like so: + +```bash +go get github.com/gophercloud/gophercloud + +# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud" + +godep save ./... +``` + +This will install all the source files you need into a `Godeps/_workspace` directory, which is +referenceable from your own source files when you use the `godep go` command. + +## Getting started + +### Credentials + +Because you'll be hitting an API, you will need to retrieve your OpenStack +credentials and either store them as environment variables or in your local Go +files. The first method is recommended because it decouples credential +information from source code, allowing you to push the latter to your version +control system without any security risk. + +You will need to retrieve the following: + +* username +* password +* a valid Keystone identity URL + +For users that have the OpenStack dashboard installed, there's a shortcut. If +you visit the `project/access_and_security` path in Horizon and click on the +"Download OpenStack RC File" button at the top right hand corner, you will +download a bash file that exports all of your access details to environment +variables. To execute the file, run `source admin-openrc.sh` and you will be +prompted for your password. + +### Authentication + +Once you have access to your credentials, you can begin plugging them into +Gophercloud. The next step is authentication, and this is handled by a base +"Provider" struct. To get one, you can either pass in your credentials +explicitly, or tell Gophercloud to use environment variables: + +```go +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/utils" +) + +// Option 1: Pass in the values yourself +opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", +} + +// Option 2: Use a utility function to retrieve all your environment variables +opts, err := openstack.AuthOptionsFromEnv() +``` + +Once you have the `opts` variable, you can pass it in and get back a +`ProviderClient` struct: + +```go +provider, err := openstack.AuthenticatedClient(opts) +``` + +The `ProviderClient` is the top-level client that all of your OpenStack services +derive from. The provider contains all of the authentication details that allow +your Go code to access the API - such as the base URL and token ID. + +### Provision a server + +Once we have a base Provider, we inject it as a dependency into each OpenStack +service. In order to work with the Compute API, we need a Compute service +client; which can be created like so: + +```go +client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), +}) +``` + +We then use this `client` for any Compute API operation we want. In our case, +we want to provision a new server - so we invoke the `Create` method and pass +in the flavor ID (hardware specification) and image ID (operating system) we're +interested in: + +```go +import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + +server, err := servers.Create(client, servers.CreateOpts{ + Name: "My new server!", + FlavorRef: "flavor_id", + ImageRef: "image_id", +}).Extract() +``` + +The above code sample creates a new server with the parameters, and embodies the +new resource in the `server` variable (a +[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct). + +## Advanced Usage + +Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works. + +## Backwards-Compatibility Guarantees + +None. Vendor it and write tests covering the parts you use. + +## Contributing + +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](https://github.com/gophercloud/gophercloud/issues). + +## Thank You + +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/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go new file mode 100644 index 00000000000..5ffa8d1e0a7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -0,0 +1,437 @@ +package gophercloud + +/* +AuthOptions stores information needed to authenticate to an OpenStack Cloud. +You can populate one manually, or use a provider's AuthOptionsFromEnv() function +to read relevant information from the standard environment variables. Pass one +to a provider's AuthenticatedClient function to authenticate and obtain a +ProviderClient representing an active session on that provider. + +Its fields are the union of those recognized by each identity implementation and +provider. + +An example of manually providing authentication information: + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(opts) + +An example of using AuthOptionsFromEnv(), where the environment variables can +be read from a file, such as a standard openrc file: + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(opts) +*/ +type AuthOptions struct { + // IdentityEndpoint specifies the HTTP endpoint that is required to work with + // the Identity API of the appropriate version. While it's ultimately needed by + // all of the identity services, it will often be populated by a provider-level + // function. + // + // The IdentityEndpoint is typically referred to as the "auth_url" or + // "OS_AUTH_URL" in the information provided by the cloud operator. + IdentityEndpoint string `json:"-"` + + // Username is required if using Identity V2 API. Consult with your provider's + // control panel to discover your account's username. In Identity V3, either + // UserID or a combination of Username and DomainID or DomainName are needed. + Username string `json:"username,omitempty"` + UserID string `json:"-"` + + Password string `json:"password,omitempty"` + + // At most one of DomainID and DomainName must be provided if using Username + // with Identity V3. Otherwise, either are optional. + DomainID string `json:"-"` + DomainName string `json:"name,omitempty"` + + // The TenantID and TenantName fields are optional for the Identity V2 API. + // The same fields are known as project_id and project_name in the Identity + // V3 API, but are collected as TenantID and TenantName here in both cases. + // Some providers allow you to specify a TenantName instead of the TenantId. + // Some require both. Your provider's authentication policies will determine + // how these fields influence authentication. + // If DomainID or DomainName are provided, they will also apply to TenantName. + // It is not currently possible to authenticate with Username and a Domain + // and scope to a Project in a different Domain by using TenantName. To + // accomplish that, the ProjectID will need to be provided as the TenantID + // option. + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` + + // AllowReauth should be set to true if you grant permission for Gophercloud to + // cache your credentials in memory, and to allow Gophercloud to attempt to + // re-authenticate automatically if/when your token expires. If you set it to + // false, it will not cache these settings, but re-authentication will not be + // possible. This setting defaults to false. + // + // NOTE: The reauth function will try to re-authenticate endlessly if left + // unchecked. The way to limit the number of attempts is to provide a custom + // HTTP client to the provider client and provide a transport that implements + // the RoundTripper interface and stores the number of failed retries. For an + // example of this, see here: + // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 + AllowReauth bool `json:"-"` + + // 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 +// interface in the v2 tokens package +func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { + // Populate the request map. + authMap := make(map[string]interface{}) + + if opts.Username != "" { + if opts.Password != "" { + authMap["passwordCredentials"] = map[string]interface{}{ + "username": opts.Username, + "password": opts.Password, + } + } else { + return nil, ErrMissingInput{Argument: "Password"} + } + } else if opts.TokenID != "" { + authMap["token"] = map[string]interface{}{ + "id": opts.TokenID, + } + } else { + return nil, ErrMissingInput{Argument: "Username"} + } + + if opts.TenantID != "" { + authMap["tenantId"] = opts.TenantID + } + if opts.TenantName != "" { + authMap["tenantName"] = opts.TenantName + } + + return map[string]interface{}{"auth": authMap}, nil +} + +func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { + type domainReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + } + + type projectReq struct { + Domain *domainReq `json:"domain,omitempty"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + } + + type userReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Password string `json:"password,omitempty"` + Domain *domainReq `json:"domain,omitempty"` + } + + type passwordReq struct { + User userReq `json:"user"` + } + + type tokenReq struct { + 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"` + ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` + } + + type authReq struct { + Identity identityReq `json:"identity"` + } + + type request struct { + Auth authReq `json:"auth"` + } + + // Populate the request structure based on the provided arguments. Create and return an error + // if insufficient or incompatible information is present. + var req request + + if opts.Password == "" { + if opts.TokenID != "" { + // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication + // parameters. + if opts.Username != "" { + return nil, ErrUsernameWithToken{} + } + if opts.UserID != "" { + return nil, ErrUserIDWithToken{} + } + if opts.DomainID != "" { + return nil, ErrDomainIDWithToken{} + } + if opts.DomainName != "" { + return nil, ErrDomainNameWithToken{} + } + + // Configure the request for Token authentication. + req.Auth.Identity.Methods = []string{"token"} + 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 or ApplicationCredential are available, authentication can't continue. + return nil, ErrMissingPassword{} + } + } else { + // Password authentication. + req.Auth.Identity.Methods = []string{"password"} + + // At least one of Username and UserID must be specified. + if opts.Username == "" && opts.UserID == "" { + return nil, ErrUsernameOrUserID{} + } + + if opts.Username != "" { + // If Username is provided, UserID may not be provided. + if opts.UserID != "" { + return nil, ErrUsernameOrUserID{} + } + + // Either DomainID or DomainName must also be specified. + if opts.DomainID == "" && opts.DomainName == "" { + return nil, ErrDomainIDOrDomainName{} + } + + if opts.DomainID != "" { + if opts.DomainName != "" { + return nil, ErrDomainIDOrDomainName{} + } + + // Configure the request for Username and Password authentication with a DomainID. + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + Name: &opts.Username, + Password: opts.Password, + Domain: &domainReq{ID: &opts.DomainID}, + }, + } + } + + if opts.DomainName != "" { + // Configure the request for Username and Password authentication with a DomainName. + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + Name: &opts.Username, + Password: opts.Password, + Domain: &domainReq{Name: &opts.DomainName}, + }, + } + } + } + + if opts.UserID != "" { + // If UserID is specified, neither DomainID nor DomainName may be. + if opts.DomainID != "" { + return nil, ErrDomainIDWithUserID{} + } + if opts.DomainName != "" { + return nil, ErrDomainNameWithUserID{} + } + + // Configure the request for UserID and Password authentication. + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ID: &opts.UserID, Password: opts.Password}, + } + } + } + + b, err := BuildRequestBody(req, "") + if err != nil { + return nil, err + } + + if len(scope) != 0 { + b["auth"].(map[string]interface{})["scope"] = scope + } + + return b, nil +} + +func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { + // 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 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, ErrScopeDomainIDOrDomainName{} + } + if opts.Scope.ProjectID != "" { + return nil, 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, ErrScopeProjectIDAlone{} + } + if opts.Scope.DomainName != "" { + return nil, 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, 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 + } + + return nil, nil +} + +func (opts AuthOptions) CanReauth() bool { + return opts.AllowReauth +} 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 00000000000..2e4699b978c --- /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 new file mode 100644 index 00000000000..953ca822a97 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/doc.go @@ -0,0 +1,110 @@ +/* +Package gophercloud provides a multi-vendor interface to OpenStack-compatible +clouds. The library has a three-level hierarchy: providers, services, and +resources. + +Authenticating with Providers + +Provider structs represent the cloud providers that offer and manage a +collection of services. You will generally want to create one Provider +client per OpenStack cloud. + + It is now recommended to use the `clientconfig` package found at + https://github.com/gophercloud/utils/tree/master/openstack/clientconfig + for all authentication purposes. + + The below documentation is still relevant. clientconfig simply implements + the below and presents it in an easier and more flexible way. + +Use your OpenStack credentials to create a Provider client. The +IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in +information provided by the cloud operator. Additionally, the cloud may refer to +TenantID or TenantName as project_id and project_name. Credentials are +specified like so: + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(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 +OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant" +instead of "project". + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(opts) + +Service Clients + +Service structs are specific to a provider and handle all of the logic and +operations for a particular OpenStack service. Examples of services include: +Compute, Object Storage, Block Storage. In order to define one, you need to +pass in the parent provider, like so: + + opts := gophercloud.EndpointOpts{Region: "RegionOne"} + + client, err := openstack.NewComputeV2(provider, opts) + +Resources + +Resource structs are the domain models that services make use of in order +to work with and represent the state of API resources: + + server, err := servers.Get(client, "{serverId}").Extract() + +Intermediate Result structs are returned for API operations, which allow +generic access to the HTTP headers, response body, and any errors associated +with the network transaction. To turn a result into a usable resource struct, +you must call the Extract method which is chained to the response, or an +Extract function from an applicable extension: + + result := servers.Get(client, "{serverId}") + + // Attempt to extract the disk configuration from the OS-DCF disk config + // extension: + config, err := diskconfig.ExtractGet(result) + +All requests that enumerate a collection return a Pager struct that is used to +iterate through the results one page at a time. Use the EachPage method on that +Pager to handle each successive Page in a closure, then use the appropriate +extraction method from that request's package to interpret that Page as a slice +of results: + + err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + + // Handle the []servers.Server slice. + + // Return "false" or an error to prematurely stop fetching new pages. + return true, nil + }) + +If you want to obtain the entire collection of pages without doing any +intermediary processing on each page, you can use the AllPages method: + + allPages, err := servers.List(client, nil).AllPages() + allServers, err := servers.ExtractServers(allPages) + +This top-level package contains utility functions and data types that are used +throughout the provider and service packages. Of particular note for end users +are the AuthOptions and EndpointOpts structs. +*/ +package gophercloud diff --git a/vendor/github.com/gophercloud/gophercloud/endpoint_search.go b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go new file mode 100644 index 00000000000..2fbc3c97f14 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go @@ -0,0 +1,76 @@ +package gophercloud + +// Availability indicates to whom a specific service endpoint is accessible: +// the internet at large, internal networks only, or only to administrators. +// Different identity services use different terminology for these. Identity v2 +// lists them as different kinds of URLs within the service catalog ("adminURL", +// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an +// endpoint's response. +type Availability string + +const ( + // AvailabilityAdmin indicates that an endpoint is only available to + // administrators. + AvailabilityAdmin Availability = "admin" + + // AvailabilityPublic indicates that an endpoint is available to everyone on + // the internet. + AvailabilityPublic Availability = "public" + + // AvailabilityInternal indicates that an endpoint is only available within + // the cluster's internal network. + AvailabilityInternal Availability = "internal" +) + +// EndpointOpts specifies search criteria used by queries against an +// OpenStack service catalog. The options must contain enough information to +// unambiguously identify one, and only one, endpoint within the catalog. +// +// Usually, these are passed to service client factory functions in a provider +// package, like "openstack.NewComputeV2()". +type EndpointOpts struct { + // Type [required] is the service type for the client (e.g., "compute", + // "object-store"). Generally, this will be supplied by the service client + // function, but a user-given value will be honored if provided. + Type string + + // Name [optional] is the service name for the client (e.g., "nova") as it + // appears in the service catalog. Services can have the same Type but a + // different Name, which is why both Type and Name are sometimes needed. + Name string + + // Region [required] is the geographic region in which the endpoint resides, + // generally specifying which datacenter should house your resources. + // Required only for services that span multiple regions. + Region string + + // Availability [optional] is the visibility of the endpoint to be returned. + // Valid types include the constants AvailabilityPublic, AvailabilityInternal, + // or AvailabilityAdmin from this package. + // + // Availability is not required, and defaults to AvailabilityPublic. Not all + // providers or services offer all Availability options. + Availability Availability +} + +/* +EndpointLocator is an internal function to be used by provider implementations. + +It provides an implementation that locates a single endpoint from a service +catalog for a specific ProviderClient based on user-provided EndpointOpts. The +provider then uses it to discover related ServiceClients. +*/ +type EndpointLocator func(EndpointOpts) (string, error) + +// ApplyDefaults is an internal method to be used by provider implementations. +// +// It sets EndpointOpts fields if not already set, including a default type. +// Currently, EndpointOpts.Availability defaults to the public endpoint. +func (eo *EndpointOpts) ApplyDefaults(t string) { + if eo.Type == "" { + eo.Type = t + } + if eo.Availability == "" { + eo.Availability = AvailabilityPublic + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go new file mode 100644 index 00000000000..0bcb3af7f00 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/errors.go @@ -0,0 +1,471 @@ +package gophercloud + +import ( + "fmt" + "strings" +) + +// BaseError is an error type that all other error types embed. +type BaseError struct { + DefaultErrString string + Info string +} + +func (e BaseError) Error() string { + e.DefaultErrString = "An error occurred while executing a Gophercloud request." + return e.choseErrString() +} + +func (e BaseError) choseErrString() string { + if e.Info != "" { + return e.Info + } + return e.DefaultErrString +} + +// ErrMissingInput is the error when input is required in a particular +// situation but not provided by the user +type ErrMissingInput struct { + BaseError + Argument string +} + +func (e ErrMissingInput) Error() string { + e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument) + return e.choseErrString() +} + +// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors. +type ErrInvalidInput struct { + ErrMissingInput + Value interface{} +} + +func (e ErrInvalidInput) Error() string { + e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value) + 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 { + BaseError + URL string + Method string + Expected []int + Actual int + Body []byte +} + +func (e ErrUnexpectedResponseCode) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s", + e.Expected, e.Method, e.URL, e.Actual, e.Body, + ) + return e.choseErrString() +} + +// ErrDefault400 is the default error type returned on a 400 HTTP response code. +type ErrDefault400 struct { + ErrUnexpectedResponseCode +} + +// ErrDefault401 is the default error type returned on a 401 HTTP response code. +type ErrDefault401 struct { + ErrUnexpectedResponseCode +} + +// ErrDefault403 is the default error type returned on a 403 HTTP response code. +type ErrDefault403 struct { + ErrUnexpectedResponseCode +} + +// ErrDefault404 is the default error type returned on a 404 HTTP response code. +type ErrDefault404 struct { + ErrUnexpectedResponseCode +} + +// ErrDefault405 is the default error type returned on a 405 HTTP response code. +type ErrDefault405 struct { + ErrUnexpectedResponseCode +} + +// ErrDefault408 is the default error type returned on a 408 HTTP response code. +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 +} + +// ErrDefault500 is the default error type returned on a 500 HTTP response code. +type ErrDefault500 struct { + ErrUnexpectedResponseCode +} + +// ErrDefault503 is the default error type returned on a 503 HTTP response code. +type ErrDefault503 struct { + ErrUnexpectedResponseCode +} + +func (e ErrDefault400) Error() string { + 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" +} +func (e ErrDefault403) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Request forbidden: [%s %s], error message: %s", + e.Method, e.URL, e.Body, + ) + return e.choseErrString() +} +func (e ErrDefault404) Error() string { + return "Resource not found" +} +func (e ErrDefault405) Error() string { + return "Method not allowed" +} +func (e ErrDefault408) Error() string { + return "The server timed out waiting for the request" +} +func (e ErrDefault429) Error() string { + return "Too many requests have been sent in a given amount of time. Pause" + + " requests, wait up to one minute, and try again." +} +func (e ErrDefault500) Error() string { + return "Internal Server Error" +} +func (e ErrDefault503) Error() string { + return "The service is currently unable to handle the request due to a temporary" + + " overloading or maintenance. This is a temporary condition. Try again later." +} + +// Err400er is the interface resource error types implement to override the error message +// from a 400 error. +type Err400er interface { + Error400(ErrUnexpectedResponseCode) error +} + +// Err401er is the interface resource error types implement to override the error message +// from a 401 error. +type Err401er interface { + Error401(ErrUnexpectedResponseCode) error +} + +// Err403er is the interface resource error types implement to override the error message +// from a 403 error. +type Err403er interface { + Error403(ErrUnexpectedResponseCode) error +} + +// Err404er is the interface resource error types implement to override the error message +// from a 404 error. +type Err404er interface { + Error404(ErrUnexpectedResponseCode) error +} + +// Err405er is the interface resource error types implement to override the error message +// from a 405 error. +type Err405er interface { + Error405(ErrUnexpectedResponseCode) error +} + +// Err408er is the interface resource error types implement to override the error message +// from a 408 error. +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 { + Error429(ErrUnexpectedResponseCode) error +} + +// Err500er is the interface resource error types implement to override the error message +// from a 500 error. +type Err500er interface { + Error500(ErrUnexpectedResponseCode) error +} + +// Err503er is the interface resource error types implement to override the error message +// from a 503 error. +type Err503er interface { + Error503(ErrUnexpectedResponseCode) error +} + +// ErrTimeOut is the error type returned when an operations times out. +type ErrTimeOut struct { + BaseError +} + +func (e ErrTimeOut) Error() string { + e.DefaultErrString = "A time out occurred" + return e.choseErrString() +} + +// ErrUnableToReauthenticate is the error type returned when reauthentication fails. +type ErrUnableToReauthenticate struct { + BaseError + ErrOriginal error +} + +func (e ErrUnableToReauthenticate) Error() string { + e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal) + return e.choseErrString() +} + +// ErrErrorAfterReauthentication is the error type returned when reauthentication +// succeeds, but an error occurs afterword (usually an HTTP error). +type ErrErrorAfterReauthentication struct { + BaseError + ErrOriginal error +} + +func (e ErrErrorAfterReauthentication) Error() string { + e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal) + return e.choseErrString() +} + +// ErrServiceNotFound is returned when no service in a service catalog matches +// the provided EndpointOpts. This is generally returned by provider service +// factory methods like "NewComputeV2()" and can mean that a service is not +// enabled for your account. +type ErrServiceNotFound struct { + BaseError +} + +func (e ErrServiceNotFound) Error() string { + e.DefaultErrString = "No suitable service could be found in the service catalog." + return e.choseErrString() +} + +// ErrEndpointNotFound is returned when no available endpoints match the +// provided EndpointOpts. This is also generally returned by provider service +// factory methods, and usually indicates that a region was specified +// incorrectly. +type ErrEndpointNotFound struct { + BaseError +} + +func (e ErrEndpointNotFound) Error() string { + e.DefaultErrString = "No suitable endpoint could be found in the service catalog." + return e.choseErrString() +} + +// ErrResourceNotFound is the error when trying to retrieve a resource's +// ID by name and the resource doesn't exist. +type ErrResourceNotFound struct { + BaseError + Name string + ResourceType string +} + +func (e ErrResourceNotFound) Error() string { + e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name) + return e.choseErrString() +} + +// ErrMultipleResourcesFound is the error when trying to retrieve a resource's +// ID by name and multiple resources have the user-provided name. +type ErrMultipleResourcesFound struct { + BaseError + Name string + Count int + ResourceType string +} + +func (e ErrMultipleResourcesFound) Error() string { + e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name) + return e.choseErrString() +} + +// ErrUnexpectedType is the error when an unexpected type is encountered +type ErrUnexpectedType struct { + BaseError + Expected string + Actual string +} + +func (e ErrUnexpectedType) Error() string { + e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual) + return e.choseErrString() +} + +func unacceptedAttributeErr(attribute string) string { + return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute) +} + +func redundantWithTokenErr(attribute string) string { + return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute) +} + +func redundantWithUserID(attribute string) string { + return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute) +} + +// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used. +type ErrAPIKeyProvided struct{ BaseError } + +func (e ErrAPIKeyProvided) Error() string { + return unacceptedAttributeErr("APIKey") +} + +// ErrTenantIDProvided indicates that a TenantID was provided but can't be used. +type ErrTenantIDProvided struct{ BaseError } + +func (e ErrTenantIDProvided) Error() string { + return unacceptedAttributeErr("TenantID") +} + +// ErrTenantNameProvided indicates that a TenantName was provided but can't be used. +type ErrTenantNameProvided struct{ BaseError } + +func (e ErrTenantNameProvided) Error() string { + return unacceptedAttributeErr("TenantName") +} + +// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead. +type ErrUsernameWithToken struct{ BaseError } + +func (e ErrUsernameWithToken) Error() string { + return redundantWithTokenErr("Username") +} + +// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead. +type ErrUserIDWithToken struct{ BaseError } + +func (e ErrUserIDWithToken) Error() string { + return redundantWithTokenErr("UserID") +} + +// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead. +type ErrDomainIDWithToken struct{ BaseError } + +func (e ErrDomainIDWithToken) Error() string { + return redundantWithTokenErr("DomainID") +} + +// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s +type ErrDomainNameWithToken struct{ BaseError } + +func (e ErrDomainNameWithToken) Error() string { + return redundantWithTokenErr("DomainName") +} + +// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once. +type ErrUsernameOrUserID struct{ BaseError } + +func (e ErrUsernameOrUserID) Error() string { + return "Exactly one of Username and UserID must be provided for password authentication" +} + +// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used. +type ErrDomainIDWithUserID struct{ BaseError } + +func (e ErrDomainIDWithUserID) Error() string { + return redundantWithUserID("DomainID") +} + +// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used. +type ErrDomainNameWithUserID struct{ BaseError } + +func (e ErrDomainNameWithUserID) Error() string { + return redundantWithUserID("DomainName") +} + +// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it. +// It may also indicate that both a DomainID and a DomainName were provided at once. +type ErrDomainIDOrDomainName struct{ BaseError } + +func (e ErrDomainIDOrDomainName) Error() string { + return "You must provide exactly one of DomainID or DomainName to authenticate by Username" +} + +// ErrMissingPassword indicates that no password was provided and no token is available. +type ErrMissingPassword struct{ BaseError } + +func (e ErrMissingPassword) Error() string { + return "You must provide a password to authenticate" +} + +// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present. +type ErrScopeDomainIDOrDomainName struct{ BaseError } + +func (e ErrScopeDomainIDOrDomainName) Error() string { + return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName" +} + +// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope. +type ErrScopeProjectIDOrProjectName struct{ BaseError } + +func (e ErrScopeProjectIDOrProjectName) Error() string { + return "You must provide at most one of ProjectID or ProjectName in a Scope" +} + +// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope. +type ErrScopeProjectIDAlone struct{ BaseError } + +func (e ErrScopeProjectIDAlone) Error() string { + return "ProjectID must be supplied alone in a Scope" +} + +// ErrScopeEmpty indicates that no credentials were provided in a Scope. +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/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go new file mode 100644 index 00000000000..0e8d90ff826 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go @@ -0,0 +1,125 @@ +package openstack + +import ( + "os" + + "github.com/gophercloud/gophercloud" +) + +var nilOptions = gophercloud.AuthOptions{} + +/* +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 and OS_PROJECT_ID. + +Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings, +or an error will result. OS_PROJECT_ID, is optional. + +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: + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(opts) +*/ +func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { + authURL := os.Getenv("OS_AUTH_URL") + username := os.Getenv("OS_USERNAME") + userID := os.Getenv("OS_USERID") + password := os.Getenv("OS_PASSWORD") + tenantID := os.Getenv("OS_TENANT_ID") + 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 != "" { + tenantID = v + } + + // If OS_PROJECT_NAME is set, overwrite tenantName with the value. + if v := os.Getenv("OS_PROJECT_NAME"); v != "" { + tenantName = v + } + + if authURL == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_AUTH_URL", + } + return nilOptions, err + } + + 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 (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, + ApplicationCredentialID: applicationCredentialID, + ApplicationCredentialName: applicationCredentialName, + ApplicationCredentialSecret: applicationCredentialSecret, + } + + return ao, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go new file mode 100644 index 00000000000..a78d3d0482c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go @@ -0,0 +1,86 @@ +/* +Package volumeactions provides information and interaction with volumes in the +OpenStack Block Storage service. A volume is a detachable block storage +device, akin to a USB hard drive. + +Example of Attaching a Volume to an Instance + + attachOpts := volumeactions.AttachOpts{ + MountPoint: "/mnt", + Mode: "rw", + InstanceUUID: server.ID, + } + + err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr() + if err != nil { + panic(err) + } + + detachOpts := volumeactions.DetachOpts{ + AttachmentID: volume.Attachments[0].AttachmentID, + } + + err = volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr() + if err != nil { + panic(err) + } + + +Example of Creating an Image from a Volume + + uploadImageOpts := volumeactions.UploadImageOpts{ + ImageName: "my_vol", + Force: true, + } + + volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", volumeImage) + +Example of Extending a Volume's Size + + extendOpts := volumeactions.ExtendSizeOpts{ + NewSize: 100, + } + + err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example of Initializing a Volume Connection + + connectOpts := &volumeactions.InitializeConnectionOpts{ + IP: "127.0.0.1", + Host: "stack", + Initiator: "iqn.1994-05.com.redhat:17cf566367d2", + Multipath: gophercloud.Disabled, + Platform: "x86_64", + OSType: "linux2", + } + + connectionInfo, err := volumeactions.InitializeConnection(client, volume.ID, connectOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", connectionInfo["data"]) + + terminateOpts := &volumeactions.InitializeConnectionOpts{ + IP: "127.0.0.1", + Host: "stack", + Initiator: "iqn.1994-05.com.redhat:17cf566367d2", + Multipath: gophercloud.Disabled, + Platform: "x86_64", + OSType: "linux2", + } + + err = volumeactions.TerminateConnection(client, volume.ID, terminateOpts).ExtractErr() + if err != nil { + panic(err) + } +*/ +package volumeactions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go new file mode 100644 index 00000000000..d18bff555b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go @@ -0,0 +1,269 @@ +package volumeactions + +import ( + "github.com/gophercloud/gophercloud" +) + +// AttachOptsBuilder allows extensions to add additional parameters to the +// Attach request. +type AttachOptsBuilder interface { + ToVolumeAttachMap() (map[string]interface{}, error) +} + +// AttachMode describes the attachment mode for volumes. +type AttachMode string + +// These constants determine how a volume is attached. +const ( + ReadOnly AttachMode = "ro" + ReadWrite AttachMode = "rw" +) + +// AttachOpts contains options for attaching a Volume. +type AttachOpts struct { + // The mountpoint of this volume. + MountPoint string `json:"mountpoint,omitempty"` + + // The nova instance ID, can't set simultaneously with HostName. + InstanceUUID string `json:"instance_uuid,omitempty"` + + // The hostname of baremetal host, can't set simultaneously with InstanceUUID. + HostName string `json:"host_name,omitempty"` + + // Mount mode of this volume. + Mode AttachMode `json:"mode,omitempty"` +} + +// ToVolumeAttachMap assembles a request body based on the contents of a +// AttachOpts. +func (opts AttachOpts) ToVolumeAttachMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-attach") +} + +// Attach will attach a volume based on the values in AttachOpts. +func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder) (r AttachResult) { + b, err := opts.ToVolumeAttachMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// BeginDetach will mark the volume as detaching. +func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) { + b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})} + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// DetachOptsBuilder allows extensions to add additional parameters to the +// Detach request. +type DetachOptsBuilder interface { + ToVolumeDetachMap() (map[string]interface{}, error) +} + +// DetachOpts contains options for detaching a Volume. +type DetachOpts struct { + // AttachmentID is the ID of the attachment between a volume and instance. + AttachmentID string `json:"attachment_id,omitempty"` +} + +// ToVolumeDetachMap assembles a request body based on the contents of a +// DetachOpts. +func (opts DetachOpts) ToVolumeDetachMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-detach") +} + +// Detach will detach a volume based on volume ID. +func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder) (r DetachResult) { + b, err := opts.ToVolumeDetachMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// Reserve will reserve a volume based on volume ID. +func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) { + b := map[string]interface{}{"os-reserve": make(map[string]interface{})} + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// Unreserve will unreserve a volume based on volume ID. +func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) { + b := map[string]interface{}{"os-unreserve": make(map[string]interface{})} + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// InitializeConnectionOptsBuilder allows extensions to add additional parameters to the +// InitializeConnection request. +type InitializeConnectionOptsBuilder interface { + ToVolumeInitializeConnectionMap() (map[string]interface{}, error) +} + +// InitializeConnectionOpts hosts options for InitializeConnection. +// The fields are specific to the storage driver in use and the destination +// attachment. +type InitializeConnectionOpts struct { + IP string `json:"ip,omitempty"` + Host string `json:"host,omitempty"` + Initiator string `json:"initiator,omitempty"` + Wwpns []string `json:"wwpns,omitempty"` + Wwnns string `json:"wwnns,omitempty"` + Multipath *bool `json:"multipath,omitempty"` + Platform string `json:"platform,omitempty"` + OSType string `json:"os_type,omitempty"` +} + +// ToVolumeInitializeConnectionMap assembles a request body based on the contents of a +// InitializeConnectionOpts. +func (opts InitializeConnectionOpts) ToVolumeInitializeConnectionMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "connector") + return map[string]interface{}{"os-initialize_connection": b}, err +} + +// InitializeConnection initializes an iSCSI connection by volume ID. +func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) { + b, err := opts.ToVolumeInitializeConnectionMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// TerminateConnectionOptsBuilder allows extensions to add additional parameters to the +// TerminateConnection request. +type TerminateConnectionOptsBuilder interface { + ToVolumeTerminateConnectionMap() (map[string]interface{}, error) +} + +// TerminateConnectionOpts hosts options for TerminateConnection. +type TerminateConnectionOpts struct { + IP string `json:"ip,omitempty"` + Host string `json:"host,omitempty"` + Initiator string `json:"initiator,omitempty"` + Wwpns []string `json:"wwpns,omitempty"` + Wwnns string `json:"wwnns,omitempty"` + Multipath *bool `json:"multipath,omitempty"` + Platform string `json:"platform,omitempty"` + OSType string `json:"os_type,omitempty"` +} + +// ToVolumeTerminateConnectionMap assembles a request body based on the contents of a +// TerminateConnectionOpts. +func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "connector") + return map[string]interface{}{"os-terminate_connection": b}, err +} + +// TerminateConnection terminates an iSCSI connection by volume ID. +func TerminateConnection(client *gophercloud.ServiceClient, id string, opts TerminateConnectionOptsBuilder) (r TerminateConnectionResult) { + b, err := opts.ToVolumeTerminateConnectionMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// ExtendSizeOptsBuilder allows extensions to add additional parameters to the +// ExtendSize request. +type ExtendSizeOptsBuilder interface { + ToVolumeExtendSizeMap() (map[string]interface{}, error) +} + +// ExtendSizeOpts contains options for extending the size of an existing Volume. +// This object is passed to the volumes.ExtendSize function. +type ExtendSizeOpts struct { + // NewSize is the new size of the volume, in GB. + NewSize int `json:"new_size" required:"true"` +} + +// ToVolumeExtendSizeMap assembles a request body based on the contents of an +// ExtendSizeOpts. +func (opts ExtendSizeOpts) ToVolumeExtendSizeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-extend") +} + +// ExtendSize will extend the size of the volume based on the provided information. +// This operation does not return a response body. +func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOptsBuilder) (r ExtendSizeResult) { + b, err := opts.ToVolumeExtendSizeMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// UploadImageOptsBuilder allows extensions to add additional parameters to the +// UploadImage request. +type UploadImageOptsBuilder interface { + ToVolumeUploadImageMap() (map[string]interface{}, error) +} + +// UploadImageOpts contains options for uploading a Volume to image storage. +type UploadImageOpts struct { + // Container format, may be bare, ofv, ova, etc. + ContainerFormat string `json:"container_format,omitempty"` + + // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. + DiskFormat string `json:"disk_format,omitempty"` + + // The name of image that will be stored in glance. + ImageName string `json:"image_name,omitempty"` + + // Force image creation, usable if volume attached to instance. + Force bool `json:"force,omitempty"` +} + +// ToVolumeUploadImageMap assembles a request body based on the contents of a +// UploadImageOpts. +func (opts UploadImageOpts) ToVolumeUploadImageMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "os-volume_upload_image") +} + +// UploadImage will upload an image based on the values in UploadImageOptsBuilder. +func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageOptsBuilder) (r UploadImageResult) { + b, err := opts.ToVolumeUploadImageMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// ForceDelete will delete the volume regardless of state. +func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-force_delete": ""}, nil, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go new file mode 100644 index 00000000000..5cadd360f20 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go @@ -0,0 +1,191 @@ +package volumeactions + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" +) + +// AttachResult contains the response body and error from an Attach request. +type AttachResult struct { + gophercloud.ErrResult +} + +// BeginDetachingResult contains the response body and error from a BeginDetach +// request. +type BeginDetachingResult struct { + gophercloud.ErrResult +} + +// DetachResult contains the response body and error from a Detach request. +type DetachResult struct { + gophercloud.ErrResult +} + +// UploadImageResult contains the response body and error from an UploadImage +// request. +type UploadImageResult struct { + gophercloud.Result +} + +// ReserveResult contains the response body and error from a Reserve request. +type ReserveResult struct { + gophercloud.ErrResult +} + +// UnreserveResult contains the response body and error from an Unreserve +// request. +type UnreserveResult struct { + gophercloud.ErrResult +} + +// TerminateConnectionResult contains the response body and error from a +// TerminateConnection request. +type TerminateConnectionResult struct { + gophercloud.ErrResult +} + +// InitializeConnectionResult contains the response body and error from an +// InitializeConnection request. +type InitializeConnectionResult struct { + gophercloud.Result +} + +// ExtendSizeResult contains the response body and error from an ExtendSize request. +type ExtendSizeResult struct { + gophercloud.ErrResult +} + +// Extract will get the connection information out of the +// InitializeConnectionResult object. +// +// This will be a generic map[string]interface{} and the results will be +// dependent on the type of connection made. +func (r InitializeConnectionResult) Extract() (map[string]interface{}, error) { + var s struct { + ConnectionInfo map[string]interface{} `json:"connection_info"` + } + err := r.ExtractInto(&s) + return s.ConnectionInfo, err +} + +// ImageVolumeType contains volume type information obtained from UploadImage +// action. +type ImageVolumeType struct { + // The ID of a volume type. + ID string `json:"id"` + + // Human-readable display name for the volume type. + Name string `json:"name"` + + // Human-readable description for the volume type. + Description string `json:"display_description"` + + // Flag for public access. + IsPublic bool `json:"is_public"` + + // Extra specifications for volume type. + ExtraSpecs map[string]interface{} `json:"extra_specs"` + + // ID of quality of service specs. + QosSpecsID string `json:"qos_specs_id"` + + // Flag for deletion status of volume type. + Deleted bool `json:"deleted"` + + // The date when volume type was deleted. + DeletedAt time.Time `json:"-"` + + // The date when volume type was created. + CreatedAt time.Time `json:"-"` + + // The date when this volume was last updated. + UpdatedAt time.Time `json:"-"` +} + +func (r *ImageVolumeType) UnmarshalJSON(b []byte) error { + type tmp ImageVolumeType + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + DeletedAt gophercloud.JSONRFC3339MilliNoZ `json:"deleted_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ImageVolumeType(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + r.DeletedAt = time.Time(s.DeletedAt) + + return err +} + +// VolumeImage contains information about volume uploaded to an image service. +type VolumeImage struct { + // The ID of a volume an image is created from. + VolumeID string `json:"id"` + + // Container format, may be bare, ofv, ova, etc. + ContainerFormat string `json:"container_format"` + + // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. + DiskFormat string `json:"disk_format"` + + // Human-readable description for the volume. + Description string `json:"display_description"` + + // The ID of the created image. + ImageID string `json:"image_id"` + + // Human-readable display name for the image. + ImageName string `json:"image_name"` + + // Size of the volume in GB. + Size int `json:"size"` + + // Current status of the volume. + Status string `json:"status"` + + // The date when this volume was last updated. + UpdatedAt time.Time `json:"-"` + + // Volume type object of used volume. + VolumeType ImageVolumeType `json:"volume_type"` +} + +func (r *VolumeImage) UnmarshalJSON(b []byte) error { + type tmp VolumeImage + var s struct { + tmp + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = VolumeImage(s.tmp) + + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + +// Extract will get an object with info about the uploaded image out of the +// UploadImageResult object. +func (r UploadImageResult) Extract() (VolumeImage, error) { + var s struct { + VolumeImage VolumeImage `json:"os-volume_upload_image"` + } + err := r.ExtractInto(&s) + return s.VolumeImage, err +} + +// ForceDeleteResult contains the response body and error from a ForceDelete request. +type ForceDeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go new file mode 100644 index 00000000000..20486ed7194 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go @@ -0,0 +1,7 @@ +package volumeactions + +import "github.com/gophercloud/gophercloud" + +func actionURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("volumes", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go new file mode 100644 index 00000000000..307b8b12d2f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go @@ -0,0 +1,5 @@ +// Package volumes provides information and interaction with volumes in the +// OpenStack Block Storage service. A volume is a detachable block storage +// device, akin to a USB hard drive. It can only be attached to one instance at +// a time. +package volumes 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 new file mode 100644 index 00000000000..1da94238b9c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go @@ -0,0 +1,172 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToVolumeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a Volume. This object is passed to +// the volumes.Create function. For more information about these parameters, +// see the Volume object. +type CreateOpts struct { + Size int `json:"size" required:"true"` + AvailabilityZone string `json:"availability_zone,omitempty"` + Description string `json:"display_description,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + Name string `json:"display_name,omitempty"` + SnapshotID string `json:"snapshot_id,omitempty"` + SourceVolID string `json:"source_volid,omitempty"` + ImageID string `json:"imageRef,omitempty"` + VolumeType string `json:"volume_type,omitempty"` +} + +// ToVolumeCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Create will create a new Volume based on the values in CreateOpts. To extract +// the Volume object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToVolumeCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// 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) + return +} + +// Get retrieves the Volume with the provided ID. To extract the Volume 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 { + ToVolumeListQuery() (string, error) +} + +// ListOpts holds options for listing Volumes. It is passed to the volumes.List +// function. +type ListOpts struct { + // admin-only option. Set it to true to see all tenant volumes. + AllTenants bool `q:"all_tenants"` + // List only volumes that contain Metadata. + Metadata map[string]string `q:"metadata"` + // List only volumes that have Name as the display name. + Name string `q:"display_name"` + // List only volumes that have a status of Status. + Status string `q:"status"` +} + +// ToVolumeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToVolumeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Volumes 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.ToVolumeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return VolumePage{pagination.SinglePageBase(r)} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToVolumeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Volume. This object is passed +// 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"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToVolumeUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Update will update the Volume with provided information. To extract the updated +// Volume from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToVolumeUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// IDFromName is a convienience function that returns a server's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractVolumes(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go new file mode 100644 index 00000000000..7f68d148639 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go @@ -0,0 +1,109 @@ +package volumes + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Volume contains all the information associated with an OpenStack Volume. +type Volume struct { + // Current status of the volume. + Status string `json:"status"` + // Human-readable display name for the volume. + Name string `json:"display_name"` + // Instances onto which the volume is attached. + Attachments []map[string]interface{} `json:"attachments"` + // This parameter is no longer used. + AvailabilityZone string `json:"availability_zone"` + // Indicates whether this is a bootable volume. + Bootable string `json:"bootable"` + // The date when this volume was created. + CreatedAt time.Time `json:"-"` + // Human-readable description for the volume. + Description string `json:"display_description"` + // The type of volume to create, either SATA or SSD. + VolumeType string `json:"volume_type"` + // The ID of the snapshot from which the volume was created + SnapshotID string `json:"snapshot_id"` + // The ID of another block storage volume from which the current volume was created + SourceVolID string `json:"source_volid"` + // Arbitrary key-value pairs defined by the user. + Metadata map[string]string `json:"metadata"` + // Unique identifier for the volume. + ID string `json:"id"` + // Size of the volume in GB. + Size int `json:"size"` +} + +func (r *Volume) UnmarshalJSON(b []byte) error { + type tmp Volume + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Volume(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + + return err +} + +// 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 +} + +// VolumePage is a pagination.pager that is returned from a call to the List function. +type VolumePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a VolumePage contains no Volumes. +func (r VolumePage) IsEmpty() (bool, error) { + volumes, err := ExtractVolumes(r) + return len(volumes) == 0, err +} + +// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. +func ExtractVolumes(r pagination.Page) ([]Volume, error) { + var s struct { + Volumes []Volume `json:"volumes"` + } + err := (r.(VolumePage)).ExtractInto(&s) + return s.Volumes, 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 Volume object out of the commonResult object. +func (r commonResult) Extract() (*Volume, error) { + var s struct { + Volume *Volume `json:"volume"` + } + err := r.ExtractInto(&s) + return s.Volume, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go new file mode 100644 index 00000000000..8a00f97e98c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go @@ -0,0 +1,23 @@ +package volumes + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes") +} + +func listURL(c *gophercloud.ServiceClient) string { + return createURL(c) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("volumes", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go new file mode 100644 index 00000000000..e86c1b4b4ee --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go @@ -0,0 +1,22 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" +) + +// WaitForStatus will continually poll the resource, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := Get(c, 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/openstack/blockstorage/v2/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go new file mode 100644 index 00000000000..307b8b12d2f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go @@ -0,0 +1,5 @@ +// Package volumes provides information and interaction with volumes in the +// OpenStack Block Storage service. A volume is a detachable block storage +// device, akin to a USB hard drive. It can only be attached to one instance at +// a time. +package volumes 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 new file mode 100644 index 00000000000..c27ddbf67c3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go @@ -0,0 +1,235 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToVolumeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a Volume. This object is passed to +// the volumes.Create function. For more information about these parameters, +// see the Volume object. +type CreateOpts struct { + // The size of the volume, in GB + Size int `json:"size" required:"true"` + // The availability zone + AvailabilityZone string `json:"availability_zone,omitempty"` + // ConsistencyGroupID is the ID of a consistency group + ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` + // The volume description + Description string `json:"description,omitempty"` + // One or more metadata key and value pairs to associate with the volume + Metadata map[string]string `json:"metadata,omitempty"` + // The volume name + Name string `json:"name,omitempty"` + // the ID of the existing volume snapshot + SnapshotID string `json:"snapshot_id,omitempty"` + // SourceReplica is a UUID of an existing volume to replicate with + SourceReplica string `json:"source_replica,omitempty"` + // the ID of the existing volume + SourceVolID string `json:"source_volid,omitempty"` + // The ID of the image from which you want to create the volume. + // Required to create a bootable volume. + ImageID string `json:"imageRef,omitempty"` + // The associated volume type + VolumeType string `json:"volume_type,omitempty"` +} + +// ToVolumeCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Create will create a new Volume based on the values in CreateOpts. To extract +// the Volume object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToVolumeCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + 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, 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 +} + +// Get retrieves the Volume with the provided ID. To extract the Volume 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 { + ToVolumeListQuery() (string, error) +} + +// ListOpts holds options for listing Volumes. It is passed to the volumes.List +// function. +type ListOpts struct { + // AllTenants will retrieve volumes of all tenants/projects. + AllTenants bool `q:"all_tenants"` + + // Metadata will filter results based on specified metadata. + Metadata map[string]string `q:"metadata"` + + // Name will filter by the specified volume name. + Name string `q:"name"` + + // Status will filter by the specified status. + Status string `q:"status"` + + // 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. +func (opts ListOpts) ToVolumeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Volumes 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.ToVolumeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return VolumePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToVolumeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Volume. This object is passed +// 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"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToVolumeUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Update will update the Volume with provided information. To extract the updated +// Volume from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToVolumeUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// IDFromName is a convienience function that returns a server's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractVolumes(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} + } +} 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 new file mode 100644 index 00000000000..96572b01b45 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go @@ -0,0 +1,167 @@ +package volumes + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Attachment struct { + AttachedAt time.Time `json:"-"` + AttachmentID string `json:"attachment_id"` + Device string `json:"device"` + HostName string `json:"host_name"` + ID string `json:"id"` + ServerID string `json:"server_id"` + VolumeID string `json:"volume_id"` +} + +func (r *Attachment) UnmarshalJSON(b []byte) error { + type tmp Attachment + var s struct { + tmp + AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Attachment(s.tmp) + + r.AttachedAt = time.Time(s.AttachedAt) + + return err +} + +// Volume contains all the information associated with an OpenStack Volume. +type Volume struct { + // Unique identifier for the volume. + ID string `json:"id"` + // Current status of the volume. + Status string `json:"status"` + // Size of the volume in GB. + Size int `json:"size"` + // AvailabilityZone is which availability zone the volume is in. + AvailabilityZone string `json:"availability_zone"` + // The date when this volume was created. + CreatedAt time.Time `json:"-"` + // The date when this volume was last updated + UpdatedAt time.Time `json:"-"` + // Instances onto which the volume is attached. + Attachments []Attachment `json:"attachments"` + // Human-readable display name for the volume. + Name string `json:"name"` + // Human-readable description for the volume. + Description string `json:"description"` + // The type of volume to create, either SATA or SSD. + VolumeType string `json:"volume_type"` + // The ID of the snapshot from which the volume was created + SnapshotID string `json:"snapshot_id"` + // The ID of another block storage volume from which the current volume was created + SourceVolID string `json:"source_volid"` + // Arbitrary key-value pairs defined by the user. + Metadata map[string]string `json:"metadata"` + // UserID is the id of the user who created the volume. + UserID string `json:"user_id"` + // Indicates whether this is a bootable volume. + Bootable string `json:"bootable"` + // Encrypted denotes if the volume is encrypted. + Encrypted bool `json:"encrypted"` + // ReplicationStatus is the status of replication. + ReplicationStatus string `json:"replication_status"` + // ConsistencyGroupID is the consistency group ID. + ConsistencyGroupID string `json:"consistencygroup_id"` + // Multiattach denotes if the volume is multi-attach capable. + Multiattach bool `json:"multiattach"` +} + +func (r *Volume) UnmarshalJSON(b []byte) error { + type tmp Volume + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Volume(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + +// VolumePage is a pagination.pager that is returned from a call to the List function. +type VolumePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Volumes. +func (r VolumePage) IsEmpty() (bool, error) { + volumes, err := ExtractVolumes(r) + 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 + err := ExtractVolumesInto(r, &s) + return s, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Volume object out of the commonResult object. +func (r commonResult) Extract() (*Volume, error) { + var s Volume + err := r.ExtractInto(&s) + return &s, err +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "volume") +} + +func ExtractVolumesInto(r pagination.Page, v interface{}) error { + return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") +} + +// 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 +} + +// UpdateResult contains the response body and error from an Update request. +type UpdateResult struct { + commonResult +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go new file mode 100644 index 00000000000..170724905ab --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go @@ -0,0 +1,23 @@ +package volumes + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes") +} + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes", "detail") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("volumes", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go new file mode 100644 index 00000000000..e86c1b4b4ee --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go @@ -0,0 +1,22 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" +) + +// WaitForStatus will continually poll the resource, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := Get(c, 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/openstack/blockstorage/v3/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go new file mode 100644 index 00000000000..307b8b12d2f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go @@ -0,0 +1,5 @@ +// Package volumes provides information and interaction with volumes in the +// OpenStack Block Storage service. A volume is a detachable block storage +// device, akin to a USB hard drive. It can only be attached to one instance at +// a time. +package volumes 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 new file mode 100644 index 00000000000..25f70b27c1a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go @@ -0,0 +1,237 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToVolumeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a Volume. This object is passed to +// the volumes.Create function. For more information about these parameters, +// see the Volume object. +type CreateOpts struct { + // The size of the volume, in GB + Size int `json:"size" required:"true"` + // The availability zone + AvailabilityZone string `json:"availability_zone,omitempty"` + // ConsistencyGroupID is the ID of a consistency group + ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` + // The volume description + Description string `json:"description,omitempty"` + // One or more metadata key and value pairs to associate with the volume + Metadata map[string]string `json:"metadata,omitempty"` + // The volume name + Name string `json:"name,omitempty"` + // the ID of the existing volume snapshot + SnapshotID string `json:"snapshot_id,omitempty"` + // SourceReplica is a UUID of an existing volume to replicate with + SourceReplica string `json:"source_replica,omitempty"` + // the ID of the existing volume + SourceVolID string `json:"source_volid,omitempty"` + // The ID of the image from which you want to create the volume. + // Required to create a bootable volume. + 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 +// CreateOpts. +func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Create will create a new Volume based on the values in CreateOpts. To extract +// the Volume object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToVolumeCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + 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, 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 +} + +// Get retrieves the Volume with the provided ID. To extract the Volume 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 { + ToVolumeListQuery() (string, error) +} + +// ListOpts holds options for listing Volumes. It is passed to the volumes.List +// function. +type ListOpts struct { + // AllTenants will retrieve volumes of all tenants/projects. + AllTenants bool `q:"all_tenants"` + + // Metadata will filter results based on specified metadata. + Metadata map[string]string `q:"metadata"` + + // Name will filter by the specified volume name. + Name string `q:"name"` + + // Status will filter by the specified status. + Status string `q:"status"` + + // 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. +func (opts ListOpts) ToVolumeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Volumes 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.ToVolumeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return VolumePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToVolumeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Volume. This object is passed +// 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"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToVolumeUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Update will update the Volume with provided information. To extract the updated +// Volume from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToVolumeUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// IDFromName is a convienience function that returns a server's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractVolumes(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} + } +} 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 new file mode 100644 index 00000000000..3a33b5864bb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go @@ -0,0 +1,172 @@ +package volumes + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Attachment represents a Volume Attachment record +type Attachment struct { + AttachedAt time.Time `json:"-"` + AttachmentID string `json:"attachment_id"` + Device string `json:"device"` + HostName string `json:"host_name"` + ID string `json:"id"` + ServerID string `json:"server_id"` + VolumeID string `json:"volume_id"` +} + +// UnmarshalJSON is our unmarshalling helper +func (r *Attachment) UnmarshalJSON(b []byte) error { + type tmp Attachment + var s struct { + tmp + AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Attachment(s.tmp) + + r.AttachedAt = time.Time(s.AttachedAt) + + return err +} + +// Volume contains all the information associated with an OpenStack Volume. +type Volume struct { + // Unique identifier for the volume. + ID string `json:"id"` + // Current status of the volume. + Status string `json:"status"` + // Size of the volume in GB. + Size int `json:"size"` + // AvailabilityZone is which availability zone the volume is in. + AvailabilityZone string `json:"availability_zone"` + // The date when this volume was created. + CreatedAt time.Time `json:"-"` + // The date when this volume was last updated + UpdatedAt time.Time `json:"-"` + // Instances onto which the volume is attached. + Attachments []Attachment `json:"attachments"` + // Human-readable display name for the volume. + Name string `json:"name"` + // Human-readable description for the volume. + Description string `json:"description"` + // The type of volume to create, either SATA or SSD. + VolumeType string `json:"volume_type"` + // The ID of the snapshot from which the volume was created + SnapshotID string `json:"snapshot_id"` + // The ID of another block storage volume from which the current volume was created + SourceVolID string `json:"source_volid"` + // Arbitrary key-value pairs defined by the user. + Metadata map[string]string `json:"metadata"` + // UserID is the id of the user who created the volume. + UserID string `json:"user_id"` + // Indicates whether this is a bootable volume. + Bootable string `json:"bootable"` + // Encrypted denotes if the volume is encrypted. + Encrypted bool `json:"encrypted"` + // ReplicationStatus is the status of replication. + ReplicationStatus string `json:"replication_status"` + // ConsistencyGroupID is the consistency group ID. + 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 +func (r *Volume) UnmarshalJSON(b []byte) error { + type tmp Volume + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Volume(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + +// VolumePage is a pagination.pager that is returned from a call to the List function. +type VolumePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Volumes. +func (r VolumePage) IsEmpty() (bool, error) { + volumes, err := ExtractVolumes(r) + return len(volumes) == 0, err +} + +func (page VolumePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"volumes_links"` + } + err := page.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 + err := ExtractVolumesInto(r, &s) + return s, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Volume object out of the commonResult object. +func (r commonResult) Extract() (*Volume, error) { + var s Volume + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractInto converts our response data into a volume struct +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "volume") +} + +// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes +func ExtractVolumesInto(r pagination.Page, v interface{}) error { + return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") +} + +// 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 +} + +// UpdateResult contains the response body and error from an Update request. +type UpdateResult struct { + commonResult +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go new file mode 100644 index 00000000000..170724905ab --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go @@ -0,0 +1,23 @@ +package volumes + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes") +} + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes", "detail") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("volumes", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go new file mode 100644 index 00000000000..e86c1b4b4ee --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go @@ -0,0 +1,22 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" +) + +// WaitForStatus will continually poll the resource, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := Get(c, 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/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go new file mode 100644 index 00000000000..50f239711e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -0,0 +1,438 @@ +package openstack + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud" + tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/openstack/utils" +) + +const ( + // v2 represents Keystone v2. + // It should never increase beyond 2.0. + v2 = "v2.0" + + // v3 represents Keystone v3. + // The version can be anything from v3 to v3.x. + v3 = "v3" +) + +/* +NewClient prepares an unauthenticated ProviderClient instance. +Most users will probably prefer using the AuthenticatedClient function +instead. + +This is useful if you wish to explicitly control the version of the identity +service that's used for authentication explicitly, for example. + +A basic example of using this would be: + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.NewClient(ao.IdentityEndpoint) + client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) +*/ +func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { + base, err := utils.BaseEndpoint(endpoint) + if err != nil { + return nil, err + } + + endpoint = gophercloud.NormalizeURL(endpoint) + base = gophercloud.NormalizeURL(base) + + p := new(gophercloud.ProviderClient) + p.IdentityBase = base + p.IdentityEndpoint = endpoint + p.UseTokenLock() + + return p, nil +} + +/* +AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint +specified by the options, acquires a token, and returns a Provider Client +instance that's ready to operate. + +If the full path to a versioned identity endpoint was specified (example: +http://example.com:5000/v3), that path will be used as the endpoint to query. + +If a versionless endpoint was specified (example: http://example.com:5000/), +the endpoint will be queried to determine which versions of the identity service +are available, then chooses the most recent or most supported version. + +Example: + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(ao) + client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +*/ +func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { + client, err := NewClient(options.IdentityEndpoint) + if err != nil { + return nil, err + } + + err = Authenticate(client, options) + if err != nil { + return nil, err + } + return client, nil +} + +// Authenticate or re-authenticate against the most recent identity service +// supported at the provided endpoint. +func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error { + versions := []*utils.Version{ + {ID: v2, Priority: 20, Suffix: "/v2.0/"}, + {ID: v3, Priority: 30, Suffix: "/v3/"}, + } + + chosen, endpoint, err := utils.ChooseVersion(client, versions) + if err != nil { + return err + } + + switch chosen.ID { + case v2: + return v2auth(client, endpoint, options, gophercloud.EndpointOpts{}) + case v3: + return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{}) + default: + // The switch statement must be out of date from the versions list. + return fmt.Errorf("Unrecognized identity version: %s", chosen.ID) + } +} + +// AuthenticateV2 explicitly authenticates against the identity v2 endpoint. +func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error { + return v2auth(client, "", options, eo) +} + +func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error { + v2Client, err := NewIdentityV2(client, eo) + if err != nil { + return err + } + + if endpoint != "" { + v2Client.Endpoint = endpoint + } + + v2Opts := tokens2.AuthOptions{ + IdentityEndpoint: options.IdentityEndpoint, + Username: options.Username, + Password: options.Password, + TenantID: options.TenantID, + TenantName: options.TenantName, + AllowReauth: options.AllowReauth, + TokenID: options.TokenID, + } + + result := tokens2.Create(v2Client, v2Opts) + + err = client.SetTokenAndAuthResult(result) + if err != nil { + return err + } + + catalog, err := result.ExtractServiceCatalog() + if err != nil { + return err + } + + if options.AllowReauth { + // 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.SetTokenAndAuthResult(nil) + tao := options + tao.AllowReauth = false + client.ReauthFunc = func() error { + err := v2auth(&tac, endpoint, tao, eo) + if err != nil { + return err + } + client.CopyTokenFrom(&tac) + return nil + } + } + client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { + return V2EndpointURL(catalog, opts) + } + + return nil +} + +// AuthenticateV3 explicitly authenticates against the identity v3 service. +func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { + return v3auth(client, "", options, eo) +} + +func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { + // Override the generated service endpoint with the one returned by the version endpoint. + v3Client, err := NewIdentityV3(client, eo) + if err != nil { + return err + } + + if endpoint != "" { + v3Client.Endpoint = endpoint + } + + result := tokens3.Create(v3Client, opts) + + err = client.SetTokenAndAuthResult(result) + if err != nil { + return err + } + + catalog, err := result.ExtractServiceCatalog() + if err != nil { + return err + } + + 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.SetTokenAndAuthResult(nil) + var tao tokens3.AuthOptionsBuilder + switch ot := opts.(type) { + case *gophercloud.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + case *tokens3.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + default: + tao = opts + } + client.ReauthFunc = func() error { + err := v3auth(&tac, endpoint, tao, eo) + if err != nil { + return err + } + client.CopyTokenFrom(&tac) + return nil + } + } + client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { + return V3EndpointURL(catalog, opts) + } + + return nil +} + +// NewIdentityV2 creates a ServiceClient that may be used to interact with the +// v2 identity service. +func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + endpoint := client.IdentityBase + "v2.0/" + clientType := "identity" + var err error + if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { + eo.ApplyDefaults(clientType) + endpoint, err = client.EndpointLocator(eo) + if err != nil { + return nil, err + } + } + + return &gophercloud.ServiceClient{ + ProviderClient: client, + Endpoint: endpoint, + Type: clientType, + }, nil +} + +// NewIdentityV3 creates a ServiceClient that may be used to access the v3 +// identity service. +func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + endpoint := client.IdentityBase + "v3/" + clientType := "identity" + var err error + if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { + eo.ApplyDefaults(clientType) + endpoint, err = client.EndpointLocator(eo) + if err != nil { + return nil, err + } + } + + // Ensure endpoint still has a suffix of v3. + // This is because EndpointLocator might have found a versionless + // 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, + Type: clientType, + }, nil +} + +func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + eo.ApplyDefaults(clientType) + url, err := client.EndpointLocator(eo) + if err != nil { + return sc, err + } + sc.ProviderClient = client + sc.Endpoint = url + sc.Type = clientType + 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) { + return initClientOpts(client, eo, "object-store") +} + +// NewComputeV2 creates a ServiceClient that may be used with the v2 compute +// package. +func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "compute") +} + +// NewNetworkV2 creates a ServiceClient that may be used with the v2 network +// package. +func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "network") + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc, err +} + +// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 +// block storage service. +func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volume") +} + +// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 +// block storage service. +func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volumev2") +} + +// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service. +func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volumev3") +} + +// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. +func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "sharev2") +} + +// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 +// CDN service. +func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "cdn") +} + +// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 +// orchestration service. +func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "orchestration") +} + +// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. +func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "database") +} + +// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS +// service. +func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "dns") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err +} + +// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 +// image service. +func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "image") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err +} + +// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2 +// load balancer service. +func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "load-balancer") + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc, err +} + +// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering +// package. +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/common/extensions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go new file mode 100644 index 00000000000..5510f265395 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go @@ -0,0 +1,52 @@ +/* +Package extensions provides information and interaction with the different +extensions available for an OpenStack service. + +The purpose of OpenStack API extensions is to: + +- Introduce new features in the API without requiring a version change. +- Introduce vendor-specific niche functionality. +- Act as a proving ground for experimental functionalities that might be +included in a future version of the API. + +Extensions usually have tags that prevent conflicts with other extensions that +define attributes or resources with the same names, and with core resources and +attributes. Because an extension might not be supported by all plug-ins, its +availability varies with deployments and the specific plug-in. + +The results of this package vary depending on the type of Service Client used. +In the following examples, note how the only difference is the creation of the +Service Client. + +Example of Retrieving Compute Extensions + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(ao) + computeClient, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + + allPages, err := extensions.List(computeClient).Allpages() + allExtensions, err := extensions.ExtractExtensions(allPages) + + for _, extension := range allExtensions{ + fmt.Println("%+v\n", extension) + } + + +Example of Retrieving Network Extensions + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(ao) + networkClient, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + + allPages, err := extensions.List(networkClient).Allpages() + allExtensions, err := extensions.ExtractExtensions(allPages) + + for _, extension := range allExtensions{ + fmt.Println("%+v\n", extension) + } +*/ +package extensions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go new file mode 100644 index 00000000000..46b7d60cd69 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go @@ -0,0 +1,20 @@ +package extensions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Get retrieves information for a specific extension using its alias. +func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) { + _, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil) + return +} + +// List returns a Pager which allows you to iterate over the full collection of extensions. +// It does not accept query parameters. +func List(c *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(c, ListExtensionURL(c), func(r pagination.PageResult) pagination.Page { + return ExtensionPage{pagination.SinglePageBase(r)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go new file mode 100644 index 00000000000..8a26edd1c59 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go @@ -0,0 +1,53 @@ +package extensions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// GetResult temporarily stores the result of a Get call. +// Use its Extract() method to interpret it as an Extension. +type GetResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult as an Extension. +func (r GetResult) Extract() (*Extension, error) { + var s struct { + Extension *Extension `json:"extension"` + } + err := r.ExtractInto(&s) + return s.Extension, err +} + +// Extension is a struct that represents an OpenStack extension. +type Extension struct { + Updated string `json:"updated"` + Name string `json:"name"` + Links []interface{} `json:"links"` + Namespace string `json:"namespace"` + Alias string `json:"alias"` + Description string `json:"description"` +} + +// ExtensionPage is the page returned by a pager when traversing over a collection of extensions. +type ExtensionPage struct { + pagination.SinglePageBase +} + +// IsEmpty checks whether an ExtensionPage struct is empty. +func (r ExtensionPage) IsEmpty() (bool, error) { + is, err := ExtractExtensions(r) + return len(is) == 0, err +} + +// ExtractExtensions accepts a Page struct, specifically an ExtensionPage +// struct, and extracts the elements into a slice of Extension structs. +// In other words, a generic collection is mapped into a relevant slice. +func ExtractExtensions(r pagination.Page) ([]Extension, error) { + var s struct { + Extensions []Extension `json:"extensions"` + } + err := (r.(ExtensionPage)).ExtractInto(&s) + return s.Extensions, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go new file mode 100644 index 00000000000..eaf38b2d191 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go @@ -0,0 +1,13 @@ +package extensions + +import "github.com/gophercloud/gophercloud" + +// ExtensionURL generates the URL for an extension resource by name. +func ExtensionURL(c *gophercloud.ServiceClient, name string) string { + return c.ServiceURL("extensions", name) +} + +// ListExtensionURL generates the URL for the extensions resource collection. +func ListExtensionURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("extensions") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go new file mode 100644 index 00000000000..3653122bf30 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go @@ -0,0 +1,52 @@ +/* +Package attachinterfaces provides the ability to retrieve and manage network +interfaces through Nova. + +Example of Listing a Server's Interfaces + + serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" + allPages, err := attachinterfaces.List(computeClient, serverID).AllPages() + if err != nil { + panic(err) + } + + allInterfaces, err := attachinterfaces.ExtractInterfaces(allPages) + if err != nil { + panic(err) + } + + for _, interface := range allInterfaces { + fmt.Printf("%+v\n", interface) + } + +Example to Get a Server's Interface + + portID = "0dde1598-b374-474e-986f-5b8dd1df1d4e" + serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" + interface, err := attachinterfaces.Get(computeClient, serverID, portID).Extract() + if err != nil { + panic(err) + } + +Example to Create a new Interface attachment on the Server + + networkID := "8a5fe506-7e9f-4091-899b-96336909d93c" + serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" + attachOpts := attachinterfaces.CreateOpts{ + NetworkID: networkID, + } + interface, err := attachinterfaces.Create(computeClient, serverID, attachOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Interface attachment from the Server + + portID = "0dde1598-b374-474e-986f-5b8dd1df1d4e" + serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" + err := attachinterfaces.Delete(computeClient, serverID, portID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package attachinterfaces 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 new file mode 100644 index 00000000000..874f7a61ec4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go @@ -0,0 +1,72 @@ +package attachinterfaces + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List makes a request against the nova API to list the server's interfaces. +func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager { + return pagination.NewPager(client, listInterfaceURL(client, serverID), func(r pagination.PageResult) pagination.Page { + return InterfacePage{pagination.SinglePageBase(r)} + }) +} + +// Get requests details on a single interface attachment by the server and port IDs. +func Get(client *gophercloud.ServiceClient, serverID, portID string) (r GetResult) { + _, r.Err = client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAttachInterfacesCreateMap() (map[string]interface{}, error) +} + +// 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 + // v2.0 allocates a port and creates an interface for it on the network. + PortID string `json:"port_id,omitempty"` + + // NetworkID is the ID of the network for which you want to create an interface. + // The NetworkID and PortID parameters are mutually exclusive. + // If you do not specify the NetworkID parameter, the OpenStack Networking + // API v2.0 uses the network information cache that is associated with the instance. + NetworkID string `json:"net_id,omitempty"` + + // 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"` +} + +// ToAttachInterfacesCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToAttachInterfacesCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "interfaceAttachment") +} + +// Create requests the creation of a new interface attachment on the server. +func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToAttachInterfacesCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete makes a request against the nova API to detach a single interface from the server. +// It needs server and port IDs to make a such request. +func Delete(client *gophercloud.ServiceClient, serverID, portID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteInterfaceURL(client, serverID, portID), nil) + return +} 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 new file mode 100644 index 00000000000..7d15e1ecb4b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go @@ -0,0 +1,80 @@ +package attachinterfaces + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type attachInterfaceResult struct { + gophercloud.Result +} + +// Extract interprets any attachInterfaceResult as an Interface, if possible. +func (r attachInterfaceResult) Extract() (*Interface, error) { + var s struct { + Interface *Interface `json:"interfaceAttachment"` + } + err := r.ExtractInto(&s) + return s.Interface, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as an Interface. +type GetResult struct { + attachInterfaceResult +} + +// CreateResult is the response from a Create operation. Call its Extract +// method to interpret it as an Interface. +type CreateResult struct { + attachInterfaceResult +} + +// 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 +} + +// 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,omitempty"` + IPAddress string `json:"ip_address"` +} + +// Interface represents a network interface on a server. +type Interface struct { + PortState string `json:"port_state"` + FixedIPs []FixedIP `json:"fixed_ips"` + PortID string `json:"port_id"` + NetID string `json:"net_id"` + MACAddr string `json:"mac_addr"` +} + +// InterfacePage 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 ExtractInterfaces call. +type InterfacePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if an InterfacePage contains no interfaces. +func (r InterfacePage) IsEmpty() (bool, error) { + interfaces, err := ExtractInterfaces(r) + return len(interfaces) == 0, err +} + +// ExtractInterfaces interprets the results of a single page from a List() call, +// producing a slice of Interface structs. +func ExtractInterfaces(r pagination.Page) ([]Interface, error) { + var s struct { + Interfaces []Interface `json:"interfaceAttachments"` + } + err := (r.(InterfacePage)).ExtractInto(&s) + return s.Interfaces, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go new file mode 100644 index 00000000000..50292e8b5a5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go @@ -0,0 +1,18 @@ +package attachinterfaces + +import "github.com/gophercloud/gophercloud" + +func listInterfaceURL(client *gophercloud.ServiceClient, serverID string) string { + return client.ServiceURL("servers", serverID, "os-interface") +} + +func getInterfaceURL(client *gophercloud.ServiceClient, serverID, portID string) string { + return client.ServiceURL("servers", serverID, "os-interface", portID) +} + +func createInterfaceURL(client *gophercloud.ServiceClient, serverID string) string { + return client.ServiceURL("servers", serverID, "os-interface") +} +func deleteInterfaceURL(client *gophercloud.ServiceClient, serverID, portID string) string { + return client.ServiceURL("servers", serverID, "os-interface", portID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go new file mode 100644 index 00000000000..484eb20000c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go @@ -0,0 +1,30 @@ +/* +Package volumeattach provides the ability to attach and detach volumes +from servers. + +Example to Attach a Volume + + serverID := "7ac8686c-de71-4acb-9600-ec18b1a1ed6d" + volumeID := "87463836-f0e2-4029-abf6-20c8892a3103" + + createOpts := volumeattach.CreateOpts{ + Device: "/dev/vdc", + VolumeID: volumeID, + } + + result, err := volumeattach.Create(computeClient, serverID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Detach a Volume + + serverID := "7ac8686c-de71-4acb-9600-ec18b1a1ed6d" + attachmentID := "ed081613-1c9b-4231-aa5e-ebfd4d87f983" + + err := volumeattach.Delete(computeClient, serverID, attachmentID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package volumeattach diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go new file mode 100644 index 00000000000..6a262c212e1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go @@ -0,0 +1,60 @@ +package volumeattach + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List returns a Pager that allows you to iterate over a collection of +// VolumeAttachments. +func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager { + return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page { + return VolumeAttachmentPage{pagination.SinglePageBase(r)} + }) +} + +// CreateOptsBuilder allows extensions to add parameters to the Create request. +type CreateOptsBuilder interface { + ToVolumeAttachmentCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies volume attachment creation or import parameters. +type CreateOpts struct { + // Device is the device that the volume will attach to the instance as. + // Omit for "auto". + Device string `json:"device,omitempty"` + + // VolumeID is the ID of the volume to attach to the instance. + VolumeID string `json:"volumeId" required:"true"` +} + +// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volumeAttachment") +} + +// Create requests the creation of a new volume attachment on the server. +func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToVolumeAttachmentCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Get returns public data about a previously created VolumeAttachment. +func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r GetResult) { + _, r.Err = client.Get(getURL(client, serverID, attachmentID), &r.Body, nil) + return +} + +// Delete requests the deletion of a previous stored VolumeAttachment from +// the server. +func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go new file mode 100644 index 00000000000..56d50347291 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go @@ -0,0 +1,77 @@ +package volumeattach + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// VolumeAttachment contains attachment information between a volume +// and server. +type VolumeAttachment struct { + // ID is a unique id of the attachment. + ID string `json:"id"` + + // Device is what device the volume is attached as. + Device string `json:"device"` + + // VolumeID is the ID of the attached volume. + VolumeID string `json:"volumeId"` + + // ServerID is the ID of the instance that has the volume attached. + ServerID string `json:"serverId"` +} + +// VolumeAttachmentPage stores a single page all of VolumeAttachment +// results from a List call. +type VolumeAttachmentPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines whether or not a VolumeAttachmentPage is empty. +func (page VolumeAttachmentPage) IsEmpty() (bool, error) { + va, err := ExtractVolumeAttachments(page) + return len(va) == 0, err +} + +// ExtractVolumeAttachments interprets a page of results as a slice of +// VolumeAttachment. +func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) { + var s struct { + VolumeAttachments []VolumeAttachment `json:"volumeAttachments"` + } + err := (r.(VolumeAttachmentPage)).ExtractInto(&s) + return s.VolumeAttachments, err +} + +// VolumeAttachmentResult is the result from a volume attachment operation. +type VolumeAttachmentResult struct { + gophercloud.Result +} + +// Extract is a method that attempts to interpret any VolumeAttachment resource +// response as a VolumeAttachment struct. +func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) { + var s struct { + VolumeAttachment *VolumeAttachment `json:"volumeAttachment"` + } + err := r.ExtractInto(&s) + return s.VolumeAttachment, err +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a VolumeAttachment. +type CreateResult struct { + VolumeAttachmentResult +} + +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a VolumeAttachment. +type GetResult struct { + VolumeAttachmentResult +} + +// 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/compute/v2/extensions/volumeattach/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go new file mode 100644 index 00000000000..083f8dc4554 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go @@ -0,0 +1,25 @@ +package volumeattach + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "os-volume_attachments" + +func resourceURL(c *gophercloud.ServiceClient, serverID string) string { + return c.ServiceURL("servers", serverID, resourcePath) +} + +func listURL(c *gophercloud.ServiceClient, serverID string) string { + return resourceURL(c, serverID) +} + +func createURL(c *gophercloud.ServiceClient, serverID string) string { + return resourceURL(c, serverID) +} + +func getURL(c *gophercloud.ServiceClient, serverID, aID string) string { + return c.ServiceURL("servers", serverID, resourcePath, aID) +} + +func deleteURL(c *gophercloud.ServiceClient, serverID, aID string) string { + return getURL(c, serverID, aID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go new file mode 100644 index 00000000000..34d8764fadb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go @@ -0,0 +1,137 @@ +/* +Package flavors provides information and interaction with the flavor API +in the OpenStack Compute service. + +A flavor is an available hardware configuration for a server. Each flavor +has a unique combination of disk space, memory capacity and priority for CPU +time. + +Example to List Flavors + + listOpts := flavors.ListOpts{ + AccessType: flavors.PublicAccess, + } + + allPages, err := flavors.ListDetail(computeClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allFlavors, err := flavors.ExtractFlavors(allPages) + if err != nil { + panic(err) + } + + for _, flavor := range allFlavors { + fmt.Printf("%+v\n", flavor) + } + +Example to Create a Flavor + + createOpts := flavors.CreateOpts{ + ID: "1", + Name: "m1.tiny", + Disk: gophercloud.IntToPointer(1), + RAM: 512, + VCPUs: 1, + RxTxFactor: 1.0, + } + + flavor, err := flavors.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List Flavor Access + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + allPages, err := flavors.ListAccesses(computeClient, flavorID).AllPages() + if err != nil { + panic(err) + } + + allAccesses, err := flavors.ExtractAccesses(allPages) + if err != nil { + panic(err) + } + + for _, access := range allAccesses { + fmt.Printf("%+v", access) + } + +Example to Grant Access to a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + accessOpts := flavors.AddAccessOpts{ + Tenant: "15153a0979884b59b0592248ef947921", + } + + accessList, err := flavors.AddAccess(computeClient, flavor.ID, accessOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove/Revoke Access to a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + accessOpts := flavors.RemoveAccessOpts{ + Tenant: "15153a0979884b59b0592248ef947921", + } + + accessList, err := flavors.RemoveAccess(computeClient, flavor.ID, accessOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create Extra Specs for a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + createOpts := flavors.ExtraSpecsOpts{ + "hw:cpu_policy": "CPU-POLICY", + "hw:cpu_thread_policy": "CPU-THREAD-POLICY", + } + createdExtraSpecs, err := flavors.CreateExtraSpecs(computeClient, flavorID, createOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v", createdExtraSpecs) + +Example to Get Extra Specs for a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + extraSpecs, err := flavors.ListExtraSpecs(computeClient, flavorID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v", extraSpecs) + +Example to Update Extra Specs for a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + + updateOpts := flavors.ExtraSpecsOpts{ + "hw:cpu_thread_policy": "CPU-THREAD-POLICY-UPDATED", + } + updatedExtraSpec, err := flavors.UpdateExtraSpec(computeClient, flavorID, updateOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v", updatedExtraSpec) + +Example to Delete an Extra Spec for a Flavor + + flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" + err := flavors.DeleteExtraSpec(computeClient, flavorID, "hw:cpu_thread_policy").ExtractErr() + if err != nil { + panic(err) + } +*/ +package flavors 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 new file mode 100644 index 00000000000..753024a18b7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go @@ -0,0 +1,357 @@ +package flavors + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToFlavorListQuery() (string, error) +} + +/* + AccessType maps to OpenStack's Flavor.is_public field. Although the is_public + field is boolean, the request options are ternary, which is why AccessType is + a string. The following values are allowed: + + The AccessType arguement is optional, and if it is not supplied, OpenStack + returns the PublicAccess flavors. +*/ +type AccessType string + +const ( + // PublicAccess returns public flavors and private flavors associated with + // that project. + PublicAccess AccessType = "true" + + // PrivateAccess (admin only) returns private flavors, across all projects. + PrivateAccess AccessType = "false" + + // AllAccess (admin only) returns public and private flavors across all + // projects. + AllAccess AccessType = "None" +) + +/* + ListOpts filters the results returned by the List() function. + For example, a flavor with a minDisk field of 10 will not be returned if you + specify MinDisk set to 20. + + Typically, software will use the last ID of the previous call to List to set + the Marker for the current call. +*/ +type ListOpts struct { + // ChangesSince, if provided, instructs List to return only those things which + // have changed since the timestamp provided. + ChangesSince string `q:"changes-since"` + + // MinDisk and MinRAM, if provided, elides flavors which do not meet your + // criteria. + 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"` + + // Limit instructs List to refrain from sending excessively large lists of + // flavors. + Limit int `q:"limit"` + + // AccessType, if provided, instructs List which set of flavors to return. + // If IsPublic not provided, flavors for the current project are returned. + AccessType AccessType `q:"is_public"` +} + +// ToFlavorListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToFlavorListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail instructs OpenStack to provide a list of flavors. +// You may provide criteria by which List curtails its results for easier +// processing. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToFlavorListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return FlavorPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type CreateOptsBuilder interface { + ToFlavorCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters used for creating a flavor. +type CreateOpts struct { + // Name is the name of the flavor. + Name string `json:"name" required:"true"` + + // RAM is the memory of the flavor, measured in MB. + RAM int `json:"ram" required:"true"` + + // VCPUs is the number of vcpus for the flavor. + VCPUs int `json:"vcpus" required:"true"` + + // Disk the amount of root disk space, measured in GB. + Disk *int `json:"disk" required:"true"` + + // ID is a unique ID for the flavor. + ID string `json:"id,omitempty"` + + // Swap is the amount of swap space for the flavor, measured in MB. + Swap *int `json:"swap,omitempty"` + + // RxTxFactor alters the network bandwidth of a flavor. + RxTxFactor float64 `json:"rxtx_factor,omitempty"` + + // IsPublic flags a flavor as being available to all projects or not. + IsPublic *bool `json:"os-flavor-access:is_public,omitempty"` + + // Ephemeral is the amount of ephemeral disk space, measured in GB. + Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` +} + +// ToFlavorCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "flavor") +} + +// Create requests the creation of a new flavor. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToFlavorCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// 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) + return +} + +// Delete deletes the specified flavor ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListAccesses retrieves the tenants which have access to a flavor. +func ListAccesses(client *gophercloud.ServiceClient, id string) pagination.Pager { + url := accessURL(client, id) + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AccessPage{pagination.SinglePageBase(r)} + }) +} + +// AddAccessOptsBuilder allows extensions to add additional parameters to the +// AddAccess requests. +type AddAccessOptsBuilder interface { + ToFlavorAddAccessMap() (map[string]interface{}, error) +} + +// AddAccessOpts represents options for adding access to a flavor. +type AddAccessOpts struct { + // Tenant is the project/tenant ID to grant access. + Tenant string `json:"tenant"` +} + +// ToFlavorAddAccessMap constructs a request body from AddAccessOpts. +func (opts AddAccessOpts) ToFlavorAddAccessMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "addTenantAccess") +} + +// AddAccess grants a tenant/project access to a flavor. +func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsBuilder) (r AddAccessResult) { + b, err := opts.ToFlavorAddAccessMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// RemoveAccessOptsBuilder allows extensions to add additional parameters to the +// RemoveAccess requests. +type RemoveAccessOptsBuilder interface { + ToFlavorRemoveAccessMap() (map[string]interface{}, error) +} + +// RemoveAccessOpts represents options for removing access to a flavor. +type RemoveAccessOpts struct { + // Tenant is the project/tenant ID to grant access. + Tenant string `json:"tenant"` +} + +// ToFlavorRemoveAccessMap constructs a request body from RemoveAccessOpts. +func (opts RemoveAccessOpts) ToFlavorRemoveAccessMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "removeTenantAccess") +} + +// RemoveAccess removes/revokes a tenant/project access to a flavor. +func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAccessOptsBuilder) (r RemoveAccessResult) { + b, err := opts.ToFlavorRemoveAccessMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ExtraSpecs requests all the extra-specs for the given flavor ID. +func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) { + _, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil) + return +} + +func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) { + _, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil) + return +} + +// CreateExtraSpecsOptsBuilder allows extensions to add additional parameters to the +// CreateExtraSpecs requests. +type CreateExtraSpecsOptsBuilder interface { + ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) +} + +// ExtraSpecsOpts is a map that contains key-value pairs. +type ExtraSpecsOpts map[string]string + +// ToFlavorExtraSpecsCreateMap assembles a body for a Create request based on +// the contents of ExtraSpecsOpts. +func (opts ExtraSpecsOpts) ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) { + return map[string]interface{}{"extra_specs": opts}, nil +} + +// CreateExtraSpecs will create or update the extra-specs key-value pairs for +// the specified Flavor. +func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts CreateExtraSpecsOptsBuilder) (r CreateExtraSpecsResult) { + b, err := opts.ToFlavorExtraSpecsCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// UpdateExtraSpecOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateExtraSpecOptsBuilder interface { + ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) +} + +// ToFlavorExtraSpecUpdateMap assembles a body for an Update request based on +// the contents of a ExtraSpecOpts. +func (opts ExtraSpecsOpts) ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) { + if len(opts) != 1 { + err := gophercloud.ErrInvalidInput{} + err.Argument = "flavors.ExtraSpecOpts" + err.Info = "Must have 1 and only one key-value pair" + return nil, "", err + } + + var key string + for k := range opts { + key = k + } + + return opts, key, nil +} + +// UpdateExtraSpec will updates the value of the specified flavor's extra spec +// for the key in opts. +func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts UpdateExtraSpecOptsBuilder) (r UpdateExtraSpecResult) { + b, key, err := opts.ToFlavorExtraSpecUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteExtraSpec will delete the key-value pair with the given key for the given +// flavor ID. +func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) { + _, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// IDFromName is a convienience function that returns a flavor's ID given its +// name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := ListDetail(client, nil).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractFlavors(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + err := &gophercloud.ErrResourceNotFound{} + err.ResourceType = "flavor" + err.Name = name + return "", err + case 1: + return id, nil + default: + err := &gophercloud.ErrMultipleResourcesFound{} + err.ResourceType = "flavor" + err.Name = name + err.Count = count + return "", err + } +} 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 new file mode 100644 index 00000000000..92fe1b1809d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go @@ -0,0 +1,252 @@ +package flavors + +import ( + "encoding/json" + "strconv" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response of a Get operations. Call its Extract method to +// interpret it as a Flavor. +type CreateResult struct { + commonResult +} + +// GetResult is the response of a Get operations. Call its Extract method to +// interpret it as a Flavor. +type GetResult 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 +} + +// Extract provides access to the individual Flavor returned by the Get and +// Create functions. +func (r commonResult) Extract() (*Flavor, error) { + var s struct { + Flavor *Flavor `json:"flavor"` + } + err := r.ExtractInto(&s) + return s.Flavor, err +} + +// Flavor represent (virtual) hardware configurations for server resources +// in a region. +type Flavor struct { + // ID is the flavor's unique ID. + ID string `json:"id"` + + // Disk is the amount of root disk, measured in GB. + Disk int `json:"disk"` + + // RAM is the amount of memory, measured in MB. + RAM int `json:"ram"` + + // Name is the name of the flavor. + Name string `json:"name"` + + // RxTxFactor describes bandwidth alterations of the flavor. + RxTxFactor float64 `json:"rxtx_factor"` + + // Swap is the amount of swap space, measured in MB. + Swap int `json:"-"` + + // VCPUs indicates how many (virtual) CPUs are available for this flavor. + VCPUs int `json:"vcpus"` + + // IsPublic indicates whether the flavor is public. + IsPublic bool `json:"os-flavor-access:is_public"` + + // Ephemeral is the amount of ephemeral disk space, measured in GB. + Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"` +} + +func (r *Flavor) UnmarshalJSON(b []byte) error { + type tmp Flavor + var s struct { + tmp + Swap interface{} `json:"swap"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Flavor(s.tmp) + + switch t := s.Swap.(type) { + case float64: + r.Swap = int(t) + case string: + switch t { + case "": + r.Swap = 0 + default: + swap, err := strconv.ParseFloat(t, 64) + if err != nil { + return err + } + r.Swap = int(swap) + } + } + + return nil +} + +// FlavorPage contains a single page of all flavors from a ListDetails call. +type FlavorPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a FlavorPage contains any results. +func (page FlavorPage) IsEmpty() (bool, error) { + flavors, err := ExtractFlavors(page) + return len(flavors) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (page FlavorPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"flavors_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractFlavors provides access to the list of flavors in a page acquired +// from the ListDetail operation. +func ExtractFlavors(r pagination.Page) ([]Flavor, error) { + var s struct { + Flavors []Flavor `json:"flavors"` + } + err := (r.(FlavorPage)).ExtractInto(&s) + return s.Flavors, err +} + +// AccessPage contains a single page of all FlavorAccess entries for a flavor. +type AccessPage struct { + pagination.SinglePageBase +} + +// IsEmpty indicates whether an AccessPage is empty. +func (page AccessPage) IsEmpty() (bool, error) { + v, err := ExtractAccesses(page) + return len(v) == 0, err +} + +// ExtractAccesses interprets a page of results as a slice of FlavorAccess. +func ExtractAccesses(r pagination.Page) ([]FlavorAccess, error) { + var s struct { + FlavorAccesses []FlavorAccess `json:"flavor_access"` + } + err := (r.(AccessPage)).ExtractInto(&s) + return s.FlavorAccesses, err +} + +type accessResult struct { + gophercloud.Result +} + +// AddAccessResult is the response of an AddAccess operation. Call its +// Extract method to interpret it as a slice of FlavorAccess. +type AddAccessResult struct { + accessResult +} + +// RemoveAccessResult is the response of a RemoveAccess operation. Call its +// Extract method to interpret it as a slice of FlavorAccess. +type RemoveAccessResult struct { + accessResult +} + +// Extract provides access to the result of an access create or delete. +// The result will be all accesses that the flavor has. +func (r accessResult) Extract() ([]FlavorAccess, error) { + var s struct { + FlavorAccesses []FlavorAccess `json:"flavor_access"` + } + err := r.ExtractInto(&s) + return s.FlavorAccesses, err +} + +// FlavorAccess represents an ACL of tenant access to a specific Flavor. +type FlavorAccess struct { + // FlavorID is the unique ID of the flavor. + FlavorID string `json:"flavor_id"` + + // TenantID is the unique ID of the tenant. + TenantID string `json:"tenant_id"` +} + +// Extract interprets any extraSpecsResult as ExtraSpecs, if possible. +func (r extraSpecsResult) Extract() (map[string]string, error) { + var s struct { + ExtraSpecs map[string]string `json:"extra_specs"` + } + err := r.ExtractInto(&s) + return s.ExtraSpecs, err +} + +// extraSpecsResult contains the result of a call for (potentially) multiple +// key-value pairs. Call its Extract method to interpret it as a +// map[string]interface. +type extraSpecsResult struct { + gophercloud.Result +} + +// ListExtraSpecsResult contains the result of a Get operation. Call its Extract +// method to interpret it as a map[string]interface. +type ListExtraSpecsResult struct { + extraSpecsResult +} + +// CreateExtraSpecResult contains the result of a Create operation. Call its +// Extract method to interpret it as a map[string]interface. +type CreateExtraSpecsResult struct { + extraSpecsResult +} + +// extraSpecResult contains the result of a call for individual a single +// key-value pair. +type extraSpecResult struct { + gophercloud.Result +} + +// GetExtraSpecResult contains the result of a Get operation. Call its Extract +// method to interpret it as a map[string]interface. +type GetExtraSpecResult struct { + extraSpecResult +} + +// UpdateExtraSpecResult contains the result of an Update operation. Call its +// Extract method to interpret it as a map[string]interface. +type UpdateExtraSpecResult struct { + extraSpecResult +} + +// DeleteExtraSpecResult contains the result of a Delete operation. Call its +// ExtractErr method to determine if the call succeeded or failed. +type DeleteExtraSpecResult struct { + gophercloud.ErrResult +} + +// Extract interprets any extraSpecResult as an ExtraSpec, if possible. +func (r extraSpecResult) Extract() (map[string]string, error) { + var s map[string]string + err := r.ExtractInto(&s) + return s, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go new file mode 100644 index 00000000000..8620dd78ad0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go @@ -0,0 +1,49 @@ +package flavors + +import ( + "github.com/gophercloud/gophercloud" +) + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("flavors", "detail") +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("flavors") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id) +} + +func accessURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id, "os-flavor-access") +} + +func accessActionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id, "action") +} + +func extraSpecsListURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id, "os-extra_specs") +} + +func extraSpecsGetURL(client *gophercloud.ServiceClient, id, key string) string { + return client.ServiceURL("flavors", id, "os-extra_specs", key) +} + +func extraSpecsCreateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id, "os-extra_specs") +} + +func extraSpecUpdateURL(client *gophercloud.ServiceClient, id, key string) string { + return client.ServiceURL("flavors", id, "os-extra_specs", key) +} + +func extraSpecDeleteURL(client *gophercloud.ServiceClient, id, key string) string { + return client.ServiceURL("flavors", id, "os-extra_specs", key) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go new file mode 100644 index 00000000000..22410a79a27 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go @@ -0,0 +1,32 @@ +/* +Package images provides information and interaction with the images through +the OpenStack Compute service. + +This API is deprecated and will be removed from a future version of the Nova +API service. + +An image is a collection of files used to create or rebuild a server. +Operators provide a number of pre-built OS images by default. You may also +create custom images from cloud servers you have launched. + +Example to List Images + + listOpts := images.ListOpts{ + Limit: 2, + } + + allPages, err := images.ListDetail(computeClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allImages, err := images.ExtractImages(allPages) + if err != nil { + panic(err) + } + + for _, image := range allImages { + fmt.Printf("%+v\n", image) + } +*/ +package images diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go new file mode 100644 index 00000000000..558b481b9e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go @@ -0,0 +1,109 @@ +package images + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// ListDetail request. +type ListOptsBuilder interface { + ToImageListQuery() (string, error) +} + +// ListOpts contain options filtering Images returned from a call to ListDetail. +type ListOpts struct { + // ChangesSince filters Images based on the last changed status (in date-time + // format). + ChangesSince string `q:"changes-since"` + + // Limit limits the number of Images to return. + Limit int `q:"limit"` + + // Mark is an Image UUID at which to set a marker. + Marker string `q:"marker"` + + // Name is the name of the Image. + Name string `q:"name"` + + // Server is the name of the Server (in URL format). + Server string `q:"server"` + + // Status is the current status of the Image. + Status string `q:"status"` + + // Type is the type of image (e.g. BASE, SERVER, ALL). + Type string `q:"type"` +} + +// ToImageListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToImageListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail enumerates the available images. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToImageListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ImagePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get returns data about a specific image by its ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// Delete deletes the specified image ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// IDFromName is a convienience function that returns an image's ID given its +// name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := ListDetail(client, nil).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractImages(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + err := &gophercloud.ErrResourceNotFound{} + err.ResourceType = "image" + err.Name = name + return "", err + case 1: + return id, nil + default: + err := &gophercloud.ErrMultipleResourcesFound{} + err.ResourceType = "image" + err.Name = name + err.Count = count + return "", err + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go new file mode 100644 index 00000000000..70d1018c721 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go @@ -0,0 +1,95 @@ +package images + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as an Image. +type GetResult 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 +} + +// Extract interprets a GetResult as an Image. +func (r GetResult) Extract() (*Image, error) { + var s struct { + Image *Image `json:"image"` + } + err := r.ExtractInto(&s) + return s.Image, err +} + +// Image represents an Image returned by the Compute API. +type Image struct { + // ID is the unique ID of an image. + ID string + + // Created is the date when the image was created. + Created string + + // MinDisk is the minimum amount of disk a flavor must have to be able + // to create a server based on the image, measured in GB. + MinDisk int + + // MinRAM is the minimum amount of RAM a flavor must have to be able + // to create a server based on the image, measured in MB. + MinRAM int + + // Name provides a human-readable moniker for the OS image. + Name string + + // The Progress and Status fields indicate image-creation status. + Progress int + + // Status is the current status of the image. + Status string + + // Update is the date when the image was updated. + Updated string + + // Metadata provides free-form key/value pairs that further describe the + // image. + Metadata map[string]interface{} +} + +// ImagePage contains a single page of all Images returne from a ListDetail +// operation. Use ExtractImages to convert it into a slice of usable structs. +type ImagePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if an ImagePage contains no Image results. +func (page ImagePage) IsEmpty() (bool, error) { + images, err := ExtractImages(page) + return len(images) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (page ImagePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"images_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractImages converts a page of List results into a slice of usable Image +// structs. +func ExtractImages(r pagination.Page) ([]Image, error) { + var s struct { + Images []Image `json:"images"` + } + err := (r.(ImagePage)).ExtractInto(&s) + return s.Images, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go new file mode 100644 index 00000000000..57787fb725e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go @@ -0,0 +1,15 @@ +package images + +import "github.com/gophercloud/gophercloud" + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("images", "detail") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("images", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("images", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go new file mode 100644 index 00000000000..3b0ab783626 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go @@ -0,0 +1,115 @@ +/* +Package servers provides information and interaction with the server API +resource in the OpenStack Compute service. + +A server is a virtual machine instance in the compute system. In order for +one to be provisioned, a valid flavor and image are required. + +Example to List Servers + + listOpts := servers.ListOpts{ + AllTenants: true, + } + + allPages, err := servers.List(computeClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allServers, err := servers.ExtractServers(allPages) + if err != nil { + panic(err) + } + + for _, server := range allServers { + fmt.Printf("%+v\n", server) + } + +Example to Create a Server + + createOpts := servers.CreateOpts{ + Name: "server_name", + ImageRef: "image-uuid", + FlavorRef: "flavor-uuid", + } + + server, err := servers.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Server + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + err := servers.Delete(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Force Delete a Server + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + err := servers.ForceDelete(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Reboot a Server + + rebootOpts := servers.RebootOpts{ + Type: servers.SoftReboot, + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + err := servers.Reboot(computeClient, serverID, rebootOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Rebuild a Server + + rebuildOpts := servers.RebuildOpts{ + Name: "new_name", + ImageID: "image-uuid", + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + server, err := servers.Rebuilt(computeClient, serverID, rebuildOpts).Extract() + if err != nil { + panic(err) + } + +Example to Resize a Server + + resizeOpts := servers.ResizeOpts{ + FlavorRef: "flavor-uuid", + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + err := servers.Resize(computeClient, serverID, resizeOpts).ExtractErr() + if err != nil { + panic(err) + } + + err = servers.ConfirmResize(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Snapshot a Server + + snapshotOpts := servers.CreateImageOpts{ + Name: "snapshot_name", + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + image, err := servers.CreateImage(computeClient, serverID, snapshotOpts).ExtractImageID() + if err != nil { + panic(err) + } +*/ +package servers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go new file mode 100644 index 00000000000..c9f0e3c20b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go @@ -0,0 +1,71 @@ +package servers + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// ErrNeitherImageIDNorImageNameProvided is the error when neither the image +// ID nor the image name is provided for a server operation +type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput } + +func (e ErrNeitherImageIDNorImageNameProvided) Error() string { + return "One and only one of the image ID and the image name must be provided." +} + +// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor +// ID nor the flavor name is provided for a server operation +type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput } + +func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string { + return "One and only one of the flavor ID and the flavor name must be provided." +} + +type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput } + +func (e ErrNoClientProvidedForIDByName) Error() string { + return "A service client must be provided to find a resource ID by name." +} + +// ErrInvalidHowParameterProvided is the error when an unknown value is given +// for the `how` argument +type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput } + +// ErrNoAdminPassProvided is the error when an administrative password isn't +// provided for a server operation +type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput } + +// ErrNoImageIDProvided is the error when an image ID isn't provided for a server +// operation +type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput } + +// ErrNoIDProvided is the error when a server ID isn't provided for a server +// operation +type ErrNoIDProvided struct{ gophercloud.ErrMissingInput } + +// ErrServer is a generic error type for servers HTTP operations. +type ErrServer struct { + gophercloud.ErrUnexpectedResponseCode + ID string +} + +func (se ErrServer) Error() string { + return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID) +} + +// Error404 overrides the generic 404 error message. +func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error { + se.ErrUnexpectedResponseCode = e + return &ErrServerNotFound{se} +} + +// ErrServerNotFound is the error when a 404 is received during server HTTP +// operations. +type ErrServerNotFound struct { + ErrServer +} + +func (e ErrServerNotFound) Error() string { + return fmt.Sprintf("I couldn't find server [%s]", e.ID) +} 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 00000000000..84ec9f31d35 --- /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 new file mode 100644 index 00000000000..ee8e93b1dcb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go @@ -0,0 +1,812 @@ +package servers + +import ( + "encoding/base64" + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" + "github.com/gophercloud/gophercloud/openstack/compute/v2/images" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToServerListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the server attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + // ChangesSince is a time/date stamp for when the server last changed status. + ChangesSince string `q:"changes-since"` + + // Image is the name of the image in URL format. + Image string `q:"image"` + + // Flavor is the name of the flavor in URL format. + Flavor string `q:"flavor"` + + // Name of the server as a string; can be queried with regular expressions. + // Realize that ?name=bob returns both bob and bobb. If you need to match bob + // only, you can use a regular expression matching the syntax of the + // underlying database server implemented for Compute. + Name string `q:"name"` + + // Status is the value of the status of the server so that you can filter on + // "ACTIVE" for example. + Status string `q:"status"` + + // Host is the name of the host as a string. + Host string `q:"host"` + + // Marker is a UUID of the server at which you want to set a marker. + Marker string `q:"marker"` + + // Limit is an integer value for the limit of values to return. + Limit int `q:"limit"` + + // AllTenants is a bool to show all tenants. + AllTenants bool `q:"all_tenants"` + + // TenantID lists servers for a particular tenant. + // Setting "AllTenants = true" is required. + TenantID string `q:"tenant_id"` +} + +// ToServerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToServerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list servers accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToServerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ServerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToServerCreateMap() (map[string]interface{}, error) +} + +// Network is used within CreateOpts to control a new server's network +// attachments. +type Network struct { + // UUID of a network to attach to the newly provisioned server. + // Required unless Port is provided. + UUID string + + // Port of a neutron network to attach to the newly provisioned server. + // Required unless UUID is provided. + Port string + + // FixedIP specifies a fixed IPv4 address to be used on this network. + FixedIP string +} + +// Personality is an array of files that are injected into the server at launch. +type Personality []*File + +// File is used within CreateOpts and RebuildOpts to inject a file into the +// server at launch. +// File implements the json.Marshaler interface, so when a Create or Rebuild +// operation is requested, json.Marshal will call File's MarshalJSON method. +type File struct { + // Path of the file. + Path string + + // Contents of the file. Maximum content size is 255 bytes. + Contents []byte +} + +// MarshalJSON marshals the escaped file, base64 encoding the contents. +func (f *File) MarshalJSON() ([]byte, error) { + file := struct { + Path string `json:"path"` + Contents string `json:"contents"` + }{ + Path: f.Path, + Contents: base64.StdEncoding.EncodeToString(f.Contents), + } + return json.Marshal(file) +} + +// CreateOpts specifies server creation parameters. +type CreateOpts struct { + // Name is the name to assign to the newly launched server. + Name string `json:"name" required:"true"` + + // ImageRef [optional; required if ImageName is not provided] is the ID or + // full URL to the image that contains the server's OS and initial state. + // Also optional if using the boot-from-volume extension. + ImageRef string `json:"imageRef"` + + // ImageName [optional; required if ImageRef is not provided] is the name of + // the image that contains the server's OS and initial state. + // Also optional if using the boot-from-volume extension. + ImageName string `json:"-"` + + // FlavorRef [optional; required if FlavorName is not provided] is the ID or + // full URL to the flavor that describes the server's specs. + FlavorRef string `json:"flavorRef"` + + // FlavorName [optional; required if FlavorRef is not provided] is the name of + // the flavor that describes the server's specs. + FlavorName string `json:"-"` + + // SecurityGroups lists the names of the security groups to which this server + // should belong. + SecurityGroups []string `json:"-"` + + // UserData contains configuration information or scripts to use upon launch. + // Create will base64-encode it for you, if it isn't already. + UserData []byte `json:"-"` + + // AvailabilityZone in which to launch the server. + AvailabilityZone string `json:"availability_zone,omitempty"` + + // Networks dictates how this server will be attached to available networks. + // By default, the server will be attached to all isolated networks for the + // tenant. + Networks []Network `json:"-"` + + // Metadata contains key-value pairs (up to 255 bytes each) to attach to the + // server. + Metadata map[string]string `json:"metadata,omitempty"` + + // Personality includes files to inject into the server at launch. + // Create will base64-encode file contents for you. + Personality Personality `json:"personality,omitempty"` + + // ConfigDrive enables metadata injection through a configuration drive. + ConfigDrive *bool `json:"config_drive,omitempty"` + + // AdminPass sets the root user password. If not set, a randomly-generated + // password will be created and returned in the response. + AdminPass string `json:"adminPass,omitempty"` + + // AccessIPv4 specifies an IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + + // 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 +// CreateOpts. +func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { + sc := opts.ServiceClient + opts.ServiceClient = nil + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.UserData != nil { + var userData string + if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil { + userData = base64.StdEncoding.EncodeToString(opts.UserData) + } else { + userData = string(opts.UserData) + } + b["user_data"] = &userData + } + + if len(opts.SecurityGroups) > 0 { + securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups)) + for i, groupName := range opts.SecurityGroups { + securityGroups[i] = map[string]interface{}{"name": groupName} + } + b["security_groups"] = securityGroups + } + + if len(opts.Networks) > 0 { + networks := make([]map[string]interface{}, len(opts.Networks)) + for i, net := range opts.Networks { + networks[i] = make(map[string]interface{}) + if net.UUID != "" { + networks[i]["uuid"] = net.UUID + } + if net.Port != "" { + networks[i]["port"] = net.Port + } + if net.FixedIP != "" { + networks[i]["fixed_ip"] = net.FixedIP + } + } + b["networks"] = networks + } + + // If ImageRef isn't provided, check if ImageName was provided to ascertain + // the image ID. + if opts.ImageRef == "" { + if opts.ImageName != "" { + if sc == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + imageID, err := images.IDFromName(sc, opts.ImageName) + if err != nil { + return nil, err + } + b["imageRef"] = imageID + } + } + + // If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID. + if opts.FlavorRef == "" { + if opts.FlavorName == "" { + err := ErrNeitherFlavorIDNorFlavorNameProvided{} + err.Argument = "FlavorRef/FlavorName" + return nil, err + } + if sc == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + flavorID, err := flavors.IDFromName(sc, opts.FlavorName) + if err != nil { + return nil, err + } + 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 +} + +// Create requests a server to be provisioned to the user in the current tenant. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToServerCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil) + return +} + +// Delete requests that a server previously provisioned be removed from your +// account. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ForceDelete forces the deletion of a server. +func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) + return +} + +// Get requests details on a single server, 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 +} + +// UpdateOptsBuilder allows extensions to add additional attributes to the +// Update request. +type UpdateOptsBuilder interface { + ToServerUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing +// server. +type UpdateOpts struct { + // Name changes the displayed name of the server. + // The server host name will *not* change. + // Server names are not constrained to be unique, even within the same tenant. + Name string `json:"name,omitempty"` + + // AccessIPv4 provides a new IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + + // AccessIPv6 provides a new IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` +} + +// ToServerUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "server") +} + +// Update requests that various attributes of the indicated server be changed. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToServerUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ChangeAdminPassword alters the administrator or root password for a specified +// server. +func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) { + b := map[string]interface{}{ + "changePassword": map[string]string{ + "adminPass": newPassword, + }, + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} + +// RebootMethod describes the mechanisms by which a server reboot can be requested. +type RebootMethod string + +// These constants determine how a server should be rebooted. +// See the Reboot() function for further details. +const ( + SoftReboot RebootMethod = "SOFT" + HardReboot RebootMethod = "HARD" + OSReboot = SoftReboot + PowerCycle = HardReboot +) + +// RebootOptsBuilder allows extensions to add additional parameters to the +// reboot request. +type RebootOptsBuilder interface { + ToServerRebootMap() (map[string]interface{}, error) +} + +// RebootOpts provides options to the reboot request. +type RebootOpts struct { + // Type is the type of reboot to perform on the server. + Type RebootMethod `json:"type" required:"true"` +} + +// ToServerRebootMap builds a body for the reboot request. +func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "reboot") +} + +/* + Reboot requests that a given server reboot. + + Two methods exist for rebooting a server: + + HardReboot (aka PowerCycle) starts the server instance by physically cutting + power to the machine, or if a VM, terminating it at the hypervisor level. + It's done. Caput. Full stop. + Then, after a brief while, power is rtored or the VM instance restarted. + + SoftReboot (aka OSReboot) simply tells the OS to restart under its own + procedure. + E.g., in Linux, asking it to enter runlevel 6, or executing + "sudo shutdown -r now", or by asking Windows to rtart the machine. +*/ +func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) { + b, err := opts.ToServerRebootMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} + +// RebuildOptsBuilder allows extensions to provide additional parameters to the +// rebuild request. +type RebuildOptsBuilder interface { + ToServerRebuildMap() (map[string]interface{}, error) +} + +// RebuildOpts represents the configuration options used in a server rebuild +// operation. +type RebuildOpts struct { + // AdminPass is the server's admin password + AdminPass string `json:"adminPass,omitempty"` + + // ImageID is the ID of the image you want your server to be provisioned on. + ImageID string `json:"imageRef"` + + // ImageName is readable name of an image. + ImageName string `json:"-"` + + // Name to set the server to + Name string `json:"name,omitempty"` + + // AccessIPv4 [optional] provides a new IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + + // AccessIPv6 [optional] provides a new IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` + + // Metadata [optional] contains key-value pairs (up to 255 bytes each) + // to attach to the server. + Metadata map[string]string `json:"metadata,omitempty"` + + // Personality [optional] includes files to inject into the server at launch. + // Rebuild will base64-encode file contents for you. + Personality Personality `json:"personality,omitempty"` + + // ServiceClient will allow calls to be made to retrieve an image or + // flavor ID by name. + ServiceClient *gophercloud.ServiceClient `json:"-"` +} + +// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON +func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + // If ImageRef isn't provided, check if ImageName was provided to ascertain + // the image ID. + if opts.ImageID == "" { + if opts.ImageName != "" { + if opts.ServiceClient == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName) + if err != nil { + return nil, err + } + b["imageRef"] = imageID + } + } + + return map[string]interface{}{"rebuild": b}, nil +} + +// Rebuild will reprovision the server according to the configuration options +// provided in the RebuildOpts struct. +func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) { + b, err := opts.ToServerRebuildMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil) + return +} + +// ResizeOptsBuilder allows extensions to add additional parameters to the +// resize request. +type ResizeOptsBuilder interface { + ToServerResizeMap() (map[string]interface{}, error) +} + +// ResizeOpts represents the configuration options used to control a Resize +// operation. +type ResizeOpts struct { + // FlavorRef is the ID of the flavor you wish your server to become. + FlavorRef string `json:"flavorRef" required:"true"` +} + +// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON +// request body for the Resize request. +func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "resize") +} + +// Resize instructs the provider to change the flavor of the server. +// +// Note that this implies rebuilding it. +// +// Unfortunately, one cannot pass rebuild parameters to the resize function. +// When the resize completes, the server will be in VERIFY_RESIZE state. +// While in this state, you can explore the use of the new server's +// configuration. If you like it, call ConfirmResize() to commit the resize +// permanently. Otherwise, call RevertResize() to restore the old configuration. +func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { + b, err := opts.ToServerResizeMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} + +// ConfirmResize confirms a previous resize operation on a server. +// See Resize() for more details. +func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201, 202, 204}, + }) + return +} + +// RevertResize cancels a previous resize operation on a server. +// See Resize() for more details. +func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) + return +} + +// ResetMetadataOptsBuilder allows extensions to add additional parameters to +// the Reset request. +type ResetMetadataOptsBuilder interface { + ToMetadataResetMap() (map[string]interface{}, error) +} + +// MetadataOpts is a map that contains key-value pairs. +type MetadataOpts map[string]string + +// ToMetadataResetMap assembles a body for a Reset request based on the contents +// of a MetadataOpts. +func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) { + return map[string]interface{}{"metadata": opts}, nil +} + +// ToMetadataUpdateMap assembles a body for an Update request based on the +// contents of a MetadataOpts. +func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) { + return map[string]interface{}{"metadata": opts}, nil +} + +// ResetMetadata will create multiple new key-value pairs for the given server +// ID. +// Note: Using this operation will erase any already-existing metadata and +// create the new metadata provided. To keep any already-existing metadata, +// use the UpdateMetadatas or UpdateMetadata function. +func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) { + b, err := opts.ToMetadataResetMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Metadata requests all the metadata for the given server ID. +func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) { + _, r.Err = client.Get(metadataURL(client, id), &r.Body, nil) + return +} + +// UpdateMetadataOptsBuilder allows extensions to add additional parameters to +// the Create request. +type UpdateMetadataOptsBuilder interface { + ToMetadataUpdateMap() (map[string]interface{}, error) +} + +// UpdateMetadata updates (or creates) all the metadata specified by opts for +// the given server ID. This operation does not affect already-existing metadata +// that is not specified by opts. +func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { + b, err := opts.ToMetadataUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// MetadatumOptsBuilder allows extensions to add additional parameters to the +// Create request. +type MetadatumOptsBuilder interface { + ToMetadatumCreateMap() (map[string]interface{}, string, error) +} + +// MetadatumOpts is a map of length one that contains a key-value pair. +type MetadatumOpts map[string]string + +// ToMetadatumCreateMap assembles a body for a Create request based on the +// contents of a MetadataumOpts. +func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) { + if len(opts) != 1 { + err := gophercloud.ErrInvalidInput{} + err.Argument = "servers.MetadatumOpts" + err.Info = "Must have 1 and only 1 key-value pair" + return nil, "", err + } + metadatum := map[string]interface{}{"meta": opts} + var key string + for k := range metadatum["meta"].(MetadatumOpts) { + key = k + } + return metadatum, key, nil +} + +// CreateMetadatum will create or update the key-value pair with the given key +// for the given server ID. +func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) { + b, key, err := opts.ToMetadatumCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Metadatum requests the key-value pair with the given key for the given +// server ID. +func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) { + _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) + return +} + +// DeleteMetadatum will delete the key-value pair with the given key for the +// given server ID. +func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) { + _, r.Err = client.Delete(metadatumURL(client, id, key), nil) + return +} + +// ListAddresses makes a request against the API to list the servers IP +// addresses. +func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager { + return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page { + return AddressPage{pagination.SinglePageBase(r)} + }) +} + +// ListAddressesByNetwork makes a request against the API to list the servers IP +// addresses for the given network. +func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager { + return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page { + return NetworkAddressPage{pagination.SinglePageBase(r)} + }) +} + +// CreateImageOptsBuilder allows extensions to add additional parameters to the +// CreateImage request. +type CreateImageOptsBuilder interface { + ToServerCreateImageMap() (map[string]interface{}, error) +} + +// CreateImageOpts provides options to pass to the CreateImage request. +type CreateImageOpts struct { + // Name of the image/snapshot. + Name string `json:"name" required:"true"` + + // Metadata contains key-value pairs (up to 255 bytes each) to attach to + // the created image. + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToServerCreateImageMap formats a CreateImageOpts structure into a request +// body. +func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "createImage") +} + +// CreateImage makes a request against the nova API to schedule an image to be +// created of the server +func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) { + b, err := opts.ToServerCreateImageMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + r.Err = err + r.Header = resp.Header + return +} + +// IDFromName is a convienience function that returns a server's ID given its +// name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + allPages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractServers(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} + } +} + +// GetPassword makes a request against the nova API to get the encrypted +// administrative password. +func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) { + _, 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 new file mode 100644 index 00000000000..f973d1ea0e1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go @@ -0,0 +1,414 @@ +package servers + +import ( + "crypto/rsa" + "encoding/base64" + "encoding/json" + "fmt" + "net/url" + "path" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type serverResult struct { + gophercloud.Result +} + +// Extract interprets any serverResult as a Server, if possible. +func (r serverResult) Extract() (*Server, error) { + var s Server + err := r.ExtractInto(&s) + return &s, err +} + +func (r serverResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "server") +} + +func ExtractServersInto(r pagination.Page, v interface{}) error { + return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") +} + +// CreateResult is the response from a Create operation. Call its Extract +// method to interpret it as a Server. +type CreateResult struct { + serverResult +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Server. +type GetResult struct { + serverResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Server. +type UpdateResult struct { + serverResult +} + +// 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 +} + +// RebuildResult is the response from a Rebuild operation. Call its Extract +// method to interpret it as a Server. +type RebuildResult struct { + serverResult +} + +// ActionResult represents the result of server action operations, like reboot. +// Call its ExtractErr method to determine if the action succeeded or failed. +type ActionResult struct { + gophercloud.ErrResult +} + +// 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 { + gophercloud.Result +} + +// ExtractPassword gets the encrypted password. +// If privateKey != nil the password is decrypted with the private key. +// If privateKey == nil the encrypted password is returned and can be decrypted +// with: +// echo '' | base64 -D | openssl rsautl -decrypt -inkey +func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { + var s struct { + Password string `json:"password"` + } + err := r.ExtractInto(&s) + if err == nil && privateKey != nil && s.Password != "" { + return decryptPassword(s.Password, privateKey) + } + return s.Password, err +} + +func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) { + b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword))) + + n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) + if err != nil { + return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err) + } + password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) + if err != nil { + return "", fmt.Errorf("Failed to decrypt password: %s", err) + } + + return string(password), nil +} + +// ExtractImageID gets the ID of the newly created server image from the header. +func (r CreateImageResult) ExtractImageID() (string, error) { + if r.Err != nil { + return "", r.Err + } + // Get the image id from the header + u, err := url.ParseRequestURI(r.Header.Get("Location")) + if err != nil { + return "", err + } + imageID := path.Base(u.Path) + if imageID == "." || imageID == "/" { + return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u) + } + return imageID, nil +} + +// Server represents a server/instance in the OpenStack cloud. +type Server struct { + // ID uniquely identifies this server amongst all other servers, + // including those not accessible to the current tenant. + ID string `json:"id"` + + // TenantID identifies the tenant owning this server resource. + TenantID string `json:"tenant_id"` + + // UserID uniquely identifies the user account owning the tenant. + UserID string `json:"user_id"` + + // Name contains the human-readable name for the server. + Name string `json:"name"` + + // Updated and Created contain ISO-8601 timestamps of when the state of the + // server last changed, and when it was created. + Updated time.Time `json:"updated"` + Created time.Time `json:"created"` + + // HostID is the host where the server is located in the cloud. + HostID string `json:"hostid"` + + // Status contains the current operational status of the server, + // such as IN_PROGRESS or ACTIVE. + Status string `json:"status"` + + // Progress ranges from 0..100. + // A request made against the server completes only once Progress reaches 100. + Progress int `json:"progress"` + + // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, + // suitable for remote access for administration. + AccessIPv4 string `json:"accessIPv4"` + AccessIPv6 string `json:"accessIPv6"` + + // Image refers to a JSON object, which itself indicates the OS image used to + // deploy the server. + Image map[string]interface{} `json:"-"` + + // Flavor refers to a JSON object, which itself indicates the hardware + // configuration of the deployed server. + Flavor map[string]interface{} `json:"flavor"` + + // Addresses includes a list of all IP addresses assigned to the server, + // keyed by pool. + Addresses map[string]interface{} `json:"addresses"` + + // Metadata includes a list of all user-specified key-value pairs attached + // to the server. + Metadata map[string]string `json:"metadata"` + + // Links includes HTTP references to the itself, useful for passing along to + // other APIs that might want a server reference. + Links []interface{} `json:"links"` + + // KeyName indicates which public key was injected into the server on launch. + KeyName string `json:"key_name"` + + // AdminPass will generally be empty (""). However, it will contain the + // administrative password chosen when provisioning a new server without a + // set AdminPass setting in the first place. + // Note that this is the ONLY time this field will be valid. + AdminPass string `json:"adminPass"` + + // SecurityGroups includes the security groups that this instance has applied + // to it. + SecurityGroups []map[string]interface{} `json:"security_groups"` + + // Fault contains failure information about a server. + Fault Fault `json:"fault"` +} + +type Fault struct { + Code int `json:"code"` + Created time.Time `json:"created"` + Details string `json:"details"` + Message string `json:"message"` +} + +func (r *Server) UnmarshalJSON(b []byte) error { + type tmp Server + var s struct { + tmp + Image interface{} `json:"image"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Server(s.tmp) + + switch t := s.Image.(type) { + case map[string]interface{}: + r.Image = t + case string: + switch t { + case "": + r.Image = nil + } + } + + return err +} + +// ServerPage 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 ExtractServers call. +type ServerPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Server results. +func (r ServerPage) IsEmpty() (bool, error) { + s, err := ExtractServers(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r ServerPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"servers_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractServers interprets the results of a single page from a List() call, +// producing a slice of Server entities. +func ExtractServers(r pagination.Page) ([]Server, error) { + var s []Server + err := ExtractServersInto(r, &s) + return s, err +} + +// MetadataResult contains the result of a call for (potentially) multiple +// key-value pairs. Call its Extract method to interpret it as a +// map[string]interface. +type MetadataResult struct { + gophercloud.Result +} + +// GetMetadataResult contains the result of a Get operation. Call its Extract +// method to interpret it as a map[string]interface. +type GetMetadataResult struct { + MetadataResult +} + +// ResetMetadataResult contains the result of a Reset operation. Call its +// Extract method to interpret it as a map[string]interface. +type ResetMetadataResult struct { + MetadataResult +} + +// UpdateMetadataResult contains the result of an Update operation. Call its +// Extract method to interpret it as a map[string]interface. +type UpdateMetadataResult struct { + MetadataResult +} + +// MetadatumResult contains the result of a call for individual a single +// key-value pair. +type MetadatumResult struct { + gophercloud.Result +} + +// GetMetadatumResult contains the result of a Get operation. Call its Extract +// method to interpret it as a map[string]interface. +type GetMetadatumResult struct { + MetadatumResult +} + +// CreateMetadatumResult contains the result of a Create operation. Call its +// Extract method to interpret it as a map[string]interface. +type CreateMetadatumResult struct { + MetadatumResult +} + +// DeleteMetadatumResult contains the result of a Delete operation. Call its +// ExtractErr method to determine if the call succeeded or failed. +type DeleteMetadatumResult struct { + gophercloud.ErrResult +} + +// Extract interprets any MetadataResult as a Metadata, if possible. +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 +} + +// Extract interprets any MetadatumResult as a Metadatum, if possible. +func (r MetadatumResult) Extract() (map[string]string, error) { + var s struct { + Metadatum map[string]string `json:"meta"` + } + err := r.ExtractInto(&s) + return s.Metadatum, err +} + +// Address represents an IP address. +type Address struct { + Version int `json:"version"` + Address string `json:"addr"` +} + +// AddressPage abstracts the raw results of making a ListAddresses() 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 ExtractAddresses call. +type AddressPage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if an AddressPage contains no networks. +func (r AddressPage) IsEmpty() (bool, error) { + addresses, err := ExtractAddresses(r) + return len(addresses) == 0, err +} + +// ExtractAddresses interprets the results of a single page from a +// ListAddresses() call, producing a map of addresses. +func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { + var s struct { + Addresses map[string][]Address `json:"addresses"` + } + err := (r.(AddressPage)).ExtractInto(&s) + return s.Addresses, err +} + +// NetworkAddressPage abstracts the raw results of making a +// ListAddressesByNetwork() 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 ExtractAddresses call. +type NetworkAddressPage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a NetworkAddressPage contains no addresses. +func (r NetworkAddressPage) IsEmpty() (bool, error) { + addresses, err := ExtractNetworkAddresses(r) + return len(addresses) == 0, err +} + +// ExtractNetworkAddresses interprets the results of a single page from a +// ListAddressesByNetwork() call, producing a slice of addresses. +func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { + var s map[string][]Address + err := (r.(NetworkAddressPage)).ExtractInto(&s) + if err != nil { + return nil, err + } + + var key string + for k := range s { + key = k + } + + return s[key], err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go new file mode 100644 index 00000000000..e892e8d9259 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go @@ -0,0 +1,51 @@ +package servers + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("servers") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("servers", "detail") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", 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 actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} + +func metadatumURL(client *gophercloud.ServiceClient, id, key string) string { + return client.ServiceURL("servers", id, "metadata", key) +} + +func metadataURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "metadata") +} + +func listAddressesURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "ips") +} + +func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string { + return client.ServiceURL("servers", id, "ips", network) +} + +func passwordURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "os-server-password") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go new file mode 100644 index 00000000000..cadef054506 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go @@ -0,0 +1,21 @@ +package servers + +import "github.com/gophercloud/gophercloud" + +// WaitForStatus will continually poll a server until it successfully +// transitions to a specified status. It will do this for at most the number +// of seconds specified. +func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := Get(c, 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/openstack/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go new file mode 100644 index 00000000000..cedf1f4d3a3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go @@ -0,0 +1,14 @@ +/* +Package openstack contains resources for the individual OpenStack projects +supported in Gophercloud. It also includes functions to authenticate to an +OpenStack cloud and for provisioning various service-level clients. + +Example of Creating a Service Client + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(ao) + client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +*/ +package openstack diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go new file mode 100644 index 00000000000..12c8aebcf77 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go @@ -0,0 +1,107 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud" + tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" +) + +/* +V2EndpointURL discovers the endpoint URL for a specific service from a +ServiceCatalog acquired during the v2 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ +func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. + var endpoints = make([]tokens2.Endpoint, 0, 1) + for _, entry := range catalog.Entries { + if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + for _, endpoint := range entry.Endpoints { + if opts.Region == "" || endpoint.Region == opts.Region { + endpoints = append(endpoints, endpoint) + } + } + } + } + + // Report an error if the options were ambiguous. + if len(endpoints) > 1 { + err := &ErrMultipleMatchingEndpointsV2{} + err.Endpoints = endpoints + return "", err + } + + // Extract the appropriate URL from the matching Endpoint. + for _, endpoint := range endpoints { + switch opts.Availability { + case gophercloud.AvailabilityPublic: + return gophercloud.NormalizeURL(endpoint.PublicURL), nil + case gophercloud.AvailabilityInternal: + return gophercloud.NormalizeURL(endpoint.InternalURL), nil + case gophercloud.AvailabilityAdmin: + return gophercloud.NormalizeURL(endpoint.AdminURL), nil + default: + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + } + + // Report an error if there were no matching endpoints. + err := &gophercloud.ErrEndpointNotFound{} + return "", err +} + +/* +V3EndpointURL discovers the endpoint URL for a specific service from a Catalog +acquired during the v3 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ +func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + // Extract Endpoints from the catalog entries that match the requested Type, Interface, + // Name if provided, and Region if provided. + var endpoints = make([]tokens3.Endpoint, 0, 1) + for _, entry := range catalog.Entries { + if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + for _, endpoint := range entry.Endpoints { + if opts.Availability != gophercloud.AvailabilityAdmin && + opts.Availability != gophercloud.AvailabilityPublic && + opts.Availability != gophercloud.AvailabilityInternal { + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + if (opts.Availability == gophercloud.Availability(endpoint.Interface)) && + (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) { + endpoints = append(endpoints, endpoint) + } + } + } + } + + // Report an error if the options were ambiguous. + if len(endpoints) > 1 { + return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints} + } + + // Extract the URL from the matching Endpoint. + for _, endpoint := range endpoints { + return gophercloud.NormalizeURL(endpoint.URL), nil + } + + // Report an error if there were no matching endpoints. + err := &gophercloud.ErrEndpointNotFound{} + return "", err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go new file mode 100644 index 00000000000..df410b1c611 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go @@ -0,0 +1,71 @@ +package openstack + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" +) + +// ErrEndpointNotFound is the error when no suitable endpoint can be found +// in the user's catalog +type ErrEndpointNotFound struct{ gophercloud.BaseError } + +func (e ErrEndpointNotFound) Error() string { + return "No suitable endpoint could be found in the service catalog." +} + +// ErrInvalidAvailabilityProvided is the error when an invalid endpoint +// availability is provided +type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput } + +func (e ErrInvalidAvailabilityProvided) Error() string { + return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value) +} + +// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint +// for the given options is found in the v2 catalog +type ErrMultipleMatchingEndpointsV2 struct { + gophercloud.BaseError + Endpoints []tokens2.Endpoint +} + +func (e ErrMultipleMatchingEndpointsV2) Error() string { + return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints) +} + +// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint +// for the given options is found in the v3 catalog +type ErrMultipleMatchingEndpointsV3 struct { + gophercloud.BaseError + Endpoints []tokens3.Endpoint +} + +func (e ErrMultipleMatchingEndpointsV3) Error() string { + return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints) +} + +// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not +// found +type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput } + +func (e ErrNoAuthURL) Error() string { + return "Environment variable OS_AUTH_URL needs to be set." +} + +// ErrNoUsername is the error when the OS_USERNAME environment variable is not +// found +type ErrNoUsername struct{ gophercloud.ErrInvalidInput } + +func (e ErrNoUsername) Error() string { + return "Environment variable OS_USERNAME needs to be set." +} + +// ErrNoPassword is the error when the OS_PASSWORD environment variable is not +// found +type ErrNoPassword struct{ gophercloud.ErrInvalidInput } + +func (e ErrNoPassword) Error() string { + return "Environment variable OS_PASSWORD needs to be set." +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go new file mode 100644 index 00000000000..45623369e18 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go @@ -0,0 +1,65 @@ +/* +Package tenants provides information and interaction with the +tenants API resource for the OpenStack Identity service. + +See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 +and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants +for more information. + +Example to List Tenants + + listOpts := tenants.ListOpts{ + Limit: 2, + } + + allPages, err := tenants.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allTenants, err := tenants.ExtractTenants(allPages) + if err != nil { + panic(err) + } + + for _, tenant := range allTenants { + fmt.Printf("%+v\n", tenant) + } + +Example to Create a Tenant + + createOpts := tenants.CreateOpts{ + Name: "tenant_name", + Description: "this is a tenant", + Enabled: gophercloud.Enabled, + } + + tenant, err := tenants.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Tenant + + tenantID := "e6db6ed6277c461a853458589063b295" + + updateOpts := tenants.UpdateOpts{ + Description: "this is a new description", + Enabled: gophercloud.Disabled, + } + + tenant, err := tenants.Update(identityClient, tenantID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Tenant + + tenantID := "e6db6ed6277c461a853458589063b295" + + err := tenants.Delete(identitYClient, tenantID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package tenants 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 new file mode 100644 index 00000000000..f21a58f10c8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go @@ -0,0 +1,116 @@ +package tenants + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOpts filters the Tenants that are returned by the List call. +type ListOpts struct { + // Marker is the ID of the last Tenant on the previous page. + Marker string `q:"marker"` + + // Limit specifies the page size. + Limit int `q:"limit"` +} + +// List enumerates the Tenants to which the current token has access. +func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager { + url := listURL(client) + if opts != nil { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + url += q.String() + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return TenantPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOpts represents the options needed when creating new tenant. +type CreateOpts struct { + // Name is the name of the tenant. + Name string `json:"name" required:"true"` + + // Description is the description of the tenant. + Description string `json:"description,omitempty"` + + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// CreateOptsBuilder enables extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToTenantCreateMap() (map[string]interface{}, error) +} + +// ToTenantCreateMap assembles a request body based on the contents of +// a CreateOpts. +func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Create is the operation responsible for creating new tenant. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTenantCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// Get requests details on a single tenant by ID. +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 { + ToTenantUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing +// tenant. +type UpdateOpts struct { + // Name is the name of the tenant. + Name string `json:"name,omitempty"` + + // Description is the description of the tenant. + Description *string `json:"description,omitempty"` + + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// ToTenantUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Update is the operation responsible for updating exist tenants by their TenantID. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToTenantUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete is the operation responsible for permanently deleting a tenant. +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/identity/v2/tenants/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go new file mode 100644 index 00000000000..bb6c2c6b08a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go @@ -0,0 +1,91 @@ +package tenants + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Tenant is a grouping of users in the identity service. +type Tenant struct { + // ID is a unique identifier for this tenant. + ID string `json:"id"` + + // Name is a friendlier user-facing name for this tenant. + Name string `json:"name"` + + // Description is a human-readable explanation of this Tenant's purpose. + Description string `json:"description"` + + // Enabled indicates whether or not a tenant is active. + Enabled bool `json:"enabled"` +} + +// TenantPage is a single page of Tenant results. +type TenantPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Tenants contains any results. +func (r TenantPage) IsEmpty() (bool, error) { + tenants, err := ExtractTenants(r) + return len(tenants) == 0, err +} + +// NextPageURL extracts the "next" link from the tenants_links section of the result. +func (r TenantPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"tenants_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractTenants returns a slice of Tenants contained in a single page of +// results. +func ExtractTenants(r pagination.Page) ([]Tenant, error) { + var s struct { + Tenants []Tenant `json:"tenants"` + } + err := (r.(TenantPage)).ExtractInto(&s) + return s.Tenants, err +} + +type tenantResult struct { + gophercloud.Result +} + +// Extract interprets any tenantResults as a Tenant. +func (r tenantResult) Extract() (*Tenant, error) { + var s struct { + Tenant *Tenant `json:"tenant"` + } + err := r.ExtractInto(&s) + return s.Tenant, err +} + +// GetResult is the response from a Get request. Call its Extract method to +// interpret it as a Tenant. +type GetResult struct { + tenantResult +} + +// CreateResult is the response from a Create request. Call its Extract method +// to interpret it as a Tenant. +type CreateResult struct { + tenantResult +} + +// DeleteResult is the response from a Get request. Call its ExtractErr method +// to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the response from a Update request. Call its Extract method +// to interpret it as a Tenant. +type UpdateResult struct { + tenantResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go new file mode 100644 index 00000000000..0f026690790 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go @@ -0,0 +1,23 @@ +package tenants + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tenants") +} + +func getURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tenants") +} + +func deleteURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func updateURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go new file mode 100644 index 00000000000..5375eea8726 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go @@ -0,0 +1,46 @@ +/* +Package tokens provides information and interaction with the token API +resource for the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 + +Example to Create an Unscoped Token from a Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "pass" + } + + token, err := tokens.Create(identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Tenant ID and Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "password", + TenantID: "fc394f2ab2df4114bde39905f800dc57" + } + + token, err := tokens.Create(identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Tenant Name and Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "password", + TenantName: "tenantname" + } + + token, err := tokens.Create(identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } +*/ +package tokens diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go new file mode 100644 index 00000000000..ab32368cc6e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go @@ -0,0 +1,103 @@ +package tokens + +import "github.com/gophercloud/gophercloud" + +// PasswordCredentialsV2 represents the required options to authenticate +// with a username and password. +type PasswordCredentialsV2 struct { + Username string `json:"username" required:"true"` + Password string `json:"password" required:"true"` +} + +// TokenCredentialsV2 represents the required options to authenticate +// with a token. +type TokenCredentialsV2 struct { + ID string `json:"id,omitempty" required:"true"` +} + +// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the +// AuthOptionsBuilder interface. +type AuthOptionsV2 struct { + PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"` + + // The TenantID and TenantName fields are optional for the Identity V2 API. + // Some providers allow you to specify a TenantName instead of the TenantId. + // Some require both. Your provider's authentication policies will determine + // how these fields influence authentication. + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` + + // TokenCredentials allows users to authenticate (possibly as another user) + // with an authentication token ID. + TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"` +} + +// AuthOptionsBuilder allows extensions to add additional parameters to the +// token create request. +type AuthOptionsBuilder interface { + // ToTokenCreateMap assembles the Create request body, returning an error + // if parameters are missing or inconsistent. + ToTokenV2CreateMap() (map[string]interface{}, error) +} + +// AuthOptions are the valid options for Openstack Identity v2 authentication. +// For field descriptions, see gophercloud.AuthOptions. +type AuthOptions struct { + IdentityEndpoint string `json:"-"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` + AllowReauth bool `json:"-"` + TokenID string +} + +// ToTokenV2CreateMap builds a token request body from the given AuthOptions. +func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { + v2Opts := AuthOptionsV2{ + TenantID: opts.TenantID, + TenantName: opts.TenantName, + } + + if opts.Password != "" { + v2Opts.PasswordCredentials = &PasswordCredentialsV2{ + Username: opts.Username, + Password: opts.Password, + } + } else { + v2Opts.TokenCredentials = &TokenCredentialsV2{ + ID: opts.TokenID, + } + } + + b, err := gophercloud.BuildRequestBody(v2Opts, "auth") + if err != nil { + return nil, err + } + return b, nil +} + +// Create authenticates to the identity service and attempts to acquire a Token. +// Generally, rather than interact with this call directly, end users should +// call openstack.AuthenticatedClient(), which abstracts all of the gory details +// about navigating service catalogs and such. +func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) { + b, err := auth.ToTokenV2CreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + MoreHeaders: map[string]string{"X-Auth-Token": ""}, + }) + return +} + +// Get validates and retrieves information for user's token. +func Get(client *gophercloud.ServiceClient, token string) (r GetResult) { + _, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + }) + return +} 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 new file mode 100644 index 00000000000..ee5da37f465 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go @@ -0,0 +1,174 @@ +package tokens + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants" +) + +// Token provides only the most basic information related to an authentication +// token. +type Token struct { + // ID provides the primary means of identifying a user to the OpenStack API. + // OpenStack defines this field as an opaque value, so do not depend on its + // content. It is safe, however, to compare for equality. + ID string + + // ExpiresAt provides a timestamp in ISO 8601 format, indicating when the + // authentication token becomes invalid. After this point in time, future + // API requests made using this authentication token will respond with + // errors. Either the caller will need to reauthenticate manually, or more + // preferably, the caller should exploit automatic re-authentication. + // See the AuthOptions structure for more details. + ExpiresAt time.Time + + // Tenant provides information about the tenant to which this token grants + // access. + Tenant tenants.Tenant +} + +// Role is a role for a user. +type Role struct { + Name string `json:"name"` +} + +// User is an OpenStack user. +type User struct { + ID string `json:"id"` + Name string `json:"name"` + UserName string `json:"username"` + Roles []Role `json:"roles"` +} + +// Endpoint represents a single API endpoint offered by a service. +// It provides the public and internal URLs, if supported, along with a region +// specifier, again if provided. +// +// The significance of the Region field will depend upon your provider. +// +// In addition, the interface offered by the service will have version +// information associated with it through the VersionId, VersionInfo, and +// VersionList fields, if provided or supported. +// +// In all cases, fields which aren't supported by the provider and service +// combined will assume a zero-value (""). +type Endpoint struct { + TenantID string `json:"tenantId"` + PublicURL string `json:"publicURL"` + InternalURL string `json:"internalURL"` + AdminURL string `json:"adminURL"` + Region string `json:"region"` + VersionID string `json:"versionId"` + VersionInfo string `json:"versionInfo"` + VersionList string `json:"versionList"` +} + +// CatalogEntry provides a type-safe interface to an Identity API V2 service +// catalog listing. +// +// Each class of service, such as cloud DNS or block storage services, will have +// a single CatalogEntry representing it. +// +// Note: when looking for the desired service, try, whenever possible, to key +// off the type field. Otherwise, you'll tie the representation of the service +// to a specific provider. +type CatalogEntry struct { + // Name will contain the provider-specified name for the service. + Name string `json:"name"` + + // Type will contain a type string if OpenStack defines a type for the + // service. Otherwise, for provider-specific services, the provider may assign + // their own type strings. + Type string `json:"type"` + + // Endpoints will let the caller iterate over all the different endpoints that + // may exist for the service. + Endpoints []Endpoint `json:"endpoints"` +} + +// ServiceCatalog provides a view into the service catalog from a previous, +// successful authentication. +type ServiceCatalog struct { + Entries []CatalogEntry +} + +// CreateResult is the response from a Create request. Use ExtractToken() to +// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a +// service catalog. +type CreateResult struct { + gophercloud.Result +} + +// GetResult is the deferred response from a Get call, which is the same with a +// Created token. Use ExtractUser() to interpret it as a User. +type GetResult struct { + CreateResult +} + +// ExtractToken returns the just-created Token from a CreateResult. +func (r CreateResult) ExtractToken() (*Token, error) { + var s struct { + Access struct { + Token struct { + Expires string `json:"expires"` + ID string `json:"id"` + Tenant tenants.Tenant `json:"tenant"` + } `json:"token"` + } `json:"access"` + } + + err := r.ExtractInto(&s) + if err != nil { + return nil, err + } + + expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires) + if err != nil { + return nil, err + } + + return &Token{ + ID: s.Access.Token.ID, + ExpiresAt: expiresTs, + Tenant: s.Access.Token.Tenant, + }, 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) { + var s struct { + Access struct { + Entries []CatalogEntry `json:"serviceCatalog"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return &ServiceCatalog{Entries: s.Access.Entries}, err +} + +// ExtractUser returns the User from a GetResult. +func (r GetResult) ExtractUser() (*User, error) { + var s struct { + Access struct { + User User `json:"user"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return &s.Access.User, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go new file mode 100644 index 00000000000..ee0a28f2004 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go @@ -0,0 +1,13 @@ +package tokens + +import "github.com/gophercloud/gophercloud" + +// CreateURL generates the URL used to create new Tokens. +func CreateURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tokens") +} + +// GetURL generates the URL used to Validate Tokens. +func GetURL(client *gophercloud.ServiceClient, token string) string { + return client.ServiceURL("tokens", token) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go new file mode 100644 index 00000000000..8db7724f2b0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go @@ -0,0 +1,26 @@ +/* +Package trusts enables management of OpenStack Identity Trusts. + +Example to Create a Token with Username, Password, and Trust ID + + var trustToken struct { + tokens.Token + trusts.TokenExt + } + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + } + + createOpts := trusts.AuthOptsExt{ + AuthOptionsBuilder: authOptions, + TrustID: "de0945a", + } + + err := tokens.Create(identityClient, createOpts).ExtractInto(&trustToken) + if err != nil { + panic(err) + } +*/ +package trusts diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go new file mode 100644 index 00000000000..438fba61de5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go @@ -0,0 +1,39 @@ +package trusts + +import "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + +// AuthOptsExt extends the base Identity v3 tokens AuthOpts with a TrustID. +type AuthOptsExt struct { + tokens.AuthOptionsBuilder + + // TrustID is the ID of the trust. + TrustID string `json:"id"` +} + +// ToTokenV3CreateMap builds a create request body from the AuthOpts. +func (opts AuthOptsExt) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { + return opts.AuthOptionsBuilder.ToTokenV3CreateMap(scope) +} + +// ToTokenV3ScopeMap builds a scope from AuthOpts. +func (opts AuthOptsExt) ToTokenV3ScopeMap() (map[string]interface{}, error) { + b, err := opts.AuthOptionsBuilder.ToTokenV3ScopeMap() + if err != nil { + return nil, err + } + + if opts.TrustID != "" { + if b == nil { + b = make(map[string]interface{}) + } + b["OS-TRUST:trust"] = map[string]interface{}{ + "id": opts.TrustID, + } + } + + return b, nil +} + +func (opts AuthOptsExt) CanReauth() bool { + return opts.AuthOptionsBuilder.CanReauth() +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go new file mode 100644 index 00000000000..e6912e612c2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go @@ -0,0 +1,27 @@ +package trusts + +// TrusteeUser represents the trusted user ID of a trust. +type TrusteeUser struct { + ID string `json:"id"` +} + +// TrustorUser represents the trusting user ID of a trust. +type TrustorUser struct { + ID string `json:"id"` +} + +// Trust represents a delegated authorization request between two +// identities. +type Trust struct { + ID string `json:"id"` + Impersonation bool `json:"impersonation"` + TrusteeUser TrusteeUser `json:"trustee_user"` + TrustorUser TrustorUser `json:"trustor_user"` + RedelegatedTrustID string `json:"redelegated_trust_id"` + RedelegationCount int `json:"redelegation_count"` +} + +// TokenExt represents an extension of the base token result. +type TokenExt struct { + Trust Trust `json:"OS-TRUST:trust"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go new file mode 100644 index 00000000000..966e128f128 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go @@ -0,0 +1,108 @@ +/* +Package tokens provides information and interaction with the token API +resource for the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3 + +Example to Create a Token From a Username and Password + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + } + + token, err := tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token From a Username, Password, and Domain + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + DomainID: "default", + } + + token, err := tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + + authOptions = tokens.AuthOptions{ + UserID: "username", + Password: "password", + DomainName: "default", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token From a Token + + authOptions := tokens.AuthOptions{ + TokenID: "token_id", + } + + token, err := tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Project ID Scope + + scope := tokens.Scope{ + ProjectID: "0fe36e73809d46aeae6705c39077b1b3", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Domain ID Scope + + scope := tokens.Scope{ + DomainID: "default", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Project Name Scope + + scope := tokens.Scope{ + ProjectName: "project_name", + DomainID: "default", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +*/ +package tokens 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 new file mode 100644 index 00000000000..e4d766b2327 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go @@ -0,0 +1,162 @@ +package tokens + +import "github.com/gophercloud/gophercloud" + +// Scope allows a created token to be limited to a specific domain or project. +type Scope struct { + ProjectID string + ProjectName string + DomainID string + DomainName string +} + +// AuthOptionsBuilder provides the ability for extensions to add additional +// parameters to AuthOptions. Extensions must satisfy all required methods. +type AuthOptionsBuilder interface { + // ToTokenV3CreateMap assembles the Create request body, returning an error + // if parameters are missing or inconsistent. + ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) + ToTokenV3ScopeMap() (map[string]interface{}, error) + CanReauth() bool +} + +// AuthOptions represents options for authenticating a user. +type AuthOptions struct { + // IdentityEndpoint specifies the HTTP endpoint that is required to work with + // the Identity API of the appropriate version. While it's ultimately needed + // by all of the identity services, it will often be populated by a + // provider-level function. + IdentityEndpoint string `json:"-"` + + // Username is required if using Identity V2 API. Consult with your provider's + // control panel to discover your account's username. In Identity V3, either + // UserID or a combination of Username and DomainID or DomainName are needed. + Username string `json:"username,omitempty"` + UserID string `json:"id,omitempty"` + + Password string `json:"password,omitempty"` + + // At most one of DomainID and DomainName must be provided if using Username + // with Identity V3. Otherwise, either are optional. + DomainID string `json:"-"` + DomainName string `json:"name,omitempty"` + + // AllowReauth should be set to true if you grant permission for Gophercloud + // to cache your credentials in memory, and to allow Gophercloud to attempt + // to re-authenticate automatically if/when your token expires. If you set + // it to false, it will not cache these settings, but re-authentication will + // not be possible. This setting defaults to false. + AllowReauth bool `json:"-"` + + // TokenID allows users to authenticate (possibly as another user) with an + // 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, + ApplicationCredentialID: opts.ApplicationCredentialID, + ApplicationCredentialName: opts.ApplicationCredentialName, + ApplicationCredentialSecret: opts.ApplicationCredentialSecret, + } + + return gophercloudAuthOpts.ToTokenV3CreateMap(scope) +} + +// ToTokenV3CreateMap builds a scope request body from AuthOptions. +func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { + scope := gophercloud.AuthScope(opts.Scope) + + gophercloudAuthOpts := gophercloud.AuthOptions{ + Scope: &scope, + DomainID: opts.DomainID, + DomainName: opts.DomainName, + } + + return gophercloudAuthOpts.ToTokenV3ScopeMap() +} + +func (opts *AuthOptions) CanReauth() bool { + return opts.AllowReauth +} + +func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string { + return map[string]string{ + "X-Subject-Token": subjectToken, + } +} + +// Create authenticates and either generates a new token, or changes the Scope +// of an existing token. +func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) { + scope, err := opts.ToTokenV3ScopeMap() + if err != nil { + r.Err = err + return + } + + b, err := opts.ToTokenV3CreateMap(scope) + if err != nil { + r.Err = err + return + } + + resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{ + MoreHeaders: map[string]string{"X-Auth-Token": ""}, + }) + r.Err = err + if resp != nil { + r.Header = resp.Header + } + return +} + +// Get validates and retrieves information about another token. +func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { + resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(c, token), + OkCodes: []int{200, 203}, + }) + if resp != nil { + 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.Head(tokenURL(c), &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(c, token), + OkCodes: []int{200, 204, 404}, + }) + if err != nil { + return false, err + } + + return resp.StatusCode == 200 || resp.StatusCode == 204, nil +} + +// Revoke immediately makes specified token invalid. +func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) { + _, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(c, token), + }) + return +} 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 new file mode 100644 index 00000000000..6f26c96bcdc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go @@ -0,0 +1,178 @@ +package tokens + +import ( + "time" + + "github.com/gophercloud/gophercloud" +) + +// Endpoint represents a single API endpoint offered by a service. +// It matches either a public, internal or admin URL. +// If supported, it contains a region specifier, again if provided. +// The significance of the Region field will depend upon your provider. +type Endpoint struct { + ID string `json:"id"` + Region string `json:"region"` + RegionID string `json:"region_id"` + Interface string `json:"interface"` + URL string `json:"url"` +} + +// CatalogEntry provides a type-safe interface to an Identity API V3 service +// catalog listing. Each class of service, such as cloud DNS or block storage +// services, could have multiple CatalogEntry representing it (one by interface +// type, e.g public, admin or internal). +// +// Note: when looking for the desired service, try, whenever possible, to key +// off the type field. Otherwise, you'll tie the representation of the service +// to a specific provider. +type CatalogEntry struct { + // Service ID + ID string `json:"id"` + + // Name will contain the provider-specified name for the service. + Name string `json:"name"` + + // Type will contain a type string if OpenStack defines a type for the + // service. Otherwise, for provider-specific services, the provider may + // assign their own type strings. + Type string `json:"type"` + + // Endpoints will let the caller iterate over all the different endpoints that + // may exist for the service. + Endpoints []Endpoint `json:"endpoints"` +} + +// ServiceCatalog provides a view into the service catalog from a previous, +// successful authentication. +type ServiceCatalog struct { + Entries []CatalogEntry `json:"catalog"` +} + +// Domain provides information about the domain to which this token grants +// access. +type Domain struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// User represents a user resource that exists in the Identity Service. +type User struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +// Role provides information about roles to which User is authorized. +type Role struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Project provides information about project to which User is authorized. +type Project struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +// commonResult is the response from a request. A commonResult has various +// methods which can be used to extract different details about the result. +type commonResult struct { + gophercloud.Result +} + +// Extract is a shortcut for ExtractToken. +// This function is deprecated and still present for backward compatibility. +func (r commonResult) Extract() (*Token, error) { + return r.ExtractToken() +} + +// ExtractToken interprets a commonResult as a Token. +func (r commonResult) ExtractToken() (*Token, error) { + var s Token + err := r.ExtractInto(&s) + if err != nil { + return nil, err + } + + // Parse the token itself from the stored headers. + s.ID = r.Header.Get("X-Subject-Token") + + 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) { + var s ServiceCatalog + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractUser returns the User that is the owner of the Token. +func (r commonResult) ExtractUser() (*User, error) { + var s struct { + User *User `json:"user"` + } + err := r.ExtractInto(&s) + return s.User, err +} + +// ExtractRoles returns Roles to which User is authorized. +func (r commonResult) ExtractRoles() ([]Role, error) { + var s struct { + Roles []Role `json:"roles"` + } + err := r.ExtractInto(&s) + return s.Roles, err +} + +// ExtractProject returns Project to which User is authorized. +func (r commonResult) ExtractProject() (*Project, error) { + var s struct { + Project *Project `json:"project"` + } + err := r.ExtractInto(&s) + return s.Project, err +} + +// CreateResult is the response from a Create request. Use ExtractToken() +// to interpret it as a Token, or ExtractServiceCatalog() to interpret it +// as a service catalog. +type CreateResult struct { + commonResult +} + +// GetResult is the response from a Get request. Use ExtractToken() +// to interpret it as a Token, or ExtractServiceCatalog() to interpret it +// as a service catalog. +type GetResult struct { + commonResult +} + +// RevokeResult is response from a Revoke request. +type RevokeResult struct { + commonResult +} + +// Token is a string that grants a user access to a controlled set of services +// in an OpenStack provider. Each Token is valid for a set length of time. +type Token struct { + // ID is the issued token. + ID string `json:"id"` + + // ExpiresAt is the timestamp at which this token will no longer be accepted. + ExpiresAt time.Time `json:"expires_at"` +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.ExtractIntoStructPtr(v, "token") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go new file mode 100644 index 00000000000..2f864a31c8b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go @@ -0,0 +1,7 @@ +package tokens + +import "github.com/gophercloud/gophercloud" + +func tokenURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("auth", "tokens") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go new file mode 100644 index 00000000000..0c43689bb80 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go @@ -0,0 +1,41 @@ +package extensions + +import ( + "github.com/gophercloud/gophercloud" + common "github.com/gophercloud/gophercloud/openstack/common/extensions" + "github.com/gophercloud/gophercloud/pagination" +) + +// Extension is a single OpenStack extension. +type Extension struct { + common.Extension +} + +// GetResult wraps a GetResult from common. +type GetResult struct { + common.GetResult +} + +// ExtractExtensions interprets a Page as a slice of Extensions. +func ExtractExtensions(page pagination.Page) ([]Extension, error) { + inner, err := common.ExtractExtensions(page) + if err != nil { + return nil, err + } + outer := make([]Extension, len(inner)) + for index, ext := range inner { + outer[index] = Extension{ext} + } + return outer, nil +} + +// Get retrieves information for a specific extension using its alias. +func Get(c *gophercloud.ServiceClient, alias string) GetResult { + return GetResult{common.Get(c, alias)} +} + +// List returns a Pager which allows you to iterate over the full collection of extensions. +// It does not accept query parameters. +func List(c *gophercloud.ServiceClient) pagination.Pager { + return common.List(c) +} 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 new file mode 100644 index 00000000000..eda010cb0c8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go @@ -0,0 +1,53 @@ +/* +Package external provides information and interaction with the external +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 + } + + var allNetworks []NetworkWithExternalExt + + 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 to Create a Network with External Information + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "private", + AdminStateUp: &iTrue, + } + + createOpts := external.CreateOptsExt{ + networkCreateOpts, + &iTrue, + } + + network, err := networks.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } +*/ +package external 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 new file mode 100644 index 00000000000..ced5efed8d9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go @@ -0,0 +1,84 @@ +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. +type CreateOptsExt struct { + networks.CreateOptsBuilder + External *bool `json:"router:external,omitempty"` +} + +// ToNetworkCreateMap adds the router:external options 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.External == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["router:external"] = opts.External + + return base, nil +} + +// UpdateOptsExt is the structure used when updating existing external network +// resources. It embeds networks.UpdateOpts and so inherits all of its required +// and optional fields, with the addition of the External field. +type UpdateOptsExt struct { + networks.UpdateOptsBuilder + External *bool `json:"router:external,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.External == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["router:external"] = opts.External + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go new file mode 100644 index 00000000000..7cbbffdcf8a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go @@ -0,0 +1,8 @@ +package external + +// NetworkExternalExt represents a decorated form of a Network with based on the +// "external-net" extension. +type NetworkExternalExt struct { + // Specifies whether the network is an external network or not. + External bool `json:"router:external"` +} 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 new file mode 100644 index 00000000000..a71a3ec88a9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go @@ -0,0 +1,71 @@ +/* +package floatingips enables management and retrieval of Floating IPs from the +OpenStack Networking service. + +Example to List Floating IPs + + listOpts := floatingips.ListOpts{ + FloatingNetworkID: "a6917946-38ab-4ffd-a55a-26c0980ce5ee", + } + + allPages, err := floatingips.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allFIPs, err := floatingips.ExtractFloatingIPs(allPages) + if err != nil { + panic(err) + } + + for _, fip := range allFIPs { + fmt.Printf("%+v\n", fip) + } + +Example to Create a Floating IP + + createOpts := floatingips.CreateOpts{ + FloatingNetworkID: "a6917946-38ab-4ffd-a55a-26c0980ce5ee", + } + + fip, err := floatingips.Create(networkingClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Floating IP + + fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + portID := "76d0a61b-b8e5-490c-9892-4cf674f2bec8" + + updateOpts := floatingips.UpdateOpts{ + PortID: &portID, + } + + fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Disassociate a Floating IP with a Port + + fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + + updateOpts := floatingips.UpdateOpts{ + PortID: new(string), + } + + fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Floating IP + + fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + err := floatingips.Delete(networkClient, fipID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package floatingips 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 new file mode 100644 index 00000000000..0c0db64d8d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go @@ -0,0 +1,182 @@ +package floatingips + +import ( + "github.com/gophercloud/gophercloud" + "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 +// 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"` + Description string `q:"description"` + FloatingNetworkID string `q:"floating_network_id"` + PortID string `q:"port_id"` + FixedIP string `q:"fixed_ip_address"` + FloatingIP string `q:"floating_ip_address"` + 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"` + 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 ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToFloatingIPListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return FloatingIPPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToFloatingIPCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new floating IP +// 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"` + FixedIP string `json:"fixed_ip_address,omitempty"` + SubnetID string `json:"subnet_id,omitempty"` + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` +} + +// ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder +// interface +func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "floatingip") +} + +// Create accepts a CreateOpts struct and uses the values provided to create a +// new floating IP resource. You can create floating IPs on external networks +// only. If you provide a FloatingNetworkID which refers to a network that is +// not external (i.e. its `router:external' attribute is False), the operation +// will fail and return a 400 error. +// +// If you do not specify a FloatingIP address value, the operation will +// automatically allocate an available address for the new resource. If you do +// choose to specify one, it must fall within the subnet range for the external +// network - otherwise the operation returns a 400 error. If the FloatingIP +// address is already in use, the operation returns a 409 error code. +// +// You can associate the new resource with an internal port by using the PortID +// field. If you specify a PortID that is not valid, the operation will fail and +// return 404 error code. +// +// You must also configure an IP address for the port associated with the PortID +// you have provided - this is what the FixedIP refers to: an IP fixed to a +// port. Because a port might be associated with multiple IP addresses, you can +// use the FixedIP field to associate a particular IP address rather than have +// the API assume for you. If you specify an IP address that is not valid, the +// operation will fail and return a 400 error code. If the PortID and FixedIP +// are already associated with another resource, the operation will fail and +// returns a 409 error code. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToFloatingIPCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular floating IP resource 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 { + ToFloatingIPUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a floating IP resource. The +// only value that can be updated is which internal port the floating IP is +// 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 { + 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) { + 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 +// "update" a floating IP is to associate it with a new internal port, or +// disassociated it from all ports. See UpdateOpts for instructions of how to +// do this. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToFloatingIPUpdateMap() + 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 floating IP resource. Please +// ensure this is what you want - you can also disassociate the IP from existing +// internal ports. +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/networking/v2/extensions/layer3/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go new file mode 100644 index 00000000000..a9709ccec3f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go @@ -0,0 +1,131 @@ +package floatingips + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// FloatingIP represents a floating IP resource. A floating IP is an external +// IP address that is mapped to an internal port and, optionally, a specific +// IP address on a private network. In other words, it enables access to an +// instance on a private network from an external network. For this reason, +// floating IPs can only be defined on networks where the `router:external' +// attribute (provided by the external network extension) is set to True. +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"` + + // FloatingIP is the address of the floating IP on the external network. + FloatingIP string `json:"floating_ip_address"` + + // PortID is the UUID of the port on an internal network that is associated + // with the floating IP. + PortID string `json:"port_id"` + + // FixedIP is the specific IP address of the internal port which should be + // associated with the floating IP. + FixedIP string `json:"fixed_ip_address"` + + // TenantID is the project owner of the floating IP. Only admin users can + // specify a project identifier other than its own. + TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the floating IP. + ProjectID string `json:"project_id"` + + // Status is the condition of the API resource. + Status string `json:"status"` + + // 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 { + gophercloud.Result +} + +// Extract will extract a FloatingIP resource from a result. +func (r commonResult) Extract() (*FloatingIP, error) { + var s FloatingIP + err := r.ExtractInto(&s) + 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 +// method to interpret it as a FloatingIP. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a FloatingIP. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a FloatingIP. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of an update operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// FloatingIPPage is the page returned by a pager when traversing over a +// collection of floating IPs. +type FloatingIPPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of floating IPs has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r FloatingIPPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"floatingips_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a FloatingIPPage struct is empty. +func (r FloatingIPPage) IsEmpty() (bool, error) { + is, err := ExtractFloatingIPs(r) + return len(is) == 0, err +} + +// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage +// struct, and extracts the elements into a slice of FloatingIP structs. In +// other words, a generic collection is mapped into a relevant slice. +func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { + var s struct { + FloatingIPs []FloatingIP `json:"floatingips"` + } + 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/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go new file mode 100644 index 00000000000..1318a184caa --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go @@ -0,0 +1,13 @@ +package floatingips + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "floatingips" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go new file mode 100644 index 00000000000..6ede7f5e171 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go @@ -0,0 +1,108 @@ +/* +Package routers enables management and retrieval of Routers from the OpenStack +Networking service. + +Example to List Routers + + listOpts := routers.ListOpts{} + allPages, err := routers.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRouters, err := routers.ExtractRouters(allPages) + if err != nil { + panic(err) + } + + for _, router := range allRoutes { + fmt.Printf("%+v\n", router) + } + +Example to Create a Router + + iTrue := true + gwi := routers.GatewayInfo{ + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + } + + createOpts := routers.CreateOpts{ + Name: "router_1", + AdminStateUp: &iTrue, + GatewayInfo: &gwi, + } + + router, err := routers.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + routes := []routers.Route{{ + DestinationCIDR: "40.0.1.0/24", + NextHop: "10.1.0.10", + }} + + updateOpts := routers.UpdateOpts{ + Name: "new_name", + Routes: routes, + } + + router, err := routers.Update(networkClient, routerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove all Routes from a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + routes := []routers.Route{} + + updateOpts := routers.UpdateOpts{ + Routes: routes, + } + + router, err := routers.Update(networkClient, routerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + err := routers.Delete(networkClient, routerID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Add an Interface to a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + intOpts := routers.AddInterfaceOpts{ + SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1", + } + + interface, err := routers.AddInterface(networkClient, routerID, intOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove an Interface from a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + intOpts := routers.RemoveInterfaceOpts{ + SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1", + } + + interface, err := routers.RemoveInterface(networkClient, routerID, intOpts).Extract() + if err != nil { + panic(err) + } +*/ +package routers 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 new file mode 100644 index 00000000000..cf499f9873d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go @@ -0,0 +1,233 @@ +package routers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + Distributed *bool `q:"distributed"` + Status string `q:"status"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` +} + +// List returns a Pager which allows you to iterate over a collection of +// routers. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those routers that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return RouterPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToRouterCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new router. There are +// 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"` + ProjectID string `json:"project_id,omitempty"` + GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` + AvailabilityZoneHints []string `json:"availability_zone_hints,omitempty"` +} + +// ToRouterCreateMap builds a create request body from CreateOpts. +func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "router") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// logical router. When it is created, the router does not have an internal +// interface - it is not associated to any subnet. +// +// You can optionally specify an external gateway for a router using the +// GatewayInfo struct. The external gateway for the router must be plugged into +// an external network (it is external if its `router:external' field is set to +// true). +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToRouterCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular router 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 { + ToRouterUpdateMap() (map[string]interface{}, error) +} + +// 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"` + Routes []Route `json:"routes"` +} + +// ToRouterUpdateMap builds an update body based on UpdateOpts. +func (opts UpdateOpts) ToRouterUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "router") +} + +// Update allows routers to be updated. You can update the name, administrative +// state, and the external gateway. For more information about how to set the +// external gateway for a router, see Create. This operation does not enable +// the update of router interfaces. To do this, use the AddInterface and +// RemoveInterface functions. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToRouterUpdateMap() + 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 router based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// AddInterfaceOptsBuilder allows extensions to add additional parameters to +// the AddInterface request. +type AddInterfaceOptsBuilder interface { + ToRouterAddInterfaceMap() (map[string]interface{}, error) +} + +// AddInterfaceOpts represents the options for adding an interface to a router. +type AddInterfaceOpts struct { + SubnetID string `json:"subnet_id,omitempty" xor:"PortID"` + PortID string `json:"port_id,omitempty" xor:"SubnetID"` +} + +// ToRouterAddInterfaceMap builds a request body from AddInterfaceOpts. +func (opts AddInterfaceOpts) ToRouterAddInterfaceMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// AddInterface attaches a subnet to an internal router interface. You must +// specify either a SubnetID or PortID in the request body. If you specify both, +// the operation will fail and an error will be returned. +// +// If you specify a SubnetID, the gateway IP address for that particular subnet +// is used to create the router interface. Alternatively, if you specify a +// PortID, the IP address associated with the port is used to create the router +// interface. +// +// If you reference a port that is associated with multiple IP addresses, or +// if the port is associated with zero IP addresses, the operation will fail and +// a 400 Bad Request error will be returned. +// +// If you reference a port already in use, the operation will fail and a 409 +// Conflict error will be returned. +// +// The PortID that is returned after using Extract() on the result of this +// operation can either be the same PortID passed in or, on the other hand, the +// identifier of a new port created by this operation. After the operation +// completes, the device ID of the port is set to the router ID, and the +// device owner attribute is set to `network:router_interface'. +func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOptsBuilder) (r InterfaceResult) { + b, err := opts.ToRouterAddInterfaceMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// RemoveInterfaceOptsBuilder allows extensions to add additional parameters to +// the RemoveInterface request. +type RemoveInterfaceOptsBuilder interface { + ToRouterRemoveInterfaceMap() (map[string]interface{}, error) +} + +// RemoveInterfaceOpts represents options for removing an interface from +// a router. +type RemoveInterfaceOpts struct { + SubnetID string `json:"subnet_id,omitempty" or:"PortID"` + PortID string `json:"port_id,omitempty" or:"SubnetID"` +} + +// ToRouterRemoveInterfaceMap builds a request body based on +// RemoveInterfaceOpts. +func (opts RemoveInterfaceOpts) ToRouterRemoveInterfaceMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// RemoveInterface removes an internal router interface, which detaches a +// subnet from the router. You must specify either a SubnetID or PortID, since +// these values are used to identify the router interface to remove. +// +// Unlike AddInterface, you can also specify both a SubnetID and PortID. If you +// choose to specify both, the subnet ID must correspond to the subnet ID of +// the first IP address on the port specified by the port ID. Otherwise, the +// operation will fail and return a 409 Conflict error. +// +// If the router, subnet or port which are referenced do not exist or are not +// visible to you, the operation will fail and a 404 Not Found error will be +// returned. After this operation completes, the port connecting the router +// with the subnet is removed from the subnet for the network. +func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfaceOptsBuilder) (r InterfaceResult) { + b, err := opts.ToRouterRemoveInterfaceMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} 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 new file mode 100644 index 00000000000..857e1947e1f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go @@ -0,0 +1,181 @@ +package routers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// GatewayInfo represents the information of an external gateway for any +// particular network router. +type GatewayInfo struct { + NetworkID string `json:"network_id,omitempty"` + EnableSNAT *bool `json:"enable_snat,omitempty"` + ExternalFixedIPs []ExternalFixedIP `json:"external_fixed_ips,omitempty"` +} + +// ExternalFixedIP is the IP address and subnet ID of the external gateway of a +// router. +type ExternalFixedIP struct { + IPAddress string `json:"ip_address,omitempty"` + SubnetID string `json:"subnet_id"` +} + +// Route is a possible route in a router. +type Route struct { + NextHop string `json:"nexthop"` + DestinationCIDR string `json:"destination"` +} + +// Router represents a Neutron router. A router is a logical entity that +// forwards packets across internal subnets and NATs (network address +// translation) them on external networks through an appropriate gateway. +// +// A router has an interface for each subnet with which it is associated. By +// default, the IP address of such interface is the subnet's gateway IP. Also, +// whenever a router is associated with a subnet, a port for that router +// interface is added to the subnet's network. +type Router struct { + // Status indicates whether or not a router is currently operational. + Status string `json:"status"` + + // GateayInfo provides information on external gateway for the router. + GatewayInfo GatewayInfo `json:"external_gateway_info"` + + // AdminStateUp is the administrative state of the router. + AdminStateUp bool `json:"admin_state_up"` + + // Distributed is whether router is disitrubted or not. + Distributed bool `json:"distributed"` + + // Name is the human readable name for the router. It does not have to be + // 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"` + + // TenantID is the project owner of the router. Only admin users can + // specify a project identifier other than its own. + TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the router. + ProjectID string `json:"project_id"` + + // Routes are a collection of static routes that the router will host. + Routes []Route `json:"routes"` + + // 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 +// collection of routers. +type RouterPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of routers has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r RouterPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"routers_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a RouterPage struct is empty. +func (r RouterPage) IsEmpty() (bool, error) { + is, err := ExtractRouters(r) + return len(is) == 0, err +} + +// ExtractRouters accepts a Page struct, specifically a RouterPage struct, +// and extracts the elements into a slice of Router structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRouters(r pagination.Page) ([]Router, error) { + var s struct { + Routers []Router `json:"routers"` + } + err := (r.(RouterPage)).ExtractInto(&s) + return s.Routers, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a router. +func (r commonResult) Extract() (*Router, error) { + var s struct { + Router *Router `json:"router"` + } + err := r.ExtractInto(&s) + return s.Router, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Router. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Router. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Router. +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 +} + +// InterfaceInfo represents information about a particular router interface. As +// mentioned above, in order for a router to forward to a subnet, it needs an +// interface. +type InterfaceInfo struct { + // SubnetID is the ID of the subnet which this interface is associated with. + SubnetID string `json:"subnet_id"` + + // PortID is the ID of the port that is a part of the subnet. + PortID string `json:"port_id"` + + // ID is the UUID of the interface. + ID string `json:"id"` + + // TenantID is the owner of the interface. + TenantID string `json:"tenant_id"` +} + +// InterfaceResult represents the result of interface operations, such as +// AddInterface() and RemoveInterface(). Call its Extract method to interpret +// the result as a InterfaceInfo. +type InterfaceResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an information struct. +func (r InterfaceResult) Extract() (*InterfaceInfo, error) { + var s InterfaceInfo + err := r.ExtractInto(&s) + return &s, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go new file mode 100644 index 00000000000..f9e9da32117 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go @@ -0,0 +1,21 @@ +package routers + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "routers" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func addInterfaceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "add_router_interface") +} + +func removeInterfaceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "remove_router_interface") +} 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 00000000000..813579905c2 --- /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 00000000000..9d2b3a0d351 --- /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 00000000000..5153b1b90c8 --- /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/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go new file mode 100644 index 00000000000..ecb607a8e89 --- /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/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go new file mode 100644 index 00000000000..108cdb03d8b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go @@ -0,0 +1,63 @@ +/* +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 + updateOpts := listeners.UpdateOpts{ + ConnLimit: &i1001, + } + + 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) + } +*/ +package listeners 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 new file mode 100644 index 00000000000..f2966b6c44a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go @@ -0,0 +1,212 @@ +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" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" + ProtocolTerminatedHTTPS Protocol = "TERMINATED_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"` + TenantID string `q:"tenant_id"` + 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"` +} + +// 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 +// 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.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"` + + // TenantID is only required if the caller has an admin role and wants + // to create a pool for another project. + TenantID string `json:"tenant_id,omitempty"` + + // 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"` +} + +// 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 tenants by +// specifying a TenantID 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"` +} + +// 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 +} 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 new file mode 100644 index 00000000000..ae105793225 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go @@ -0,0 +1,141 @@ +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" +) + +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. + TenantID string `json:"tenant_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. + // 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 +// 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go new file mode 100644 index 00000000000..02fb1eb39ec --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go @@ -0,0 +1,16 @@ +package listeners + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "listeners" +) + +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/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go new file mode 100644 index 00000000000..c6d53a7b052 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go @@ -0,0 +1,79 @@ +/* +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", + } + + lb, err := loadbalancers.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + + i1001 := 1001 + updateOpts := loadbalancers.UpdateOpts{ + Name: "new-name", + } + + lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Load Balancers + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := loadbalancers.Delete(networkClient, lbID).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) + } +*/ +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 new file mode 100644 index 00000000000..f5b14134821 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go @@ -0,0 +1,204 @@ +package loadbalancers + +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 { + 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"` + TenantID string `q:"tenant_id"` + 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"` + 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"` +} + +// 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 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.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"` + + // 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). + VipSubnetID string `json:"vip_subnet_id" required:"true"` + + // TenantID is the UUID of the project who owns the Loadbalancer. + // 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 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"` +} + +// 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"` +} + +// 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 +} + +// Delete will permanently delete a particular LoadBalancer based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// CascadingDelete is like `Delete`, but will also delete any of the load balancer's +// children (listener, monitor, etc). +// NOTE: This function will only work with Octavia load balancers; Neutron does not +// support this. +func CascadingDelete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + if c.Type != "load-balancer" { + r.Err = fmt.Errorf("error prior to running cascade delete: only Octavia LBs supported") + return + } + u := fmt.Sprintf("%s?cascade=true", resourceURL(c, id)) + _, r.Err = c.Delete(u, 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 +} 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 new file mode 100644 index 00000000000..7f423c933dd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go @@ -0,0 +1,186 @@ +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" +) + +// 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. + TenantID string `json:"tenant_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 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"` +} + +// 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 +} 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 new file mode 100644 index 00000000000..2d2a99b7797 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go @@ -0,0 +1,26 @@ +package loadbalancers + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "loadbalancers" + statusPath = "statuses" + 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 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/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go new file mode 100644 index 00000000000..6ed8c8fb5ff --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_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/networking/v2/extensions/lbaas_v2/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go new file mode 100644 index 00000000000..f728f5a8237 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_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/networking/v2/extensions/lbaas_v2/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go new file mode 100644 index 00000000000..a78f7aeb0ff --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go @@ -0,0 +1,153 @@ +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"` + + // TenantID is the owner of the Monitor. + TenantID string `json:"tenant_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"` +} + +// 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/networking/v2/extensions/lbaas_v2/monitors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go new file mode 100644 index 00000000000..a222e52a93d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_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/networking/v2/extensions/lbaas_v2/pools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go new file mode 100644 index 00000000000..06971486806 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go @@ -0,0 +1,126 @@ +/* +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.ExtractPools(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) + } +*/ +package pools 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 new file mode 100644 index 00000000000..f427ae7bf57 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go @@ -0,0 +1,356 @@ +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"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + AdminStateUp *bool `q:"admin_state_up"` + Name string `q:"name"` + ID string `q:"id"` + LoadbalancerID string `q:"loadbalancer_id"` + ListenerID string `q:"listener_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 +// 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.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" + 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, 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"` + + // TenantID is the UUID of the project who owns the Pool. + // 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 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"` + TenantID string `q:"tenant_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 +// tenant 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"` + + // TenantID is the UUID of the project who owns the Member. + // 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 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 +} + +// 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/networking/v2/extensions/lbaas_v2/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go new file mode 100644 index 00000000000..fba0d3a8782 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go @@ -0,0 +1,291 @@ +package pools + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_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. + TenantID string `json:"tenant_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. + // 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 +// 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. + TenantID string `json:"tenant_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 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 +// 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 +} + +// 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 +} + +// 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/networking/v2/extensions/lbaas_v2/pools/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go new file mode 100644 index 00000000000..bceca67707f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_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/networking/v2/extensions/security/groups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go new file mode 100644 index 00000000000..7d8bbcaacba --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go @@ -0,0 +1,58 @@ +/* +Package groups provides information and interaction with Security Groups +for the OpenStack Networking service. + +Example to List Security Groups + + listOpts := groups.ListOpts{ + TenantID: "966b3c7d36a24facaf20b7e458bf2192", + } + + allPages, err := groups.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + panic(err) + } + + for _, group := range allGroups { + fmt.Printf("%+v\n", group) + } + +Example to Create a Security Group + + createOpts := groups.CreateOpts{ + Name: "group_name", + Description: "A Security Group", + } + + group, err := groups.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Security Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + + updateOpts := groups.UpdateOpts{ + Name: "new_name", + } + + group, err := groups.Update(networkClient, groupID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Security Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + err := groups.Delete(networkClient, groupID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package groups 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 new file mode 100644 index 00000000000..a22cd306e8a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -0,0 +1,166 @@ +package groups + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// 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 group attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + Description string `q:"description"` + 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 +// security groups. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SecGroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToSecGroupCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new security group. +type CreateOpts struct { + // Human-readable name for the Security Group. Does not have to be unique. + Name string `json:"name" required:"true"` + + // TenantID is the UUID of the project who owns the Group. + // Only administrative users can specify a tenant UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the UUID of the project who owns the Group. + // Only administrative users can specify a tenant UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // Describes the security group. + Description string `json:"description,omitempty"` +} + +// ToSecGroupCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "security_group") +} + +// Create is an operation which provisions a new security group with default +// security group rules for the IPv4 and IPv6 ether types. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSecGroupCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToSecGroupUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains all the values needed to update an existing security +// group. +type UpdateOpts struct { + // Human-readable name for the Security Group. Does not have to be unique. + Name string `json:"name,omitempty"` + + // Describes the security group. + Description *string `json:"description,omitempty"` +} + +// ToSecGroupUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "security_group") +} + +// Update is an operation which updates an existing security group. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSecGroupUpdateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Get retrieves a particular security group 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 security group based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// IDFromName is a convenience function that returns a security group's ID, +// given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractGroups(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "security group"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"} + } +} 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 new file mode 100644 index 00000000000..468952b3e4e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go @@ -0,0 +1,108 @@ +package groups + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" + "github.com/gophercloud/gophercloud/pagination" +) + +// SecGroup represents a container for security group rules. +type SecGroup struct { + // The UUID for the security group. + ID string + + // Human-readable name for the security group. Might not be unique. + // Cannot be named "default" as that is automatically created for a tenant. + Name string + + // The security group description. + Description string + + // A slice of security group rules that dictate the permitted behaviour for + // traffic entering and leaving the group. + Rules []rules.SecGroupRule `json:"security_group_rules"` + + // TenantID is the project owner of the security group. + TenantID string `json:"tenant_id"` + + // 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 +// collection of security groups. +type SecGroupPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of security groups has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (r SecGroupPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"security_groups_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a SecGroupPage struct is empty. +func (r SecGroupPage) IsEmpty() (bool, error) { + is, err := ExtractGroups(r) + return len(is) == 0, err +} + +// ExtractGroups accepts a Page struct, specifically a SecGroupPage struct, +// and extracts the elements into a slice of SecGroup structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractGroups(r pagination.Page) ([]SecGroup, error) { + var s struct { + SecGroups []SecGroup `json:"security_groups"` + } + err := (r.(SecGroupPage)).ExtractInto(&s) + return s.SecGroups, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a security group. +func (r commonResult) Extract() (*SecGroup, error) { + var s struct { + SecGroup *SecGroup `json:"security_group"` + } + err := r.ExtractInto(&s) + return s.SecGroup, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SecGroup. +type CreateResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a SecGroup. +type UpdateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SecGroup. +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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go new file mode 100644 index 00000000000..104cbcc558d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go @@ -0,0 +1,13 @@ +package groups + +import "github.com/gophercloud/gophercloud" + +const rootPath = "security-groups" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go new file mode 100644 index 00000000000..bf66dc8b40e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go @@ -0,0 +1,50 @@ +/* +Package rules provides information and interaction with Security Group Rules +for the OpenStack Networking service. + +Example to List Security Groups Rules + + listOpts := rules.ListOpts{ + Protocol: "tcp", + } + + allPages, err := rules.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRules, err := rules.ExtractRules(allPages) + if err != nil { + panic(err) + } + + for _, rule := range allRules { + fmt.Printf("%+v\n", rule) + } + +Example to Create a Security Group Rule + + createOpts := rules.CreateOpts{ + Direction: "ingress", + PortRangeMin: 80, + EtherType: rules.EtherType4, + PortRangeMax: 80, + Protocol: "tcp", + RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + } + + rule, err := rules.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Security Group Rule + + ruleID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + err := rules.Delete(networkClient, ruleID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package rules 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 new file mode 100644 index 00000000000..c7741ffcd2c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -0,0 +1,159 @@ +package rules + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the security group rule attributes you want to see returned. SortKey allows +// you to sort by a particular network attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Direction string `q:"direction"` + EtherType string `q:"ethertype"` + ID string `q:"id"` + Description string `q:"description"` + PortRangeMax int `q:"port_range_max"` + PortRangeMin int `q:"port_range_min"` + Protocol string `q:"protocol"` + RemoteGroupID string `q:"remote_group_id"` + RemoteIPPrefix string `q:"remote_ip_prefix"` + SecGroupID string `q:"security_group_id"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// List returns a Pager which allows you to iterate over a collection of +// security group rules. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type RuleDirection string +type RuleProtocol string +type RuleEtherType string + +// Constants useful for CreateOpts +const ( + DirIngress RuleDirection = "ingress" + DirEgress RuleDirection = "egress" + EtherType4 RuleEtherType = "IPv4" + EtherType6 RuleEtherType = "IPv6" + ProtocolAH RuleProtocol = "ah" + ProtocolDCCP RuleProtocol = "dccp" + ProtocolEGP RuleProtocol = "egp" + ProtocolESP RuleProtocol = "esp" + ProtocolGRE RuleProtocol = "gre" + ProtocolICMP RuleProtocol = "icmp" + ProtocolIGMP RuleProtocol = "igmp" + ProtocolIPv6Encap RuleProtocol = "ipv6-encap" + ProtocolIPv6Frag RuleProtocol = "ipv6-frag" + ProtocolIPv6ICMP RuleProtocol = "ipv6-icmp" + ProtocolIPv6NoNxt RuleProtocol = "ipv6-nonxt" + ProtocolIPv6Opts RuleProtocol = "ipv6-opts" + ProtocolIPv6Route RuleProtocol = "ipv6-route" + ProtocolOSPF RuleProtocol = "ospf" + ProtocolPGM RuleProtocol = "pgm" + ProtocolRSVP RuleProtocol = "rsvp" + ProtocolSCTP RuleProtocol = "sctp" + ProtocolTCP RuleProtocol = "tcp" + ProtocolUDP RuleProtocol = "udp" + ProtocolUDPLite RuleProtocol = "udplite" + ProtocolVRRP RuleProtocol = "vrrp" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToSecGroupRuleCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new security group +// rule. +type CreateOpts struct { + // Must be either "ingress" or "egress": the direction in which the security + // 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"` + + // The security group ID to associate with this security group rule. + SecGroupID string `json:"security_group_id" required:"true"` + + // The maximum port number in the range that is matched by the security group + // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If + // the protocol is ICMP, this value must be an ICMP type. + PortRangeMax int `json:"port_range_max,omitempty"` + + // The minimum port number in the range that is matched by the security group + // rule. If the protocol is TCP or UDP, this value must be less than or equal + // to the value of the PortRangeMax attribute. If the protocol is ICMP, this + // value must be an ICMP type. + PortRangeMin int `json:"port_range_min,omitempty"` + + // The protocol that is matched by the security group rule. Valid values are + // "tcp", "udp", "icmp" or an empty string. + Protocol RuleProtocol `json:"protocol,omitempty"` + + // The remote group ID to be associated with this security group rule. You can + // specify either RemoteGroupID or RemoteIPPrefix. + RemoteGroupID string `json:"remote_group_id,omitempty"` + + // The remote IP prefix to be associated with this security group rule. You can + // specify either RemoteGroupID or RemoteIPPrefix. This attribute matches the + // specified IP prefix as the source IP address of the IP packet. + RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` + + // TenantID is the UUID of the project who owns the Rule. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` +} + +// ToSecGroupRuleCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToSecGroupRuleCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "security_group_rule") +} + +// Create is an operation which adds a new security group rule and associates it +// with an existing security group (whose ID is specified in CreateOpts). +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSecGroupRuleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular security group rule 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 security group rule 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/networking/v2/extensions/security/rules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go new file mode 100644 index 00000000000..3bf5501d922 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go @@ -0,0 +1,127 @@ +package rules + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// SecGroupRule represents a rule to dictate the behaviour of incoming or +// outgoing traffic for a particular security group. +type SecGroupRule struct { + // The UUID for this security group rule. + ID string + + // The direction in which the security group rule is applied. The only values + // allowed are "ingress" or "egress". For a compute instance, an ingress + // security group rule is applied to incoming (ingress) traffic for that + // 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"` + + // The security group ID to associate with this security group rule. + SecGroupID string `json:"security_group_id"` + + // The minimum port number in the range that is matched by the security group + // rule. If the protocol is TCP or UDP, this value must be less than or equal + // to the value of the PortRangeMax attribute. If the protocol is ICMP, this + // value must be an ICMP type. + PortRangeMin int `json:"port_range_min"` + + // The maximum port number in the range that is matched by the security group + // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If + // the protocol is ICMP, this value must be an ICMP type. + PortRangeMax int `json:"port_range_max"` + + // The protocol that is matched by the security group rule. Valid values are + // "tcp", "udp", "icmp" or an empty string. + Protocol string + + // The remote group ID to be associated with this security group rule. You + // can specify either RemoteGroupID or RemoteIPPrefix. + RemoteGroupID string `json:"remote_group_id"` + + // The remote IP prefix to be associated with this security group rule. You + // can specify either RemoteGroupID or RemoteIPPrefix . This attribute + // matches the specified IP prefix as the source IP address of the IP packet. + RemoteIPPrefix string `json:"remote_ip_prefix"` + + // TenantID is the project owner of this security group rule. + TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of this security group rule. + ProjectID string `json:"project_id"` +} + +// SecGroupRulePage is the page returned by a pager when traversing over a +// collection of security group rules. +type SecGroupRulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of security group rules has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (r SecGroupRulePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"security_group_rules_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a SecGroupRulePage struct is empty. +func (r SecGroupRulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(r) + return len(is) == 0, err +} + +// ExtractRules accepts a Page struct, specifically a SecGroupRulePage struct, +// and extracts the elements into a slice of SecGroupRule structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(r pagination.Page) ([]SecGroupRule, error) { + var s struct { + SecGroupRules []SecGroupRule `json:"security_group_rules"` + } + err := (r.(SecGroupRulePage)).ExtractInto(&s) + return s.SecGroupRules, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a security rule. +func (r commonResult) Extract() (*SecGroupRule, error) { + var s struct { + SecGroupRule *SecGroupRule `json:"security_group_rule"` + } + err := r.ExtractInto(&s) + return s.SecGroupRule, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SecGroupRule. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SecGroupRule. +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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go new file mode 100644 index 00000000000..a5ede0e89b9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go @@ -0,0 +1,13 @@ +package rules + +import "github.com/gophercloud/gophercloud" + +const rootPath = "security-group-rules" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} 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 new file mode 100644 index 00000000000..9d1dd5a7ea4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go @@ -0,0 +1,66 @@ +/* +Package networks contains functionality for working with Neutron network +resources. A network is an isolated virtual layer-2 broadcast domain that is +typically reserved for the tenant who created it (unless you configure the +network to be shared). Tenants can create multiple networks until the +thresholds per-tenant quota is reached. + +In the v2.0 Networking API, the network is the main entity. Ports and subnets +are always associated with a network. + +Example to List Networks + + listOpts := networks.ListOpts{ + TenantID: "a99e9b4e620e4db09a2dfb6e42a01e66", + } + + allPages, err := networks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allNetworks, err := networks.ExtractNetworks(allPages) + if err != nil { + panic(err) + } + + for _, network := range allNetworks { + fmt.Printf("%+v", network) + } + +Example to Create a Network + + iTrue := true + createOpts := networks.CreateOpts{ + Name: "network_1", + AdminStateUp: &iTrue, + } + + network, err := networks.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Network + + networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" + + name := "new_name" + updateOpts := networks.UpdateOpts{ + Name: &name, + } + + network, err := networks.Update(networkClient, networkID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Network + + networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" + err := networks.Delete(networkClient, networkID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package networks 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 new file mode 100644 index 00000000000..8006c481679 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go @@ -0,0 +1,180 @@ +package networks + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToNetworkListQuery() (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 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. +type ListOpts struct { + Status string `q:"status"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Shared *bool `q:"shared"` + ID string `q:"id"` + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` +} + +// ToNetworkListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// networks. 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 := listURL(c) + if opts != nil { + query, err := opts.ToNetworkListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return NetworkPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific network 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 +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToNetworkCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used to create a network. +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"` + AvailabilityZoneHints []string `json:"availability_zone_hints,omitempty"` +} + +// ToNetworkCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "network") +} + +// Create accepts a CreateOpts struct and creates a new network using the values +// provided. This operation does not actually require a request body, i.e. the +// CreateOpts struct argument can be empty. +// +// The tenant ID that is contained in the URI is the tenant that creates the +// network. An admin user, however, has the option of specifying another tenant +// ID in the CreateOpts struct. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToNetworkCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(createURL(c), b, &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToNetworkUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update a network. +type UpdateOpts struct { + 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. +func (opts UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "network") +} + +// Update accepts a UpdateOpts struct and updates an existing network using the +// values provided. For more information, see the Create function. +func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToNetworkUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// Delete accepts a unique ID and deletes the network associated with it. +func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, networkID), nil) + return +} + +// IDFromName is a convenience function that returns a network's ID, given +// its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractNetworks(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "network"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "network"} + } +} 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 new file mode 100644 index 00000000000..f03067415fb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go @@ -0,0 +1,124 @@ +package networks + +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 a network resource. +func (r commonResult) Extract() (*Network, error) { + var s Network + err := r.ExtractInto(&s) + return &s, err +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "network") +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Network. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Network. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Network. +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 +} + +// Network represents, well, a network. +type Network struct { + // UUID for the network + ID string `json:"id"` + + // 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"` + + // Indicates whether network is currently operational. Possible values include + // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional + // values. + Status string `json:"status"` + + // Subnets associated with this network. + Subnets []string `json:"subnets"` + + // TenantID is the project owner of the network. + TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the network. + ProjectID string `json:"project_id"` + + // Specifies whether the network resource can be accessed by any tenant. + Shared bool `json:"shared"` + + // 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 +// collection of networks. +type NetworkPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of networks has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r NetworkPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"networks_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a NetworkPage struct is empty. +func (r NetworkPage) IsEmpty() (bool, error) { + is, err := ExtractNetworks(r) + return len(is) == 0, err +} + +// ExtractNetworks accepts a Page struct, specifically a NetworkPage struct, +// and extracts the elements into a slice of Network structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractNetworks(r pagination.Page) ([]Network, error) { + var s []Network + err := ExtractNetworksInto(r, &s) + return s, err +} + +func ExtractNetworksInto(r pagination.Page, v interface{}) error { + return r.(NetworkPage).Result.ExtractIntoSlicePtr(v, "networks") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go new file mode 100644 index 00000000000..4a8fb1dc7d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go @@ -0,0 +1,31 @@ +package networks + +import "github.com/gophercloud/gophercloud" + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("networks", id) +} + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("networks") +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +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/ports/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go new file mode 100644 index 00000000000..cfb1774fb4b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go @@ -0,0 +1,73 @@ +/* +Package ports contains functionality for working with Neutron port resources. + +A port represents a virtual switch port on a logical network switch. Virtual +instances attach their interfaces into ports. The logical port also defines +the MAC address and the IP address(es) to be assigned to the interfaces +plugged into them. When IP addresses are associated to a port, this also +implies the port is associated with a subnet, as the IP address was taken +from the allocation pool for a specific subnet. + +Example to List Ports + + listOpts := ports.ListOpts{ + DeviceID: "b0b89efe-82f8-461d-958b-adbf80f50c7d", + } + + allPages, err := ports.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPorts, err := ports.ExtractPorts(allPages) + if err != nil { + panic(err) + } + + for _, port := range allPorts { + fmt.Printf("%+v\n", port) + } + +Example to Create a Port + + createOtps := 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"}, + AllowedAddressPairs: []ports.AddressPair{ + {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, + }, + } + + port, err := ports.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Port + + portID := "c34bae2b-7641-49b6-bf6d-d8e473620ed8" + + updateOpts := ports.UpdateOpts{ + Name: "new_name", + SecurityGroups: &[]string{}, + } + + port, err := ports.Update(networkClient, portID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Port + + portID := "c34bae2b-7641-49b6-bf6d-d8e473620ed8" + err := ports.Delete(networkClient, portID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package ports 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 new file mode 100644 index 00000000000..f5f7d761ce8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go @@ -0,0 +1,191 @@ +package ports + +import ( + "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) +} + +// 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 port attributes you want to see returned. SortKey allows you to sort +// by a particular port attribute. SortDir sets the direction, and is either +// `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Status string `q:"status"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + NetworkID string `q:"network_id"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + DeviceOwner string `q:"device_owner"` + MACAddress string `q:"mac_address"` + ID string `q:"id"` + DeviceID string `q:"device_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"` +} + +// ToPortListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPortListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// ports. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those ports 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.ToPortListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PortPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific port 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 +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPortCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents the attributes used when creating a new port. +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"` + DeviceID string `json:"device_id,omitempty"` + DeviceOwner string `json:"device_owner,omitempty"` + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` + SecurityGroups *[]string `json:"security_groups,omitempty"` + AllowedAddressPairs []AddressPair `json:"allowed_address_pairs,omitempty"` +} + +// ToPortCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "port") +} + +// Create accepts a CreateOpts struct and creates a new network using the values +// provided. You must remember to provide a NetworkID value. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPortCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(createURL(c), b, &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPortUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents the attributes used when updating an existing port. +type UpdateOpts struct { + 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"` + SecurityGroups *[]string `json:"security_groups,omitempty"` + AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"` +} + +// ToPortUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "port") +} + +// Update accepts a UpdateOpts struct and updates an existing port using the +// values provided. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPortUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// Delete accepts a unique ID and deletes the port associated with it. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, id), nil) + return +} + +// IDFromName is a convenience function that returns a port's ID, +// given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractPorts(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "port"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "port"} + } +} 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 new file mode 100644 index 00000000000..3941b62300d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go @@ -0,0 +1,149 @@ +package ports + +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 a port resource. +func (r commonResult) Extract() (*Port, error) { + var s Port + err := r.ExtractInto(&s) + return &s, err +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "port") +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Port. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Port. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Port. +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 +} + +// IP is a sub-struct that represents an individual IP. +type IP struct { + SubnetID string `json:"subnet_id"` + IPAddress string `json:"ip_address,omitempty"` +} + +// AddressPair contains the IP Address and the MAC address. +type AddressPair struct { + IPAddress string `json:"ip_address,omitempty"` + MACAddress string `json:"mac_address,omitempty"` +} + +// Port represents a Neutron port. See package documentation for a top-level +// description of what this is. +type Port struct { + // UUID for the port. + ID string `json:"id"` + + // Network that this port is associated with. + NetworkID string `json:"network_id"` + + // 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"` + + // Indicates whether network is currently operational. Possible values include + // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional + // values. + Status string `json:"status"` + + // Mac address to use on this port. + MACAddress string `json:"mac_address"` + + // Specifies IP addresses for the port thus associating the port itself with + // the subnets where the IP addresses are picked from + FixedIPs []IP `json:"fixed_ips"` + + // TenantID is the project owner of the port. + TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the port. + ProjectID string `json:"project_id"` + + // Identifies the entity (e.g.: dhcp agent) using this port. + DeviceOwner string `json:"device_owner"` + + // Specifies the IDs of any security groups associated with a port. + SecurityGroups []string `json:"security_groups"` + + // Identifies the device (e.g., virtual server) using this port. + DeviceID string `json:"device_id"` + + // 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 +// of network ports. +type PortPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of ports has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r PortPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"ports_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a PortPage struct is empty. +func (r PortPage) IsEmpty() (bool, error) { + is, err := ExtractPorts(r) + return len(is) == 0, err +} + +// ExtractPorts accepts a Page struct, specifically a PortPage struct, +// and extracts the elements into a slice of Port structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPorts(r pagination.Page) ([]Port, error) { + var s []Port + err := ExtractPortsInto(r, &s) + return s, err +} + +func ExtractPortsInto(r pagination.Page, v interface{}) error { + return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go new file mode 100644 index 00000000000..600d6f2fd95 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go @@ -0,0 +1,31 @@ +package ports + +import "github.com/gophercloud/gophercloud" + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("ports", id) +} + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("ports") +} + +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/utils/base_endpoint.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go new file mode 100644 index 00000000000..40080f7af20 --- /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/choose_version.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go new file mode 100644 index 00000000000..27da19f91a8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go @@ -0,0 +1,111 @@ +package utils + +import ( + "fmt" + "strings" + + "github.com/gophercloud/gophercloud" +) + +// Version is a supported API version, corresponding to a vN package within the appropriate service. +type Version struct { + ID string + Suffix string + Priority int +} + +var goodStatus = map[string]bool{ + "current": true, + "supported": true, + "stable": true, +} + +// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's +// published versions. +// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint. +func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) { + type linkResp struct { + Href string `json:"href"` + Rel string `json:"rel"` + } + + type valueResp struct { + ID string `json:"id"` + Status string `json:"status"` + Links []linkResp `json:"links"` + } + + type versionsResp struct { + Values []valueResp `json:"values"` + } + + type response struct { + Versions versionsResp `json:"versions"` + } + + normalize := func(endpoint string) string { + if !strings.HasSuffix(endpoint, "/") { + return endpoint + "/" + } + return endpoint + } + identityEndpoint := normalize(client.IdentityEndpoint) + + // If a full endpoint is specified, check version suffixes for a match first. + for _, v := range recognized { + if strings.HasSuffix(identityEndpoint, v.Suffix) { + return v, identityEndpoint, nil + } + } + + var resp response + _, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{ + JSONResponse: &resp, + OkCodes: []int{200, 300}, + }) + + if err != nil { + return nil, "", err + } + + var highest *Version + var endpoint string + + for _, value := range resp.Versions.Values { + href := "" + for _, link := range value.Links { + if link.Rel == "self" { + href = normalize(link.Href) + } + } + + for _, version := range recognized { + if strings.Contains(value.ID, version.ID) { + // Prefer a version that exactly matches the provided endpoint. + if href == identityEndpoint { + if href == "" { + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + } + return version, href, nil + } + + // Otherwise, find the highest-priority version with a whitelisted status. + if goodStatus[strings.ToLower(value.Status)] { + if highest == nil || version.Priority > highest.Priority { + highest = version + endpoint = href + } + } + } + } + } + + if highest == nil { + return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase) + } + if endpoint == "" { + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase) + } + + return highest, endpoint, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/pagination/http.go new file mode 100644 index 00000000000..757295c423a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/pagination/http.go @@ -0,0 +1,60 @@ +package pagination + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" +) + +// PageResult stores the HTTP response that returned the current page of results. +type PageResult struct { + gophercloud.Result + url.URL +} + +// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the +// results, interpreting it as JSON if the content type indicates. +func PageResultFrom(resp *http.Response) (PageResult, error) { + var parsedBody interface{} + + defer resp.Body.Close() + rawBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return PageResult{}, err + } + + if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { + err = json.Unmarshal(rawBody, &parsedBody) + if err != nil { + return PageResult{}, err + } + } else { + parsedBody = rawBody + } + + return PageResultFromParsed(resp, parsedBody), err +} + +// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its +// body parsed as JSON (and closed). +func PageResultFromParsed(resp *http.Response, body interface{}) PageResult { + return PageResult{ + Result: gophercloud.Result{ + Body: body, + Header: resp.Header, + }, + URL: *resp.Request.URL, + } +} + +// Request performs an HTTP request and extracts the http.Response from the result. +func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { + return client.Get(url, nil, &gophercloud.RequestOpts{ + MoreHeaders: headers, + OkCodes: []int{200, 204, 300}, + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/linked.go b/vendor/github.com/gophercloud/gophercloud/pagination/linked.go new file mode 100644 index 00000000000..3656fb7f8f4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/pagination/linked.go @@ -0,0 +1,92 @@ +package pagination + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud" +) + +// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result. +type LinkedPageBase struct { + PageResult + + // LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer. + // If any link along the path is missing, an empty URL will be returned. + // If any link results in an unexpected value type, an error will be returned. + // When left as "nil", []string{"links", "next"} will be used as a default. + LinkPath []string +} + +// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present. +// It assumes that the links are available in a "links" element of the top-level response object. +// If this is not the case, override NextPageURL on your result type. +func (current LinkedPageBase) NextPageURL() (string, error) { + var path []string + var key string + + if current.LinkPath == nil { + path = []string{"links", "next"} + } else { + path = current.LinkPath + } + + submap, ok := current.Body.(map[string]interface{}) + if !ok { + err := gophercloud.ErrUnexpectedType{} + err.Expected = "map[string]interface{}" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return "", err + } + + for { + key, path = path[0], path[1:len(path)] + + value, ok := submap[key] + if !ok { + return "", nil + } + + if len(path) > 0 { + submap, ok = value.(map[string]interface{}) + if !ok { + err := gophercloud.ErrUnexpectedType{} + err.Expected = "map[string]interface{}" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) + return "", err + } + } else { + if value == nil { + // Actual null element. + return "", nil + } + + url, ok := value.(string) + if !ok { + err := gophercloud.ErrUnexpectedType{} + err.Expected = "string" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) + return "", err + } + + return url, nil + } + } +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (current LinkedPageBase) IsEmpty() (bool, error) { + if b, ok := current.Body.([]interface{}); ok { + return len(b) == 0, nil + } + err := gophercloud.ErrUnexpectedType{} + err.Expected = "[]interface{}" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return true, err +} + +// GetBody returns the linked page's body. This method is needed to satisfy the +// Page interface. +func (current LinkedPageBase) GetBody() interface{} { + return current.Body +} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/marker.go b/vendor/github.com/gophercloud/gophercloud/pagination/marker.go new file mode 100644 index 00000000000..52e53bae850 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/pagination/marker.go @@ -0,0 +1,58 @@ +package pagination + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud" +) + +// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager. +// For convenience, embed the MarkedPageBase struct. +type MarkerPage interface { + Page + + // LastMarker returns the last "marker" value on this page. + LastMarker() (string, error) +} + +// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters. +type MarkerPageBase struct { + PageResult + + // Owner is a reference to the embedding struct. + Owner MarkerPage +} + +// NextPageURL generates the URL for the page of results after this one. +func (current MarkerPageBase) NextPageURL() (string, error) { + currentURL := current.URL + + mark, err := current.Owner.LastMarker() + if err != nil { + return "", err + } + + q := currentURL.Query() + q.Set("marker", mark) + currentURL.RawQuery = q.Encode() + + return currentURL.String(), nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (current MarkerPageBase) IsEmpty() (bool, error) { + if b, ok := current.Body.([]interface{}); ok { + return len(b) == 0, nil + } + err := gophercloud.ErrUnexpectedType{} + err.Expected = "[]interface{}" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return true, err +} + +// GetBody returns the linked page's body. This method is needed to satisfy the +// Page interface. +func (current MarkerPageBase) GetBody() interface{} { + return current.Body +} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go new file mode 100644 index 00000000000..42c0b2dbe5b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go @@ -0,0 +1,251 @@ +package pagination + +import ( + "errors" + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/gophercloud/gophercloud" +) + +var ( + // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist. + ErrPageNotAvailable = errors.New("The requested page does not exist.") +) + +// Page must be satisfied by the result type of any resource collection. +// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated. +// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs, +// instead. +// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type +// will need to implement. +type Page interface { + // NextPageURL generates the URL for the page of data that follows this collection. + // Return "" if no such page exists. + NextPageURL() (string, error) + + // IsEmpty returns true if this Page has no items in it. + IsEmpty() (bool, error) + + // GetBody returns the Page Body. This is used in the `AllPages` method. + GetBody() interface{} +} + +// Pager knows how to advance through a specific resource collection, one page at a time. +type Pager struct { + client *gophercloud.ServiceClient + + initialURL string + + createPage func(r PageResult) Page + + firstPage Page + + Err error + + // Headers supplies additional HTTP headers to populate on each paged request. + Headers map[string]string +} + +// NewPager constructs a manually-configured pager. +// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page. +func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager { + return Pager{ + client: client, + initialURL: initialURL, + createPage: createPage, + } +} + +// WithPageCreator returns a new Pager that substitutes a different page creation function. This is +// useful for overriding List functions in delegation. +func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager { + return Pager{ + client: p.client, + initialURL: p.initialURL, + createPage: createPage, + } +} + +func (p Pager) fetchNextPage(url string) (Page, error) { + resp, err := Request(p.client, p.Headers, url) + if err != nil { + return nil, err + } + + remembered, err := PageResultFrom(resp) + if err != nil { + return nil, err + } + + return p.createPage(remembered), nil +} + +// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function. +// Return "false" from the handler to prematurely stop iterating. +func (p Pager) EachPage(handler func(Page) (bool, error)) error { + if p.Err != nil { + return p.Err + } + currentURL := p.initialURL + for { + 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() + if err != nil { + return err + } + if empty { + return nil + } + + ok, err := handler(currentPage) + if err != nil { + return err + } + if !ok { + return nil + } + + currentURL, err = currentPage.NextPageURL() + if err != nil { + return err + } + if currentURL == "" { + return nil + } + } +} + +// AllPages returns all the pages from a `List` operation in a single page, +// allowing the user to retrieve all the pages at once. +func (p Pager) AllPages() (Page, error) { + // pagesSlice holds all the pages until they get converted into as Page Body. + var pagesSlice []interface{} + // body will contain the final concatenated Page body. + var body reflect.Value + + // 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(firstPage) + + // if it's a single page, just return the firstPage (first page) + if _, found := pageType.FieldByName("SinglePageBase"); found { + 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 := 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 + // Iterate over the pages to concatenate the bodies. + err = p.EachPage(func(page Page) (bool, error) { + b := page.GetBody().(map[string]interface{}) + for k, v := range b { + // If it's a linked page, we don't want the `links`, we want the other one. + if !strings.HasSuffix(k, "links") { + // check the field's type. we only want []interface{} (which is really []map[string]interface{}) + switch vt := v.(type) { + case []interface{}: + key = k + pagesSlice = append(pagesSlice, vt...) + } + } + } + return true, nil + }) + if err != nil { + return nil, err + } + // Set body to value of type `map[string]interface{}` + body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice))) + body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice)) + case []byte: + // Iterate over the pages to concatenate the bodies. + err = p.EachPage(func(page Page) (bool, error) { + b := page.GetBody().([]byte) + pagesSlice = append(pagesSlice, b) + // seperate pages with a comma + pagesSlice = append(pagesSlice, []byte{10}) + return true, nil + }) + if err != nil { + return nil, err + } + if len(pagesSlice) > 0 { + // Remove the trailing comma. + pagesSlice = pagesSlice[:len(pagesSlice)-1] + } + var b []byte + // Combine the slice of slices in to a single slice. + for _, slice := range pagesSlice { + b = append(b, slice.([]byte)...) + } + // Set body to value of type `bytes`. + body = reflect.New(reflect.TypeOf(b)).Elem() + body.SetBytes(b) + case []interface{}: + // Iterate over the pages to concatenate the bodies. + err = p.EachPage(func(page Page) (bool, error) { + b := page.GetBody().([]interface{}) + pagesSlice = append(pagesSlice, b...) + return true, nil + }) + if err != nil { + return nil, err + } + // Set body to value of type `[]interface{}` + body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice)) + for i, s := range pagesSlice { + body.Index(i).Set(reflect.ValueOf(s)) + } + default: + err := gophercloud.ErrUnexpectedType{} + err.Expected = "map[string]interface{}/[]byte/[]interface{}" + err.Actual = fmt.Sprintf("%T", pb) + return nil, err + } + + // Each `Extract*` function is expecting a specific type of page coming back, + // otherwise the type assertion in those functions will fail. pageType is needed + // to create a type in this method that has the same type that the `Extract*` + // function is expecting and set the Body of that object to the concatenated + // pages. + page := reflect.New(pageType) + // Set the page body to be the concatenated pages. + page.Elem().FieldByName("Body").Set(body) + // Set any additional headers that were pass along. The `objectstorage` pacakge, + // for example, passes a Content-Type header. + h := make(http.Header) + for k, v := range p.Headers { + h.Add(k, v) + } + page.Elem().FieldByName("Header").Set(reflect.ValueOf(h)) + // Type assert the page to a Page interface so that the type assertion in the + // `Extract*` methods will work. + return page.Elem().Interface().(Page), err +} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go b/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go new file mode 100644 index 00000000000..912daea3642 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go @@ -0,0 +1,4 @@ +/* +Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs. +*/ +package pagination diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/single.go b/vendor/github.com/gophercloud/gophercloud/pagination/single.go new file mode 100644 index 00000000000..4251d6491ef --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/pagination/single.go @@ -0,0 +1,33 @@ +package pagination + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud" +) + +// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once. +type SinglePageBase PageResult + +// NextPageURL always returns "" to indicate that there are no more pages to return. +func (current SinglePageBase) NextPageURL() (string, error) { + return "", nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (current SinglePageBase) IsEmpty() (bool, error) { + if b, ok := current.Body.([]interface{}); ok { + return len(b) == 0, nil + } + err := gophercloud.ErrUnexpectedType{} + err.Expected = "[]interface{}" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return true, err +} + +// GetBody returns the single page's body. This method is needed to satisfy the +// Page interface. +func (current SinglePageBase) GetBody() interface{} { + return current.Body +} diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go new file mode 100644 index 00000000000..b9986660cbd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/params.go @@ -0,0 +1,491 @@ +package gophercloud + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +/* +BuildRequestBody builds a map[string]interface from the given `struct`. If +parent is not an empty string, the final map[string]interface returned will +encapsulate the built one. For example: + + disk := 1 + createOpts := flavors.CreateOpts{ + ID: "1", + Name: "m1.tiny", + Disk: &disk, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1.0, + } + + body, err := gophercloud.BuildRequestBody(createOpts, "flavor") + +The above example can be run as-is, however it is recommended to look at how +BuildRequestBody is used within Gophercloud to more fully understand how it +fits within the request process as a whole rather than use it directly as shown +above. +*/ +func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) { + optsValue := reflect.ValueOf(opts) + if optsValue.Kind() == reflect.Ptr { + optsValue = optsValue.Elem() + } + + optsType := reflect.TypeOf(opts) + if optsType.Kind() == reflect.Ptr { + optsType = optsType.Elem() + } + + optsMap := make(map[string]interface{}) + if optsValue.Kind() == reflect.Struct { + //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind()) + for i := 0; i < optsValue.NumField(); i++ { + v := optsValue.Field(i) + f := optsType.Field(i) + + if f.Name != strings.Title(f.Name) { + //fmt.Printf("Skipping field: %s...\n", f.Name) + continue + } + + //fmt.Printf("Starting on field: %s...\n", f.Name) + + zero := isZero(v) + //fmt.Printf("v is zero?: %v\n", zero) + + // if the field has a required tag that's set to "true" + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + //fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero) + // if the field's value is zero, return a missing-argument error + if zero { + // if the field has a 'required' tag, it can't have a zero-value + err := ErrMissingInput{} + err.Argument = f.Name + return nil, err + } + } + + if xorTag := f.Tag.Get("xor"); xorTag != "" { + //fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag) + xorField := optsValue.FieldByName(xorTag) + var xorFieldIsZero bool + if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) { + xorFieldIsZero = true + } else { + if xorField.Kind() == reflect.Ptr { + xorField = xorField.Elem() + } + xorFieldIsZero = isZero(xorField) + } + if !(zero != xorFieldIsZero) { + err := ErrMissingInput{} + err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag) + err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag) + return nil, err + } + } + + if orTag := f.Tag.Get("or"); orTag != "" { + //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag) + //fmt.Printf("field is zero?: %v\n", zero) + if zero { + orField := optsValue.FieldByName(orTag) + var orFieldIsZero bool + if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) { + orFieldIsZero = true + } else { + if orField.Kind() == reflect.Ptr { + orField = orField.Elem() + } + orFieldIsZero = isZero(orField) + } + if orFieldIsZero { + err := ErrMissingInput{} + err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag) + err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag) + return nil, err + } + } + } + + jsonTag := f.Tag.Get("json") + if jsonTag == "-" { + 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)) + if jsonTag != "" { + jsonTagPieces := strings.Split(jsonTag, ",") + if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" { + if v.CanSet() { + if !v.IsNil() { + if v.Kind() == reflect.Ptr { + v.Set(reflect.Zero(v.Type())) + } + } + //fmt.Printf("value after change: %+v\n", optsValue.Field(i)) + } + } + } + continue + } + + //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name) + _, err := BuildRequestBody(v.Interface(), f.Name) + if err != nil { + return nil, err + } + } + } + + //fmt.Printf("opts: %+v \n", opts) + + b, err := json.Marshal(opts) + if err != nil { + return nil, err + } + + //fmt.Printf("string(b): %s\n", string(b)) + + err = json.Unmarshal(b, &optsMap) + if err != nil { + return nil, err + } + + //fmt.Printf("optsMap: %+v\n", optsMap) + + if parent != "" { + optsMap = map[string]interface{}{parent: optsMap} + } + //fmt.Printf("optsMap after parent added: %+v\n", optsMap) + return optsMap, nil + } + // Return an error if the underlying type of 'opts' isn't a struct. + return nil, fmt.Errorf("Options type is not a struct.") +} + +// EnabledState is a convenience type, mostly used in Create and Update +// operations. Because the zero value of a bool is FALSE, we need to use a +// pointer instead to indicate zero-ness. +type EnabledState *bool + +// Convenience vars for EnabledState values. +var ( + iTrue = true + iFalse = false + + Enabled EnabledState = &iTrue + Disabled EnabledState = &iFalse +) + +// IPVersion is a type for the possible IP address versions. Valid instances +// are IPv4 and IPv6 +type IPVersion int + +const ( + // IPv4 is used for IP version 4 addresses + IPv4 IPVersion = 4 + // IPv6 is used for IP version 6 addresses + IPv6 IPVersion = 6 +) + +// IntToPointer is a function for converting integers into integer pointers. +// This is useful when passing in options to operations. +func IntToPointer(i int) *int { + return &i +} + +/* +MaybeString is an internal function to be used by request methods in individual +resource packages. + +It takes a string that might be a zero value and returns either a pointer to its +address or nil. This is useful for allowing users to conveniently omit values +from an options struct by leaving them zeroed, but still pass nil to the JSON +serializer so they'll be omitted from the request body. +*/ +func MaybeString(original string) *string { + if original != "" { + return &original + } + return nil +} + +/* +MaybeInt is an internal function to be used by request methods in individual +resource packages. + +Like MaybeString, it accepts an int that may or may not be a zero value, and +returns either a pointer to its address or nil. It's intended to hint that the +JSON serializer should omit its field. +*/ +func MaybeInt(original int) *int { + if original != 0 { + return &original + } + return nil +} + +/* +func isUnderlyingStructZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Ptr: + return isUnderlyingStructZero(v.Elem()) + default: + return isZero(v) + } +} +*/ + +var t time.Time + +func isZero(v reflect.Value) bool { + //fmt.Printf("\n\nchecking isZero for value: %+v\n", v) + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return true + } + return false + case reflect.Func, reflect.Map, reflect.Slice: + return v.IsNil() + case reflect.Array: + z := true + for i := 0; i < v.Len(); i++ { + z = z && isZero(v.Index(i)) + } + return z + case reflect.Struct: + if v.Type() == reflect.TypeOf(t) { + if v.Interface().(time.Time).IsZero() { + return true + } + return false + } + z := true + for i := 0; i < v.NumField(); i++ { + z = z && isZero(v.Field(i)) + } + return z + } + // Compare other types directly: + z := reflect.Zero(v.Type()) + //fmt.Printf("zero type for value: %+v\n\n\n", z) + return v.Interface() == z.Interface() +} + +/* +BuildQueryString is an internal function to be used by request methods in +individual resource packages. + +It accepts a tagged structure and expands it into a URL struct. Field names are +converted into query parameters based on a "q" tag. For example: + + type struct Something { + Bar string `q:"x_bar"` + Baz int `q:"lorem_ipsum"` + } + + instance := Something{ + Bar: "AAA", + Baz: "BBB", + } + +will be converted into "?x_bar=AAA&lorem_ipsum=BBB". + +The struct's fields may be strings, integers, or boolean values. Fields left at +their type's zero value will be omitted from the query. +*/ +func BuildQueryString(opts interface{}) (*url.URL, error) { + optsValue := reflect.ValueOf(opts) + if optsValue.Kind() == reflect.Ptr { + optsValue = optsValue.Elem() + } + + optsType := reflect.TypeOf(opts) + if optsType.Kind() == reflect.Ptr { + optsType = optsType.Elem() + } + + params := url.Values{} + + if optsValue.Kind() == reflect.Struct { + for i := 0; i < optsValue.NumField(); i++ { + v := optsValue.Field(i) + f := optsType.Field(i) + qTag := f.Tag.Get("q") + + // if the field has a 'q' tag, it goes in the query string + if qTag != "" { + tags := strings.Split(qTag, ",") + + // if the field is set, add it to the slice of query pieces + if !isZero(v) { + loop: + switch v.Kind() { + case reflect.Ptr: + v = v.Elem() + goto loop + case reflect.String: + params.Add(tags[0], v.String()) + case reflect.Int: + params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) + case reflect.Bool: + params.Add(tags[0], strconv.FormatBool(v.Bool())) + case reflect.Slice: + switch v.Type().Elem() { + case reflect.TypeOf(0): + for i := 0; i < v.Len(); i++ { + params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10)) + } + default: + for i := 0; i < v.Len(); i++ { + params.Add(tags[0], v.Index(i).String()) + } + } + case reflect.Map: + if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String { + var s []string + for _, k := range v.MapKeys() { + value := v.MapIndex(k).String() + s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value)) + } + params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", "))) + } + } + } else { + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) + } + } + } + } + + return &url.URL{RawQuery: params.Encode()}, nil + } + // Return an error if the underlying type of 'opts' isn't a struct. + return nil, fmt.Errorf("Options type is not a struct.") +} + +/* +BuildHeaders is an internal function to be used by request methods in +individual resource packages. + +It accepts an arbitrary tagged structure and produces a string map that's +suitable for use as the HTTP headers of an outgoing request. Field names are +mapped to header names based in "h" tags. + + type struct Something { + Bar string `h:"x_bar"` + Baz int `h:"lorem_ipsum"` + } + + instance := Something{ + Bar: "AAA", + Baz: "BBB", + } + +will be converted into: + + map[string]string{ + "x_bar": "AAA", + "lorem_ipsum": "BBB", + } + +Untagged fields and fields left at their zero values are skipped. Integers, +booleans and string values are supported. +*/ +func BuildHeaders(opts interface{}) (map[string]string, error) { + optsValue := reflect.ValueOf(opts) + if optsValue.Kind() == reflect.Ptr { + optsValue = optsValue.Elem() + } + + optsType := reflect.TypeOf(opts) + if optsType.Kind() == reflect.Ptr { + optsType = optsType.Elem() + } + + optsMap := make(map[string]string) + if optsValue.Kind() == reflect.Struct { + for i := 0; i < optsValue.NumField(); i++ { + v := optsValue.Field(i) + f := optsType.Field(i) + hTag := f.Tag.Get("h") + + // if the field has a 'h' tag, it goes in the header + if hTag != "" { + tags := strings.Split(hTag, ",") + + // if the field is set, add it to the slice of query pieces + if !isZero(v) { + switch v.Kind() { + case reflect.String: + optsMap[tags[0]] = v.String() + case reflect.Int: + optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) + case reflect.Bool: + optsMap[tags[0]] = strconv.FormatBool(v.Bool()) + } + } else { + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) + } + } + } + + } + return optsMap, nil + } + // Return an error if the underlying type of 'opts' isn't a struct. + return optsMap, fmt.Errorf("Options type is not a struct.") +} + +// IDSliceToQueryString takes a slice of elements and converts them into a query +// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the +// result would be `?name=20&name=40&name=60' +func IDSliceToQueryString(name string, ids []int) string { + str := "" + for k, v := range ids { + if k == 0 { + str += "?" + } else { + str += "&" + } + str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v)) + } + return str +} + +// IntWithinRange returns TRUE if an integer falls within a defined range, and +// FALSE if not. +func IntWithinRange(val, min, max int) bool { + return val > min && val < max +} diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go new file mode 100644 index 00000000000..fce00462fd3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go @@ -0,0 +1,501 @@ +package gophercloud + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net/http" + "strings" + "sync" +) + +// DefaultUserAgent is the default User-Agent string set in the request header. +const DefaultUserAgent = "gophercloud/2.0.0" + +// UserAgent represents a User-Agent header. +type UserAgent struct { + // prepend is the slice of User-Agent strings to prepend to DefaultUserAgent. + // All the strings to prepend are accumulated and prepended in the Join method. + prepend []string +} + +// Prepend prepends a user-defined string to the default User-Agent string. Users +// may pass in one or more strings to prepend. +func (ua *UserAgent) Prepend(s ...string) { + ua.prepend = append(s, ua.prepend...) +} + +// Join concatenates all the user-defined User-Agend strings with the default +// Gophercloud User-Agent string. +func (ua *UserAgent) Join() string { + uaSlice := append(ua.prepend, DefaultUserAgent) + return strings.Join(uaSlice, " ") +} + +// ProviderClient stores details that are required to interact with any +// services within a specific provider's API. +// +// Generally, you acquire a ProviderClient by calling the NewClient method in +// the appropriate provider's child package, providing whatever authentication +// credentials are required. +type ProviderClient struct { + // IdentityBase is the base URL used for a particular provider's identity + // service - it will be used when issuing authenticatation requests. It + // should point to the root resource of the identity service, not a specific + // identity version. + IdentityBase string + + // IdentityEndpoint is the identity endpoint. This may be a specific version + // of the identity service. If this is the case, this endpoint is used rather + // than querying versions first. + IdentityEndpoint string + + // TokenID is the ID of the most recently issued valid token. + // NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application. + // To safely read or write this value, call `Token` or `SetToken`, respectively + TokenID string + + // EndpointLocator describes how this provider discovers the endpoints for + // its constituent services. + EndpointLocator EndpointLocator + + // HTTPClient allows users to interject arbitrary http, https, or other transit behaviors. + HTTPClient http.Client + + // UserAgent represents the User-Agent header in the HTTP request. + UserAgent UserAgent + + // ReauthFunc is the function used to re-authenticate the user if the request + // fails with a 401 HTTP response code. This a needed because there may be multiple + // 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 + reauthingErr error + done *sync.Cond +} + +// AuthenticatedHeaders returns a map of HTTP headers that are common for all +// 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.Lock() + for client.reauthmut.reauthing { + client.reauthmut.done.Wait() + } + client.reauthmut.Unlock() + } + t := client.Token() + if t == "" { + return + } + return map[string]string{"X-Auth-Token": t} +} + +// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token. +// If the application's ProviderClient is not used concurrently, this doesn't need to be called. +func (client *ProviderClient) UseTokenLock() { + client.mut = new(sync.RWMutex) + 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 { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.TokenID +} + +// SetToken safely sets the value of the auth token in the ProviderClient. Applications may +// 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 +} + +// 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.reauthmut == nil { + return client.ReauthFunc() + } + + 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 { + err = client.ReauthFunc() + } + + client.reauthmut.Lock() + client.reauthmut.reauthing = false + client.reauthmut.reauthingErr = err + client.reauthmut.done.Broadcast() + client.reauthmut.Unlock() + return +} + +// RequestOpts customizes the behavior of the provider.Request() method. +type RequestOpts struct { + // JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The + // content type of the request will default to "application/json" unless overridden by MoreHeaders. + // It's an error to specify both a JSONBody and a RawBody. + JSONBody interface{} + // RawBody contains an io.Reader that will be consumed by the request directly. No content-type + // will be set unless one is provided explicitly by MoreHeaders. + RawBody io.Reader + // JSONResponse, if provided, will be populated with the contents of the response body parsed as + // JSON. + JSONResponse interface{} + // OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If + // the response has a different code, an error will be returned. + OkCodes []int + // MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is + // provided with a blank value (""), that header will be *omitted* instead: use this to suppress + // the default Accept header or an inferred Content-Type, for example. + MoreHeaders map[string]string + // ErrorContext specifies the resource error type to return if an error is encountered. + // This lets resources override default error messages based on the response status code. + ErrorContext error +} + +var applicationJSON = "application/json" + +// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication +// header will automatically be provided. +func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) { + var body io.Reader + var contentType *string + + // Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided + // io.ReadSeeker as-is. Default the content-type to application/json. + if options.JSONBody != nil { + if options.RawBody != nil { + return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()") + } + + rendered, err := json.Marshal(options.JSONBody) + if err != nil { + return nil, err + } + + body = bytes.NewReader(rendered) + contentType = &applicationJSON + } + + if options.RawBody != nil { + body = options.RawBody + } + + // Construct the http.Request. + req, err := http.NewRequest(method, url, body) + 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. + if contentType != nil { + req.Header.Set("Content-Type", *contentType) + } + req.Header.Set("Accept", applicationJSON) + + // Set the User-Agent header + req.Header.Set("User-Agent", client.UserAgent.Join()) + + if options.MoreHeaders != nil { + for k, v := range options.MoreHeaders { + if v != "" { + req.Header.Set(k, v) + } else { + req.Header.Del(k) + } + } + } + + // get latest token from client + for k, v := range client.AuthenticatedHeaders() { + req.Header.Set(k, v) + } + + // Set connection parameter to close the connection immediately when we've got the response + req.Close = true + + prereqtok := req.Header.Get("X-Auth-Token") + + // Issue the request. + resp, err := client.HTTPClient.Do(req) + if err != nil { + return nil, err + } + + // Allow default OkCodes if none explicitly set + okc := options.OkCodes + if okc == nil { + okc = defaultOkCodes(method) + } + + // Validate the HTTP response status. + var ok bool + for _, code := range okc { + if resp.StatusCode == code { + ok = true + break + } + } + + if !ok { + body, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + respErr := ErrUnexpectedResponseCode{ + URL: url, + Method: method, + Expected: options.OkCodes, + Actual: resp.StatusCode, + Body: body, + } + + errType := options.ErrorContext + switch resp.StatusCode { + case http.StatusBadRequest: + err = ErrDefault400{respErr} + if error400er, ok := errType.(Err400er); ok { + err = error400er.Error400(respErr) + } + case http.StatusUnauthorized: + if client.ReauthFunc != nil { + err = client.Reauthenticate(prereqtok) + if err != nil { + e := &ErrUnableToReauthenticate{} + e.ErrOriginal = respErr + return nil, e + } + if options.RawBody != nil { + if seeker, ok := options.RawBody.(io.Seeker); ok { + seeker.Seek(0, 0) + } + } + resp, err = client.Request(method, url, options) + if err != nil { + switch err.(type) { + case *ErrUnexpectedResponseCode: + e := &ErrErrorAfterReauthentication{} + e.ErrOriginal = err.(*ErrUnexpectedResponseCode) + return nil, e + default: + e := &ErrErrorAfterReauthentication{} + e.ErrOriginal = err + return nil, e + } + } + return resp, nil + } + err = ErrDefault401{respErr} + if error401er, ok := errType.(Err401er); ok { + err = error401er.Error401(respErr) + } + case http.StatusForbidden: + err = ErrDefault403{respErr} + if error403er, ok := errType.(Err403er); ok { + err = error403er.Error403(respErr) + } + case http.StatusNotFound: + err = ErrDefault404{respErr} + if error404er, ok := errType.(Err404er); ok { + err = error404er.Error404(respErr) + } + case http.StatusMethodNotAllowed: + err = ErrDefault405{respErr} + if error405er, ok := errType.(Err405er); ok { + err = error405er.Error405(respErr) + } + case http.StatusRequestTimeout: + err = ErrDefault408{respErr} + 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 { + err = error429er.Error429(respErr) + } + case http.StatusInternalServerError: + err = ErrDefault500{respErr} + if error500er, ok := errType.(Err500er); ok { + err = error500er.Error500(respErr) + } + case http.StatusServiceUnavailable: + err = ErrDefault503{respErr} + if error503er, ok := errType.(Err503er); ok { + err = error503er.Error503(respErr) + } + } + + if err == nil { + err = respErr + } + + return resp, err + } + + // Parse the response body as JSON, if requested to do so. + if options.JSONResponse != nil { + defer resp.Body.Close() + if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil { + return nil, err + } + } + + return resp, nil +} + +func defaultOkCodes(method string) []int { + switch { + case method == "GET": + return []int{200} + case method == "POST": + return []int{201, 202} + case method == "PUT": + return []int{201, 202} + case method == "PATCH": + return []int{200, 202, 204} + case method == "DELETE": + return []int{202, 204} + } + + return []int{} +} diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go new file mode 100644 index 00000000000..94a16bff0b4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/results.go @@ -0,0 +1,448 @@ +package gophercloud + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "reflect" + "strconv" + "time" +) + +/* +Result is an internal type to be used by individual resource packages, but its +methods will be available on a wide variety of user-facing embedding types. + +It acts as a base struct that other Result types, returned from request +functions, can embed for convenience. All Results capture basic information +from the HTTP transaction that was performed, including the response body, +HTTP headers, and any errors that happened. + +Generally, each Result type will have an Extract method that can be used to +further interpret the result's payload in a specific context. Extensions or +providers can then provide additional extraction functions to pull out +provider- or extension-specific information as well. +*/ +type Result struct { + // Body is the payload of the HTTP response from the server. In most cases, + // this will be the deserialized JSON structure. + Body interface{} + + // Header contains the HTTP header structure from the original response. + Header http.Header + + // Err is an error that occurred during the operation. It's deferred until + // extraction to make it easier to chain the Extract call. + Err error +} + +// ExtractInto allows users to provide an object into which `Extract` will extract +// the `Result.Body`. This would be useful for OpenStack providers that have +// different fields in the response object than OpenStack proper. +func (r Result) ExtractInto(to interface{}) error { + if r.Err != nil { + return r.Err + } + + if reader, ok := r.Body.(io.Reader); ok { + if readCloser, ok := reader.(io.Closer); ok { + defer readCloser.Close() + } + return json.NewDecoder(reader).Decode(to) + } + + b, err := json.Marshal(r.Body) + if err != nil { + return err + } + err = json.Unmarshal(b, to) + + return err +} + +func (r Result) extractIntoPtr(to interface{}, label string) error { + if label == "" { + return r.ExtractInto(&to) + } + + var m map[string]interface{} + err := r.ExtractInto(&m) + if err != nil { + return err + } + + b, err := json.Marshal(m[label]) + if err != nil { + return err + } + + toValue := reflect.ValueOf(to) + if toValue.Kind() == reflect.Ptr { + toValue = toValue.Elem() + } + + switch toValue.Kind() { + case reflect.Slice: + typeOfV := toValue.Type().Elem() + if typeOfV.Kind() == reflect.Struct { + if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { + newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) + + 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) + } + } + + // "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) + } + } + case reflect.Struct: + typeOfV := toValue.Type() + if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { + for i := 0; i < toValue.NumField(); i++ { + toField := toValue.Field(i) + if toField.Kind() == reflect.Struct { + s := toField.Addr().Interface() + err = json.NewDecoder(bytes.NewReader(b)).Decode(s) + if err != nil { + return err + } + } + } + } + } + + err = json.Unmarshal(b, &to) + return err +} + +// ExtractIntoStructPtr will unmarshal the Result (r) into the provided +// interface{} (to). +// +// NOTE: For internal use only +// +// `to` must be a pointer to an underlying struct type +// +// If provided, `label` will be filtered out of the response +// body prior to `r` being unmarshalled into `to`. +func (r Result) ExtractIntoStructPtr(to interface{}, label string) error { + if r.Err != nil { + return r.Err + } + + t := reflect.TypeOf(to) + if k := t.Kind(); k != reflect.Ptr { + return fmt.Errorf("Expected pointer, got %v", k) + } + switch t.Elem().Kind() { + case reflect.Struct: + return r.extractIntoPtr(to, label) + default: + return fmt.Errorf("Expected pointer to struct, got: %v", t) + } +} + +// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided +// interface{} (to). +// +// NOTE: For internal use only +// +// `to` must be a pointer to an underlying slice type +// +// If provided, `label` will be filtered out of the response +// body prior to `r` being unmarshalled into `to`. +func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error { + if r.Err != nil { + return r.Err + } + + t := reflect.TypeOf(to) + if k := t.Kind(); k != reflect.Ptr { + return fmt.Errorf("Expected pointer, got %v", k) + } + switch t.Elem().Kind() { + case reflect.Slice: + return r.extractIntoPtr(to, label) + default: + return fmt.Errorf("Expected pointer to slice, got: %v", t) + } +} + +// PrettyPrintJSON creates a string containing the full response body as +// pretty-printed JSON. It's useful for capturing test fixtures and for +// debugging extraction bugs. If you include its output in an issue related to +// a buggy extraction function, we will all love you forever. +func (r Result) PrettyPrintJSON() string { + pretty, err := json.MarshalIndent(r.Body, "", " ") + if err != nil { + panic(err.Error()) + } + return string(pretty) +} + +// ErrResult is an internal type to be used by individual resource packages, but +// its methods will be available on a wide variety of user-facing embedding +// types. +// +// It represents results that only contain a potential error and +// nothing else. Usually, if the operation executed successfully, the Err field +// will be nil; otherwise it will be stocked with a relevant error. Use the +// ExtractErr method +// to cleanly pull it out. +type ErrResult struct { + Result +} + +// ExtractErr is a function that extracts error information, or nil, from a result. +func (r ErrResult) ExtractErr() error { + return r.Err +} + +/* +HeaderResult is an internal type to be used by individual resource packages, but +its methods will be available on a wide variety of user-facing embedding types. + +It represents a result that only contains an error (possibly nil) and an +http.Header. This is used, for example, by the objectstorage packages in +openstack, because most of the operations don't return response bodies, but do +have relevant information in headers. +*/ +type HeaderResult struct { + Result +} + +// ExtractInto allows users to provide an object into which `Extract` will +// extract the http.Header headers of the result. +func (r HeaderResult) ExtractInto(to interface{}) error { + if r.Err != nil { + return r.Err + } + + tmpHeaderMap := map[string]string{} + for k, v := range r.Header { + if len(v) > 0 { + tmpHeaderMap[k] = v[0] + } + } + + b, err := json.Marshal(tmpHeaderMap) + if err != nil { + return err + } + err = json.Unmarshal(b, to) + + return err +} + +// RFC3339Milli describes a common time format used by some API responses. +const RFC3339Milli = "2006-01-02T15:04:05.999999Z" + +type JSONRFC3339Milli time.Time + +func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error { + b := bytes.NewBuffer(data) + dec := json.NewDecoder(b) + var s string + if err := dec.Decode(&s); err != nil { + return err + } + t, err := time.Parse(RFC3339Milli, s) + if err != nil { + return err + } + *jt = JSONRFC3339Milli(t) + return nil +} + +const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999" + +type JSONRFC3339MilliNoZ time.Time + +func (jt *JSONRFC3339MilliNoZ) 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(RFC3339MilliNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339MilliNoZ(t) + return nil +} + +type JSONRFC1123 time.Time + +func (jt *JSONRFC1123) 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(time.RFC1123, s) + if err != nil { + return err + } + *jt = JSONRFC1123(t) + return nil +} + +type JSONUnix time.Time + +func (jt *JSONUnix) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + unix, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + t = time.Unix(unix, 0) + *jt = JSONUnix(t) + return nil +} + +// RFC3339NoZ is the time format used in Heat (Orchestration). +const RFC3339NoZ = "2006-01-02T15:04:05" + +type JSONRFC3339NoZ time.Time + +func (jt *JSONRFC3339NoZ) 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(RFC3339NoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339NoZ(t) + 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. + +It's a response substructure common to many paginated collection results that is +used to point to related pages. Usually, the one we care about is the one with +Rel field set to "next". +*/ +type Link struct { + Href string `json:"href"` + Rel string `json:"rel"` +} + +/* +ExtractNextURL is an internal function useful for packages of collection +resources that are paginated in a certain way. + +It attempts to extract the "next" URL from slice of Link structs, or +"" if no such URL is present. +*/ +func ExtractNextURL(links []Link) (string, error) { + var url string + + for _, l := range links { + if l.Rel == "next" { + url = l.Href + } + } + + if url == "" { + return "", nil + } + + return url, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go new file mode 100644 index 00000000000..f222f05a66d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/service_client.go @@ -0,0 +1,154 @@ +package gophercloud + +import ( + "io" + "net/http" + "strings" +) + +// ServiceClient stores details required to interact with a specific service API implemented by a provider. +// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient. +type ServiceClient struct { + // ProviderClient is a reference to the provider that implements this service. + *ProviderClient + + // Endpoint is the base URL of the service's API, acquired from a service catalog. + // It MUST end with a /. + Endpoint string + + // ResourceBase is the base URL shared by the resources within a service's API. It should include + // the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used + // as-is, instead. + ResourceBase string + + // This is the service client type (e.g. compute, sharev2). + // NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS. + // It is only exported because it gets set in a different package. + Type string + + // 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 /. +func (client *ServiceClient) ResourceBaseURL() string { + if client.ResourceBase != "" { + return client.ResourceBase + } + return client.Endpoint +} + +// ServiceURL constructs a URL for a resource belonging to this provider. +func (client *ServiceClient) ServiceURL(parts ...string) string { + return client.ResourceBaseURL() + strings.Join(parts, "/") +} + +func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) { + if v, ok := (JSONBody).(io.Reader); ok { + opts.RawBody = v + } else if JSONBody != nil { + opts.JSONBody = JSONBody + } + + if JSONResponse != nil { + opts.JSONResponse = JSONResponse + } + + if opts.MoreHeaders == nil { + opts.MoreHeaders = make(map[string]string) + } + + if client.Microversion != "" { + client.setMicroversionHeader(opts) + } +} + +// Get calls `Request` with the "GET" HTTP verb. +func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, JSONResponse, opts) + return client.Request("GET", url, opts) +} + +// Post calls `Request` with the "POST" HTTP verb. +func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, JSONBody, JSONResponse, opts) + return client.Request("POST", url, opts) +} + +// Put calls `Request` with the "PUT" HTTP verb. +func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, JSONBody, JSONResponse, opts) + return client.Request("PUT", url, opts) +} + +// Patch calls `Request` with the "PATCH" HTTP verb. +func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, JSONBody, JSONResponse, opts) + return client.Request("PATCH", url, opts) +} + +// Delete calls `Request` with the "DELETE" HTTP verb. +func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, nil, opts) + 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": + opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion + case "sharev2": + 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/util.go b/vendor/github.com/gophercloud/gophercloud/util.go new file mode 100644 index 00000000000..68f9a5d3eca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/util.go @@ -0,0 +1,102 @@ +package gophercloud + +import ( + "fmt" + "net/url" + "path/filepath" + "strings" + "time" +) + +// WaitFor polls a predicate function, once per second, up to a timeout limit. +// This is useful to wait for a resource to transition to a certain state. +// To handle situations when the predicate might hang indefinitely, the +// predicate will be prematurely cancelled after the timeout. +// Resource packages will wrap this in a more convenient function that's +// specific to a certain resource, but it can also be useful on its own. +func WaitFor(timeout int, predicate func() (bool, error)) error { + type WaitForResult struct { + Success bool + Error error + } + + start := time.Now().Unix() + + for { + // If a timeout is set, and that's been exceeded, shut it down. + if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) { + return fmt.Errorf("A timeout occurred") + } + + time.Sleep(1 * time.Second) + + var result WaitForResult + ch := make(chan bool, 1) + go func() { + defer close(ch) + satisfied, err := predicate() + result.Success = satisfied + result.Error = err + }() + + select { + case <-ch: + if result.Error != nil { + return result.Error + } + if result.Success { + return nil + } + // If the predicate has not finished by the timeout, cancel it. + case <-time.After(time.Duration(timeout) * time.Second): + return fmt.Errorf("A timeout occurred") + } + } +} + +// NormalizeURL is an internal function to be used by provider clients. +// +// It ensures that each endpoint URL has a closing `/`, as expected by +// ServiceClient's methods. +func NormalizeURL(url string) string { + if !strings.HasSuffix(url, "/") { + return url + "/" + } + return url +} + +// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as +// a reference in the filesystem, if necessary. basePath is assumed to contain +// either '.' when first used, or the file:// type fqdn of the parent resource. +// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml +func NormalizePathURL(basePath, rawPath string) (string, error) { + u, err := url.Parse(rawPath) + if err != nil { + return "", err + } + // if a scheme is defined, it must be a fqdn already + if u.Scheme != "" { + return u.String(), nil + } + // if basePath is a url, then child resources are assumed to be relative to it + bu, err := url.Parse(basePath) + if err != nil { + return "", err + } + var basePathSys, absPathSys string + if bu.Scheme != "" { + basePathSys = filepath.FromSlash(bu.Path) + absPathSys = filepath.Join(basePathSys, rawPath) + bu.Path = filepath.ToSlash(absPathSys) + return bu.String(), nil + } + + absPathSys = filepath.Join(basePath, rawPath) + u.Path = filepath.ToSlash(absPathSys) + if err != nil { + return "", err + } + u.Scheme = "file" + return u.String(), nil + +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 908aef11871..3cdc9753262 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -457,6 +457,39 @@ github.com/google/uuid github.com/googleapis/gax-go/v2 github.com/googleapis/gax-go/v2/apierror github.com/googleapis/gax-go/v2/apierror/internal/proto +# github.com/gophercloud/gophercloud v0.1.0 => github.com/gophercloud/gophercloud v0.1.0 +## explicit +github.com/gophercloud/gophercloud +github.com/gophercloud/gophercloud/openstack +github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions +github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes +github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes +github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes +github.com/gophercloud/gophercloud/openstack/common/extensions +github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces +github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach +github.com/gophercloud/gophercloud/openstack/compute/v2/flavors +github.com/gophercloud/gophercloud/openstack/compute/v2/images +github.com/gophercloud/gophercloud/openstack/compute/v2/servers +github.com/gophercloud/gophercloud/openstack/identity/v2/tenants +github.com/gophercloud/gophercloud/openstack/identity/v2/tokens +github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts +github.com/gophercloud/gophercloud/openstack/identity/v3/tokens +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external +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/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 +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups +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 +github.com/gophercloud/gophercloud/openstack/utils +github.com/gophercloud/gophercloud/pagination # github.com/gorilla/websocket v1.4.2 ## explicit; go 1.12 github.com/gorilla/websocket @@ -1882,6 +1915,7 @@ k8s.io/client-go/plugin/pkg/client/auth/azure k8s.io/client-go/plugin/pkg/client/auth/exec k8s.io/client-go/plugin/pkg/client/auth/gcp k8s.io/client-go/plugin/pkg/client/auth/oidc +k8s.io/client-go/plugin/pkg/client/auth/openstack k8s.io/client-go/rest k8s.io/client-go/rest/fake k8s.io/client-go/rest/watch @@ -2289,6 +2323,7 @@ k8s.io/legacy-cloud-providers/azure/metrics k8s.io/legacy-cloud-providers/azure/retry k8s.io/legacy-cloud-providers/gce k8s.io/legacy-cloud-providers/gce/gcpcredential +k8s.io/legacy-cloud-providers/openstack k8s.io/legacy-cloud-providers/vsphere k8s.io/legacy-cloud-providers/vsphere/testing k8s.io/legacy-cloud-providers/vsphere/vclib @@ -2488,6 +2523,7 @@ sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.3.0 ## explicit; go 1.12 sigs.k8s.io/yaml +# github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.1.0 # k8s.io/api => ./staging/src/k8s.io/api # k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver # k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery From 6005c3b5120d2bfa1ce72b02389ac951a0482545 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Thu, 10 Nov 2022 18:26:00 +0100 Subject: [PATCH 2/2] Remove OpenStack cloud provider And keep OpenStack Cinder CSI translation --- .../gophercloud/gophercloud/LICENSE | 195 -- .../storage-class/openstack/default.yaml | 9 - cmd/cloud-controller-manager/providers.go | 1 - .../app/plugins_providers.go | 2 - cmd/kubelet/app/plugins_providers.go | 2 - go.mod | 2 - go.sum | 4 - hack/local-up-cluster.sh | 12 - pkg/cloudprovider/providers/providers.go | 1 - pkg/features/kube_features.go | 9 - pkg/volume/cinder/OWNERS | 14 - pkg/volume/cinder/attacher.go | 434 ----- pkg/volume/cinder/attacher_test.go | 758 -------- pkg/volume/cinder/cinder.go | 635 ------- pkg/volume/cinder/cinder_block.go | 179 -- pkg/volume/cinder/cinder_block_test.go | 151 -- pkg/volume/cinder/cinder_test.go | 365 ---- pkg/volume/cinder/cinder_util.go | 278 --- pkg/volume/cinder/doc.go | 18 - .../persistentvolume/label/admission.go | 71 +- .../persistentvolume/label/admission_test.go | 69 +- staging/publishing/rules.yaml | 2 - .../src/k8s.io/client-go/examples/README.md | 1 - .../create-update-delete-deployment/main.go | 1 - .../main.go | 1 - .../in-cluster-client-configuration/main.go | 1 - .../main.go | 1 - .../client/auth/openstack/openstack_stub.go | 36 - .../pkg/client/auth/plugins_providers.go | 1 - staging/src/k8s.io/cloud-provider/plugins.go | 1 - .../src/k8s.io/legacy-cloud-providers/go.mod | 6 - .../src/k8s.io/legacy-cloud-providers/go.sum | 8 - .../openstack/MAINTAINERS.md | 4 - .../legacy-cloud-providers/openstack/OWNERS | 13 - .../openstack/metadata.go | 201 --- .../openstack/metadata_test.go | 118 -- .../openstack/openstack.go | 949 ---------- .../openstack/openstack_client.go | 101 -- .../openstack/openstack_instances.go | 244 --- .../openstack/openstack_loadbalancer.go | 1578 ----------------- .../openstack/openstack_metrics.go | 64 - .../openstack/openstack_routes.go | 347 ---- .../openstack/openstack_routes_test.go | 128 -- .../openstack/openstack_test.go | 733 -------- .../openstack/openstack_volumes.go | 769 -------- test/e2e/common/storage/volumes.go | 4 +- .../providers/openstack/openstack.go | 3 + test/e2e/framework/test_context.go | 2 +- test/e2e/framework/volume/fixtures.go | 4 +- test/e2e/storage/drivers/in_tree.go | 114 +- test/e2e/storage/volume_provisioning.go | 4 +- .../gophercloud/gophercloud/.gitignore | 3 - .../gophercloud/gophercloud/.travis.yml | 25 - .../gophercloud/gophercloud/.zuul.yaml | 114 -- .../gophercloud/gophercloud/CHANGELOG.md | 0 .../gophercloud/gophercloud/LICENSE | 191 -- .../gophercloud/gophercloud/README.md | 159 -- .../gophercloud/gophercloud/auth_options.go | 437 ----- .../gophercloud/gophercloud/auth_result.go | 52 - .../github.com/gophercloud/gophercloud/doc.go | 110 -- .../gophercloud/endpoint_search.go | 76 - .../gophercloud/gophercloud/errors.go | 471 ----- .../gophercloud/openstack/auth_env.go | 125 -- .../extensions/volumeactions/doc.go | 86 - .../extensions/volumeactions/requests.go | 269 --- .../extensions/volumeactions/results.go | 191 -- .../extensions/volumeactions/urls.go | 7 - .../openstack/blockstorage/v1/volumes/doc.go | 5 - .../blockstorage/v1/volumes/requests.go | 172 -- .../blockstorage/v1/volumes/results.go | 109 -- .../openstack/blockstorage/v1/volumes/urls.go | 23 - .../openstack/blockstorage/v1/volumes/util.go | 22 - .../openstack/blockstorage/v2/volumes/doc.go | 5 - .../blockstorage/v2/volumes/requests.go | 235 --- .../blockstorage/v2/volumes/results.go | 167 -- .../openstack/blockstorage/v2/volumes/urls.go | 23 - .../openstack/blockstorage/v2/volumes/util.go | 22 - .../openstack/blockstorage/v3/volumes/doc.go | 5 - .../blockstorage/v3/volumes/requests.go | 237 --- .../blockstorage/v3/volumes/results.go | 172 -- .../openstack/blockstorage/v3/volumes/urls.go | 23 - .../openstack/blockstorage/v3/volumes/util.go | 22 - .../gophercloud/openstack/client.go | 438 ----- .../openstack/common/extensions/doc.go | 52 - .../openstack/common/extensions/requests.go | 20 - .../openstack/common/extensions/results.go | 53 - .../openstack/common/extensions/urls.go | 13 - .../v2/extensions/attachinterfaces/doc.go | 52 - .../extensions/attachinterfaces/requests.go | 72 - .../v2/extensions/attachinterfaces/results.go | 80 - .../v2/extensions/attachinterfaces/urls.go | 18 - .../compute/v2/extensions/volumeattach/doc.go | 30 - .../v2/extensions/volumeattach/requests.go | 60 - .../v2/extensions/volumeattach/results.go | 77 - .../v2/extensions/volumeattach/urls.go | 25 - .../openstack/compute/v2/flavors/doc.go | 137 -- .../openstack/compute/v2/flavors/requests.go | 357 ---- .../openstack/compute/v2/flavors/results.go | 252 --- .../openstack/compute/v2/flavors/urls.go | 49 - .../openstack/compute/v2/images/doc.go | 32 - .../openstack/compute/v2/images/requests.go | 109 -- .../openstack/compute/v2/images/results.go | 95 - .../openstack/compute/v2/images/urls.go | 15 - .../openstack/compute/v2/servers/doc.go | 115 -- .../openstack/compute/v2/servers/errors.go | 71 - .../compute/v2/servers/microversions.go | 11 - .../openstack/compute/v2/servers/requests.go | 812 --------- .../openstack/compute/v2/servers/results.go | 414 ----- .../openstack/compute/v2/servers/urls.go | 51 - .../openstack/compute/v2/servers/util.go | 21 - .../gophercloud/gophercloud/openstack/doc.go | 14 - .../openstack/endpoint_location.go | 107 -- .../gophercloud/openstack/errors.go | 71 - .../openstack/identity/v2/tenants/doc.go | 65 - .../openstack/identity/v2/tenants/requests.go | 116 -- .../openstack/identity/v2/tenants/results.go | 91 - .../openstack/identity/v2/tenants/urls.go | 23 - .../openstack/identity/v2/tokens/doc.go | 46 - .../openstack/identity/v2/tokens/requests.go | 103 -- .../openstack/identity/v2/tokens/results.go | 174 -- .../openstack/identity/v2/tokens/urls.go | 13 - .../identity/v3/extensions/trusts/doc.go | 26 - .../identity/v3/extensions/trusts/requests.go | 39 - .../identity/v3/extensions/trusts/results.go | 27 - .../openstack/identity/v3/tokens/doc.go | 108 -- .../openstack/identity/v3/tokens/requests.go | 162 -- .../openstack/identity/v3/tokens/results.go | 178 -- .../openstack/identity/v3/tokens/urls.go | 7 - .../networking/v2/extensions/delegate.go | 41 - .../networking/v2/extensions/external/doc.go | 53 - .../v2/extensions/external/requests.go | 84 - .../v2/extensions/external/results.go | 8 - .../v2/extensions/layer3/floatingips/doc.go | 71 - .../extensions/layer3/floatingips/requests.go | 182 -- .../extensions/layer3/floatingips/results.go | 131 -- .../v2/extensions/layer3/floatingips/urls.go | 13 - .../v2/extensions/layer3/routers/doc.go | 108 -- .../v2/extensions/layer3/routers/requests.go | 233 --- .../v2/extensions/layer3/routers/results.go | 181 -- .../v2/extensions/layer3/routers/urls.go | 21 - .../v2/extensions/lbaas_v2/l7policies/doc.go | 123 -- .../lbaas_v2/l7policies/requests.go | 376 ---- .../extensions/lbaas_v2/l7policies/results.go | 245 --- .../v2/extensions/lbaas_v2/l7policies/urls.go | 25 - .../v2/extensions/lbaas_v2/listeners/doc.go | 63 - .../extensions/lbaas_v2/listeners/requests.go | 212 --- .../extensions/lbaas_v2/listeners/results.go | 141 -- .../v2/extensions/lbaas_v2/listeners/urls.go | 16 - .../extensions/lbaas_v2/loadbalancers/doc.go | 79 - .../lbaas_v2/loadbalancers/requests.go | 204 --- .../lbaas_v2/loadbalancers/results.go | 186 -- .../extensions/lbaas_v2/loadbalancers/urls.go | 26 - .../v2/extensions/lbaas_v2/monitors/doc.go | 69 - .../extensions/lbaas_v2/monitors/requests.go | 257 --- .../extensions/lbaas_v2/monitors/results.go | 153 -- .../v2/extensions/lbaas_v2/monitors/urls.go | 16 - .../v2/extensions/lbaas_v2/pools/doc.go | 126 -- .../v2/extensions/lbaas_v2/pools/requests.go | 356 ---- .../v2/extensions/lbaas_v2/pools/results.go | 291 --- .../v2/extensions/lbaas_v2/pools/urls.go | 25 - .../v2/extensions/security/groups/doc.go | 58 - .../v2/extensions/security/groups/requests.go | 166 -- .../v2/extensions/security/groups/results.go | 108 -- .../v2/extensions/security/groups/urls.go | 13 - .../v2/extensions/security/rules/doc.go | 50 - .../v2/extensions/security/rules/requests.go | 159 -- .../v2/extensions/security/rules/results.go | 127 -- .../v2/extensions/security/rules/urls.go | 13 - .../openstack/networking/v2/networks/doc.go | 66 - .../networking/v2/networks/requests.go | 180 -- .../networking/v2/networks/results.go | 124 -- .../openstack/networking/v2/networks/urls.go | 31 - .../openstack/networking/v2/ports/doc.go | 73 - .../openstack/networking/v2/ports/requests.go | 191 -- .../openstack/networking/v2/ports/results.go | 149 -- .../openstack/networking/v2/ports/urls.go | 31 - .../openstack/utils/base_endpoint.go | 28 - .../openstack/utils/choose_version.go | 111 -- .../gophercloud/pagination/http.go | 60 - .../gophercloud/pagination/linked.go | 92 - .../gophercloud/pagination/marker.go | 58 - .../gophercloud/pagination/pager.go | 251 --- .../gophercloud/gophercloud/pagination/pkg.go | 4 - .../gophercloud/pagination/single.go | 33 - .../gophercloud/gophercloud/params.go | 491 ----- .../gophercloud/provider_client.go | 501 ------ .../gophercloud/gophercloud/results.go | 448 ----- .../gophercloud/gophercloud/service_client.go | 154 -- .../gophercloud/gophercloud/util.go | 102 -- vendor/modules.txt | 36 - 190 files changed, 20 insertions(+), 25110 deletions(-) delete mode 100644 LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE delete mode 100644 cluster/addons/storage-class/openstack/default.yaml delete mode 100644 pkg/volume/cinder/OWNERS delete mode 100644 pkg/volume/cinder/attacher.go delete mode 100644 pkg/volume/cinder/attacher_test.go delete mode 100644 pkg/volume/cinder/cinder.go delete mode 100644 pkg/volume/cinder/cinder_block.go delete mode 100644 pkg/volume/cinder/cinder_block_test.go delete mode 100644 pkg/volume/cinder/cinder_test.go delete mode 100644 pkg/volume/cinder/cinder_util.go delete mode 100644 pkg/volume/cinder/doc.go delete mode 100644 staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go delete mode 100644 staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/.gitignore delete mode 100644 vendor/github.com/gophercloud/gophercloud/.travis.yml delete mode 100644 vendor/github.com/gophercloud/gophercloud/.zuul.yaml delete mode 100644 vendor/github.com/gophercloud/gophercloud/CHANGELOG.md delete mode 100644 vendor/github.com/gophercloud/gophercloud/LICENSE delete mode 100644 vendor/github.com/gophercloud/gophercloud/README.md delete mode 100644 vendor/github.com/gophercloud/gophercloud/auth_options.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/auth_result.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/endpoint_search.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/errors.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/client.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/errors.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/http.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/linked.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/marker.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/pager.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/pkg.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/pagination/single.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/params.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/provider_client.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/results.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/service_client.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/util.go diff --git a/LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE b/LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE deleted file mode 100644 index 19c07b7c731..00000000000 --- a/LICENSES/vendor/github.com/gophercloud/gophercloud/LICENSE +++ /dev/null @@ -1,195 +0,0 @@ -= vendor/github.com/gophercloud/gophercloud 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 diff --git a/cluster/addons/storage-class/openstack/default.yaml b/cluster/addons/storage-class/openstack/default.yaml deleted file mode 100644 index 435b31d222b..00000000000 --- a/cluster/addons/storage-class/openstack/default.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: standard - annotations: - storageclass.kubernetes.io/is-default-class: "true" - labels: - addonmanager.kubernetes.io/mode: EnsureExists -provisioner: kubernetes.io/cinder diff --git a/cmd/cloud-controller-manager/providers.go b/cmd/cloud-controller-manager/providers.go index aa063d92844..b75e7b07cbe 100644 --- a/cmd/cloud-controller-manager/providers.go +++ b/cmd/cloud-controller-manager/providers.go @@ -28,6 +28,5 @@ import ( _ "k8s.io/legacy-cloud-providers/aws" _ "k8s.io/legacy-cloud-providers/azure" _ "k8s.io/legacy-cloud-providers/gce" - _ "k8s.io/legacy-cloud-providers/openstack" _ "k8s.io/legacy-cloud-providers/vsphere" ) diff --git a/cmd/kube-controller-manager/app/plugins_providers.go b/cmd/kube-controller-manager/app/plugins_providers.go index 99b1586d203..8b7d66d2f86 100644 --- a/cmd/kube-controller-manager/app/plugins_providers.go +++ b/cmd/kube-controller-manager/app/plugins_providers.go @@ -28,7 +28,6 @@ import ( "k8s.io/kubernetes/pkg/volume/awsebs" "k8s.io/kubernetes/pkg/volume/azure_file" "k8s.io/kubernetes/pkg/volume/azuredd" - "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/gcepd" "k8s.io/kubernetes/pkg/volume/portworx" @@ -66,7 +65,6 @@ func appendAttachableLegacyProviderVolumes(allPlugins []volume.VolumePlugin, fea pluginMigrationStatus := make(map[string]pluginInfo) pluginMigrationStatus[plugins.AWSEBSInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAWS, pluginUnregisterFeature: features.InTreePluginAWSUnregister, pluginProbeFunction: awsebs.ProbeVolumePlugins} pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginUnregisterFeature: features.InTreePluginGCEUnregister, pluginProbeFunction: gcepd.ProbeVolumePlugins} - pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginUnregisterFeature: features.InTreePluginOpenStackUnregister, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins} pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} pluginMigrationStatus[plugins.PortworxVolumePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationPortworx, pluginUnregisterFeature: features.InTreePluginPortworxUnregister, pluginProbeFunction: portworx.ProbeVolumePlugins} diff --git a/cmd/kubelet/app/plugins_providers.go b/cmd/kubelet/app/plugins_providers.go index 9b4a6391f76..26e408e3dbe 100644 --- a/cmd/kubelet/app/plugins_providers.go +++ b/cmd/kubelet/app/plugins_providers.go @@ -33,7 +33,6 @@ import ( "k8s.io/kubernetes/pkg/volume/awsebs" "k8s.io/kubernetes/pkg/volume/azure_file" "k8s.io/kubernetes/pkg/volume/azuredd" - "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csimigration" "k8s.io/kubernetes/pkg/volume/gcepd" "k8s.io/kubernetes/pkg/volume/portworx" @@ -72,7 +71,6 @@ func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate f pluginMigrationStatus := make(map[string]pluginInfo) pluginMigrationStatus[plugins.AWSEBSInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAWS, pluginUnregisterFeature: features.InTreePluginAWSUnregister, pluginProbeFunction: awsebs.ProbeVolumePlugins} pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginUnregisterFeature: features.InTreePluginGCEUnregister, pluginProbeFunction: gcepd.ProbeVolumePlugins} - pluginMigrationStatus[plugins.CinderInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationOpenStack, pluginUnregisterFeature: features.InTreePluginOpenStackUnregister, pluginProbeFunction: cinder.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins} pluginMigrationStatus[plugins.AzureFileInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureFile, pluginUnregisterFeature: features.InTreePluginAzureFileUnregister, pluginProbeFunction: azure_file.ProbeVolumePlugins} pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins} diff --git a/go.mod b/go.mod index 72432a4ad65..cd36227f931 100644 --- a/go.mod +++ b/go.mod @@ -175,7 +175,6 @@ require ( github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect - github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -248,7 +247,6 @@ require ( ) replace ( - github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.1.0 k8s.io/api => ./staging/src/k8s.io/api k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery diff --git a/go.sum b/go.sum index 13e6a965584..cf031607eb7 100644 --- a/go.sum +++ b/go.sum @@ -409,8 +409,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -779,7 +777,6 @@ go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -915,7 +912,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 1c0cf514eb6..6ec7f1ded28 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -133,18 +133,6 @@ KUBE_CONTROLLERS="${KUBE_CONTROLLERS:-"*"}" # Audit policy AUDIT_POLICY_FILE=${AUDIT_POLICY_FILE:-""} -# sanity check for OpenStack provider -if [ "${CLOUD_PROVIDER}" == "openstack" ]; then - if [ "${CLOUD_CONFIG}" == "" ]; then - echo "Missing CLOUD_CONFIG env for OpenStack provider!" - exit 1 - fi - if [ ! -f "${CLOUD_CONFIG}" ]; then - echo "Cloud config ${CLOUD_CONFIG} doesn't exist" - exit 1 - fi -fi - # Stop right away if the build fails set -e diff --git a/pkg/cloudprovider/providers/providers.go b/pkg/cloudprovider/providers/providers.go index a78450a0e41..dda8b56585f 100644 --- a/pkg/cloudprovider/providers/providers.go +++ b/pkg/cloudprovider/providers/providers.go @@ -24,6 +24,5 @@ import ( _ "k8s.io/legacy-cloud-providers/aws" _ "k8s.io/legacy-cloud-providers/azure" _ "k8s.io/legacy-cloud-providers/gce" - _ "k8s.io/legacy-cloud-providers/openstack" _ "k8s.io/legacy-cloud-providers/vsphere" ) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 436ff9d2974..091f1c2384c 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -148,13 +148,6 @@ const ( // Enables the GCE PD in-tree driver to GCE CSI Driver migration feature. CSIMigrationGCE featuregate.Feature = "CSIMigrationGCE" - // owner: @adisky - // alpha: v1.14 - // beta: v1.18 - // - // Enables the OpenStack Cinder in-tree driver to OpenStack Cinder CSI Driver migration feature. - CSIMigrationOpenStack featuregate.Feature = "CSIMigrationOpenStack" - // owner: @trierra // alpha: v1.23 // @@ -934,8 +927,6 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CSIMigrationGCE: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.27 - CSIMigrationOpenStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26 - CSIMigrationPortworx: {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires Portworx CSI driver) CSIMigrationRBD: {Default: false, PreRelease: featuregate.Alpha}, // Off by default (requires RBD CSI driver) diff --git a/pkg/volume/cinder/OWNERS b/pkg/volume/cinder/OWNERS deleted file mode 100644 index 798d555dfae..00000000000 --- a/pkg/volume/cinder/OWNERS +++ /dev/null @@ -1,14 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - jsafrane - - anguslees - - dims -reviewers: - - anguslees - - saad-ali - - jsafrane - - jingxu97 - - msau42 -emeritus_approvers: - - FengyunPan2 diff --git a/pkg/volume/cinder/attacher.go b/pkg/volume/cinder/attacher.go deleted file mode 100644 index 94d1ede0403..00000000000 --- a/pkg/volume/cinder/attacher.go +++ /dev/null @@ -1,434 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "context" - "fmt" - "os" - "path" - "strings" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/klog/v2" - "k8s.io/mount-utils" - - "k8s.io/kubernetes/pkg/volume" - volumeutil "k8s.io/kubernetes/pkg/volume/util" -) - -type cinderDiskAttacher struct { - host volume.VolumeHost - cinderProvider BlockStorageProvider -} - -var _ volume.Attacher = &cinderDiskAttacher{} - -var _ volume.DeviceMounter = &cinderDiskAttacher{} - -var _ volume.AttachableVolumePlugin = &cinderPlugin{} - -var _ volume.DeviceMountableVolumePlugin = &cinderPlugin{} - -const ( - probeVolumeInitDelay = 1 * time.Second - probeVolumeFactor = 2.0 - operationFinishInitDelay = 1 * time.Second - operationFinishFactor = 1.1 - operationFinishSteps = 10 - diskAttachInitDelay = 1 * time.Second - diskAttachFactor = 1.2 - diskAttachSteps = 15 - diskDetachInitDelay = 1 * time.Second - diskDetachFactor = 1.2 - diskDetachSteps = 13 -) - -func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) { - cinder, err := plugin.getCloudProvider() - if err != nil { - return nil, err - } - return &cinderDiskAttacher{ - host: plugin.host, - cinderProvider: cinder, - }, nil -} - -func (plugin *cinderPlugin) NewDeviceMounter() (volume.DeviceMounter, error) { - return plugin.NewAttacher() -} - -func (plugin *cinderPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { - mounter := plugin.host.GetMounter(plugin.GetPluginName()) - return mounter.GetMountRefs(deviceMountPath) -} - -func (attacher *cinderDiskAttacher) waitOperationFinished(volumeID string) error { - backoff := wait.Backoff{ - Duration: operationFinishInitDelay, - Factor: operationFinishFactor, - Steps: operationFinishSteps, - } - - var volumeStatus string - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - var pending bool - var err error - pending, volumeStatus, err = attacher.cinderProvider.OperationPending(volumeID) - if err != nil { - return false, err - } - return !pending, nil - }) - - if err == wait.ErrWaitTimeout { - err = fmt.Errorf("volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus) - } - - return err -} - -func (attacher *cinderDiskAttacher) waitDiskAttached(instanceID, volumeID string) error { - backoff := wait.Backoff{ - Duration: diskAttachInitDelay, - Factor: diskAttachFactor, - Steps: diskAttachSteps, - } - - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID) - if err != nil { - return false, err - } - return attached, nil - }) - - if err == wait.ErrWaitTimeout { - err = fmt.Errorf("volume %q failed to be attached within the alloted time", volumeID) - } - - return err -} - -func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { - volumeID, _, _, err := getVolumeInfo(spec) - if err != nil { - return "", err - } - - instanceID, err := attacher.nodeInstanceID(nodeName) - if err != nil { - return "", err - } - - if err := attacher.waitOperationFinished(volumeID); err != nil { - return "", err - } - - attached, err := attacher.cinderProvider.DiskIsAttached(instanceID, volumeID) - if err != nil { - // Log error and continue with attach - klog.Warningf( - "Error checking if volume (%q) is already attached to current instance (%q). Will continue and try attach anyway. err=%v", - volumeID, instanceID, err) - } - - if err == nil && attached { - // Volume is already attached to instance. - klog.Infof("Attach operation is successful. volume %q is already attached to instance %q.", volumeID, instanceID) - } else { - _, err = attacher.cinderProvider.AttachDisk(instanceID, volumeID) - if err == nil { - if err = attacher.waitDiskAttached(instanceID, volumeID); err != nil { - klog.Errorf("Error waiting for volume %q to be attached from node %q: %v", volumeID, nodeName, err) - return "", err - } - klog.Infof("Attach operation successful: volume %q attached to instance %q.", volumeID, instanceID) - } else { - klog.Infof("Attach volume %q to instance %q failed with: %v", volumeID, instanceID, err) - return "", err - } - } - - devicePath, err := attacher.cinderProvider.GetAttachmentDiskPath(instanceID, volumeID) - if err != nil { - klog.Infof("Can not get device path of volume %q which be attached to instance %q, failed with: %v", volumeID, instanceID, err) - return "", err - } - - return devicePath, nil -} - -func (attacher *cinderDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { - volumesAttachedCheck := make(map[*volume.Spec]bool) - volumeSpecMap := make(map[string]*volume.Spec) - volumeIDList := []string{} - for _, spec := range specs { - volumeID, _, _, err := getVolumeInfo(spec) - if err != nil { - klog.Errorf("Error getting volume (%q) source : %v", spec.Name(), err) - continue - } - - volumeIDList = append(volumeIDList, volumeID) - volumesAttachedCheck[spec] = true - volumeSpecMap[volumeID] = spec - } - - attachedResult, err := attacher.cinderProvider.DisksAreAttachedByName(nodeName, volumeIDList) - if err != nil { - // Log error and continue with attach - klog.Errorf( - "Error checking if Volumes (%v) are already attached to current node (%q). Will continue and try attach anyway. err=%v", - volumeIDList, nodeName, err) - return volumesAttachedCheck, err - } - - for volumeID, attached := range attachedResult { - if !attached { - spec := volumeSpecMap[volumeID] - volumesAttachedCheck[spec] = false - klog.V(2).Infof("VolumesAreAttached: check volume %q (specName: %q) is no longer attached", volumeID, spec.Name()) - } - } - return volumesAttachedCheck, nil -} - -func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) { - // NOTE: devicePath is path as reported by Cinder, which may be incorrect and should not be used. See Issue #33128 - volumeID, _, _, err := getVolumeInfo(spec) - if err != nil { - return "", err - } - - if devicePath == "" { - return "", fmt.Errorf("WaitForAttach failed for Cinder disk %q: devicePath is empty", volumeID) - } - - ticker := time.NewTicker(probeVolumeInitDelay) - defer ticker.Stop() - timer := time.NewTimer(timeout) - defer timer.Stop() - - duration := probeVolumeInitDelay - for { - select { - case <-ticker.C: - klog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID) - probeAttachedVolume() - if !attacher.cinderProvider.ShouldTrustDevicePath() { - // Using the Cinder volume ID, find the real device path (See Issue #33128) - devicePath = attacher.cinderProvider.GetDevicePath(volumeID) - } - exists, err := mount.PathExists(devicePath) - if exists && err == nil { - klog.Infof("Successfully found attached Cinder disk %q at %v.", volumeID, devicePath) - return devicePath, nil - } - // Log an error, and continue checking periodically - klog.Errorf("Error: could not find attached Cinder disk %q (path: %q): %v", volumeID, devicePath, err) - // Using exponential backoff instead of linear - ticker.Stop() - duration = time.Duration(float64(duration) * probeVolumeFactor) - ticker = time.NewTicker(duration) - case <-timer.C: - return "", fmt.Errorf("could not find attached Cinder disk %q. Timeout waiting for mount paths to be created", volumeID) - } - } -} - -func (attacher *cinderDiskAttacher) GetDeviceMountPath( - spec *volume.Spec) (string, error) { - volumeID, _, _, err := getVolumeInfo(spec) - if err != nil { - return "", err - } - - return makeGlobalPDName(attacher.host, volumeID), nil -} - -// FIXME: this method can be further pruned. -func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error { - mounter := attacher.host.GetMounter(cinderVolumePluginName) - notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) - if err != nil { - if os.IsNotExist(err) { - if err := os.MkdirAll(deviceMountPath, 0750); err != nil { - return err - } - notMnt = true - } else { - return err - } - } - - _, volumeFSType, readOnly, err := getVolumeInfo(spec) - if err != nil { - return err - } - - options := []string{} - if readOnly { - options = append(options, "ro") - } - if notMnt { - diskMounter := volumeutil.NewSafeFormatAndMountFromHost(cinderVolumePluginName, attacher.host) - mountOptions := volumeutil.MountOptionFromSpec(spec, options...) - err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeFSType, mountOptions) - if err != nil { - os.Remove(deviceMountPath) - return err - } - } - return nil -} - -type cinderDiskDetacher struct { - mounter mount.Interface - cinderProvider BlockStorageProvider -} - -var _ volume.Detacher = &cinderDiskDetacher{} - -var _ volume.DeviceUnmounter = &cinderDiskDetacher{} - -func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) { - cinder, err := plugin.getCloudProvider() - if err != nil { - return nil, err - } - return &cinderDiskDetacher{ - mounter: plugin.host.GetMounter(plugin.GetPluginName()), - cinderProvider: cinder, - }, nil -} - -func (plugin *cinderPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { - return plugin.NewDetacher() -} - -func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error { - backoff := wait.Backoff{ - Duration: operationFinishInitDelay, - Factor: operationFinishFactor, - Steps: operationFinishSteps, - } - - var volumeStatus string - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - var pending bool - var err error - pending, volumeStatus, err = detacher.cinderProvider.OperationPending(volumeID) - if err != nil { - return false, err - } - return !pending, nil - }) - - if err == wait.ErrWaitTimeout { - err = fmt.Errorf("volume %q is %s, can't finish within the alloted time", volumeID, volumeStatus) - } - - return err -} - -func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string) error { - backoff := wait.Backoff{ - Duration: diskDetachInitDelay, - Factor: diskDetachFactor, - Steps: diskDetachSteps, - } - - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - attached, err := detacher.cinderProvider.DiskIsAttached(instanceID, volumeID) - if err != nil { - return false, err - } - return !attached, nil - }) - - if err == wait.ErrWaitTimeout { - err = fmt.Errorf("volume %q failed to detach within the alloted time", volumeID) - } - - return err -} - -func (detacher *cinderDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error { - volumeID := path.Base(volumeName) - if err := detacher.waitOperationFinished(volumeID); err != nil { - return err - } - attached, instanceID, err := detacher.cinderProvider.DiskIsAttachedByName(nodeName, volumeID) - if err != nil { - // Log error and continue with detach - klog.Errorf( - "Error checking if volume (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v", - volumeID, nodeName, err) - } - - if err == nil && !attached { - // Volume is already detached from node. - klog.Infof("detach operation was successful. volume %q is already detached from node %q.", volumeID, nodeName) - return nil - } - - if err = detacher.cinderProvider.DetachDisk(instanceID, volumeID); err != nil { - klog.Errorf("Error detaching volume %q from node %q: %v", volumeID, nodeName, err) - return err - } - if err = detacher.waitDiskDetached(instanceID, volumeID); err != nil { - klog.Errorf("Error waiting for volume %q to detach from node %q: %v", volumeID, nodeName, err) - return err - } - klog.Infof("detached volume %q from node %q", volumeID, nodeName) - return nil -} - -func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error { - return mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false) -} - -func (plugin *cinderPlugin) CanAttach(spec *volume.Spec) (bool, error) { - return true, nil -} - -func (plugin *cinderPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { - return true, nil -} - -func (attacher *cinderDiskAttacher) nodeInstanceID(nodeName types.NodeName) (string, error) { - instances, res := attacher.cinderProvider.Instances() - if !res { - return "", fmt.Errorf("failed to list openstack instances") - } - instanceID, err := instances.InstanceID(context.TODO(), nodeName) - if err != nil { - return "", err - } - if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { - instanceID = instanceID[(ind + 1):] - } - return instanceID, nil -} diff --git a/pkg/volume/cinder/attacher_test.go b/pkg/volume/cinder/attacher_test.go deleted file mode 100644 index f5b3ab0ec5e..00000000000 --- a/pkg/volume/cinder/attacher_test.go +++ /dev/null @@ -1,758 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "context" - "errors" - "os" - "path/filepath" - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - cloudprovider "k8s.io/cloud-provider" - "k8s.io/kubernetes/pkg/volume" - volumetest "k8s.io/kubernetes/pkg/volume/testing" - - "fmt" - "sort" - - "k8s.io/apimachinery/pkg/types" - "k8s.io/klog/v2" -) - -const ( - VolumeStatusPending = "pending" - VolumeStatusDone = "done" -) - -var attachStatus = "Attach" -var detachStatus = "Detach" - -func TestGetDeviceName_Volume(t *testing.T) { - plugin := newPlugin(t) - name := "my-cinder-volume" - spec := createVolSpec(name, false) - - deviceName, err := plugin.GetVolumeName(spec) - if err != nil { - t.Errorf("GetDeviceName error: %v", err) - } - if deviceName != name { - t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName) - } -} - -func TestGetDeviceName_PersistentVolume(t *testing.T) { - plugin := newPlugin(t) - name := "my-cinder-pv" - spec := createPVSpec(name, true) - - deviceName, err := plugin.GetVolumeName(spec) - if err != nil { - t.Errorf("GetDeviceName error: %v", err) - } - if deviceName != name { - t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName) - } -} - -func TestGetDeviceMountPath(t *testing.T) { - name := "cinder-volume-id" - spec := createVolSpec(name, false) - rootDir := "/var/lib/kubelet/" - host := volumetest.NewFakeVolumeHost(t, rootDir, nil, nil) - - attacher := &cinderDiskAttacher{ - host: host, - } - - //test the path - path, err := attacher.GetDeviceMountPath(spec) - if err != nil { - t.Errorf("Get device mount path error") - } - expectedPath := filepath.Join(rootDir, "plugins/kubernetes.io/cinder/mounts", name) - if path != expectedPath { - t.Errorf("Device mount path error: expected %s, got %s ", expectedPath, path) - } -} - -// One testcase for TestAttachDetach table test below -type testcase struct { - name string - // For fake GCE: - attach attachCall - detach detachCall - operationPending operationPendingCall - diskIsAttached diskIsAttachedCall - disksAreAttached disksAreAttachedCall - diskPath diskPathCall - t *testing.T - attachOrDetach *string - - instanceID string - // Actual test to run - test func(test *testcase) (string, error) - // Expected return of the test - expectedResult string - expectedError error -} - -func TestAttachDetach(t *testing.T) { - volumeID := "disk" - instanceID := "instance" - pending := VolumeStatusPending - done := VolumeStatusDone - nodeName := types.NodeName("nodeName") - readOnly := false - spec := createVolSpec(volumeID, readOnly) - attachError := errors.New("fake attach error") - detachError := errors.New("fake detach error") - diskCheckError := errors.New("fake DiskIsAttached error") - diskPathError := errors.New("fake GetAttachmentDiskPath error") - disksCheckError := errors.New("fake DisksAreAttached error") - operationFinishTimeout := errors.New("fake waitOperationFinished error") - tests := []testcase{ - // Successful Attach call - { - name: "Attach_Positive", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, nil}, - attach: attachCall{instanceID, volumeID, "", nil}, - diskPath: diskPathCall{instanceID, volumeID, "/dev/sda", nil}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - return attacher.Attach(spec, nodeName) - }, - expectedResult: "/dev/sda", - }, - - // Disk is already attached - { - name: "Attach_Positive_AlreadyAttached", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, true, nil}, - diskPath: diskPathCall{instanceID, volumeID, "/dev/sda", nil}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - return attacher.Attach(spec, nodeName) - }, - expectedResult: "/dev/sda", - }, - - // Disk is attaching - { - name: "Attach_is_attaching", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, true, pending, operationFinishTimeout}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - return attacher.Attach(spec, nodeName) - }, - expectedError: operationFinishTimeout, - }, - - // Attach call fails - { - name: "Attach_Negative", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, diskCheckError}, - attach: attachCall{instanceID, volumeID, "/dev/sda", attachError}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - return attacher.Attach(spec, nodeName) - }, - expectedError: attachError, - }, - - // GetAttachmentDiskPath call fails - { - name: "Attach_Negative_DiskPatchFails", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, nil}, - attach: attachCall{instanceID, volumeID, "", nil}, - diskPath: diskPathCall{instanceID, volumeID, "", diskPathError}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - return attacher.Attach(spec, nodeName) - }, - expectedError: diskPathError, - }, - - // Successful VolumesAreAttached call, attached - { - name: "VolumesAreAttached_Positive", - instanceID: instanceID, - disksAreAttached: disksAreAttachedCall{instanceID, nodeName, []string{volumeID}, map[string]bool{volumeID: true}, nil}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - attachments, err := attacher.VolumesAreAttached([]*volume.Spec{spec}, nodeName) - return serializeAttachments(attachments), err - }, - expectedResult: serializeAttachments(map[*volume.Spec]bool{spec: true}), - }, - - // Successful VolumesAreAttached call, not attached - { - name: "VolumesAreAttached_Negative", - instanceID: instanceID, - disksAreAttached: disksAreAttachedCall{instanceID, nodeName, []string{volumeID}, map[string]bool{volumeID: false}, nil}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - attachments, err := attacher.VolumesAreAttached([]*volume.Spec{spec}, nodeName) - return serializeAttachments(attachments), err - }, - expectedResult: serializeAttachments(map[*volume.Spec]bool{spec: false}), - }, - - // Treat as attached when DisksAreAttached call fails - { - name: "VolumesAreAttached_CinderFailed", - instanceID: instanceID, - disksAreAttached: disksAreAttachedCall{instanceID, nodeName, []string{volumeID}, nil, disksCheckError}, - test: func(testcase *testcase) (string, error) { - attacher := newAttacher(testcase) - attachments, err := attacher.VolumesAreAttached([]*volume.Spec{spec}, nodeName) - return serializeAttachments(attachments), err - }, - expectedResult: serializeAttachments(map[*volume.Spec]bool{spec: true}), - expectedError: disksCheckError, - }, - - // Detach succeeds - { - name: "Detach_Positive", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, true, nil}, - detach: detachCall{instanceID, volumeID, nil}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - return "", detacher.Detach(volumeID, nodeName) - }, - }, - - // Disk is already detached - { - name: "Detach_Positive_AlreadyDetached", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, nil}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - return "", detacher.Detach(volumeID, nodeName) - }, - }, - - // Detach succeeds when DiskIsAttached fails - { - name: "Detach_Positive_CheckFails", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, diskCheckError}, - detach: detachCall{instanceID, volumeID, nil}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - return "", detacher.Detach(volumeID, nodeName) - }, - }, - - // Detach fails - { - name: "Detach_Negative", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, false, done, nil}, - diskIsAttached: diskIsAttachedCall{instanceID, nodeName, volumeID, false, diskCheckError}, - detach: detachCall{instanceID, volumeID, detachError}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - return "", detacher.Detach(volumeID, nodeName) - }, - expectedError: detachError, - }, - - // // Disk is detaching - { - name: "Detach_Is_Detaching", - instanceID: instanceID, - operationPending: operationPendingCall{volumeID, true, pending, operationFinishTimeout}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - return "", detacher.Detach(volumeID, nodeName) - }, - expectedError: operationFinishTimeout, - }, - } - - for _, testcase := range tests { - testcase.t = t - attachOrDetach := "" - testcase.attachOrDetach = &attachOrDetach - result, err := testcase.test(&testcase) - if err != testcase.expectedError { - t.Errorf("%s failed: expected err=%q, got %q", testcase.name, testcase.expectedError, err) - } - if result != testcase.expectedResult { - t.Errorf("%s failed: expected result=%q, got %q", testcase.name, testcase.expectedResult, result) - } - } -} - -type volumeAttachmentFlag struct { - volumeID string - attached bool -} - -type volumeAttachmentFlags []volumeAttachmentFlag - -func (va volumeAttachmentFlags) Len() int { - return len(va) -} - -func (va volumeAttachmentFlags) Swap(i, j int) { - va[i], va[j] = va[j], va[i] -} - -func (va volumeAttachmentFlags) Less(i, j int) bool { - if va[i].volumeID < va[j].volumeID { - return true - } - if va[i].volumeID > va[j].volumeID { - return false - } - return va[j].attached -} - -func serializeAttachments(attachments map[*volume.Spec]bool) string { - var attachmentFlags volumeAttachmentFlags - for spec, attached := range attachments { - attachmentFlags = append(attachmentFlags, volumeAttachmentFlag{spec.Name(), attached}) - } - sort.Sort(attachmentFlags) - return fmt.Sprint(attachmentFlags) -} - -// newPlugin creates a new gcePersistentDiskPlugin with fake cloud, NewAttacher -// and NewDetacher won't work. -func newPlugin(t *testing.T) *cinderPlugin { - host := volumetest.NewFakeVolumeHost(t, os.TempDir(), nil, nil) - plugins := ProbeVolumePlugins() - plugin := plugins[0] - plugin.Init(host) - return plugin.(*cinderPlugin) -} - -func newAttacher(testcase *testcase) *cinderDiskAttacher { - return &cinderDiskAttacher{ - host: nil, - cinderProvider: testcase, - } -} - -func newDetacher(testcase *testcase) *cinderDiskDetacher { - return &cinderDiskDetacher{ - cinderProvider: testcase, - } -} - -func createVolSpec(name string, readOnly bool) *volume.Spec { - return &volume.Spec{ - Volume: &v1.Volume{ - Name: name, - VolumeSource: v1.VolumeSource{ - Cinder: &v1.CinderVolumeSource{ - VolumeID: name, - ReadOnly: readOnly, - }, - }, - }, - } -} - -func createPVSpec(name string, readOnly bool) *volume.Spec { - return &volume.Spec{ - PersistentVolume: &v1.PersistentVolume{ - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeSource: v1.PersistentVolumeSource{ - Cinder: &v1.CinderPersistentVolumeSource{ - VolumeID: name, - ReadOnly: readOnly, - }, - }, - }, - }, - } -} - -// Fake GCE implementation - -type attachCall struct { - instanceID string - volumeID string - retDeviceName string - ret error -} - -type detachCall struct { - instanceID string - devicePath string - ret error -} - -type operationPendingCall struct { - diskName string - pending bool - volumeStatus string - ret error -} - -type diskIsAttachedCall struct { - instanceID string - nodeName types.NodeName - volumeID string - isAttached bool - ret error -} - -type diskPathCall struct { - instanceID string - volumeID string - retPath string - ret error -} - -type disksAreAttachedCall struct { - instanceID string - nodeName types.NodeName - volumeIDs []string - areAttached map[string]bool - ret error -} - -func (testcase *testcase) AttachDisk(instanceID, volumeID string) (string, error) { - expected := &testcase.attach - - if expected.volumeID == "" && expected.instanceID == "" { - // testcase.attach looks uninitialized, test did not expect to call - // AttachDisk - testcase.t.Errorf("unexpected AttachDisk call") - return "", errors.New("unexpected AttachDisk call") - } - - if expected.volumeID != volumeID { - testcase.t.Errorf("unexpected AttachDisk call: expected volumeID %s, got %s", expected.volumeID, volumeID) - return "", errors.New("unexpected AttachDisk call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("unexpected AttachDisk call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return "", errors.New("unexpected AttachDisk call: wrong instanceID") - } - - klog.V(4).Infof("AttachDisk call: %s, %s, returning %q, %v", volumeID, instanceID, expected.retDeviceName, expected.ret) - - testcase.attachOrDetach = &attachStatus - return expected.retDeviceName, expected.ret -} - -func (testcase *testcase) DetachDisk(instanceID, volumeID string) error { - expected := &testcase.detach - - if expected.devicePath == "" && expected.instanceID == "" { - // testcase.detach looks uninitialized, test did not expect to call - // DetachDisk - testcase.t.Errorf("unexpected DetachDisk call") - return errors.New("unexpected DetachDisk call") - } - - if expected.devicePath != volumeID { - testcase.t.Errorf("unexpected DetachDisk call: expected volumeID %s, got %s", expected.devicePath, volumeID) - return errors.New("unexpected DetachDisk call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("unexpected DetachDisk call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return errors.New("unexpected DetachDisk call: wrong instanceID") - } - - klog.V(4).Infof("DetachDisk call: %s, %s, returning %v", volumeID, instanceID, expected.ret) - - testcase.attachOrDetach = &detachStatus - return expected.ret -} - -func (testcase *testcase) OperationPending(diskName string) (bool, string, error) { - expected := &testcase.operationPending - - if expected.volumeStatus == VolumeStatusPending { - klog.V(4).Infof("OperationPending call: %s, returning %v, %v, %v", diskName, expected.pending, expected.volumeStatus, expected.ret) - return true, expected.volumeStatus, expected.ret - } - - klog.V(4).Infof("OperationPending call: %s, returning %v, %v, %v", diskName, expected.pending, expected.volumeStatus, expected.ret) - - return false, expected.volumeStatus, expected.ret -} - -func (testcase *testcase) DiskIsAttached(instanceID, volumeID string) (bool, error) { - expected := &testcase.diskIsAttached - // If testcase call DetachDisk*, return false - if *testcase.attachOrDetach == detachStatus { - return false, nil - } - - // If testcase call AttachDisk*, return true - if *testcase.attachOrDetach == attachStatus { - return true, nil - } - - if expected.volumeID == "" && expected.instanceID == "" { - // testcase.diskIsAttached looks uninitialized, test did not expect to - // call DiskIsAttached - testcase.t.Errorf("unexpected DiskIsAttached call") - return false, errors.New("unexpected DiskIsAttached call") - } - - if expected.volumeID != volumeID { - testcase.t.Errorf("unexpected DiskIsAttached call: expected volumeID %s, got %s", expected.volumeID, volumeID) - return false, errors.New("unexpected DiskIsAttached call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("unexpected DiskIsAttached call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return false, errors.New("unexpected DiskIsAttached call: wrong instanceID") - } - - klog.V(4).Infof("DiskIsAttached call: %s, %s, returning %v, %v", volumeID, instanceID, expected.isAttached, expected.ret) - - return expected.isAttached, expected.ret -} - -func (testcase *testcase) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) { - expected := &testcase.diskPath - if expected.volumeID == "" && expected.instanceID == "" { - // testcase.diskPath looks uninitialized, test did not expect to - // call GetAttachmentDiskPath - testcase.t.Errorf("unexpected GetAttachmentDiskPath call") - return "", errors.New("unexpected GetAttachmentDiskPath call") - } - - if expected.volumeID != volumeID { - testcase.t.Errorf("unexpected GetAttachmentDiskPath call: expected volumeID %s, got %s", expected.volumeID, volumeID) - return "", errors.New("unexpected GetAttachmentDiskPath call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("unexpected GetAttachmentDiskPath call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return "", errors.New("unexpected GetAttachmentDiskPath call: wrong instanceID") - } - - klog.V(4).Infof("GetAttachmentDiskPath call: %s, %s, returning %v, %v", volumeID, instanceID, expected.retPath, expected.ret) - - return expected.retPath, expected.ret -} - -func (testcase *testcase) ShouldTrustDevicePath() bool { - return true -} - -func (testcase *testcase) DiskIsAttachedByName(nodeName types.NodeName, volumeID string) (bool, string, error) { - expected := &testcase.diskIsAttached - instanceID := expected.instanceID - // If testcase call DetachDisk*, return false - if *testcase.attachOrDetach == detachStatus { - return false, instanceID, nil - } - - // If testcase call AttachDisk*, return true - if *testcase.attachOrDetach == attachStatus { - return true, instanceID, nil - } - - if expected.nodeName != nodeName { - testcase.t.Errorf("unexpected DiskIsAttachedByName call: expected nodename %s, got %s", expected.nodeName, nodeName) - return false, instanceID, errors.New("unexpected DiskIsAttachedByName call: wrong nodename") - } - - if expected.volumeID == "" && expected.instanceID == "" { - // testcase.diskIsAttached looks uninitialized, test did not expect to - // call DiskIsAttached - testcase.t.Errorf("unexpected DiskIsAttachedByName call") - return false, instanceID, errors.New("unexpected DiskIsAttachedByName call") - } - - if expected.volumeID != volumeID { - testcase.t.Errorf("unexpected DiskIsAttachedByName call: expected volumeID %s, got %s", expected.volumeID, volumeID) - return false, instanceID, errors.New("unexpected DiskIsAttachedByName call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("unexpected DiskIsAttachedByName call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return false, instanceID, errors.New("unexpected DiskIsAttachedByName call: wrong instanceID") - } - - klog.V(4).Infof("DiskIsAttachedByName call: %s, %s, returning %v, %v, %v", volumeID, nodeName, expected.isAttached, expected.instanceID, expected.ret) - - return expected.isAttached, expected.instanceID, expected.ret -} - -func (testcase *testcase) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, string, bool, error) { - return "", "", "", false, errors.New("not implemented") -} - -func (testcase *testcase) GetDevicePath(volumeID string) string { - return "" -} - -func (testcase *testcase) InstanceID() (string, error) { - return testcase.instanceID, nil -} - -func (testcase *testcase) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { - return resource.Quantity{}, nil -} - -func (testcase *testcase) DeleteVolume(volumeID string) error { - return errors.New("not implemented") -} - -func (testcase *testcase) GetAutoLabelsForPD(name string) (map[string]string, error) { - return map[string]string{}, errors.New("not implemented") -} - -func (testcase *testcase) Instances() (cloudprovider.Instances, bool) { - return &instances{testcase.instanceID}, true -} - -func (testcase *testcase) InstancesV2() (cloudprovider.InstancesV2, bool) { - return nil, false -} - -func (testcase *testcase) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) { - expected := &testcase.disksAreAttached - - areAttached := make(map[string]bool) - - if len(expected.volumeIDs) == 0 && expected.instanceID == "" { - // testcase.volumeIDs looks uninitialized, test did not expect to call DisksAreAttached - testcase.t.Errorf("Unexpected DisksAreAttached call!") - return areAttached, errors.New("unexpected DisksAreAttached call") - } - - if !reflect.DeepEqual(expected.volumeIDs, volumeIDs) { - testcase.t.Errorf("Unexpected DisksAreAttached call: expected volumeIDs %v, got %v", expected.volumeIDs, volumeIDs) - return areAttached, errors.New("unexpected DisksAreAttached call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("Unexpected DisksAreAttached call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return areAttached, errors.New("unexpected DisksAreAttached call: wrong instanceID") - } - - klog.V(4).Infof("DisksAreAttached call: %v, %s, returning %v, %v", volumeIDs, instanceID, expected.areAttached, expected.ret) - - return expected.areAttached, expected.ret -} - -func (testcase *testcase) DisksAreAttachedByName(nodeName types.NodeName, volumeIDs []string) (map[string]bool, error) { - expected := &testcase.disksAreAttached - areAttached := make(map[string]bool) - - instanceID := expected.instanceID - if expected.nodeName != nodeName { - testcase.t.Errorf("Unexpected DisksAreAttachedByName call: expected nodeName %s, got %s", expected.nodeName, nodeName) - return areAttached, errors.New("unexpected DisksAreAttachedByName call: wrong nodename") - } - if len(expected.volumeIDs) == 0 && expected.instanceID == "" { - // testcase.volumeIDs looks uninitialized, test did not expect to call DisksAreAttached - testcase.t.Errorf("Unexpected DisksAreAttachedByName call!") - return areAttached, errors.New("unexpected DisksAreAttachedByName call") - } - - if !reflect.DeepEqual(expected.volumeIDs, volumeIDs) { - testcase.t.Errorf("Unexpected DisksAreAttachedByName call: expected volumeIDs %v, got %v", expected.volumeIDs, volumeIDs) - return areAttached, errors.New("unexpected DisksAreAttachedByName call: wrong volumeID") - } - - if expected.instanceID != instanceID { - testcase.t.Errorf("Unexpected DisksAreAttachedByName call: expected instanceID %s, got %s", expected.instanceID, instanceID) - return areAttached, errors.New("unexpected DisksAreAttachedByName call: wrong instanceID") - } - - klog.V(4).Infof("DisksAreAttachedByName call: %v, %s, returning %v, %v", volumeIDs, nodeName, expected.areAttached, expected.ret) - - return expected.areAttached, expected.ret -} - -// Implementation of fake cloudprovider.Instances -type instances struct { - instanceID string -} - -func (instances *instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) { - return []v1.NodeAddress{}, errors.New("not implemented") -} - -func (instances *instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) { - return []v1.NodeAddress{}, errors.New("not implemented") -} - -func (instances *instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) { - return instances.instanceID, nil -} - -func (instances *instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) { - return "", errors.New("not implemented") -} - -func (instances *instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) { - return "", errors.New("not implemented") -} - -func (instances *instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) { - return false, errors.New("unimplemented") -} - -func (instances *instances) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) { - return false, errors.New("unimplemented") -} - -func (instances *instances) InstanceMetadataByProviderID(ctx context.Context, providerID string) (*cloudprovider.InstanceMetadata, error) { - return nil, errors.New("unimplemented") -} - -func (instances *instances) List(filter string) ([]types.NodeName, error) { - return []types.NodeName{}, errors.New("not implemented") -} - -func (instances *instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error { - return cloudprovider.NotImplemented -} - -func (instances *instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) { - return "", errors.New("not implemented") -} diff --git a/pkg/volume/cinder/cinder.go b/pkg/volume/cinder/cinder.go deleted file mode 100644 index ef422e24c5c..00000000000 --- a/pkg/volume/cinder/cinder.go +++ /dev/null @@ -1,635 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2015 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "errors" - "fmt" - "os" - "path" - "path/filepath" - - "k8s.io/klog/v2" - "k8s.io/mount-utils" - "k8s.io/utils/keymutex" - utilstrings "k8s.io/utils/strings" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - cloudprovider "k8s.io/cloud-provider" - "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/pkg/volume/util" - "k8s.io/legacy-cloud-providers/openstack" -) - -const ( - // DefaultCloudConfigPath is the default path for cloud configuration - DefaultCloudConfigPath = "/etc/kubernetes/cloud-config" -) - -// ProbeVolumePlugins is the primary entrypoint for volume plugins. -func ProbeVolumePlugins() []volume.VolumePlugin { - return []volume.VolumePlugin{&cinderPlugin{}} -} - -// BlockStorageProvider is the interface for accessing cinder functionality. -type BlockStorageProvider interface { - AttachDisk(instanceID, volumeID string) (string, error) - DetachDisk(instanceID, volumeID string) error - DeleteVolume(volumeID string) error - CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, string, bool, error) - GetDevicePath(volumeID string) string - InstanceID() (string, error) - GetAttachmentDiskPath(instanceID, volumeID string) (string, error) - OperationPending(diskName string) (bool, string, error) - DiskIsAttached(instanceID, volumeID string) (bool, error) - DiskIsAttachedByName(nodeName types.NodeName, volumeID string) (bool, string, error) - DisksAreAttachedByName(nodeName types.NodeName, volumeIDs []string) (map[string]bool, error) - ShouldTrustDevicePath() bool - Instances() (cloudprovider.Instances, bool) - ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) -} - -type cinderPlugin struct { - host volume.VolumeHost - // Guarding SetUp and TearDown operations - volumeLocks keymutex.KeyMutex -} - -var _ volume.VolumePlugin = &cinderPlugin{} -var _ volume.PersistentVolumePlugin = &cinderPlugin{} -var _ volume.DeletableVolumePlugin = &cinderPlugin{} -var _ volume.ProvisionableVolumePlugin = &cinderPlugin{} - -const ( - cinderVolumePluginName = "kubernetes.io/cinder" -) - -func getPath(uid types.UID, volName string, host volume.VolumeHost) string { - return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(cinderVolumePluginName), volName) -} - -func (plugin *cinderPlugin) Init(host volume.VolumeHost) error { - plugin.host = host - plugin.volumeLocks = keymutex.NewHashed(0) - return nil -} - -func (plugin *cinderPlugin) GetPluginName() string { - return cinderVolumePluginName -} - -func (plugin *cinderPlugin) GetVolumeName(spec *volume.Spec) (string, error) { - volumeID, _, _, err := getVolumeInfo(spec) - if err != nil { - return "", err - } - - return volumeID, nil -} - -func (plugin *cinderPlugin) CanSupport(spec *volume.Spec) bool { - return (spec.Volume != nil && spec.Volume.Cinder != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Cinder != nil) -} - -func (plugin *cinderPlugin) RequiresRemount(spec *volume.Spec) bool { - return false -} - -func (plugin *cinderPlugin) SupportsMountOption() bool { - return true - -} -func (plugin *cinderPlugin) SupportsBulkVolumeVerification() bool { - return false -} - -func (plugin *cinderPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { - return false, nil -} - -var _ volume.VolumePluginWithAttachLimits = &cinderPlugin{} - -func (plugin *cinderPlugin) GetVolumeLimits() (map[string]int64, error) { - volumeLimits := map[string]int64{ - util.CinderVolumeLimitKey: util.DefaultMaxCinderVolumes, - } - cloud := plugin.host.GetCloudProvider() - - // if we can't fetch cloudprovider we return an error - // hoping external CCM or admin can set it. Returning - // default values from here will mean, no one can - // override them. - if cloud == nil { - return nil, fmt.Errorf("no cloudprovider present") - } - - if cloud.ProviderName() != openstack.ProviderName { - return nil, fmt.Errorf("expected Openstack cloud, found %s", cloud.ProviderName()) - } - - openstackCloud, ok := cloud.(*openstack.OpenStack) - if ok && openstackCloud.NodeVolumeAttachLimit() > 0 { - volumeLimits[util.CinderVolumeLimitKey] = int64(openstackCloud.NodeVolumeAttachLimit()) - } - - return volumeLimits, nil -} - -func (plugin *cinderPlugin) VolumeLimitKey(spec *volume.Spec) string { - return util.CinderVolumeLimitKey -} - -func (plugin *cinderPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { - return []v1.PersistentVolumeAccessMode{ - v1.ReadWriteOnce, - } -} - -func (plugin *cinderPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { - return plugin.newMounterInternal(spec, pod.UID, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) -} - -func (plugin *cinderPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Mounter, error) { - pdName, fsType, readOnly, err := getVolumeInfo(spec) - if err != nil { - return nil, err - } - - return &cinderVolumeMounter{ - cinderVolume: &cinderVolume{ - podUID: podUID, - volName: spec.Name(), - pdName: pdName, - mounter: mounter, - manager: manager, - plugin: plugin, - MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)), - }, - fsType: fsType, - readOnly: readOnly, - blockDeviceMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), - mountOptions: util.MountOptionFromSpec(spec), - }, nil -} - -func (plugin *cinderPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { - return plugin.newUnmounterInternal(volName, podUID, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) -} - -func (plugin *cinderPlugin) newUnmounterInternal(volName string, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Unmounter, error) { - return &cinderVolumeUnmounter{ - &cinderVolume{ - podUID: podUID, - volName: volName, - manager: manager, - mounter: mounter, - plugin: plugin, - MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), - }}, nil -} - -func (plugin *cinderPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { - return plugin.newDeleterInternal(spec, &DiskUtil{}) -} - -func (plugin *cinderPlugin) newDeleterInternal(spec *volume.Spec, manager cdManager) (volume.Deleter, error) { - if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Cinder == nil { - return nil, fmt.Errorf("spec.PersistentVolumeSource.Cinder is nil") - } - return &cinderVolumeDeleter{ - &cinderVolume{ - volName: spec.Name(), - pdName: spec.PersistentVolume.Spec.Cinder.VolumeID, - manager: manager, - plugin: plugin, - }}, nil -} - -func (plugin *cinderPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) { - return plugin.newProvisionerInternal(options, &DiskUtil{}) -} - -func (plugin *cinderPlugin) newProvisionerInternal(options volume.VolumeOptions, manager cdManager) (volume.Provisioner, error) { - return &cinderVolumeProvisioner{ - cinderVolume: &cinderVolume{ - manager: manager, - plugin: plugin, - }, - options: options, - }, nil -} - -func (plugin *cinderPlugin) getCloudProvider() (BlockStorageProvider, error) { - cloud := plugin.host.GetCloudProvider() - if cloud == nil { - if _, err := os.Stat(DefaultCloudConfigPath); err == nil { - var config *os.File - config, err = os.Open(DefaultCloudConfigPath) - if err != nil { - return nil, fmt.Errorf("unable to load OpenStack configuration from default path : %v", err) - } - defer config.Close() - cloud, err = cloudprovider.GetCloudProvider(openstack.ProviderName, config) - if err != nil { - return nil, fmt.Errorf("unable to create OpenStack cloud provider from default path : %v", err) - } - } else { - return nil, fmt.Errorf("OpenStack cloud provider was not initialized properly : %v", err) - } - } - - switch cloud := cloud.(type) { - case *openstack.OpenStack: - return cloud, nil - default: - return nil, errors.New("invalid cloud provider: expected OpenStack") - } -} - -func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { - mounter := plugin.host.GetMounter(plugin.GetPluginName()) - kvh, ok := plugin.host.(volume.KubeletVolumeHost) - if !ok { - return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") - } - hu := kvh.GetHostUtil() - pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) - if err != nil { - return nil, err - } - klog.V(4).Infof("Found volume %s mounted to %s", sourceName, mountPath) - cinderVolume := &v1.Volume{ - Name: volumeName, - VolumeSource: v1.VolumeSource{ - Cinder: &v1.CinderVolumeSource{ - VolumeID: sourceName, - }, - }, - } - return volume.NewSpecFromVolume(cinderVolume), nil -} - -var _ volume.ExpandableVolumePlugin = &cinderPlugin{} - -func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { - volumeID, _, _, err := getVolumeInfo(spec) - if err != nil { - return oldSize, err - } - cloud, err := plugin.getCloudProvider() - if err != nil { - return oldSize, err - } - - expandedSize, err := cloud.ExpandVolume(volumeID, oldSize, newSize) - if err != nil { - return oldSize, err - } - - klog.V(2).Infof("volume %s expanded to new size %d successfully", volumeID, int(newSize.Value())) - return expandedSize, nil -} - -func (plugin *cinderPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) { - fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec) - if err != nil { - return false, fmt.Errorf("error checking VolumeMode: %v", err) - } - // if volume is not a fs file system, there is nothing for us to do here. - if !fsVolume { - return true, nil - } - - _, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath) - if err != nil { - return false, err - } - return true, nil -} - -var _ volume.NodeExpandableVolumePlugin = &cinderPlugin{} - -func (plugin *cinderPlugin) RequiresFSResize() bool { - return true -} - -// Abstract interface to PD operations. -type cdManager interface { - // Attaches the disk to the kubelet's host machine. - AttachDisk(mounter *cinderVolumeMounter, globalPDPath string) error - // Detaches the disk from the kubelet's host machine. - DetachDisk(unmounter *cinderVolumeUnmounter) error - // Creates a volume - CreateVolume(provisioner *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, labels map[string]string, fstype string, err error) - // Deletes a volume - DeleteVolume(deleter *cinderVolumeDeleter) error -} - -var _ volume.Mounter = &cinderVolumeMounter{} - -type cinderVolumeMounter struct { - *cinderVolume - fsType string - readOnly bool - blockDeviceMounter *mount.SafeFormatAndMount - mountOptions []string -} - -// cinderPersistentDisk volumes are disk resources provided by C3 -// that are attached to the kubelet's host machine and exposed to the pod. -type cinderVolume struct { - volName string - podUID types.UID - // Unique identifier of the volume, used to find the disk resource in the provider. - pdName string - // Filesystem type, optional. - fsType string - // Utility interface that provides API calls to the provider to attach/detach disks. - manager cdManager - // Mounter interface that provides system calls to mount the global path to the pod local path. - mounter mount.Interface - plugin *cinderPlugin - volume.MetricsProvider -} - -func (b *cinderVolumeMounter) GetAttributes() volume.Attributes { - return volume.Attributes{ - ReadOnly: b.readOnly, - Managed: !b.readOnly, - SELinuxRelabel: true, - } -} - -func (b *cinderVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { - return b.SetUpAt(b.GetPath(), mounterArgs) -} - -// SetUp bind mounts to the volume path. -func (b *cinderVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { - klog.V(5).Infof("Cinder SetUp %s to %s", b.pdName, dir) - - b.plugin.volumeLocks.LockKey(b.pdName) - defer b.plugin.volumeLocks.UnlockKey(b.pdName) - - notmnt, err := b.mounter.IsLikelyNotMountPoint(dir) - if err != nil && !os.IsNotExist(err) { - klog.Errorf("Cannot validate mount point: %s %v", dir, err) - return err - } - if !notmnt { - klog.V(4).Infof("Something is already mounted to target %s", dir) - return nil - } - globalPDPath := makeGlobalPDName(b.plugin.host, b.pdName) - - options := []string{"bind"} - if b.readOnly { - options = append(options, "ro") - } - - if err := os.MkdirAll(dir, 0750); err != nil { - klog.V(4).Infof("Could not create directory %s: %v", dir, err) - return err - } - - mountOptions := util.JoinMountOptions(options, b.mountOptions) - // Perform a bind mount to the full path to allow duplicate mounts of the same PD. - klog.V(4).Infof("Attempting to mount cinder volume %s to %s with options %v", b.pdName, dir, mountOptions) - err = b.mounter.MountSensitiveWithoutSystemd(globalPDPath, dir, "", options, nil) - if err != nil { - klog.V(4).Infof("Mount failed: %v", err) - notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) - if mntErr != nil { - klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) - return err - } - if !notmnt { - if mntErr = b.mounter.Unmount(dir); mntErr != nil { - klog.Errorf("Failed to unmount: %v", mntErr) - return err - } - notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) - if mntErr != nil { - klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) - return err - } - if !notmnt { - // This is very odd, we don't expect it. We'll try again next sync loop. - klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", b.GetPath()) - return err - } - } - os.Remove(dir) - klog.Errorf("Failed to mount %s: %v", dir, err) - return err - } - - if !b.readOnly { - volume.SetVolumeOwnership(b, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil)) - } - klog.V(3).Infof("Cinder volume %s mounted to %s", b.pdName, dir) - - return nil -} - -func makeGlobalPDName(host volume.VolumeHost, devName string) string { - return filepath.Join(host.GetPluginDir(cinderVolumePluginName), util.MountsInGlobalPDPath, devName) -} - -func (cd *cinderVolume) GetPath() string { - return getPath(cd.podUID, cd.volName, cd.plugin.host) -} - -type cinderVolumeUnmounter struct { - *cinderVolume -} - -var _ volume.Unmounter = &cinderVolumeUnmounter{} - -func (c *cinderVolumeUnmounter) TearDown() error { - return c.TearDownAt(c.GetPath()) -} - -// Unmounts the bind mount, and detaches the disk only if the PD -// resource was the last reference to that disk on the kubelet. -func (c *cinderVolumeUnmounter) TearDownAt(dir string) error { - if pathExists, pathErr := mount.PathExists(dir); pathErr != nil { - return fmt.Errorf("error checking if path exists: %v", pathErr) - } else if !pathExists { - klog.Warningf("Warning: Unmount skipped because path does not exist: %w", dir) - return nil - } - - klog.V(5).Infof("Cinder TearDown of %s", dir) - notmnt, err := c.mounter.IsLikelyNotMountPoint(dir) - if err != nil { - klog.V(4).Infof("IsLikelyNotMountPoint check failed: %v", err) - return err - } - if notmnt { - klog.V(4).Infof("Nothing is mounted to %s, ignoring", dir) - return os.Remove(dir) - } - - // Find Cinder volumeID to lock the right volume - // TODO: refactor VolumePlugin.NewUnmounter to get full volume.Spec just like - // NewMounter. We could then find volumeID there without probing MountRefs. - refs, err := c.mounter.GetMountRefs(dir) - if err != nil { - klog.V(4).Infof("GetMountRefs failed: %v", err) - return err - } - if len(refs) == 0 { - klog.V(4).Infof("Directory %s is not mounted", dir) - return fmt.Errorf("directory %s is not mounted", dir) - } - c.pdName = path.Base(refs[0]) - klog.V(4).Infof("Found volume %s mounted to %s", c.pdName, dir) - - // lock the volume (and thus wait for any concurrent SetUpAt to finish) - c.plugin.volumeLocks.LockKey(c.pdName) - defer c.plugin.volumeLocks.UnlockKey(c.pdName) - - // Reload list of references, there might be SetUpAt finished in the meantime - _, err = c.mounter.GetMountRefs(dir) - if err != nil { - klog.V(4).Infof("GetMountRefs failed: %v", err) - return err - } - if err := c.mounter.Unmount(dir); err != nil { - klog.V(4).Infof("Unmount failed: %v", err) - return err - } - klog.V(3).Infof("Successfully unmounted: %s\n", dir) - - notmnt, mntErr := c.mounter.IsLikelyNotMountPoint(dir) - if mntErr != nil { - klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) - return err - } - if notmnt { - if err := os.Remove(dir); err != nil { - klog.V(4).Infof("Failed to remove directory after unmount: %v", err) - return err - } - } - return nil -} - -type cinderVolumeDeleter struct { - *cinderVolume -} - -var _ volume.Deleter = &cinderVolumeDeleter{} - -func (r *cinderVolumeDeleter) GetPath() string { - return getPath(r.podUID, r.volName, r.plugin.host) -} - -func (r *cinderVolumeDeleter) Delete() error { - return r.manager.DeleteVolume(r) -} - -type cinderVolumeProvisioner struct { - *cinderVolume - options volume.VolumeOptions -} - -var _ volume.Provisioner = &cinderVolumeProvisioner{} - -func (c *cinderVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { - if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) { - return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) - } - - volumeID, sizeGB, labels, fstype, err := c.manager.CreateVolume(c, selectedNode, allowedTopologies) - if err != nil { - return nil, err - } - - if fstype == "" { - fstype = "ext4" - } - - volumeMode := c.options.PVC.Spec.VolumeMode - if volumeMode != nil && *volumeMode == v1.PersistentVolumeBlock { - // Block volumes should not have any FSType - fstype = "" - } - - pv := &v1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: c.options.PVName, - Labels: labels, - Annotations: map[string]string{ - util.VolumeDynamicallyCreatedByKey: "cinder-dynamic-provisioner", - }, - }, - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy, - AccessModes: c.options.PVC.Spec.AccessModes, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)), - }, - VolumeMode: volumeMode, - PersistentVolumeSource: v1.PersistentVolumeSource{ - Cinder: &v1.CinderPersistentVolumeSource{ - VolumeID: volumeID, - FSType: fstype, - ReadOnly: false, - }, - }, - MountOptions: c.options.MountOptions, - }, - } - if len(c.options.PVC.Spec.AccessModes) == 0 { - pv.Spec.AccessModes = c.plugin.GetAccessModes() - } - - requirements := make([]v1.NodeSelectorRequirement, 0) - for k, v := range labels { - if v != "" { - requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: []string{v}}) - } - } - if len(requirements) > 0 { - pv.Spec.NodeAffinity = new(v1.VolumeNodeAffinity) - pv.Spec.NodeAffinity.Required = new(v1.NodeSelector) - pv.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1) - pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = requirements - } - - return pv, nil -} - -func getVolumeInfo(spec *volume.Spec) (string, string, bool, error) { - if spec.Volume != nil && spec.Volume.Cinder != nil { - return spec.Volume.Cinder.VolumeID, spec.Volume.Cinder.FSType, spec.Volume.Cinder.ReadOnly, nil - } else if spec.PersistentVolume != nil && - spec.PersistentVolume.Spec.Cinder != nil { - return spec.PersistentVolume.Spec.Cinder.VolumeID, spec.PersistentVolume.Spec.Cinder.FSType, spec.ReadOnly, nil - } - - return "", "", false, fmt.Errorf("Spec does not reference a Cinder volume type") -} diff --git a/pkg/volume/cinder/cinder_block.go b/pkg/volume/cinder/cinder_block.go deleted file mode 100644 index 618115770e3..00000000000 --- a/pkg/volume/cinder/cinder_block.go +++ /dev/null @@ -1,179 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "fmt" - "path/filepath" - - "k8s.io/klog/v2" - "k8s.io/mount-utils" - utilstrings "k8s.io/utils/strings" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" -) - -var _ volume.VolumePlugin = &cinderPlugin{} -var _ volume.PersistentVolumePlugin = &cinderPlugin{} -var _ volume.BlockVolumePlugin = &cinderPlugin{} -var _ volume.DeletableVolumePlugin = &cinderPlugin{} -var _ volume.ProvisionableVolumePlugin = &cinderPlugin{} -var _ volume.ExpandableVolumePlugin = &cinderPlugin{} - -func (plugin *cinderPlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) { - pluginDir := plugin.host.GetVolumeDevicePluginDir(cinderVolumePluginName) - blkutil := volumepathhandler.NewBlockVolumePathHandler() - globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID) - if err != nil { - return nil, err - } - klog.V(5).Infof("globalMapPathUUID: %v, err: %v", globalMapPathUUID, err) - - globalMapPath := filepath.Dir(globalMapPathUUID) - if len(globalMapPath) <= 1 { - return nil, fmt.Errorf("failed to get volume plugin information from globalMapPathUUID: %v", globalMapPathUUID) - } - - return getVolumeSpecFromGlobalMapPath(volumeName, globalMapPath) -} - -func getVolumeSpecFromGlobalMapPath(volumeName, globalMapPath string) (*volume.Spec, error) { - // Get volume spec information from globalMapPath - // globalMapPath example: - // plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumeID} - // plugins/kubernetes.io/cinder/volumeDevices/vol-XXXXXX - vID := filepath.Base(globalMapPath) - if len(vID) <= 1 { - return nil, fmt.Errorf("failed to get volumeID from global path=%s", globalMapPath) - } - block := v1.PersistentVolumeBlock - cinderVolume := &v1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: volumeName, - }, - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeSource: v1.PersistentVolumeSource{ - Cinder: &v1.CinderPersistentVolumeSource{ - VolumeID: vID, - }, - }, - VolumeMode: &block, - }, - } - return volume.NewSpecFromPersistentVolume(cinderVolume, true), nil -} - -// NewBlockVolumeMapper creates a new volume.BlockVolumeMapper from an API specification. -func (plugin *cinderPlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) { - // If this is called via GenerateUnmapDeviceFunc(), pod is nil. - // Pass empty string as dummy uid since uid isn't used in the case. - var uid types.UID - if pod != nil { - uid = pod.UID - } - - return plugin.newBlockVolumeMapperInternal(spec, uid, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) -} - -func (plugin *cinderPlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.BlockVolumeMapper, error) { - pdName, fsType, readOnly, err := getVolumeInfo(spec) - if err != nil { - return nil, err - } - - mapper := &cinderVolumeMapper{ - cinderVolume: &cinderVolume{ - podUID: podUID, - volName: spec.Name(), - pdName: pdName, - fsType: fsType, - manager: manager, - mounter: mounter, - plugin: plugin, - }, - readOnly: readOnly, - } - - blockPath, err := mapper.GetGlobalMapPath(spec) - if err != nil { - return nil, fmt.Errorf("failed to get device path: %v", err) - } - mapper.MetricsProvider = volume.NewMetricsBlock(filepath.Join(blockPath, string(podUID))) - - return mapper, nil -} - -func (plugin *cinderPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) { - return plugin.newUnmapperInternal(volName, podUID, &DiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) -} - -func (plugin *cinderPlugin) newUnmapperInternal(volName string, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.BlockVolumeUnmapper, error) { - return &cinderPluginUnmapper{ - cinderVolume: &cinderVolume{ - podUID: podUID, - volName: volName, - manager: manager, - mounter: mounter, - plugin: plugin, - }}, nil -} - -type cinderPluginUnmapper struct { - *cinderVolume - volume.MetricsNil -} - -var _ volume.BlockVolumeUnmapper = &cinderPluginUnmapper{} - -type cinderVolumeMapper struct { - *cinderVolume - readOnly bool -} - -var _ volume.BlockVolumeMapper = &cinderVolumeMapper{} - -// GetGlobalMapPath returns global map path and error -// path: plugins/kubernetes.io/{PluginName}/volumeDevices/volumeID -// -// plugins/kubernetes.io/cinder/volumeDevices/vol-XXXXXX -func (cd *cinderVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) { - pdName, _, _, err := getVolumeInfo(spec) - if err != nil { - return "", err - } - return filepath.Join(cd.plugin.host.GetVolumeDevicePluginDir(cinderVolumePluginName), pdName), nil -} - -// GetPodDeviceMapPath returns pod device map path and volume name -// path: pods/{podUid}/volumeDevices/kubernetes.io~cinder -func (cd *cinderVolume) GetPodDeviceMapPath() (string, string) { - name := cinderVolumePluginName - return cd.plugin.host.GetPodVolumeDeviceDir(cd.podUID, utilstrings.EscapeQualifiedName(name)), cd.volName -} - -// SupportsMetrics returns true for cinderVolumeMapper as it initializes the -// MetricsProvider. -func (cvm *cinderVolumeMapper) SupportsMetrics() bool { - return true -} diff --git a/pkg/volume/cinder/cinder_block_test.go b/pkg/volume/cinder/cinder_block_test.go deleted file mode 100644 index 0b0aa20321d..00000000000 --- a/pkg/volume/cinder/cinder_block_test.go +++ /dev/null @@ -1,151 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "os" - "path/filepath" - "testing" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/volume" - volumetest "k8s.io/kubernetes/pkg/volume/testing" -) - -const ( - testVolName = "vol-1234" - testPVName = "pv1" - testGlobalPath = "plugins/kubernetes.io/cinder/volumeDevices/vol-1234" - testPodPath = "pods/poduid/volumeDevices/kubernetes.io~cinder" -) - -func TestGetVolumeSpecFromGlobalMapPath(t *testing.T) { - // make our test path for fake GlobalMapPath - // /tmp symbolized our pluginDir - // /tmp/testGlobalPathXXXXX/plugins/kubernetes.io/cinder/volumeDevices/pdVol1 - tmpVDir, err := utiltesting.MkTmpdir("cinderBlockTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - //deferred clean up - defer os.RemoveAll(tmpVDir) - - expectedGlobalPath := filepath.Join(tmpVDir, testGlobalPath) - - //Bad Path - badspec, err := getVolumeSpecFromGlobalMapPath("", "") - if badspec != nil || err == nil { - t.Errorf("Expected not to get spec from GlobalMapPath but did") - } - - // Good Path - spec, err := getVolumeSpecFromGlobalMapPath("myVolume", expectedGlobalPath) - if spec == nil || err != nil { - t.Fatalf("Failed to get spec from GlobalMapPath: %v", err) - } - if spec.PersistentVolume.Name != "myVolume" { - t.Errorf("Invalid PV name from GlobalMapPath spec: %s", spec.PersistentVolume.Name) - } - if spec.PersistentVolume.Spec.Cinder.VolumeID != testVolName { - t.Errorf("Invalid volumeID from GlobalMapPath spec: %s", spec.PersistentVolume.Spec.Cinder.VolumeID) - } - block := v1.PersistentVolumeBlock - specMode := spec.PersistentVolume.Spec.VolumeMode - if specMode == nil { - t.Fatalf("Failed to get volumeMode from PersistentVolumeBlock") - } - if *specMode != block { - t.Errorf("Invalid volumeMode from GlobalMapPath spec: %v expected: %v", *specMode, block) - } -} - -func getTestVolume(readOnly bool, isBlock bool) *volume.Spec { - pv := &v1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: testPVName, - }, - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeSource: v1.PersistentVolumeSource{ - Cinder: &v1.CinderPersistentVolumeSource{ - VolumeID: testVolName, - }, - }, - }, - } - - if isBlock { - blockMode := v1.PersistentVolumeBlock - pv.Spec.VolumeMode = &blockMode - } - return volume.NewSpecFromPersistentVolume(pv, readOnly) -} - -func TestGetPodAndPluginMapPaths(t *testing.T) { - tmpVDir, err := utiltesting.MkTmpdir("cinderBlockTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - //deferred clean up - defer os.RemoveAll(tmpVDir) - - expectedGlobalPath := filepath.Join(tmpVDir, testGlobalPath) - expectedPodPath := filepath.Join(tmpVDir, testPodPath) - - spec := getTestVolume(false, true /*isBlock*/) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpVDir, nil, nil)) - plug, err := plugMgr.FindMapperPluginByName(cinderVolumePluginName) - if err != nil { - os.RemoveAll(tmpVDir) - t.Fatalf("Can't find the plugin by name: %q", cinderVolumePluginName) - } - if plug.GetPluginName() != cinderVolumePluginName { - t.Fatalf("Wrong name: %s", plug.GetPluginName()) - } - pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} - mapper, err := plug.NewBlockVolumeMapper(spec, pod, volume.VolumeOptions{}) - if err != nil { - t.Fatalf("Failed to make a new Mounter: %v", err) - } - if mapper == nil { - t.Fatalf("Got a nil Mounter") - } - - //GetGlobalMapPath - gMapPath, err := mapper.GetGlobalMapPath(spec) - if err != nil || len(gMapPath) == 0 { - t.Fatalf("Invalid GlobalMapPath from spec: %s", spec.PersistentVolume.Spec.Cinder.VolumeID) - } - if gMapPath != expectedGlobalPath { - t.Errorf("Failed to get GlobalMapPath: %s %s", gMapPath, expectedGlobalPath) - } - - //GetPodDeviceMapPath - gDevicePath, gVolName := mapper.GetPodDeviceMapPath() - if gDevicePath != expectedPodPath { - t.Errorf("Got unexpected pod path: %s, expected %s", gDevicePath, expectedPodPath) - } - if gVolName != testPVName { - t.Errorf("Got unexpected volNamne: %s, expected %s", gVolName, testPVName) - } -} diff --git a/pkg/volume/cinder/cinder_test.go b/pkg/volume/cinder/cinder_test.go deleted file mode 100644 index 61c9bc27ac9..00000000000 --- a/pkg/volume/cinder/cinder_test.go +++ /dev/null @@ -1,365 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2015 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "fmt" - "os" - "path/filepath" - "testing" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - utiltesting "k8s.io/client-go/util/testing" - "k8s.io/mount-utils" - - "k8s.io/kubernetes/pkg/volume" - volumetest "k8s.io/kubernetes/pkg/volume/testing" - "k8s.io/kubernetes/pkg/volume/util" - "k8s.io/legacy-cloud-providers/openstack" -) - -func TestCanSupport(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("cinderTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil)) - - plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") - if err != nil { - t.Fatal("Can't find the plugin by name") - } - if plug.GetPluginName() != "kubernetes.io/cinder" { - t.Errorf("Wrong name: %s", plug.GetPluginName()) - } - if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{Cinder: &v1.CinderVolumeSource{}}}}) { - t.Errorf("Expected true") - } - - if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{Cinder: &v1.CinderPersistentVolumeSource{}}}}}) { - t.Errorf("Expected true") - } -} - -type fakePDManager struct { - // How long should AttachDisk/DetachDisk take - we need slower AttachDisk in a test. - attachDetachDuration time.Duration -} - -func getFakeDeviceName(host volume.VolumeHost, pdName string) string { - return filepath.Join(host.GetPluginDir(cinderVolumePluginName), "device", pdName) -} - -// Real Cinder AttachDisk attaches a cinder volume. If it is not yet mounted, -// it mounts it to globalPDPath. -// We create a dummy directory (="device") and bind-mount it to globalPDPath -func (fake *fakePDManager) AttachDisk(b *cinderVolumeMounter, globalPDPath string) error { - globalPath := makeGlobalPDName(b.plugin.host, b.pdName) - fakeDeviceName := getFakeDeviceName(b.plugin.host, b.pdName) - err := os.MkdirAll(fakeDeviceName, 0750) - if err != nil { - return err - } - // Attaching a Cinder volume can be slow... - time.Sleep(fake.attachDetachDuration) - - // The volume is "attached", bind-mount it if it's not mounted yet. - notmnt, err := b.mounter.IsLikelyNotMountPoint(globalPath) - if err != nil { - if os.IsNotExist(err) { - if err := os.MkdirAll(globalPath, 0750); err != nil { - return err - } - notmnt = true - } else { - return err - } - } - if notmnt { - err = b.mounter.MountSensitiveWithoutSystemd(fakeDeviceName, globalPath, "", []string{"bind"}, nil) - if err != nil { - return err - } - } - return nil -} - -func (fake *fakePDManager) DetachDisk(c *cinderVolumeUnmounter) error { - globalPath := makeGlobalPDName(c.plugin.host, c.pdName) - fakeDeviceName := getFakeDeviceName(c.plugin.host, c.pdName) - // unmount the bind-mount - should be fast - err := c.mounter.Unmount(globalPath) - if err != nil { - return err - } - - // "Detach" the fake "device" - err = os.RemoveAll(fakeDeviceName) - if err != nil { - return err - } - return nil -} - -func (fake *fakePDManager) CreateVolume(c *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, labels map[string]string, fstype string, err error) { - labels = make(map[string]string) - labels[v1.LabelTopologyZone] = "nova" - return "test-volume-name", 1, labels, "", nil -} - -func (fake *fakePDManager) DeleteVolume(cd *cinderVolumeDeleter) error { - if cd.pdName != "test-volume-name" { - return fmt.Errorf("Deleter got unexpected volume name: %s", cd.pdName) - } - return nil -} - -func TestPlugin(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("cinderTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil)) - - plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") - if err != nil { - t.Errorf("Can't find the plugin by name") - } - spec := &v1.Volume{ - Name: "vol1", - VolumeSource: v1.VolumeSource{ - Cinder: &v1.CinderVolumeSource{ - VolumeID: "pd", - FSType: "ext4", - }, - }, - } - mounter, err := plug.(*cinderPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{0}, mount.NewFakeMounter(nil)) - if err != nil { - t.Errorf("Failed to make a new Mounter: %v", err) - } - if mounter == nil { - t.Errorf("Got a nil Mounter") - } - volPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~cinder/vol1") - path := mounter.GetPath() - if path != volPath { - t.Errorf("Got unexpected path: %s", path) - } - - if err := mounter.SetUp(volume.MounterArgs{}); err != nil { - t.Errorf("Expected success, got: %v", err) - } - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - t.Errorf("SetUp() failed, volume path not created: %s", path) - } else { - t.Errorf("SetUp() failed: %v", err) - } - } - - unmounter, err := plug.(*cinderPlugin).newUnmounterInternal("vol1", types.UID("poduid"), &fakePDManager{0}, mount.NewFakeMounter(nil)) - if err != nil { - t.Errorf("Failed to make a new Unmounter: %v", err) - } - if unmounter == nil { - t.Errorf("Got a nil Unmounter") - } - - if err := unmounter.TearDown(); err != nil { - t.Errorf("Expected success, got: %v", err) - } - if _, err := os.Stat(path); err == nil { - t.Errorf("TearDown() failed, volume path still exists: %s", path) - } else if !os.IsNotExist(err) { - t.Errorf("TearDown() failed: %v", err) - } - - // Test Provisioner - options := volume.VolumeOptions{ - PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}), - PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete, - } - provisioner, err := plug.(*cinderPlugin).newProvisionerInternal(options, &fakePDManager{0}) - if err != nil { - t.Errorf("ProvisionerInternal() failed: %v", err) - } - persistentSpec, err := provisioner.Provision(nil, nil) - if err != nil { - t.Errorf("Provision() failed: %v", err) - } - - if persistentSpec.Spec.PersistentVolumeSource.Cinder.VolumeID != "test-volume-name" { - t.Errorf("Provision() returned unexpected volume ID: %s", persistentSpec.Spec.PersistentVolumeSource.Cinder.VolumeID) - } - cap := persistentSpec.Spec.Capacity[v1.ResourceStorage] - size := cap.Value() - if size != 1024*1024*1024 { - t.Errorf("Provision() returned unexpected volume size: %v", size) - } - - // check nodeaffinity members - if persistentSpec.Spec.NodeAffinity == nil { - t.Errorf("Provision() returned unexpected nil NodeAffinity") - } - - if persistentSpec.Spec.NodeAffinity.Required == nil { - t.Errorf("Provision() returned unexpected nil NodeAffinity.Required") - } - - n := len(persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms) - if n != 1 { - t.Errorf("Provision() returned unexpected number of NodeSelectorTerms %d. Expected %d", n, 1) - } - - n = len(persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions) - if n != 1 { - t.Errorf("Provision() returned unexpected number of MatchExpressions %d. Expected %d", n, 1) - } - - req := persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions[0] - - if req.Key != v1.LabelTopologyZone { - t.Errorf("Provision() returned unexpected requirement key in NodeAffinity %v", req.Key) - } - - if req.Operator != v1.NodeSelectorOpIn { - t.Errorf("Provision() returned unexpected requirement operator in NodeAffinity %v", req.Operator) - } - - if len(req.Values) != 1 || req.Values[0] != "nova" { - t.Errorf("Provision() returned unexpected requirement value in NodeAffinity %v", req.Values) - } - - // Test Deleter - volSpec := &volume.Spec{ - PersistentVolume: persistentSpec, - } - deleter, err := plug.(*cinderPlugin).newDeleterInternal(volSpec, &fakePDManager{0}) - if err != nil { - t.Errorf("DeleterInternal() failed: %v", err) - } - err = deleter.Delete() - if err != nil { - t.Errorf("Deleter() failed: %v", err) - } -} - -func TestGetVolumeLimit(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("cinderTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - - cloud, err := getOpenstackCloudProvider() - if err != nil { - t.Fatalf("can not instantiate openstack cloudprovider : %v", err) - } - - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - volumeHost := volumetest.NewFakeKubeletVolumeHostWithCloudProvider(t, tmpDir, nil, nil, cloud) - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumeHost) - - plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") - if err != nil { - t.Fatalf("Can't find the plugin by name") - } - attachablePlugin, ok := plug.(volume.VolumePluginWithAttachLimits) - if !ok { - t.Fatalf("plugin %s is not of attachable type", plug.GetPluginName()) - } - - limits, err := attachablePlugin.GetVolumeLimits() - if err != nil { - t.Errorf("error fetching limits : %v", err) - } - if len(limits) == 0 { - t.Fatalf("expecting limit from openstack got none") - } - limit, _ := limits[util.CinderVolumeLimitKey] - if limit != 10 { - t.Fatalf("expected volume limit to be 10 got %d", limit) - } -} - -func getOpenstackCloudProvider() (*openstack.OpenStack, error) { - cfg := getOpenstackConfig() - return openstack.NewFakeOpenStackCloud(cfg) -} - -func getOpenstackConfig() openstack.Config { - cfg := openstack.Config{ - Global: struct { - AuthURL string `gcfg:"auth-url"` - Username string - UserID string `gcfg:"user-id"` - Password string `datapolicy:"password"` - TenantID string `gcfg:"tenant-id"` - TenantName string `gcfg:"tenant-name"` - TrustID string `gcfg:"trust-id"` - DomainID string `gcfg:"domain-id"` - DomainName string `gcfg:"domain-name"` - Region string - CAFile string `gcfg:"ca-file"` - SecretName string `gcfg:"secret-name"` - SecretNamespace string `gcfg:"secret-namespace"` - KubeconfigPath string `gcfg:"kubeconfig-path"` - }{ - Username: "user", - Password: "pass", - TenantID: "foobar", - DomainID: "2a73b8f597c04551a0fdc8e95544be8a", - DomainName: "local", - AuthURL: "http://auth.url", - UserID: "user", - }, - BlockStorage: openstack.BlockStorageOpts{ - NodeVolumeAttachLimit: 10, - }, - } - return cfg -} - -func TestUnsupportedVolumeHost(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("cinderTest") - if err != nil { - t.Fatalf("can't make a temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) - - plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder") - if err != nil { - t.Fatal("Can't find the plugin by name") - } - - _, err = plug.ConstructVolumeSpec("", "") - if err == nil { - t.Errorf("Expected failure constructing volume spec with unsupported VolumeHost") - } -} diff --git a/pkg/volume/cinder/cinder_util.go b/pkg/volume/cinder/cinder_util.go deleted file mode 100644 index 3d0dc45796b..00000000000 --- a/pkg/volume/cinder/cinder_util.go +++ /dev/null @@ -1,278 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2015 The Kubernetes Authors. - -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. -*/ - -package cinder - -import ( - "context" - "errors" - "fmt" - "io/ioutil" - "os" - "strings" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog/v2" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - volumehelpers "k8s.io/cloud-provider/volume/helpers" - "k8s.io/kubernetes/pkg/volume" - volutil "k8s.io/kubernetes/pkg/volume/util" - "k8s.io/utils/exec" -) - -// DiskUtil has utility/helper methods -type DiskUtil struct{} - -// AttachDisk attaches a disk specified by a volume.CinderPersistenDisk to the current kubelet. -// Mounts the disk to its global path. -func (util *DiskUtil) AttachDisk(b *cinderVolumeMounter, globalPDPath string) error { - options := []string{} - if b.readOnly { - options = append(options, "ro") - } - cloud, err := b.plugin.getCloudProvider() - if err != nil { - return err - } - instanceid, err := cloud.InstanceID() - if err != nil { - return err - } - diskid, err := cloud.AttachDisk(instanceid, b.pdName) - if err != nil { - return err - } - - var devicePath string - numTries := 0 - for { - devicePath = cloud.GetDevicePath(diskid) - probeAttachedVolume() - - _, err := os.Stat(devicePath) - if err == nil { - break - } - if err != nil && !os.IsNotExist(err) { - return err - } - numTries++ - if numTries == 10 { - return errors.New("could not attach disk: Timeout after 60s") - } - time.Sleep(time.Second * 6) - } - notmnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath) - if err != nil { - if os.IsNotExist(err) { - if err := os.MkdirAll(globalPDPath, 0750); err != nil { - return err - } - notmnt = true - } else { - return err - } - } - if notmnt { - err = b.blockDeviceMounter.FormatAndMount(devicePath, globalPDPath, b.fsType, options) - if err != nil { - os.Remove(globalPDPath) - return err - } - klog.V(2).Infof("Safe mount successful: %q\n", devicePath) - } - return nil -} - -// DetachDisk unmounts the device and detaches the disk from the kubelet's host machine. -func (util *DiskUtil) DetachDisk(cd *cinderVolumeUnmounter) error { - globalPDPath := makeGlobalPDName(cd.plugin.host, cd.pdName) - if err := cd.mounter.Unmount(globalPDPath); err != nil { - return err - } - if err := os.Remove(globalPDPath); err != nil { - return err - } - klog.V(2).Infof("Successfully unmounted main device: %s\n", globalPDPath) - - cloud, err := cd.plugin.getCloudProvider() - if err != nil { - return err - } - instanceid, err := cloud.InstanceID() - if err != nil { - return err - } - if err = cloud.DetachDisk(instanceid, cd.pdName); err != nil { - return err - } - klog.V(2).Infof("Successfully detached cinder volume %s", cd.pdName) - return nil -} - -// DeleteVolume uses the cloud entrypoint to delete specified volume -func (util *DiskUtil) DeleteVolume(cd *cinderVolumeDeleter) error { - cloud, err := cd.plugin.getCloudProvider() - if err != nil { - return err - } - - if err = cloud.DeleteVolume(cd.pdName); err != nil { - // OpenStack cloud provider returns volume.tryAgainError when necessary, - // no handling needed here. - klog.V(2).Infof("Error deleting cinder volume %s: %v", cd.pdName, err) - return err - } - klog.V(2).Infof("Successfully deleted cinder volume %s", cd.pdName) - return nil -} - -func getZonesFromNodes(kubeClient clientset.Interface) (sets.String, error) { - // TODO: caching, currently it is overkill because it calls this function - // only when it creates dynamic PV - zones := make(sets.String) - nodes, err := kubeClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) - if err != nil { - klog.V(2).Infof("Error listing nodes") - return zones, err - } - for _, node := range nodes.Items { - if zone, ok := node.Labels[v1.LabelTopologyZone]; ok { - zones.Insert(zone) - } - } - klog.V(4).Infof("zones found: %v", zones) - return zones, nil -} - -// CreateVolume uses the cloud provider entrypoint for creating a volume -func (util *DiskUtil) CreateVolume(c *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, volumeLabels map[string]string, fstype string, err error) { - cloud, err := c.plugin.getCloudProvider() - if err != nil { - return "", 0, nil, "", err - } - - capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] - // Cinder works with gigabytes, convert to GiB with rounding up - volSizeGiB, err := volumehelpers.RoundUpToGiBInt(capacity) - if err != nil { - return "", 0, nil, "", err - } - - name := volutil.GenerateVolumeName(c.options.ClusterName, c.options.PVName, 255) // Cinder volume name can have up to 255 characters - vtype := "" - availability := "" - // Apply ProvisionerParameters (case-insensitive). We leave validation of - // the values to the cloud provider. - for k, v := range c.options.Parameters { - switch strings.ToLower(k) { - case "type": - vtype = v - case "availability": - availability = v - case volume.VolumeParameterFSType: - fstype = v - default: - return "", 0, nil, "", fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName()) - } - } - // TODO: implement PVC.Selector parsing - if c.options.PVC.Spec.Selector != nil { - return "", 0, nil, "", fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on Cinder") - } - - if availability == "" { - // No zone specified, choose one randomly in the same region - zones, err := getZonesFromNodes(c.plugin.host.GetKubeClient()) - if err != nil { - klog.V(2).Infof("error getting zone information: %v", err) - return "", 0, nil, "", err - } - // if we did not get any zones, lets leave it blank and gophercloud will - // use zone "nova" as default - if len(zones) > 0 { - availability, err = volumehelpers.SelectZoneForVolume(false, false, "", nil, zones, node, allowedTopologies, c.options.PVC.Name) - if err != nil { - klog.V(2).Infof("error selecting zone for volume: %v", err) - return "", 0, nil, "", err - } - } - } - - volumeID, volumeAZ, volumeRegion, IgnoreVolumeAZ, err := cloud.CreateVolume(name, volSizeGiB, vtype, availability, c.options.CloudTags) - if err != nil { - klog.V(2).Infof("Error creating cinder volume: %v", err) - return "", 0, nil, "", err - } - klog.V(2).Infof("Successfully created cinder volume %s", volumeID) - - // these are needed that pod is spawning to same AZ - volumeLabels = make(map[string]string) - if IgnoreVolumeAZ == false { - if volumeAZ != "" { - volumeLabels[v1.LabelTopologyZone] = volumeAZ - } - if volumeRegion != "" { - volumeLabels[v1.LabelTopologyRegion] = volumeRegion - } - } - return volumeID, volSizeGiB, volumeLabels, fstype, nil -} - -func probeAttachedVolume() error { - // rescan scsi bus - scsiHostRescan() - - executor := exec.New() - - // udevadm settle waits for udevd to process the device creation - // events for all hardware devices, thus ensuring that any device - // nodes have been created successfully before proceeding. - argsSettle := []string{"settle"} - cmdSettle := executor.Command("udevadm", argsSettle...) - _, errSettle := cmdSettle.CombinedOutput() - if errSettle != nil { - klog.Errorf("error running udevadm settle %v\n", errSettle) - } - - args := []string{"trigger"} - cmd := executor.Command("udevadm", args...) - _, err := cmd.CombinedOutput() - if err != nil { - klog.Errorf("error running udevadm trigger %v\n", err) - return err - } - klog.V(4).Infof("Successfully probed all attachments") - return nil -} - -func scsiHostRescan() { - scsiPath := "/sys/class/scsi_host/" - if dirs, err := ioutil.ReadDir(scsiPath); err == nil { - for _, f := range dirs { - name := scsiPath + f.Name() + "/scan" - data := []byte("- - -") - ioutil.WriteFile(name, data, 0666) - } - } -} diff --git a/pkg/volume/cinder/doc.go b/pkg/volume/cinder/doc.go deleted file mode 100644 index 08e6fa8ab1b..00000000000 --- a/pkg/volume/cinder/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2015 The Kubernetes Authors. - -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. -*/ - -// Package cinder contains the internal representation of cinder volumes. -package cinder // import "k8s.io/kubernetes/pkg/volume/cinder" diff --git a/plugin/pkg/admission/storage/persistentvolume/label/admission.go b/plugin/pkg/admission/storage/persistentvolume/label/admission.go index a7ae3ba5dfd..3f5c7782600 100644 --- a/plugin/pkg/admission/storage/persistentvolume/label/admission.go +++ b/plugin/pkg/admission/storage/persistentvolume/label/admission.go @@ -55,13 +55,12 @@ var _ = admission.Interface(&persistentVolumeLabel{}) type persistentVolumeLabel struct { *admission.Handler - mutex sync.Mutex - cloudConfig []byte - awsPVLabeler cloudprovider.PVLabeler - gcePVLabeler cloudprovider.PVLabeler - azurePVLabeler cloudprovider.PVLabeler - openStackPVLabeler cloudprovider.PVLabeler - vspherePVLabeler cloudprovider.PVLabeler + mutex sync.Mutex + cloudConfig []byte + awsPVLabeler cloudprovider.PVLabeler + gcePVLabeler cloudprovider.PVLabeler + azurePVLabeler cloudprovider.PVLabeler + vspherePVLabeler cloudprovider.PVLabeler } var _ admission.MutationInterface = &persistentVolumeLabel{} @@ -73,7 +72,7 @@ var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{} // As a side effect, the cloud provider may block invalid or non-existent volumes. func newPersistentVolumeLabel() *persistentVolumeLabel { // DEPRECATED: in a future release, we will use mutating admission webhooks to apply PV labels. - // Once the mutating admission webhook is used for AWS, Azure, GCE, and OpenStack, + // Once the mutating admission webhook is used for AWS, Azure, and GCE, // this admission controller will be removed. klog.Warning("PersistentVolumeLabel admission controller is deprecated. " + "Please remove this controller from your configuration files and scripts.") @@ -219,12 +218,6 @@ func (l *persistentVolumeLabel) findVolumeLabels(volume *api.PersistentVolume) ( return nil, fmt.Errorf("error querying AzureDisk volume %s: %v", volume.Spec.AzureDisk.DiskName, err) } return labels, nil - case volume.Spec.Cinder != nil: - labels, err := l.findCinderDiskLabels(volume) - if err != nil { - return nil, fmt.Errorf("error querying Cinder volume %s: %v", volume.Spec.Cinder.VolumeID, err) - } - return labels, nil case volume.Spec.VsphereVolume != nil: labels, err := l.findVsphereVolumeLabels(volume) if err != nil { @@ -381,56 +374,6 @@ func (l *persistentVolumeLabel) findAzureDiskLabels(volume *api.PersistentVolume return pvlabler.GetLabelsForVolume(context.TODO(), pv) } -func (l *persistentVolumeLabel) getOpenStackPVLabeler() (cloudprovider.PVLabeler, error) { - l.mutex.Lock() - defer l.mutex.Unlock() - - if l.openStackPVLabeler == nil { - var cloudConfigReader io.Reader - if len(l.cloudConfig) > 0 { - cloudConfigReader = bytes.NewReader(l.cloudConfig) - } - - cloudProvider, err := cloudprovider.GetCloudProvider("openstack", cloudConfigReader) - if err != nil || cloudProvider == nil { - return nil, err - } - - openStackPVLabeler, ok := cloudProvider.(cloudprovider.PVLabeler) - if !ok { - return nil, errors.New("OpenStack cloud provider does not implement PV labeling") - } - - l.openStackPVLabeler = openStackPVLabeler - } - - return l.openStackPVLabeler, nil - -} - -func (l *persistentVolumeLabel) findCinderDiskLabels(volume *api.PersistentVolume) (map[string]string, error) { - // Ignore any volumes that are being provisioned - if volume.Spec.Cinder.VolumeID == cloudvolume.ProvisionedVolumeName { - return nil, nil - } - - pvlabler, err := l.getOpenStackPVLabeler() - if err != nil { - return nil, err - } - if pvlabler == nil { - return nil, fmt.Errorf("unable to build OpenStack cloud provider for Cinder disk") - } - - pv := &v1.PersistentVolume{} - err = k8s_api_v1.Convert_core_PersistentVolume_To_v1_PersistentVolume(volume, pv, nil) - if err != nil { - return nil, fmt.Errorf("failed to convert PersistentVolume to core/v1: %q", err) - } - return pvlabler.GetLabelsForVolume(context.TODO(), pv) - -} - func (l *persistentVolumeLabel) findVsphereVolumeLabels(volume *api.PersistentVolume) (map[string]string, error) { pvlabler, err := l.getVspherePVLabeler() if err != nil { diff --git a/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go b/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go index 84694431994..36c46d5cbb8 100644 --- a/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go +++ b/plugin/pkg/admission/storage/persistentvolume/label/admission_test.go @@ -560,72 +560,6 @@ func Test_PVLAdmission(t *testing.T) { }, err: nil, }, - { - name: "Cinder Disk PV labeled correctly", - handler: newPersistentVolumeLabel(), - pvlabeler: mockVolumeLabels(map[string]string{ - "a": "1", - "b": "2", - v1.LabelFailureDomainBetaZone: "1__2__3", - }), - preAdmissionPV: &api.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "azurepd", - Namespace: "myns", - }, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Cinder: &api.CinderPersistentVolumeSource{ - VolumeID: "123", - }, - }, - }, - }, - postAdmissionPV: &api.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "azurepd", - Namespace: "myns", - Labels: map[string]string{ - "a": "1", - "b": "2", - v1.LabelFailureDomainBetaZone: "1__2__3", - }, - }, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Cinder: &api.CinderPersistentVolumeSource{ - VolumeID: "123", - }, - }, - NodeAffinity: &api.VolumeNodeAffinity{ - Required: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "a", - Operator: api.NodeSelectorOpIn, - Values: []string{"1"}, - }, - { - Key: "b", - Operator: api.NodeSelectorOpIn, - Values: []string{"2"}, - }, - { - Key: v1.LabelFailureDomainBetaZone, - Operator: api.NodeSelectorOpIn, - Values: []string{"1", "2", "3"}, - }, - }, - }, - }, - }, - }, - }, - }, - err: nil, - }, { name: "AWS EBS PV overrides user applied labels", handler: newPersistentVolumeLabel(), @@ -978,12 +912,11 @@ func Test_PVLAdmission(t *testing.T) { // setPVLabler applies the given mock pvlabeler to implement PV labeling for all cloud providers. // Given we mock out the values of the labels anyways, assigning the same mock labeler for every // provider does not reduce test coverage but it does simplify/clean up the tests here because -// the provider is then decided based on the type of PV (EBS, Cinder, GCEPD, Azure Disk, etc) +// the provider is then decided based on the type of PV (EBS, GCEPD, Azure Disk, etc) func setPVLabeler(handler *persistentVolumeLabel, pvlabeler cloudprovider.PVLabeler) { handler.awsPVLabeler = pvlabeler handler.gcePVLabeler = pvlabeler handler.azurePVLabeler = pvlabeler - handler.openStackPVLabeler = pvlabeler handler.vspherePVLabeler = pvlabeler } diff --git a/staging/publishing/rules.yaml b/staging/publishing/rules.yaml index 7e528783ed8..ceaa9706ebf 100644 --- a/staging/publishing/rules.yaml +++ b/staging/publishing/rules.yaml @@ -1655,8 +1655,6 @@ rules: branch: master - repository: controller-manager branch: master - - repository: mount-utils - branch: master - repository: component-helpers branch: master - repository: kms diff --git a/staging/src/k8s.io/client-go/examples/README.md b/staging/src/k8s.io/client-go/examples/README.md index 984aba00b5f..0ec0e13d6aa 100644 --- a/staging/src/k8s.io/client-go/examples/README.md +++ b/staging/src/k8s.io/client-go/examples/README.md @@ -19,7 +19,6 @@ Or you can load specific auth plugins: import _ "k8s.io/client-go/plugin/pkg/client/auth/azure" import _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" import _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" -import _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ``` ### Configuration diff --git a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go index 224dbc12519..dae3bc95f18 100644 --- a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go +++ b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go @@ -40,7 +40,6 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go b/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go index b7439e482d6..cc6b1226706 100644 --- a/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go +++ b/staging/src/k8s.io/client-go/examples/dynamic-create-update-delete-deployment/main.go @@ -41,7 +41,6 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go b/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go index a8c71612e31..b583e337338 100644 --- a/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go +++ b/staging/src/k8s.io/client-go/examples/in-cluster-client-configuration/main.go @@ -34,7 +34,6 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go b/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go index cf00d48094d..7698e132181 100644 --- a/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go +++ b/staging/src/k8s.io/client-go/examples/out-of-cluster-client-configuration/main.go @@ -37,7 +37,6 @@ import ( // _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) func main() { diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go deleted file mode 100644 index 6e404beda20..00000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_stub.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "errors" - - "k8s.io/client-go/rest" - "k8s.io/klog/v2" -) - -func init() { - if err := rest.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil { - klog.Fatalf("Failed to register openstack auth plugin: %s", err) - } -} - -func newOpenstackAuthProvider(_ string, _ map[string]string, _ rest.AuthProviderConfigPersister) (rest.AuthProvider, error) { - return nil, errors.New(`The openstack auth plugin has been removed. -Please use the "client-keystone-auth" kubectl/client-go credential plugin instead. -See https://github.com/kubernetes/cloud-provider-openstack/blob/master/docs/using-client-keystone-auth.md for further details`) -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go index ebfbd715c0c..3f0688774ee 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins_providers.go @@ -23,5 +23,4 @@ import ( // Initialize client auth plugins for cloud providers. _ "k8s.io/client-go/plugin/pkg/client/auth/azure" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) diff --git a/staging/src/k8s.io/cloud-provider/plugins.go b/staging/src/k8s.io/cloud-provider/plugins.go index bfd73163149..5300abdb4c0 100644 --- a/staging/src/k8s.io/cloud-provider/plugins.go +++ b/staging/src/k8s.io/cloud-provider/plugins.go @@ -43,7 +43,6 @@ var ( {"aws", false, "The AWS provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-aws"}, {"azure", false, "The Azure provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes-sigs/cloud-provider-azure"}, {"gce", false, "The GCE provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-gcp"}, - {"openstack", true, "https://github.com/kubernetes/cloud-provider-openstack"}, {"vsphere", false, "The vSphere provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-vsphere"}, } ) diff --git a/staging/src/k8s.io/legacy-cloud-providers/go.mod b/staging/src/k8s.io/legacy-cloud-providers/go.mod index 8fc86c5c8e0..524dcea5868 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/go.mod +++ b/staging/src/k8s.io/legacy-cloud-providers/go.mod @@ -15,8 +15,6 @@ require ( github.com/aws/aws-sdk-go v1.44.116 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 - github.com/gophercloud/gophercloud v0.1.0 - github.com/mitchellh/mapstructure v1.4.1 github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021 github.com/stretchr/testify v1.8.0 github.com/vmware/govmomi v0.20.3 @@ -31,7 +29,6 @@ require ( k8s.io/component-base v0.0.0 k8s.io/csi-translation-lib v0.0.0 k8s.io/klog/v2 v2.80.1 - k8s.io/mount-utils v0.0.0 k8s.io/utils v0.0.0-20221107191617-1a15be271d1d sigs.k8s.io/yaml v1.3.0 ) @@ -62,13 +59,11 @@ require ( github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect - github.com/imdario/mergo v0.3.6 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect - github.com/moby/sys/mountinfo v0.6.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -111,5 +106,4 @@ replace ( k8s.io/csi-translation-lib => ../csi-translation-lib k8s.io/kms => ../kms k8s.io/legacy-cloud-providers => ../legacy-cloud-providers - k8s.io/mount-utils => ../mount-utils ) diff --git a/staging/src/k8s.io/legacy-cloud-providers/go.sum b/staging/src/k8s.io/legacy-cloud-providers/go.sum index 9d0e3f33a75..2050f48714a 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/go.sum +++ b/staging/src/k8s.io/legacy-cloud-providers/go.sum @@ -237,15 +237,11 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -280,8 +276,6 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/moby/sys/mountinfo v0.6.0 h1:gUDhXQx58YNrpHlK4nSL+7y2pxFZkUcXqzFDKWdC0Oo= -github.com/moby/sys/mountinfo v0.6.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -371,7 +365,6 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -491,7 +484,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md b/staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md deleted file mode 100644 index 0802490bcd1..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/MAINTAINERS.md +++ /dev/null @@ -1,4 +0,0 @@ -# Maintainers - -* [Angus Lees](https://github.com/anguslees) - diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS b/staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS deleted file mode 100644 index 6b5183953e2..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/OWNERS +++ /dev/null @@ -1,13 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners -# We are no longer accepting features into k8s.io/legacy-cloud-providers. -# Any kind/feature PRs must be approved by SIG Cloud Provider going forward. - -emeritus_approvers: - - anguslees - - NickrenREN - - dims - - FengyunPan2 -reviewers: - - anguslees - - NickrenREN - - dims diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go deleted file mode 100644 index 948b32a5b67..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata.go +++ /dev/null @@ -1,201 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - - "k8s.io/klog/v2" - "k8s.io/mount-utils" - "k8s.io/utils/exec" -) - -const ( - // metadataURLTemplate allows building an OpenStack Metadata service URL. - // It's a hardcoded IPv4 link-local address as documented in "OpenStack Cloud - // Administrator Guide", chapter Compute - Networking with nova-network. - //https://docs.openstack.org/nova/latest/admin/networking-nova.html#metadata-service - defaultMetadataVersion = "2012-08-10" - metadataURLTemplate = "http://169.254.169.254/openstack/%s/meta_data.json" - - // metadataID is used as an identifier on the metadata search order configuration. - metadataID = "metadataService" - - // Config drive is defined as an iso9660 or vfat (deprecated) drive - // with the "config-2" label. - //https://docs.openstack.org/nova/latest/user/config-drive.html - configDriveLabel = "config-2" - configDrivePathTemplate = "openstack/%s/meta_data.json" - - // configDriveID is used as an identifier on the metadata search order configuration. - configDriveID = "configDrive" -) - -// ErrBadMetadata is used to indicate a problem parsing data from metadata server -var ErrBadMetadata = errors.New("invalid OpenStack metadata, got empty uuid") - -// DeviceMetadata is a single/simplified data structure for all kinds of device metadata types. -type DeviceMetadata struct { - Type string `json:"type"` - Bus string `json:"bus,omitempty"` - Serial string `json:"serial,omitempty"` - Address string `json:"address,omitempty"` - // .. and other fields. -} - -// Metadata has the information fetched from OpenStack metadata service or -// config drives. Assumes the "2012-08-10" meta_data.json format. -// See http://docs.openstack.org/user-guide/cli_config_drive.html -type Metadata struct { - UUID string `json:"uuid"` - Name string `json:"name"` - AvailabilityZone string `json:"availability_zone"` - Devices []DeviceMetadata `json:"devices,omitempty"` - // .. and other fields we don't care about. Expand as necessary. -} - -// parseMetadata reads JSON from OpenStack metadata server and parses -// instance ID out of it. -func parseMetadata(r io.Reader) (*Metadata, error) { - var metadata Metadata - json := json.NewDecoder(r) - if err := json.Decode(&metadata); err != nil { - return nil, err - } - - if metadata.UUID == "" { - return nil, ErrBadMetadata - } - - return &metadata, nil -} - -func getMetadataURL(metadataVersion string) string { - return fmt.Sprintf(metadataURLTemplate, metadataVersion) -} - -func getConfigDrivePath(metadataVersion string) string { - return fmt.Sprintf(configDrivePathTemplate, metadataVersion) -} - -func getMetadataFromConfigDrive(metadataVersion string) (*Metadata, error) { - // Try to read instance UUID from config drive. - dev := "/dev/disk/by-label/" + configDriveLabel - if _, err := os.Stat(dev); os.IsNotExist(err) { - out, err := exec.New().Command( - "blkid", "-l", - "-t", "LABEL="+configDriveLabel, - "-o", "device", - ).CombinedOutput() - if err != nil { - return nil, fmt.Errorf("unable to run blkid: %v", err) - } - dev = strings.TrimSpace(string(out)) - } - - mntdir, err := ioutil.TempDir("", "configdrive") - if err != nil { - return nil, err - } - defer os.Remove(mntdir) - - klog.V(4).Infof("Attempting to mount configdrive %s on %s", dev, mntdir) - - mounter := mount.New("" /* default mount path */) - err = mounter.Mount(dev, mntdir, "iso9660", []string{"ro"}) - if err != nil { - err = mounter.Mount(dev, mntdir, "vfat", []string{"ro"}) - } - if err != nil { - return nil, fmt.Errorf("error mounting configdrive %s: %v", dev, err) - } - defer mounter.Unmount(mntdir) - - klog.V(4).Infof("Configdrive mounted on %s", mntdir) - - configDrivePath := getConfigDrivePath(metadataVersion) - f, err := os.Open( - filepath.Join(mntdir, configDrivePath)) - if err != nil { - return nil, fmt.Errorf("error reading %s on config drive: %v", configDrivePath, err) - } - defer f.Close() - - return parseMetadata(f) -} - -func getMetadataFromMetadataService(metadataVersion string) (*Metadata, error) { - // Try to get JSON from metadata server. - metadataURL := getMetadataURL(metadataVersion) - klog.V(4).Infof("Attempting to fetch metadata from %s", metadataURL) - resp, err := http.Get(metadataURL) - if err != nil { - return nil, fmt.Errorf("error fetching %s: %v", metadataURL, err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - err = fmt.Errorf("unexpected status code when reading metadata from %s: %s", metadataURL, resp.Status) - return nil, err - } - - return parseMetadata(resp.Body) -} - -// Metadata is fixed for the current host, so cache the value process-wide -var metadataCache *Metadata - -func getMetadata(order string) (*Metadata, error) { - if metadataCache == nil { - var md *Metadata - var err error - - elements := strings.Split(order, ",") - for _, id := range elements { - id = strings.TrimSpace(id) - switch id { - case configDriveID: - md, err = getMetadataFromConfigDrive(defaultMetadataVersion) - case metadataID: - md, err = getMetadataFromMetadataService(defaultMetadataVersion) - default: - err = fmt.Errorf("%s is not a valid metadata search order option. Supported options are %s and %s", id, configDriveID, metadataID) - } - - if err == nil { - break - } - } - - if err != nil { - return nil, err - } - metadataCache = md - } - return metadataCache, nil -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go deleted file mode 100644 index d0eb67d54c6..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/metadata_test.go +++ /dev/null @@ -1,118 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "strings" - "testing" -) - -var FakeMetadata = Metadata{ - UUID: "83679162-1378-4288-a2d4-70e13ec132aa", - Name: "test", - AvailabilityZone: "nova", -} - -func SetMetadataFixture(value *Metadata) { - metadataCache = value -} - -func ClearMetadata() { - metadataCache = nil -} - -func TestParseMetadata(t *testing.T) { - _, err := parseMetadata(strings.NewReader("bogus")) - if err == nil { - t.Errorf("Should fail when bad data is provided: %s", err) - } - - data := strings.NewReader(` -{ - "availability_zone": "nova", - "files": [ - { - "content_path": "/content/0000", - "path": "/etc/network/interfaces" - }, - { - "content_path": "/content/0001", - "path": "known_hosts" - } - ], - "hostname": "test.novalocal", - "launch_index": 0, - "name": "test", - "meta": { - "role": "webservers", - "essential": "false" - }, - "public_keys": { - "mykey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDBqUfVvCSez0/Wfpd8dLLgZXV9GtXQ7hnMN+Z0OWQUyebVEHey1CXuin0uY1cAJMhUq8j98SiW+cU0sU4J3x5l2+xi1bodDm1BtFWVeLIOQINpfV1n8fKjHB+ynPpe1F6tMDvrFGUlJs44t30BrujMXBe8Rq44cCk6wqyjATA3rQ== Generated by Nova\n" - }, - "uuid": "83679162-1378-4288-a2d4-70e13ec132aa", - "devices": [ - { - "bus": "scsi", - "serial": "6df1888b-f373-41cf-b960-3786e60a28ef", - "tags": ["fake_tag"], - "type": "disk", - "address": "0:0:0:0" - } - ] -} -`) - md, err := parseMetadata(data) - if err != nil { - t.Fatalf("Should succeed when provided with valid data: %s", err) - } - - if md.Name != "test" { - t.Errorf("incorrect name: %s", md.Name) - } - - if md.UUID != "83679162-1378-4288-a2d4-70e13ec132aa" { - t.Errorf("incorrect uuid: %s", md.UUID) - } - - if md.AvailabilityZone != "nova" { - t.Errorf("incorrect az: %s", md.AvailabilityZone) - } - - if len(md.Devices) != 1 { - t.Errorf("expecting to find 1 device, found %d", len(md.Devices)) - } - - if md.Devices[0].Bus != "scsi" { - t.Errorf("incorrect disk bus: %s", md.Devices[0].Bus) - } - - if md.Devices[0].Address != "0:0:0:0" { - t.Errorf("incorrect disk address: %s", md.Devices[0].Address) - } - - if md.Devices[0].Type != "disk" { - t.Errorf("incorrect device type: %s", md.Devices[0].Type) - } - - if md.Devices[0].Serial != "6df1888b-f373-41cf-b960-3786e60a28ef" { - t.Errorf("incorrect device serial: %s", md.Devices[0].Serial) - } -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go deleted file mode 100644 index 668caaecc07..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack.go +++ /dev/null @@ -1,949 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2014 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "crypto/tls" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "reflect" - "regexp" - "strings" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts" - tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" - "github.com/gophercloud/gophercloud/pagination" - "github.com/mitchellh/mapstructure" - "gopkg.in/gcfg.v1" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - netutil "k8s.io/apimachinery/pkg/util/net" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" - certutil "k8s.io/client-go/util/cert" - cloudprovider "k8s.io/cloud-provider" - nodehelpers "k8s.io/cloud-provider/node/helpers" - "k8s.io/klog/v2" - netutils "k8s.io/utils/net" -) - -const ( - // ProviderName is the name of the openstack provider - ProviderName = "openstack" - - // TypeHostName is the name type of openstack instance - TypeHostName = "hostname" - availabilityZone = "availability_zone" - defaultTimeOut = 60 * time.Second -) - -// ErrNotFound is used to inform that the object is missing -var ErrNotFound = errors.New("failed to find object") - -// ErrMultipleResults is used when we unexpectedly get back multiple results -var ErrMultipleResults = errors.New("multiple results where only one expected") - -// ErrNoAddressFound is used when we cannot find an ip address for the host -var ErrNoAddressFound = errors.New("no address found for host") - -// MyDuration is the encoding.TextUnmarshaler interface for time.Duration -type MyDuration struct { - time.Duration -} - -// UnmarshalText is used to convert from text to Duration -func (d *MyDuration) UnmarshalText(text []byte) error { - res, err := time.ParseDuration(string(text)) - if err != nil { - return err - } - d.Duration = res - return nil -} - -// LoadBalancer is used for creating and maintaining load balancers -type LoadBalancer struct { - network *gophercloud.ServiceClient - compute *gophercloud.ServiceClient - lb *gophercloud.ServiceClient - opts LoadBalancerOpts -} - -// LoadBalancerOpts have the options to talk to Neutron LBaaSV2 or Octavia -type LoadBalancerOpts struct { - LBVersion string `gcfg:"lb-version"` // overrides autodetection. Only support v2. - UseOctavia bool `gcfg:"use-octavia"` // uses Octavia V2 service catalog endpoint - SubnetID string `gcfg:"subnet-id"` // overrides autodetection. - FloatingNetworkID string `gcfg:"floating-network-id"` // If specified, will create floating ip for loadbalancer, or do not create floating ip. - LBMethod string `gcfg:"lb-method"` // default to ROUND_ROBIN. - LBProvider string `gcfg:"lb-provider"` - CreateMonitor bool `gcfg:"create-monitor"` - MonitorDelay MyDuration `gcfg:"monitor-delay"` - MonitorTimeout MyDuration `gcfg:"monitor-timeout"` - MonitorMaxRetries uint `gcfg:"monitor-max-retries"` - ManageSecurityGroups bool `gcfg:"manage-security-groups"` - NodeSecurityGroupIDs []string // Do not specify, get it automatically when enable manage-security-groups. TODO(FengyunPan): move it into cache -} - -// BlockStorageOpts is used to talk to Cinder service -type BlockStorageOpts struct { - BSVersion string `gcfg:"bs-version"` // overrides autodetection. v1 or v2. Defaults to auto - TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128 - IgnoreVolumeAZ bool `gcfg:"ignore-volume-az"` - NodeVolumeAttachLimit int `gcfg:"node-volume-attach-limit"` // override volume attach limit for Cinder. Default is : 256 -} - -// RouterOpts is used for Neutron routes -type RouterOpts struct { - RouterID string `gcfg:"router-id"` // required -} - -// MetadataOpts is used for configuring how to talk to metadata service or config drive -type MetadataOpts struct { - SearchOrder string `gcfg:"search-order"` - RequestTimeout MyDuration `gcfg:"request-timeout"` -} - -var _ cloudprovider.Interface = (*OpenStack)(nil) -var _ cloudprovider.Zones = (*OpenStack)(nil) - -// OpenStack is an implementation of cloud provider Interface for OpenStack. -type OpenStack struct { - provider *gophercloud.ProviderClient - region string - lbOpts LoadBalancerOpts - bsOpts BlockStorageOpts - routeOpts RouterOpts - metadataOpts MetadataOpts - // InstanceID of the server where this OpenStack object is instantiated. - localInstanceID string -} - -// Config is used to read and store information from the cloud configuration file -// NOTE: Cloud config files should follow the same Kubernetes deprecation policy as -// flags or CLIs. Config fields should not change behavior in incompatible ways and -// should be deprecated for at least 2 release prior to removing. -// See https://kubernetes.io/docs/reference/using-api/deprecation-policy/#deprecating-a-flag-or-cli -// for more details. -type Config struct { - Global struct { - AuthURL string `gcfg:"auth-url"` - Username string - UserID string `gcfg:"user-id"` - Password string `datapolicy:"password"` - TenantID string `gcfg:"tenant-id"` - TenantName string `gcfg:"tenant-name"` - TrustID string `gcfg:"trust-id"` - DomainID string `gcfg:"domain-id"` - DomainName string `gcfg:"domain-name"` - Region string - CAFile string `gcfg:"ca-file"` - SecretName string `gcfg:"secret-name"` - SecretNamespace string `gcfg:"secret-namespace"` - KubeconfigPath string `gcfg:"kubeconfig-path"` - } - LoadBalancer LoadBalancerOpts - BlockStorage BlockStorageOpts - Route RouterOpts - Metadata MetadataOpts -} - -func init() { - registerMetrics() - - cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { - cfg, err := readConfig(config) - if err != nil { - return nil, err - } - return newOpenStack(cfg) - }) -} - -func (cfg Config) toAuthOptions() gophercloud.AuthOptions { - return gophercloud.AuthOptions{ - IdentityEndpoint: cfg.Global.AuthURL, - Username: cfg.Global.Username, - UserID: cfg.Global.UserID, - Password: cfg.Global.Password, - TenantID: cfg.Global.TenantID, - TenantName: cfg.Global.TenantName, - DomainID: cfg.Global.DomainID, - DomainName: cfg.Global.DomainName, - - // Persistent service, so we need to be able to renew tokens. - AllowReauth: true, - } -} - -func (cfg Config) toAuth3Options() tokens3.AuthOptions { - return tokens3.AuthOptions{ - IdentityEndpoint: cfg.Global.AuthURL, - Username: cfg.Global.Username, - UserID: cfg.Global.UserID, - Password: cfg.Global.Password, - DomainID: cfg.Global.DomainID, - DomainName: cfg.Global.DomainName, - AllowReauth: true, - } -} - -// configFromEnv allows setting up credentials etc using the -// standard OS_* OpenStack client environment variables. -func configFromEnv() (cfg Config, ok bool) { - cfg.Global.AuthURL = os.Getenv("OS_AUTH_URL") - cfg.Global.Username = os.Getenv("OS_USERNAME") - cfg.Global.Region = os.Getenv("OS_REGION_NAME") - cfg.Global.UserID = os.Getenv("OS_USER_ID") - cfg.Global.TrustID = os.Getenv("OS_TRUST_ID") - - cfg.Global.TenantID = os.Getenv("OS_TENANT_ID") - if cfg.Global.TenantID == "" { - cfg.Global.TenantID = os.Getenv("OS_PROJECT_ID") - } - cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME") - if cfg.Global.TenantName == "" { - cfg.Global.TenantName = os.Getenv("OS_PROJECT_NAME") - } - - cfg.Global.DomainID = os.Getenv("OS_DOMAIN_ID") - if cfg.Global.DomainID == "" { - cfg.Global.DomainID = os.Getenv("OS_USER_DOMAIN_ID") - } - cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME") - if cfg.Global.DomainName == "" { - cfg.Global.DomainName = os.Getenv("OS_USER_DOMAIN_NAME") - } - - cfg.Global.SecretName = os.Getenv("SECRET_NAME") - cfg.Global.SecretNamespace = os.Getenv("SECRET_NAMESPACE") - cfg.Global.KubeconfigPath = os.Getenv("KUBECONFIG_PATH") - - ok = cfg.Global.AuthURL != "" && - cfg.Global.Username != "" && - cfg.Global.Password != "" && - (cfg.Global.TenantID != "" || cfg.Global.TenantName != "" || - cfg.Global.DomainID != "" || cfg.Global.DomainName != "" || - cfg.Global.Region != "" || cfg.Global.UserID != "" || - cfg.Global.TrustID != "") - - cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID) - cfg.BlockStorage.BSVersion = "auto" - - return -} - -func createKubernetesClient(kubeconfigPath string) (*kubernetes.Clientset, error) { - klog.Info("Creating kubernetes API client.") - - cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - return nil, err - } - cfg.DisableCompression = true - - client, err := kubernetes.NewForConfig(cfg) - if err != nil { - return nil, err - } - - v, err := client.Discovery().ServerVersion() - if err != nil { - return nil, err - } - - klog.Infof("Kubernetes API client created, server version %s", fmt.Sprintf("v%v.%v", v.Major, v.Minor)) - return client, nil -} - -// setConfigFromSecret allows setting up the config from k8s secret -func setConfigFromSecret(cfg *Config) error { - secretName := cfg.Global.SecretName - secretNamespace := cfg.Global.SecretNamespace - kubeconfigPath := cfg.Global.KubeconfigPath - - k8sClient, err := createKubernetesClient(kubeconfigPath) - if err != nil { - return fmt.Errorf("failed to get kubernetes client: %v", err) - } - - secret, err := k8sClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) - if err != nil { - klog.Warningf("Cannot get secret %s in namespace %s. error: %q", secretName, secretNamespace, err) - return err - } - - if content, ok := secret.Data["clouds.conf"]; ok { - err = gcfg.ReadStringInto(cfg, string(content)) - if err != nil { - klog.Error("Cannot parse data from the secret.") - return fmt.Errorf("cannot parse data from the secret") - } - return nil - } - - klog.Error("Cannot find \"clouds.conf\" key in the secret.") - return fmt.Errorf("cannot find \"clouds.conf\" key in the secret") -} - -func readConfig(config io.Reader) (Config, error) { - if config == nil { - return Config{}, fmt.Errorf("no OpenStack cloud provider config file given") - } - - cfg, _ := configFromEnv() - - // Set default values for config params - cfg.BlockStorage.BSVersion = "auto" - cfg.BlockStorage.TrustDevicePath = false - cfg.BlockStorage.IgnoreVolumeAZ = false - cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID) - - err := gcfg.ReadInto(&cfg, config) - if err != nil { - // Warn instead of failing on non-fatal config parsing errors. - // This is important during the transition to external CCM we - // may be sharing user-managed configuration KCM, using legacy - // cloud provider, and CCM using external cloud provider. - // We do not want to prevent KCM from starting if the user adds - // new configuration which is only present in OpenStack CCM. - if gcfg.FatalOnly(err) == nil { - klog.Warningf("Non-fatal error parsing OpenStack cloud config. "+ - "This may happen when passing config directives exclusive to OpenStack CCM to the legacy cloud provider. "+ - "Legacy cloud provider has correctly parsed all directives it knows about: %s", err) - } else { - return cfg, err - } - } - - if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" { - klog.Infof("Set credentials from secret %s in namespace %s", cfg.Global.SecretName, cfg.Global.SecretNamespace) - err = setConfigFromSecret(&cfg) - if err != nil { - return cfg, err - } - } - - return cfg, nil -} - -// caller is a tiny helper for conditional unwind logic -type caller bool - -func newCaller() caller { return caller(true) } -func (c *caller) disarm() { *c = false } - -func (c *caller) call(f func()) { - if *c { - f() - } -} - -func readInstanceID(searchOrder string) (string, error) { - // Try to find instance ID on the local filesystem (created by cloud-init) - const instanceIDFile = "/var/lib/cloud/data/instance-id" - idBytes, err := ioutil.ReadFile(instanceIDFile) - if err == nil { - instanceID := string(idBytes) - instanceID = strings.TrimSpace(instanceID) - klog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID) - if instanceID != "" { - return instanceID, nil - } - // Fall through to metadata server lookup - } - - md, err := getMetadata(searchOrder) - if err != nil { - return "", err - } - - return md.UUID, nil -} - -// check opts for OpenStack -func checkOpenStackOpts(openstackOpts *OpenStack) error { - lbOpts := openstackOpts.lbOpts - - // if need to create health monitor for Neutron LB, - // monitor-delay, monitor-timeout and monitor-max-retries should be set. - emptyDuration := MyDuration{} - if lbOpts.CreateMonitor { - if lbOpts.MonitorDelay == emptyDuration { - return fmt.Errorf("monitor-delay not set in cloud provider config") - } - if lbOpts.MonitorTimeout == emptyDuration { - return fmt.Errorf("monitor-timeout not set in cloud provider config") - } - if lbOpts.MonitorMaxRetries == uint(0) { - return fmt.Errorf("monitor-max-retries not set in cloud provider config") - } - } - return checkMetadataSearchOrder(openstackOpts.metadataOpts.SearchOrder) -} - -func newOpenStack(cfg Config) (*OpenStack, error) { - provider, err := openstack.NewClient(cfg.Global.AuthURL) - if err != nil { - return nil, err - } - if cfg.Global.CAFile != "" { - roots, err := certutil.NewPool(cfg.Global.CAFile) - if err != nil { - return nil, err - } - config := &tls.Config{} - config.RootCAs = roots - provider.HTTPClient.Transport = netutil.SetOldTransportDefaults(&http.Transport{TLSClientConfig: config}) - - } - if cfg.Global.TrustID != "" { - opts := cfg.toAuth3Options() - authOptsExt := trusts.AuthOptsExt{ - TrustID: cfg.Global.TrustID, - AuthOptionsBuilder: &opts, - } - err = openstack.AuthenticateV3(provider, authOptsExt, gophercloud.EndpointOpts{}) - } else { - err = openstack.Authenticate(provider, cfg.toAuthOptions()) - } - - if err != nil { - return nil, err - } - - emptyDuration := MyDuration{} - if cfg.Metadata.RequestTimeout == emptyDuration { - cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut) - } - provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration - - os := OpenStack{ - provider: provider, - region: cfg.Global.Region, - lbOpts: cfg.LoadBalancer, - bsOpts: cfg.BlockStorage, - routeOpts: cfg.Route, - metadataOpts: cfg.Metadata, - } - - err = checkOpenStackOpts(&os) - if err != nil { - return nil, err - } - - return &os, nil -} - -// NewFakeOpenStackCloud creates and returns an instance of Openstack cloudprovider. -// Mainly for use in tests that require instantiating Openstack without having -// to go through cloudprovider interface. -func NewFakeOpenStackCloud(cfg Config) (*OpenStack, error) { - provider, err := openstack.NewClient(cfg.Global.AuthURL) - if err != nil { - return nil, err - } - emptyDuration := MyDuration{} - if cfg.Metadata.RequestTimeout == emptyDuration { - cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut) - } - provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration - - os := OpenStack{ - provider: provider, - region: cfg.Global.Region, - lbOpts: cfg.LoadBalancer, - bsOpts: cfg.BlockStorage, - routeOpts: cfg.Route, - metadataOpts: cfg.Metadata, - } - - return &os, nil -} - -// Initialize passes a Kubernetes clientBuilder interface to the cloud provider -func (os *OpenStack) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) { -} - -// mapNodeNameToServerName maps a k8s NodeName to an OpenStack Server Name -// This is a simple string cast. -func mapNodeNameToServerName(nodeName types.NodeName) string { - return string(nodeName) -} - -// GetNodeNameByID maps instanceid to types.NodeName -func (os *OpenStack) GetNodeNameByID(instanceID string) (types.NodeName, error) { - client, err := os.NewComputeV2() - var nodeName types.NodeName - if err != nil { - return nodeName, err - } - - server, err := servers.Get(client, instanceID).Extract() - if err != nil { - return nodeName, err - } - nodeName = mapServerToNodeName(server) - return nodeName, nil -} - -// mapServerToNodeName maps an OpenStack Server to a k8s NodeName -func mapServerToNodeName(server *servers.Server) types.NodeName { - // Node names are always lowercase, and (at least) - // routecontroller does case-sensitive string comparisons - // assuming this - return types.NodeName(strings.ToLower(server.Name)) -} - -func foreachServer(client *gophercloud.ServiceClient, opts servers.ListOptsBuilder, handler func(*servers.Server) (bool, error)) error { - pager := servers.List(client, opts) - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - s, err := servers.ExtractServers(page) - if err != nil { - return false, err - } - for _, server := range s { - ok, err := handler(&server) - if !ok || err != nil { - return false, err - } - } - return true, nil - }) - return err -} - -func getServerByName(client *gophercloud.ServiceClient, name types.NodeName) (*servers.Server, error) { - opts := servers.ListOpts{ - Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(mapNodeNameToServerName(name))), - } - - pager := servers.List(client, opts) - - serverList := make([]servers.Server, 0, 1) - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - s, err := servers.ExtractServers(page) - if err != nil { - return false, err - } - serverList = append(serverList, s...) - if len(serverList) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - return nil, err - } - - if len(serverList) == 0 { - return nil, ErrNotFound - } - - return &serverList[0], nil -} - -func nodeAddresses(srv *servers.Server) ([]v1.NodeAddress, error) { - addrs := []v1.NodeAddress{} - - type Address struct { - IPType string `mapstructure:"OS-EXT-IPS:type"` - Addr string - } - - var addresses map[string][]Address - err := mapstructure.Decode(srv.Addresses, &addresses) - if err != nil { - return nil, err - } - - for network, addrList := range addresses { - for _, props := range addrList { - var addressType v1.NodeAddressType - if props.IPType == "floating" || network == "public" { - addressType = v1.NodeExternalIP - } else { - addressType = v1.NodeInternalIP - } - - nodehelpers.AddToNodeAddresses(&addrs, - v1.NodeAddress{ - Type: addressType, - Address: props.Addr, - }, - ) - } - } - - // AccessIPs are usually duplicates of "public" addresses. - if srv.AccessIPv4 != "" { - nodehelpers.AddToNodeAddresses(&addrs, - v1.NodeAddress{ - Type: v1.NodeExternalIP, - Address: srv.AccessIPv4, - }, - ) - } - - if srv.AccessIPv6 != "" { - nodehelpers.AddToNodeAddresses(&addrs, - v1.NodeAddress{ - Type: v1.NodeExternalIP, - Address: srv.AccessIPv6, - }, - ) - } - - if srv.Metadata[TypeHostName] != "" { - nodehelpers.AddToNodeAddresses(&addrs, - v1.NodeAddress{ - Type: v1.NodeHostName, - Address: srv.Metadata[TypeHostName], - }, - ) - } - - return addrs, nil -} - -func getAddressesByName(client *gophercloud.ServiceClient, name types.NodeName) ([]v1.NodeAddress, error) { - srv, err := getServerByName(client, name) - if err != nil { - return nil, err - } - - return nodeAddresses(srv) -} - -func getAddressByName(client *gophercloud.ServiceClient, name types.NodeName, needIPv6 bool) (string, error) { - addrs, err := getAddressesByName(client, name) - if err != nil { - return "", err - } else if len(addrs) == 0 { - return "", ErrNoAddressFound - } - - for _, addr := range addrs { - isIPv6 := netutils.ParseIPSloppy(addr.Address).To4() == nil - if (addr.Type == v1.NodeInternalIP) && (isIPv6 == needIPv6) { - return addr.Address, nil - } - } - - for _, addr := range addrs { - isIPv6 := netutils.ParseIPSloppy(addr.Address).To4() == nil - if (addr.Type == v1.NodeExternalIP) && (isIPv6 == needIPv6) { - return addr.Address, nil - } - } - // It should never return an address from a different IP Address family than the one needed - return "", ErrNoAddressFound -} - -// getAttachedInterfacesByID returns the node interfaces of the specified instance. -func getAttachedInterfacesByID(client *gophercloud.ServiceClient, serviceID string) ([]attachinterfaces.Interface, error) { - var interfaces []attachinterfaces.Interface - - pager := attachinterfaces.List(client, serviceID) - err := pager.EachPage(func(page pagination.Page) (bool, error) { - s, err := attachinterfaces.ExtractInterfaces(page) - if err != nil { - return false, err - } - interfaces = append(interfaces, s...) - return true, nil - }) - if err != nil { - return interfaces, err - } - - return interfaces, nil -} - -// Clusters is a no-op -func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) { - return nil, false -} - -// ProviderName returns the cloud provider ID. -func (os *OpenStack) ProviderName() string { - return ProviderName -} - -// HasClusterID returns true if the cluster has a clusterID -func (os *OpenStack) HasClusterID() bool { - return true -} - -// LoadBalancer initializes a LbaasV2 object -func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) { - klog.V(4).Info("openstack.LoadBalancer() called") - - if reflect.DeepEqual(os.lbOpts, LoadBalancerOpts{}) { - klog.V(4).Info("LoadBalancer section is empty/not defined in cloud-config") - return nil, false - } - - network, err := os.NewNetworkV2() - if err != nil { - return nil, false - } - - compute, err := os.NewComputeV2() - if err != nil { - return nil, false - } - - lb, err := os.NewLoadBalancerV2() - if err != nil { - return nil, false - } - - // LBaaS v1 is deprecated in the OpenStack Liberty release. - // Currently kubernetes OpenStack cloud provider just support LBaaS v2. - lbVersion := os.lbOpts.LBVersion - if lbVersion != "" && lbVersion != "v2" { - klog.Warningf("Config error: currently only support LBaaS v2, unrecognised lb-version \"%v\"", lbVersion) - return nil, false - } - - klog.V(1).Info("Claiming to support LoadBalancer") - - return &LbaasV2{LoadBalancer{network, compute, lb, os.lbOpts}}, true -} - -func isNotFound(err error) bool { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return true - } - - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == http.StatusNotFound { - return true - } - } - - return false -} - -// Zones indicates that we support zones -func (os *OpenStack) Zones() (cloudprovider.Zones, bool) { - klog.V(1).Info("Claiming to support Zones") - return os, true -} - -// GetZone returns the current zone -func (os *OpenStack) GetZone(ctx context.Context) (cloudprovider.Zone, error) { - md, err := getMetadata(os.metadataOpts.SearchOrder) - if err != nil { - return cloudprovider.Zone{}, err - } - - zone := cloudprovider.Zone{ - FailureDomain: md.AvailabilityZone, - Region: os.region, - } - klog.V(4).Infof("Current zone is %v", zone) - return zone, nil -} - -// GetZoneByProviderID implements Zones.GetZoneByProviderID -// This is particularly useful in external cloud providers where the kubelet -// does not initialize node data. -func (os *OpenStack) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) { - instanceID, err := instanceIDFromProviderID(providerID) - if err != nil { - return cloudprovider.Zone{}, err - } - - compute, err := os.NewComputeV2() - if err != nil { - return cloudprovider.Zone{}, err - } - - srv, err := servers.Get(compute, instanceID).Extract() - if err != nil { - return cloudprovider.Zone{}, err - } - - zone := cloudprovider.Zone{ - FailureDomain: srv.Metadata[availabilityZone], - Region: os.region, - } - klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone) - return zone, nil -} - -// GetZoneByNodeName implements Zones.GetZoneByNodeName -// This is particularly useful in external cloud providers where the kubelet -// does not initialize node data. -func (os *OpenStack) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) { - compute, err := os.NewComputeV2() - if err != nil { - return cloudprovider.Zone{}, err - } - - srv, err := getServerByName(compute, nodeName) - if err != nil { - if err == ErrNotFound { - return cloudprovider.Zone{}, cloudprovider.InstanceNotFound - } - return cloudprovider.Zone{}, err - } - - zone := cloudprovider.Zone{ - FailureDomain: srv.Metadata[availabilityZone], - Region: os.region, - } - klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone) - return zone, nil -} - -// Routes initializes routes support -func (os *OpenStack) Routes() (cloudprovider.Routes, bool) { - klog.V(4).Info("openstack.Routes() called") - - network, err := os.NewNetworkV2() - if err != nil { - return nil, false - } - - netExts, err := networkExtensions(network) - if err != nil { - klog.Warningf("Failed to list neutron extensions: %v", err) - return nil, false - } - - if !netExts["extraroute"] { - klog.V(3).Info("Neutron extraroute extension not found, required for Routes support") - return nil, false - } - - compute, err := os.NewComputeV2() - if err != nil { - return nil, false - } - - r, err := NewRoutes(compute, network, os.routeOpts) - if err != nil { - klog.Warningf("Error initialising Routes support: %v", err) - return nil, false - } - - klog.V(1).Info("Claiming to support Routes") - return r, true -} - -func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) { - bsVersion := "" - if forceVersion == "" { - bsVersion = os.bsOpts.BSVersion - } else { - bsVersion = forceVersion - } - - switch bsVersion { - case "v1": - sClient, err := os.NewBlockStorageV1() - if err != nil { - return nil, err - } - klog.V(3).Info("Using Blockstorage API V1") - return &VolumesV1{sClient, os.bsOpts}, nil - case "v2": - sClient, err := os.NewBlockStorageV2() - if err != nil { - return nil, err - } - klog.V(3).Info("Using Blockstorage API V2") - return &VolumesV2{sClient, os.bsOpts}, nil - case "v3": - sClient, err := os.NewBlockStorageV3() - if err != nil { - return nil, err - } - klog.V(3).Info("Using Blockstorage API V3") - return &VolumesV3{sClient, os.bsOpts}, nil - case "auto": - // Currently kubernetes support Cinder v1 / Cinder v2 / Cinder v3. - // Choose Cinder v3 firstly, if kubernetes can't initialize cinder v3 client, try to initialize cinder v2 client. - // If kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client. - // Return appropriate message when kubernetes can't initialize them. - if sClient, err := os.NewBlockStorageV3(); err == nil { - klog.V(3).Info("Using Blockstorage API V3") - return &VolumesV3{sClient, os.bsOpts}, nil - } - - if sClient, err := os.NewBlockStorageV2(); err == nil { - klog.V(3).Info("Using Blockstorage API V2") - return &VolumesV2{sClient, os.bsOpts}, nil - } - - if sClient, err := os.NewBlockStorageV1(); err == nil { - klog.V(3).Info("Using Blockstorage API V1") - return &VolumesV1{sClient, os.bsOpts}, nil - } - - errTxt := "BlockStorage API version autodetection failed. " + - "Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`" - return nil, errors.New(errTxt) - default: - errTxt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion) - return nil, errors.New(errTxt) - } -} - -func checkMetadataSearchOrder(order string) error { - if order == "" { - return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot be empty") - } - - elements := strings.Split(order, ",") - if len(elements) > 2 { - return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements") - } - - for _, id := range elements { - id = strings.TrimSpace(id) - switch id { - case configDriveID: - case metadataID: - default: - return fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+ - "Supported elements include %q and %q", id, configDriveID, metadataID) - } - } - - return nil -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go deleted file mode 100644 index 305af56284a..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_client.go +++ /dev/null @@ -1,101 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2017 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "fmt" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" -) - -// NewNetworkV2 creates a ServiceClient that may be used with the neutron v2 API -func (os *OpenStack) NewNetworkV2() (*gophercloud.ServiceClient, error) { - network, err := openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil { - return nil, fmt.Errorf("failed to find network v2 endpoint for region %s: %v", os.region, err) - } - return network, nil -} - -// NewComputeV2 creates a ServiceClient that may be used with the nova v2 API -func (os *OpenStack) NewComputeV2() (*gophercloud.ServiceClient, error) { - compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil { - return nil, fmt.Errorf("failed to find compute v2 endpoint for region %s: %v", os.region, err) - } - return compute, nil -} - -// NewBlockStorageV1 creates a ServiceClient that may be used with the Cinder v1 API -func (os *OpenStack) NewBlockStorageV1() (*gophercloud.ServiceClient, error) { - storage, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil { - return nil, fmt.Errorf("unable to initialize cinder v1 client for region %s: %v", os.region, err) - } - return storage, nil -} - -// NewBlockStorageV2 creates a ServiceClient that may be used with the Cinder v2 API -func (os *OpenStack) NewBlockStorageV2() (*gophercloud.ServiceClient, error) { - storage, err := openstack.NewBlockStorageV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil { - return nil, fmt.Errorf("unable to initialize cinder v2 client for region %s: %v", os.region, err) - } - return storage, nil -} - -// NewBlockStorageV3 creates a ServiceClient that may be used with the Cinder v3 API -func (os *OpenStack) NewBlockStorageV3() (*gophercloud.ServiceClient, error) { - storage, err := openstack.NewBlockStorageV3(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil { - return nil, fmt.Errorf("unable to initialize cinder v3 client for region %s: %v", os.region, err) - } - return storage, nil -} - -// NewLoadBalancerV2 creates a ServiceClient that may be used with the Neutron LBaaS v2 API -func (os *OpenStack) NewLoadBalancerV2() (*gophercloud.ServiceClient, error) { - var lb *gophercloud.ServiceClient - var err error - if os.lbOpts.UseOctavia { - lb, err = openstack.NewLoadBalancerV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - } else { - lb, err = openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - } - if err != nil { - return nil, fmt.Errorf("failed to find load-balancer v2 endpoint for region %s: %v", os.region, err) - } - return lb, nil -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go deleted file mode 100644 index 37bc9383ad9..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_instances.go +++ /dev/null @@ -1,244 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "fmt" - "regexp" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - v1 "k8s.io/api/core/v1" - "k8s.io/klog/v2" - - "k8s.io/apimachinery/pkg/types" - cloudprovider "k8s.io/cloud-provider" -) - -var _ cloudprovider.Instances = (*Instances)(nil) - -// Instances encapsulates an implementation of Instances for OpenStack. -type Instances struct { - compute *gophercloud.ServiceClient - opts MetadataOpts -} - -const ( - instanceShutoff = "SHUTOFF" -) - -// Instances returns an implementation of Instances for OpenStack. -func (os *OpenStack) Instances() (cloudprovider.Instances, bool) { - klog.V(4).Info("openstack.Instances() called") - - compute, err := os.NewComputeV2() - if err != nil { - klog.Errorf("unable to access compute v2 API : %v", err) - return nil, false - } - - klog.V(4).Info("Claiming to support Instances") - - return &Instances{ - compute: compute, - opts: os.metadataOpts, - }, true -} - -// InstancesV2 returns an implementation of InstancesV2 for OpenStack. -// TODO: implement ONLY for external cloud provider -func (os *OpenStack) InstancesV2() (cloudprovider.InstancesV2, bool) { - return nil, false -} - -// CurrentNodeName implements Instances.CurrentNodeName -// Note this is *not* necessarily the same as hostname. -func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) { - md, err := getMetadata(i.opts.SearchOrder) - if err != nil { - return "", err - } - return types.NodeName(md.Name), nil -} - -// AddSSHKeyToAllInstances is not implemented for OpenStack -func (i *Instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error { - return cloudprovider.NotImplemented -} - -// NodeAddresses implements Instances.NodeAddresses -func (i *Instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) { - klog.V(4).Infof("NodeAddresses(%v) called", name) - - addrs, err := getAddressesByName(i.compute, name) - if err != nil { - return nil, err - } - - klog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs) - return addrs, nil -} - -// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID -// This method will not be called from the node that is requesting this ID. i.e. metadata service -// and other local methods cannot be used here -func (i *Instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) { - instanceID, err := instanceIDFromProviderID(providerID) - - if err != nil { - return []v1.NodeAddress{}, err - } - - server, err := servers.Get(i.compute, instanceID).Extract() - - if err != nil { - return []v1.NodeAddress{}, err - } - - addresses, err := nodeAddresses(server) - if err != nil { - return []v1.NodeAddress{}, err - } - - return addresses, nil -} - -// InstanceExistsByProviderID returns true if the instance with the given provider id still exist. -// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. -func (i *Instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) { - instanceID, err := instanceIDFromProviderID(providerID) - if err != nil { - return false, err - } - - _, err = servers.Get(i.compute, instanceID).Extract() - if err != nil { - if isNotFound(err) { - return false, nil - } - return false, err - } - - return true, nil -} - -// InstanceShutdownByProviderID returns true if the instances is in safe state to detach volumes -func (i *Instances) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) { - instanceID, err := instanceIDFromProviderID(providerID) - if err != nil { - return false, err - } - - server, err := servers.Get(i.compute, instanceID).Extract() - if err != nil { - return false, err - } - - // SHUTOFF is the only state where we can detach volumes immediately - if server.Status == instanceShutoff { - return true, nil - } - return false, nil -} - -// InstanceID returns the kubelet's cloud provider ID. -func (os *OpenStack) InstanceID() (string, error) { - if len(os.localInstanceID) == 0 { - id, err := readInstanceID(os.metadataOpts.SearchOrder) - if err != nil { - return "", err - } - os.localInstanceID = id - } - return os.localInstanceID, nil -} - -// InstanceID returns the cloud provider ID of the specified instance. -func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) { - srv, err := getServerByName(i.compute, name) - if err != nil { - if err == ErrNotFound { - return "", cloudprovider.InstanceNotFound - } - return "", err - } - // In the future it is possible to also return an endpoint as: - // / - return "/" + srv.ID, nil -} - -// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID -// This method will not be called from the node that is requesting this ID. i.e. metadata service -// and other local methods cannot be used here -func (i *Instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) { - instanceID, err := instanceIDFromProviderID(providerID) - - if err != nil { - return "", err - } - - server, err := servers.Get(i.compute, instanceID).Extract() - - if err != nil { - return "", err - } - - return srvInstanceType(server) -} - -// InstanceType returns the type of the specified instance. -func (i *Instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) { - srv, err := getServerByName(i.compute, name) - - if err != nil { - return "", err - } - - return srvInstanceType(srv) -} - -func srvInstanceType(srv *servers.Server) (string, error) { - keys := []string{"name", "id", "original_name"} - for _, key := range keys { - val, found := srv.Flavor[key] - if found { - flavor, ok := val.(string) - if ok { - return flavor, nil - } - } - } - return "", fmt.Errorf("flavor name/id not found") -} - -// instanceIDFromProviderID splits a provider's id and return instanceID. -// A providerID is build out of '${ProviderName}:///${instance-id}'which contains ':///'. -// See cloudprovider.GetInstanceProviderID and Instances.InstanceID. -func instanceIDFromProviderID(providerID string) (instanceID string, err error) { - // If Instances.InstanceID or cloudprovider.GetInstanceProviderID is changed, the regexp should be changed too. - var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `:///([^/]+)$`) - - matches := providerIDRegexp.FindStringSubmatch(providerID) - if len(matches) != 2 { - return "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"openstack:///InstanceID\"", providerID) - } - return matches[1], nil -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go deleted file mode 100644 index c735fbc0929..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_loadbalancer.go +++ /dev/null @@ -1,1578 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "fmt" - "reflect" - "strings" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" - v2monitors "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" - v2pools "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" - "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" - neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" - "github.com/gophercloud/gophercloud/pagination" - "k8s.io/klog/v2" - netutils "k8s.io/utils/net" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - cloudprovider "k8s.io/cloud-provider" - servicehelpers "k8s.io/cloud-provider/service/helpers" -) - -// Note: when creating a new Loadbalancer (VM), it can take some time before it is ready for use, -// this timeout is used for waiting until the Loadbalancer provisioning status goes to ACTIVE state. -const ( - // loadbalancerActive* is configuration of exponential backoff for - // going into ACTIVE loadbalancer provisioning status. Starting with 1 - // seconds, multiplying by 1.2 with each step and taking 19 steps at maximum - // it will time out after 128s, which roughly corresponds to 120s - loadbalancerActiveInitDelay = 1 * time.Second - loadbalancerActiveFactor = 1.2 - loadbalancerActiveSteps = 19 - - // loadbalancerDelete* is configuration of exponential backoff for - // waiting for delete operation to complete. Starting with 1 - // seconds, multiplying by 1.2 with each step and taking 13 steps at maximum - // it will time out after 32s, which roughly corresponds to 30s - loadbalancerDeleteInitDelay = 1 * time.Second - loadbalancerDeleteFactor = 1.2 - loadbalancerDeleteSteps = 13 - - activeStatus = "ACTIVE" - errorStatus = "ERROR" - - ServiceAnnotationLoadBalancerFloatingNetworkID = "loadbalancer.openstack.org/floating-network-id" - ServiceAnnotationLoadBalancerSubnetID = "loadbalancer.openstack.org/subnet-id" - - // ServiceAnnotationLoadBalancerInternal is the annotation used on the service - // to indicate that we want an internal loadbalancer service. - // If the value of ServiceAnnotationLoadBalancerInternal is false, it indicates that we want an external loadbalancer service. Default to false. - ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/openstack-internal-load-balancer" -) - -var _ cloudprovider.LoadBalancer = (*LbaasV2)(nil) - -// LbaasV2 is a LoadBalancer implementation for Neutron LBaaS v2 API -type LbaasV2 struct { - LoadBalancer -} - -func networkExtensions(client *gophercloud.ServiceClient) (map[string]bool, error) { - seen := make(map[string]bool) - - pager := extensions.List(client) - err := pager.EachPage(func(page pagination.Page) (bool, error) { - exts, err := extensions.ExtractExtensions(page) - if err != nil { - return false, err - } - for _, ext := range exts { - seen[ext.Alias] = true - } - return true, nil - }) - - return seen, err -} - -func getFloatingIPByPortID(client *gophercloud.ServiceClient, portID string) (*floatingips.FloatingIP, error) { - opts := floatingips.ListOpts{ - PortID: portID, - } - pager := floatingips.List(client, opts) - - floatingIPList := make([]floatingips.FloatingIP, 0, 1) - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - f, err := floatingips.ExtractFloatingIPs(page) - if err != nil { - return false, err - } - floatingIPList = append(floatingIPList, f...) - if len(floatingIPList) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - if isNotFound(err) { - return nil, ErrNotFound - } - return nil, err - } - - if len(floatingIPList) == 0 { - return nil, ErrNotFound - } else if len(floatingIPList) > 1 { - return nil, ErrMultipleResults - } - - return &floatingIPList[0], nil -} - -func getLoadbalancerByName(client *gophercloud.ServiceClient, name string) (*loadbalancers.LoadBalancer, error) { - opts := loadbalancers.ListOpts{ - Name: name, - } - pager := loadbalancers.List(client, opts) - - loadbalancerList := make([]loadbalancers.LoadBalancer, 0, 1) - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - v, err := loadbalancers.ExtractLoadBalancers(page) - if err != nil { - return false, err - } - loadbalancerList = append(loadbalancerList, v...) - if len(loadbalancerList) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - if isNotFound(err) { - return nil, ErrNotFound - } - return nil, err - } - - if len(loadbalancerList) == 0 { - return nil, ErrNotFound - } else if len(loadbalancerList) > 1 { - return nil, ErrMultipleResults - } - - return &loadbalancerList[0], nil -} - -func getListenersByLoadBalancerID(client *gophercloud.ServiceClient, id string) ([]listeners.Listener, error) { - var existingListeners []listeners.Listener - err := listeners.List(client, listeners.ListOpts{LoadbalancerID: id}).EachPage(func(page pagination.Page) (bool, error) { - listenerList, err := listeners.ExtractListeners(page) - if err != nil { - return false, err - } - for _, l := range listenerList { - for _, lb := range l.Loadbalancers { - if lb.ID == id { - existingListeners = append(existingListeners, l) - break - } - } - } - - return true, nil - }) - if err != nil { - return nil, err - } - - return existingListeners, nil -} - -// get listener for a port or nil if does not exist -func getListenerForPort(existingListeners []listeners.Listener, port v1.ServicePort) *listeners.Listener { - for _, l := range existingListeners { - if listeners.Protocol(l.Protocol) == toListenersProtocol(port.Protocol) && l.ProtocolPort == int(port.Port) { - return &l - } - } - - return nil -} - -// Get pool for a listener. A listener always has exactly one pool. -func getPoolByListenerID(client *gophercloud.ServiceClient, loadbalancerID string, listenerID string) (*v2pools.Pool, error) { - listenerPools := make([]v2pools.Pool, 0, 1) - err := v2pools.List(client, v2pools.ListOpts{LoadbalancerID: loadbalancerID}).EachPage(func(page pagination.Page) (bool, error) { - poolsList, err := v2pools.ExtractPools(page) - if err != nil { - return false, err - } - for _, p := range poolsList { - for _, l := range p.Listeners { - if l.ID == listenerID { - listenerPools = append(listenerPools, p) - } - } - } - if len(listenerPools) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - if isNotFound(err) { - return nil, ErrNotFound - } - return nil, err - } - - if len(listenerPools) == 0 { - return nil, ErrNotFound - } else if len(listenerPools) > 1 { - return nil, ErrMultipleResults - } - - return &listenerPools[0], nil -} - -func getMembersByPoolID(client *gophercloud.ServiceClient, id string) ([]v2pools.Member, error) { - var members []v2pools.Member - err := v2pools.ListMembers(client, id, v2pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) { - membersList, err := v2pools.ExtractMembers(page) - if err != nil { - return false, err - } - members = append(members, membersList...) - - return true, nil - }) - if err != nil { - return nil, err - } - - return members, nil -} - -// Check if a member exists for node -func memberExists(members []v2pools.Member, addr string, port int) bool { - for _, member := range members { - if member.Address == addr && member.ProtocolPort == port { - return true - } - } - - return false -} - -func popListener(existingListeners []listeners.Listener, id string) []listeners.Listener { - for i, existingListener := range existingListeners { - if existingListener.ID == id { - existingListeners[i] = existingListeners[len(existingListeners)-1] - existingListeners = existingListeners[:len(existingListeners)-1] - break - } - } - - return existingListeners -} - -func popMember(members []v2pools.Member, addr string, port int) []v2pools.Member { - for i, member := range members { - if member.Address == addr && member.ProtocolPort == port { - members[i] = members[len(members)-1] - members = members[:len(members)-1] - } - } - - return members -} - -func getSecurityGroupName(service *v1.Service) string { - securityGroupName := fmt.Sprintf("lb-sg-%s-%s-%s", service.UID, service.Namespace, service.Name) - //OpenStack requires that the name of a security group is shorter than 255 bytes. - if len(securityGroupName) > 255 { - securityGroupName = securityGroupName[:255] - } - - return securityGroupName -} - -func getSecurityGroupRules(client *gophercloud.ServiceClient, opts rules.ListOpts) ([]rules.SecGroupRule, error) { - - pager := rules.List(client, opts) - - var securityRules []rules.SecGroupRule - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - ruleList, err := rules.ExtractRules(page) - if err != nil { - return false, err - } - securityRules = append(securityRules, ruleList...) - return true, nil - }) - - if err != nil { - return nil, err - } - - return securityRules, nil -} - -func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) (string, error) { - backoff := wait.Backoff{ - Duration: loadbalancerActiveInitDelay, - Factor: loadbalancerActiveFactor, - Steps: loadbalancerActiveSteps, - } - - var provisioningStatus string - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - loadbalancer, err := loadbalancers.Get(client, loadbalancerID).Extract() - if err != nil { - return false, err - } - provisioningStatus = loadbalancer.ProvisioningStatus - if loadbalancer.ProvisioningStatus == activeStatus { - return true, nil - } else if loadbalancer.ProvisioningStatus == errorStatus { - return true, fmt.Errorf("loadbalancer has gone into ERROR state") - } else { - return false, nil - } - - }) - - if err == wait.ErrWaitTimeout { - err = fmt.Errorf("loadbalancer failed to go into ACTIVE provisioning status within alloted time") - } - return provisioningStatus, err -} - -func waitLoadbalancerDeleted(client *gophercloud.ServiceClient, loadbalancerID string) error { - backoff := wait.Backoff{ - Duration: loadbalancerDeleteInitDelay, - Factor: loadbalancerDeleteFactor, - Steps: loadbalancerDeleteSteps, - } - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - _, err := loadbalancers.Get(client, loadbalancerID).Extract() - if err != nil { - if isNotFound(err) { - return true, nil - } - return false, err - } - return false, nil - }) - - if err == wait.ErrWaitTimeout { - err = fmt.Errorf("loadbalancer failed to delete within the alloted time") - } - - return err -} - -func toRuleProtocol(protocol v1.Protocol) rules.RuleProtocol { - switch protocol { - case v1.ProtocolTCP: - return rules.ProtocolTCP - case v1.ProtocolUDP: - return rules.ProtocolUDP - default: - return rules.RuleProtocol(strings.ToLower(string(protocol))) - } -} - -func toListenersProtocol(protocol v1.Protocol) listeners.Protocol { - switch protocol { - case v1.ProtocolTCP: - return listeners.ProtocolTCP - default: - return listeners.Protocol(string(protocol)) - } -} - -func createNodeSecurityGroup(client *gophercloud.ServiceClient, nodeSecurityGroupID string, port int, protocol v1.Protocol, lbSecGroup string) error { - v4NodeSecGroupRuleCreateOpts := rules.CreateOpts{ - Direction: rules.DirIngress, - PortRangeMax: port, - PortRangeMin: port, - Protocol: toRuleProtocol(protocol), - RemoteGroupID: lbSecGroup, - SecGroupID: nodeSecurityGroupID, - EtherType: rules.EtherType4, - } - - v6NodeSecGroupRuleCreateOpts := rules.CreateOpts{ - Direction: rules.DirIngress, - PortRangeMax: port, - PortRangeMin: port, - Protocol: toRuleProtocol(protocol), - RemoteGroupID: lbSecGroup, - SecGroupID: nodeSecurityGroupID, - EtherType: rules.EtherType6, - } - - _, err := rules.Create(client, v4NodeSecGroupRuleCreateOpts).Extract() - - if err != nil { - return err - } - - _, err = rules.Create(client, v6NodeSecGroupRuleCreateOpts).Extract() - - if err != nil { - return err - } - return nil -} - -func (lbaas *LbaasV2) createLoadBalancer(service *v1.Service, name string, internalAnnotation bool) (*loadbalancers.LoadBalancer, error) { - createOpts := loadbalancers.CreateOpts{ - Name: name, - Description: fmt.Sprintf("Kubernetes external service %s", name), - VipSubnetID: lbaas.opts.SubnetID, - Provider: lbaas.opts.LBProvider, - } - - loadBalancerIP := service.Spec.LoadBalancerIP - if loadBalancerIP != "" && internalAnnotation { - createOpts.VipAddress = loadBalancerIP - } - - loadbalancer, err := loadbalancers.Create(lbaas.lb, createOpts).Extract() - if err != nil { - return nil, fmt.Errorf("error creating loadbalancer %v: %v", createOpts, err) - } - return loadbalancer, nil -} - -// GetLoadBalancer returns whether the specified load balancer exists and its status -func (lbaas *LbaasV2) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (*v1.LoadBalancerStatus, bool, error) { - loadBalancerName := lbaas.GetLoadBalancerName(ctx, clusterName, service) - loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) - if err == ErrNotFound { - return nil, false, nil - } - if loadbalancer == nil { - return nil, false, err - } - - status := &v1.LoadBalancerStatus{} - - portID := loadbalancer.VipPortID - if portID != "" { - floatIP, err := getFloatingIPByPortID(lbaas.network, portID) - if err != nil && err != ErrNotFound { - return nil, false, fmt.Errorf("error getting floating ip for port %s: %v", portID, err) - } - - if floatIP != nil { - status.Ingress = []v1.LoadBalancerIngress{{IP: floatIP.FloatingIP}} - } - } else { - status.Ingress = []v1.LoadBalancerIngress{{IP: loadbalancer.VipAddress}} - } - - return status, true, err -} - -// GetLoadBalancerName is an implementation of LoadBalancer.GetLoadBalancerName. -func (lbaas *LbaasV2) GetLoadBalancerName(ctx context.Context, clusterName string, service *v1.Service) string { - // TODO: replace DefaultLoadBalancerName to generate more meaningful loadbalancer names. - return cloudprovider.DefaultLoadBalancerName(service) -} - -// The LB needs to be configured with instance addresses on the same -// subnet as the LB (aka opts.SubnetID). Currently we're just -// guessing that the node's InternalIP is the right address. -// In case no InternalIP can be found, ExternalIP is tried. -// If neither InternalIP nor ExternalIP can be found an error is -// returned. -func nodeAddressForLB(node *v1.Node) (string, error) { - addrs := node.Status.Addresses - if len(addrs) == 0 { - return "", ErrNoAddressFound - } - - allowedAddrTypes := []v1.NodeAddressType{v1.NodeInternalIP, v1.NodeExternalIP} - - for _, allowedAddrType := range allowedAddrTypes { - for _, addr := range addrs { - if addr.Type == allowedAddrType { - return addr.Address, nil - } - } - } - - return "", ErrNoAddressFound -} - -// getStringFromServiceAnnotation searches a given v1.Service for a specific annotationKey and either returns the annotation's value or a specified defaultSetting -func getStringFromServiceAnnotation(service *v1.Service, annotationKey string, defaultSetting string) string { - klog.V(4).Infof("getStringFromServiceAnnotation(%v, %v, %v)", service, annotationKey, defaultSetting) - if annotationValue, ok := service.Annotations[annotationKey]; ok { - //if there is an annotation for this setting, set the "setting" var to it - // annotationValue can be empty, it is working as designed - // it makes possible for instance provisioning loadbalancer without floatingip - klog.V(4).Infof("Found a Service Annotation: %v = %v", annotationKey, annotationValue) - return annotationValue - } - //if there is no annotation, set "settings" var to the value from cloud config - klog.V(4).Infof("Could not find a Service Annotation; falling back on cloud-config setting: %v = %v", annotationKey, defaultSetting) - return defaultSetting -} - -// getSubnetIDForLB returns subnet-id for a specific node -func getSubnetIDForLB(compute *gophercloud.ServiceClient, node v1.Node) (string, error) { - ipAddress, err := nodeAddressForLB(&node) - if err != nil { - return "", err - } - - instanceID := node.Spec.ProviderID - if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { - instanceID = instanceID[(ind + 1):] - } - - interfaces, err := getAttachedInterfacesByID(compute, instanceID) - if err != nil { - return "", err - } - - for _, intf := range interfaces { - for _, fixedIP := range intf.FixedIPs { - if fixedIP.IPAddress == ipAddress { - return fixedIP.SubnetID, nil - } - } - } - - return "", ErrNotFound -} - -// getNodeSecurityGroupIDForLB lists node-security-groups for specific nodes -func getNodeSecurityGroupIDForLB(compute *gophercloud.ServiceClient, network *gophercloud.ServiceClient, nodes []*v1.Node) ([]string, error) { - secGroupNames := sets.NewString() - - for _, node := range nodes { - nodeName := types.NodeName(node.Name) - srv, err := getServerByName(compute, nodeName) - if err != nil { - return []string{}, err - } - - // use the first node-security-groups - // case 0: node1:SG1 node2:SG1 return SG1 - // case 1: node1:SG1 node2:SG2 return SG1,SG2 - // case 2: node1:SG1,SG2 node2:SG3,SG4 return SG1,SG3 - // case 3: node1:SG1,SG2 node2:SG2,SG3 return SG1,SG2 - secGroupNames.Insert(srv.SecurityGroups[0]["name"].(string)) - } - - secGroupIDs := make([]string, secGroupNames.Len()) - for i, name := range secGroupNames.List() { - secGroupID, err := groups.IDFromName(network, name) - if err != nil { - return []string{}, err - } - secGroupIDs[i] = secGroupID - } - - return secGroupIDs, nil -} - -// isSecurityGroupNotFound return true while 'err' is object of gophercloud.ErrResourceNotFound -func isSecurityGroupNotFound(err error) bool { - errType := reflect.TypeOf(err).String() - errTypeSlice := strings.Split(errType, ".") - errTypeValue := "" - if len(errTypeSlice) != 0 { - errTypeValue = errTypeSlice[len(errTypeSlice)-1] - } - if errTypeValue == "ErrResourceNotFound" { - return true - } - - return false -} - -// getFloatingNetworkIDForLB returns a floating-network-id for cluster. -func getFloatingNetworkIDForLB(client *gophercloud.ServiceClient) (string, error) { - var floatingNetworkIds []string - - type NetworkWithExternalExt struct { - networks.Network - external.NetworkExternalExt - } - - err := networks.List(client, networks.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { - var externalNetwork []NetworkWithExternalExt - err := networks.ExtractNetworksInto(page, &externalNetwork) - if err != nil { - return false, err - } - - for _, externalNet := range externalNetwork { - if externalNet.External { - floatingNetworkIds = append(floatingNetworkIds, externalNet.ID) - } - } - - if len(floatingNetworkIds) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - if isNotFound(err) { - return "", ErrNotFound - } - - if err == ErrMultipleResults { - klog.V(4).Infof("find multiple external networks, pick the first one when there are no explicit configuration.") - return floatingNetworkIds[0], nil - } - return "", err - } - - if len(floatingNetworkIds) == 0 { - return "", ErrNotFound - } - - return floatingNetworkIds[0], nil -} - -// TODO: This code currently ignores 'region' and always creates a -// loadbalancer in only the current OpenStack region. We should take -// a list of regions (from config) and query/create loadbalancers in -// each region. - -// EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. -func (lbaas *LbaasV2) EnsureLoadBalancer(ctx context.Context, clusterName string, apiService *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { - klog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", clusterName, apiService.Namespace, apiService.Name, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, nodes, apiService.Annotations) - - if len(nodes) == 0 { - return nil, fmt.Errorf("there are no available nodes for LoadBalancer service %s/%s", apiService.Namespace, apiService.Name) - } - - lbaas.opts.SubnetID = getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerSubnetID, lbaas.opts.SubnetID) - if len(lbaas.opts.SubnetID) == 0 { - // Get SubnetID automatically. - // The LB needs to be configured with instance addresses on the same subnet, so get SubnetID by one node. - subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0]) - if err != nil { - klog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - return nil, fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ - "and failed to find subnet-id from OpenStack: %v", apiService.Namespace, apiService.Name, err) - } - lbaas.opts.SubnetID = subnetID - } - - ports := apiService.Spec.Ports - if len(ports) == 0 { - return nil, fmt.Errorf("no ports provided to openstack load balancer") - } - - floatingPool := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerFloatingNetworkID, lbaas.opts.FloatingNetworkID) - if len(floatingPool) == 0 { - var err error - floatingPool, err = getFloatingNetworkIDForLB(lbaas.network) - if err != nil { - klog.Warningf("Failed to find floating-network-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - } - - var internalAnnotation bool - internal := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerInternal, "false") - switch internal { - case "true": - klog.V(4).Info("Ensure an internal loadbalancer service.") - internalAnnotation = true - case "false": - if len(floatingPool) != 0 { - klog.V(4).Infof("Ensure an external loadbalancer service, using floatingPool: %v", floatingPool) - internalAnnotation = false - } else { - return nil, fmt.Errorf("floating-network-id or loadbalancer.openstack.org/floating-network-id should be specified when ensuring an external loadbalancer service") - } - default: - return nil, fmt.Errorf("unknown service.beta.kubernetes.io/openstack-internal-load-balancer annotation: %v, specify \"true\" or \"false\" ", - internal) - } - - // Check for TCP protocol on each port - // TODO: Convert all error messages to use an event recorder - for _, port := range ports { - if port.Protocol != v1.ProtocolTCP { - return nil, fmt.Errorf("only TCP LoadBalancer is supported for openstack load balancers") - } - } - - sourceRanges, err := servicehelpers.GetLoadBalancerSourceRanges(apiService) - if err != nil { - return nil, fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - - if !servicehelpers.IsAllowAll(sourceRanges) && !lbaas.opts.ManageSecurityGroups { - return nil, fmt.Errorf("source range restrictions are not supported for openstack load balancers without managing security groups") - } - - affinity := apiService.Spec.SessionAffinity - var persistence *v2pools.SessionPersistence - switch affinity { - case v1.ServiceAffinityNone: - persistence = nil - case v1.ServiceAffinityClientIP: - persistence = &v2pools.SessionPersistence{Type: "SOURCE_IP"} - default: - return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity) - } - - name := lbaas.GetLoadBalancerName(ctx, clusterName, apiService) - loadbalancer, err := getLoadbalancerByName(lbaas.lb, name) - if err != nil { - if err != ErrNotFound { - return nil, fmt.Errorf("error getting loadbalancer %s: %v", name, err) - } - klog.V(2).Infof("Creating loadbalancer %s", name) - loadbalancer, err = lbaas.createLoadBalancer(apiService, name, internalAnnotation) - if err != nil { - // Unknown error, retry later - return nil, fmt.Errorf("error creating loadbalancer %s: %v", name, err) - } - } else { - klog.V(2).Infof("LoadBalancer %s already exists", name) - } - - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - - lbmethod := v2pools.LBMethod(lbaas.opts.LBMethod) - if lbmethod == "" { - lbmethod = v2pools.LBMethodRoundRobin - } - - oldListeners, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("error getting LB %s listeners: %v", name, err) - } - for portIndex, port := range ports { - listener := getListenerForPort(oldListeners, port) - if listener == nil { - klog.V(4).Infof("Creating listener for port %d", int(port.Port)) - listener, err = listeners.Create(lbaas.lb, listeners.CreateOpts{ - Name: fmt.Sprintf("listener_%s_%d", name, portIndex), - Protocol: listeners.Protocol(port.Protocol), - ProtocolPort: int(port.Port), - LoadbalancerID: loadbalancer.ID, - }).Extract() - if err != nil { - // Unknown error, retry later - return nil, fmt.Errorf("error creating LB listener: %v", err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - klog.V(4).Infof("Listener for %s port %d: %s", string(port.Protocol), int(port.Port), listener.ID) - - // After all ports have been processed, remaining listeners are removed as obsolete. - // Pop valid listeners. - oldListeners = popListener(oldListeners, listener.ID) - pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) - if err != nil && err != ErrNotFound { - // Unknown error, retry later - return nil, fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err) - } - if pool == nil { - klog.V(4).Infof("Creating pool for listener %s", listener.ID) - pool, err = v2pools.Create(lbaas.lb, v2pools.CreateOpts{ - Name: fmt.Sprintf("pool_%s_%d", name, portIndex), - Protocol: v2pools.Protocol(port.Protocol), - LBMethod: lbmethod, - ListenerID: listener.ID, - Persistence: persistence, - }).Extract() - if err != nil { - // Unknown error, retry later - return nil, fmt.Errorf("error creating pool for listener %s: %v", listener.ID, err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - - } - - klog.V(4).Infof("Pool for listener %s: %s", listener.ID, pool.ID) - members, err := getMembersByPoolID(lbaas.lb, pool.ID) - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error getting pool members %s: %v", pool.ID, err) - } - for _, node := range nodes { - addr, err := nodeAddressForLB(node) - if err != nil { - if err == ErrNotFound { - // Node failure, do not create member - klog.Warningf("Failed to create LB pool member for node %s: %v", node.Name, err) - continue - } else { - return nil, fmt.Errorf("error getting address for node %s: %v", node.Name, err) - } - } - - if !memberExists(members, addr, int(port.NodePort)) { - klog.V(4).Infof("Creating member for pool %s", pool.ID) - _, err := v2pools.CreateMember(lbaas.lb, pool.ID, v2pools.CreateMemberOpts{ - Name: fmt.Sprintf("member_%s_%d_%s", name, portIndex, node.Name), - ProtocolPort: int(port.NodePort), - Address: addr, - SubnetID: lbaas.opts.SubnetID, - }).Extract() - if err != nil { - return nil, fmt.Errorf("error creating LB pool member for node: %s, %v", node.Name, err) - } - - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } else { - // After all members have been processed, remaining members are deleted as obsolete. - members = popMember(members, addr, int(port.NodePort)) - } - - klog.V(4).Infof("Ensured pool %s has member for %s at %s", pool.ID, node.Name, addr) - } - - // Delete obsolete members for this pool - for _, member := range members { - klog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID, pool.ID, member.Address) - err := v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - monitorID := pool.MonitorID - if monitorID == "" && lbaas.opts.CreateMonitor { - klog.V(4).Infof("Creating monitor for pool %s", pool.ID) - monitor, err := v2monitors.Create(lbaas.lb, v2monitors.CreateOpts{ - Name: fmt.Sprintf("monitor_%s_%d", name, portIndex), - PoolID: pool.ID, - Type: string(port.Protocol), - Delay: int(lbaas.opts.MonitorDelay.Duration.Seconds()), - Timeout: int(lbaas.opts.MonitorTimeout.Duration.Seconds()), - MaxRetries: int(lbaas.opts.MonitorMaxRetries), - }).Extract() - if err != nil { - return nil, fmt.Errorf("error creating LB pool healthmonitor: %v", err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - monitorID = monitor.ID - } else if !lbaas.opts.CreateMonitor { - klog.V(4).Infof("Do not create monitor for pool %s when create-monitor is false", pool.ID) - } - - if monitorID != "" { - klog.V(4).Infof("Monitor for pool %s: %s", pool.ID, monitorID) - } - } - - // All remaining listeners are obsolete, delete - for _, listener := range oldListeners { - klog.V(4).Infof("Deleting obsolete listener %s:", listener.ID) - // get pool for listener - pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) - if err != nil && err != ErrNotFound { - return nil, fmt.Errorf("error getting pool for obsolete listener %s: %v", listener.ID, err) - } - if pool != nil { - // get and delete monitor - monitorID := pool.MonitorID - if monitorID != "" { - klog.V(4).Infof("Deleting obsolete monitor %s for pool %s", monitorID, pool.ID) - err = v2monitors.Delete(lbaas.lb, monitorID).ExtractErr() - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error deleting obsolete monitor %s for pool %s: %v", monitorID, pool.ID, err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - // get and delete pool members - members, err := getMembersByPoolID(lbaas.lb, pool.ID) - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error getting members for pool %s: %v", pool.ID, err) - } - for _, member := range members { - klog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID, pool.ID, member.Address) - err := v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - klog.V(4).Infof("Deleting obsolete pool %s for listener %s", pool.ID, listener.ID) - // delete pool - err = v2pools.Delete(lbaas.lb, pool.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error deleting obsolete pool %s for listener %s: %v", pool.ID, listener.ID, err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - // delete listener - err = listeners.Delete(lbaas.lb, listener.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("error deleteting obsolete listener: %v", err) - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - klog.V(2).Infof("Deleted obsolete listener: %s", listener.ID) - } - - portID := loadbalancer.VipPortID - floatIP, err := getFloatingIPByPortID(lbaas.network, portID) - if err != nil && err != ErrNotFound { - return nil, fmt.Errorf("error getting floating ip for port %s: %v", portID, err) - } - if floatIP == nil && floatingPool != "" && !internalAnnotation { - klog.V(4).Infof("Creating floating ip for loadbalancer %s port %s", loadbalancer.ID, portID) - floatIPOpts := floatingips.CreateOpts{ - FloatingNetworkID: floatingPool, - PortID: portID, - } - - loadBalancerIP := apiService.Spec.LoadBalancerIP - if loadBalancerIP != "" { - floatIPOpts.FloatingIP = loadBalancerIP - } - - floatIP, err = floatingips.Create(lbaas.network, floatIPOpts).Extract() - if err != nil { - return nil, fmt.Errorf("error creating LB floatingip %+v: %v", floatIPOpts, err) - } - } - - status := &v1.LoadBalancerStatus{} - - if floatIP != nil { - status.Ingress = []v1.LoadBalancerIngress{{IP: floatIP.FloatingIP}} - } else { - status.Ingress = []v1.LoadBalancerIngress{{IP: loadbalancer.VipAddress}} - } - - if lbaas.opts.ManageSecurityGroups { - err := lbaas.ensureSecurityGroup(clusterName, apiService, nodes, loadbalancer) - if err != nil { - return status, fmt.Errorf("Error reconciling security groups for LB service %v/%v: %v", apiService.Namespace, apiService.Name, err) - } - } - - return status, nil -} - -// ensureSecurityGroup ensures security group exist for specific loadbalancer service. -// Creating security group for specific loadbalancer service when it does not exist. -func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Service, nodes []*v1.Node, loadbalancer *loadbalancers.LoadBalancer) error { - // find node-security-group for service - var err error - if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { - lbaas.opts.NodeSecurityGroupIDs, err = getNodeSecurityGroupIDForLB(lbaas.compute, lbaas.network, nodes) - if err != nil { - return fmt.Errorf("failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - } - klog.V(4).Infof("find node-security-group %v for loadbalancer service %s/%s", lbaas.opts.NodeSecurityGroupIDs, apiService.Namespace, apiService.Name) - - // get service ports - ports := apiService.Spec.Ports - if len(ports) == 0 { - return fmt.Errorf("no ports provided to openstack load balancer") - } - - // get service source ranges - sourceRanges, err := servicehelpers.GetLoadBalancerSourceRanges(apiService) - if err != nil { - return fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - - // ensure security group for LB - lbSecGroupName := getSecurityGroupName(apiService) - lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) - if err != nil { - // If the security group of LB not exist, create it later - if isSecurityGroupNotFound(err) { - lbSecGroupID = "" - } else { - return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) - } - } - if len(lbSecGroupID) == 0 { - // create security group - lbSecGroupCreateOpts := groups.CreateOpts{ - Name: getSecurityGroupName(apiService), - Description: fmt.Sprintf("Security Group for %s/%s Service LoadBalancer in cluster %s", apiService.Namespace, apiService.Name, clusterName), - } - - lbSecGroup, err := groups.Create(lbaas.network, lbSecGroupCreateOpts).Extract() - if err != nil { - return fmt.Errorf("failed to create Security Group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - lbSecGroupID = lbSecGroup.ID - - //add rule in security group - for _, port := range ports { - for _, sourceRange := range sourceRanges.StringSlice() { - ethertype := rules.EtherType4 - network, _, err := netutils.ParseCIDRSloppy(sourceRange) - - if err != nil { - return fmt.Errorf("error parsing source range %s as a CIDR: %v", sourceRange, err) - } - - if network.To4() == nil { - ethertype = rules.EtherType6 - } - - lbSecGroupRuleCreateOpts := rules.CreateOpts{ - Direction: rules.DirIngress, - PortRangeMax: int(port.Port), - PortRangeMin: int(port.Port), - Protocol: toRuleProtocol(port.Protocol), - RemoteIPPrefix: sourceRange, - SecGroupID: lbSecGroup.ID, - EtherType: ethertype, - } - - _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() - - if err != nil { - return fmt.Errorf("error occurred creating rule for SecGroup %s: %v", lbSecGroup.ID, err) - } - } - } - - lbSecGroupRuleCreateOpts := rules.CreateOpts{ - Direction: rules.DirIngress, - PortRangeMax: 4, // ICMP: Code - Values for ICMP "Destination Unreachable: Fragmentation Needed and Don't Fragment was Set" - PortRangeMin: 3, // ICMP: Type - Protocol: rules.ProtocolICMP, - RemoteIPPrefix: "0.0.0.0/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all - SecGroupID: lbSecGroup.ID, - EtherType: rules.EtherType4, - } - - _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() - - if err != nil { - return fmt.Errorf("error occurred creating rule for SecGroup %s: %v", lbSecGroup.ID, err) - } - - lbSecGroupRuleCreateOpts = rules.CreateOpts{ - Direction: rules.DirIngress, - PortRangeMax: 0, // ICMP: Code - Values for ICMP "Packet Too Big" - PortRangeMin: 2, // ICMP: Type - Protocol: rules.ProtocolICMP, - RemoteIPPrefix: "::/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all - SecGroupID: lbSecGroup.ID, - EtherType: rules.EtherType6, - } - - _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() - if err != nil { - return fmt.Errorf("error occurred creating rule for SecGroup %s: %v", lbSecGroup.ID, err) - } - - // get security groups of port - portID := loadbalancer.VipPortID - port, err := getPortByID(lbaas.network, portID) - if err != nil { - return err - } - - // ensure the vip port has the security groups - found := false - for _, portSecurityGroups := range port.SecurityGroups { - if portSecurityGroups == lbSecGroup.ID { - found = true - break - } - } - - // update loadbalancer vip port - if !found { - port.SecurityGroups = append(port.SecurityGroups, lbSecGroup.ID) - updateOpts := neutronports.UpdateOpts{SecurityGroups: &port.SecurityGroups} - res := neutronports.Update(lbaas.network, portID, updateOpts) - if res.Err != nil { - msg := fmt.Sprintf("Error occurred updating port %s for loadbalancer service %s/%s: %v", portID, apiService.Namespace, apiService.Name, res.Err) - return fmt.Errorf(msg) - } - } - } - - // ensure rules for every node security group - for _, port := range ports { - for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { - opts := rules.ListOpts{ - Direction: string(rules.DirIngress), - SecGroupID: nodeSecurityGroupID, - RemoteGroupID: lbSecGroupID, - PortRangeMax: int(port.NodePort), - PortRangeMin: int(port.NodePort), - Protocol: string(port.Protocol), - } - secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) - if err != nil && !isNotFound(err) { - msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) - return fmt.Errorf(msg) - } - if len(secGroupRules) != 0 { - // Do not add rule when find rules for remote group in the Node Security Group - continue - } - - // Add the rules in the Node Security Group - err = createNodeSecurityGroup(lbaas.network, nodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroupID) - if err != nil { - return fmt.Errorf("error occurred creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - } - } - - return nil -} - -// UpdateLoadBalancer updates hosts under the specified load balancer. -func (lbaas *LbaasV2) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error { - loadBalancerName := lbaas.GetLoadBalancerName(ctx, clusterName, service) - klog.V(4).Infof("UpdateLoadBalancer(%v, %v, %v)", clusterName, loadBalancerName, nodes) - - lbaas.opts.SubnetID = getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerSubnetID, lbaas.opts.SubnetID) - if len(lbaas.opts.SubnetID) == 0 && len(nodes) > 0 { - // Get SubnetID automatically. - // The LB needs to be configured with instance addresses on the same subnet, so get SubnetID by one node. - subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0]) - if err != nil { - klog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) - return fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ - "and failed to find subnet-id from OpenStack: %v", service.Namespace, service.Name, err) - } - lbaas.opts.SubnetID = subnetID - } - - ports := service.Spec.Ports - if len(ports) == 0 { - return fmt.Errorf("no ports provided to openstack load balancer") - } - - loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) - if err != nil { - return err - } - if loadbalancer == nil { - return fmt.Errorf("loadbalancer %s does not exist", loadBalancerName) - } - - // Get all listeners for this loadbalancer, by "port key". - type portKey struct { - Protocol listeners.Protocol - Port int - } - var listenerIDs []string - lbListeners := make(map[portKey]listeners.Listener) - allListeners, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("error getting listeners for LB %s: %v", loadBalancerName, err) - } - for _, l := range allListeners { - key := portKey{Protocol: listeners.Protocol(l.Protocol), Port: l.ProtocolPort} - lbListeners[key] = l - listenerIDs = append(listenerIDs, l.ID) - } - - // Get all pools for this loadbalancer, by listener ID. - lbPools := make(map[string]v2pools.Pool) - for _, listenerID := range listenerIDs { - pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listenerID) - if err != nil { - return fmt.Errorf("error getting pool for listener %s: %v", listenerID, err) - } - lbPools[listenerID] = *pool - } - - // Compose Set of member (addresses) that _should_ exist - addrs := make(map[string]*v1.Node) - for _, node := range nodes { - addr, err := nodeAddressForLB(node) - if err != nil { - return err - } - addrs[addr] = node - } - - // Check for adding/removing members associated with each port - for portIndex, port := range ports { - // Get listener associated with this port - listener, ok := lbListeners[portKey{ - Protocol: toListenersProtocol(port.Protocol), - Port: int(port.Port), - }] - if !ok { - return fmt.Errorf("loadbalancer %s does not contain required listener for port %d and protocol %s", loadBalancerName, port.Port, port.Protocol) - } - - // Get pool associated with this listener - pool, ok := lbPools[listener.ID] - if !ok { - return fmt.Errorf("loadbalancer %s does not contain required pool for listener %s", loadBalancerName, listener.ID) - } - - // Find existing pool members (by address) for this port - getMembers, err := getMembersByPoolID(lbaas.lb, pool.ID) - if err != nil { - return fmt.Errorf("error getting pool members %s: %v", pool.ID, err) - } - members := make(map[string]v2pools.Member) - for _, member := range getMembers { - members[member.Address] = member - } - - // Add any new members for this port - for addr, node := range addrs { - if _, ok := members[addr]; ok && members[addr].ProtocolPort == int(port.NodePort) { - // Already exists, do not create member - continue - } - _, err := v2pools.CreateMember(lbaas.lb, pool.ID, v2pools.CreateMemberOpts{ - Name: fmt.Sprintf("member_%s_%d_%s", loadbalancer.Name, portIndex, node.Name), - Address: addr, - ProtocolPort: int(port.NodePort), - SubnetID: lbaas.opts.SubnetID, - }).Extract() - if err != nil { - return err - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - // Remove any old members for this port - for _, member := range members { - if _, ok := addrs[member.Address]; ok && member.ProtocolPort == int(port.NodePort) { - // Still present, do not delete member - continue - } - err = v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - } - - if lbaas.opts.ManageSecurityGroups { - err := lbaas.updateSecurityGroup(clusterName, service, nodes, loadbalancer) - if err != nil { - return fmt.Errorf("failed to update Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) - } - } - - return nil -} - -// updateSecurityGroup updating security group for specific loadbalancer service. -func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Service, nodes []*v1.Node, loadbalancer *loadbalancers.LoadBalancer) error { - originalNodeSecurityGroupIDs := lbaas.opts.NodeSecurityGroupIDs - - var err error - lbaas.opts.NodeSecurityGroupIDs, err = getNodeSecurityGroupIDForLB(lbaas.compute, lbaas.network, nodes) - if err != nil { - return fmt.Errorf("failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - klog.V(4).Infof("find node-security-group %v for loadbalancer service %s/%s", lbaas.opts.NodeSecurityGroupIDs, apiService.Namespace, apiService.Name) - - original := sets.NewString(originalNodeSecurityGroupIDs...) - current := sets.NewString(lbaas.opts.NodeSecurityGroupIDs...) - removals := original.Difference(current) - - // Generate Name - lbSecGroupName := getSecurityGroupName(apiService) - lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) - if err != nil { - return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) - } - - ports := apiService.Spec.Ports - if len(ports) == 0 { - return fmt.Errorf("no ports provided to openstack load balancer") - } - - for _, port := range ports { - for removal := range removals { - // Delete the rules in the Node Security Group - opts := rules.ListOpts{ - Direction: string(rules.DirIngress), - SecGroupID: removal, - RemoteGroupID: lbSecGroupID, - PortRangeMax: int(port.NodePort), - PortRangeMin: int(port.NodePort), - Protocol: string(port.Protocol), - } - secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) - if err != nil && !isNotFound(err) { - return fmt.Errorf("error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, removal, err) - } - - for _, rule := range secGroupRules { - res := rules.Delete(lbaas.network, rule.ID) - if res.Err != nil && !isNotFound(res.Err) { - return fmt.Errorf("error occurred deleting security group rule: %s: %v", rule.ID, res.Err) - } - } - } - - for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { - opts := rules.ListOpts{ - Direction: string(rules.DirIngress), - SecGroupID: nodeSecurityGroupID, - RemoteGroupID: lbSecGroupID, - PortRangeMax: int(port.NodePort), - PortRangeMin: int(port.NodePort), - Protocol: string(port.Protocol), - } - secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) - if err != nil && !isNotFound(err) { - return fmt.Errorf("error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) - } - if len(secGroupRules) != 0 { - // Do not add rule when find rules for remote group in the Node Security Group - continue - } - - // Add the rules in the Node Security Group - err = createNodeSecurityGroup(lbaas.network, nodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroupID) - if err != nil { - return fmt.Errorf("error occurred creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - } - } - } - - return nil -} - -// EnsureLoadBalancerDeleted deletes the specified load balancer -func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error { - loadBalancerName := lbaas.GetLoadBalancerName(ctx, clusterName, service) - klog.V(4).Infof("EnsureLoadBalancerDeleted(%v, %v)", clusterName, loadBalancerName) - - loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) - if err != nil && err != ErrNotFound { - return err - } - if loadbalancer == nil { - return nil - } - - if loadbalancer.VipPortID != "" { - portID := loadbalancer.VipPortID - floatingIP, err := getFloatingIPByPortID(lbaas.network, portID) - if err != nil && err != ErrNotFound { - return err - } - if floatingIP != nil { - err = floatingips.Delete(lbaas.network, floatingIP.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - } - } - - // get all listeners associated with this loadbalancer - listenerList, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("error getting LB %s listeners: %v", loadbalancer.ID, err) - } - - // get all pools (and health monitors) associated with this loadbalancer - var poolIDs []string - var monitorIDs []string - for _, listener := range listenerList { - pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) - if err != nil && err != ErrNotFound { - return fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err) - } - if pool != nil { - poolIDs = append(poolIDs, pool.ID) - // If create-monitor of cloud-config is false, pool has not monitor. - if pool.MonitorID != "" { - monitorIDs = append(monitorIDs, pool.MonitorID) - } - } - } - - // delete all monitors - for _, monitorID := range monitorIDs { - err := v2monitors.Delete(lbaas.lb, monitorID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - // delete all members and pools - for _, poolID := range poolIDs { - // get members for current pool - membersList, err := getMembersByPoolID(lbaas.lb, poolID) - if err != nil && !isNotFound(err) { - return fmt.Errorf("error getting pool members %s: %v", poolID, err) - } - // delete all members for this pool - for _, member := range membersList { - err := v2pools.DeleteMember(lbaas.lb, poolID, member.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - // delete pool - err = v2pools.Delete(lbaas.lb, poolID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - // delete all listeners - for _, listener := range listenerList { - err := listeners.Delete(lbaas.lb, listener.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) - } - } - - // delete loadbalancer - err = loadbalancers.Delete(lbaas.lb, loadbalancer.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - err = waitLoadbalancerDeleted(lbaas.lb, loadbalancer.ID) - if err != nil { - return fmt.Errorf("failed to delete loadbalancer: %v", err) - } - - // Delete the Security Group - if lbaas.opts.ManageSecurityGroups { - err := lbaas.EnsureSecurityGroupDeleted(clusterName, service) - if err != nil { - return fmt.Errorf("failed to delete Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) - } - } - - return nil -} - -// EnsureSecurityGroupDeleted deleting security group for specific loadbalancer service. -func (lbaas *LbaasV2) EnsureSecurityGroupDeleted(clusterName string, service *v1.Service) error { - // Generate Name - lbSecGroupName := getSecurityGroupName(service) - lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) - if err != nil { - if isSecurityGroupNotFound(err) { - // It is OK when the security group has been deleted by others. - return nil - } - return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) - } - - lbSecGroup := groups.Delete(lbaas.network, lbSecGroupID) - if lbSecGroup.Err != nil && !isNotFound(lbSecGroup.Err) { - return lbSecGroup.Err - } - - if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { - // Just happen when nodes have not Security Group, or should not happen - // UpdateLoadBalancer and EnsureLoadBalancer can set lbaas.opts.NodeSecurityGroupIDs when it is empty - // And service controller call UpdateLoadBalancer to set lbaas.opts.NodeSecurityGroupIDs when controller manager service is restarted. - klog.Warningf("Can not find node-security-group from all the nodes of this cluster when delete loadbalancer service %s/%s", - service.Namespace, service.Name) - } else { - // Delete the rules in the Node Security Group - for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { - opts := rules.ListOpts{ - SecGroupID: nodeSecurityGroupID, - RemoteGroupID: lbSecGroupID, - } - secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) - - if err != nil && !isNotFound(err) { - msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) - return fmt.Errorf(msg) - } - - for _, rule := range secGroupRules { - res := rules.Delete(lbaas.network, rule.ID) - if res.Err != nil && !isNotFound(res.Err) { - return fmt.Errorf("error occurred deleting security group rule: %s: %v", rule.ID, res.Err) - } - } - } - } - - return nil -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go deleted file mode 100644 index 8d84a7758b6..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_metrics.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2017 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "sync" - - "k8s.io/component-base/metrics" - "k8s.io/component-base/metrics/legacyregistry" -) - -const ( - openstackSubsystem = "openstack" - openstackOperationKey = "cloudprovider_openstack_api_request_duration_seconds" - openstackOperationErrorKey = "cloudprovider_openstack_api_request_errors" -) - -var ( - openstackOperationsLatency = metrics.NewHistogramVec( - &metrics.HistogramOpts{ - Subsystem: openstackSubsystem, - Name: openstackOperationKey, - Help: "Latency of openstack api call", - StabilityLevel: metrics.ALPHA, - }, - []string{"request"}, - ) - - openstackAPIRequestErrors = metrics.NewCounterVec( - &metrics.CounterOpts{ - Subsystem: openstackSubsystem, - Name: openstackOperationErrorKey, - Help: "Cumulative number of openstack Api call errors", - StabilityLevel: metrics.ALPHA, - }, - []string{"request"}, - ) -) - -var registerOnce sync.Once - -func registerMetrics() { - registerOnce.Do(func() { - legacyregistry.MustRegister(openstackOperationsLatency) - legacyregistry.MustRegister(openstackAPIRequestErrors) - }) -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go deleted file mode 100644 index 03163fcf048..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes.go +++ /dev/null @@ -1,347 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "errors" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" - neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" - - "k8s.io/apimachinery/pkg/types" - cloudprovider "k8s.io/cloud-provider" - "k8s.io/klog/v2" - netutils "k8s.io/utils/net" -) - -var errNoRouterID = errors.New("router-id not set in cloud provider config") - -var _ cloudprovider.Routes = (*Routes)(nil) - -// Routes implements the cloudprovider.Routes for OpenStack clouds -type Routes struct { - compute *gophercloud.ServiceClient - network *gophercloud.ServiceClient - opts RouterOpts -} - -// NewRoutes creates a new instance of Routes -func NewRoutes(compute *gophercloud.ServiceClient, network *gophercloud.ServiceClient, opts RouterOpts) (cloudprovider.Routes, error) { - if opts.RouterID == "" { - return nil, errNoRouterID - } - - return &Routes{ - compute: compute, - network: network, - opts: opts, - }, nil -} - -// ListRoutes lists all managed routes that belong to the specified clusterName -func (r *Routes) ListRoutes(ctx context.Context, clusterName string) ([]*cloudprovider.Route, error) { - klog.V(4).Infof("ListRoutes(%v)", clusterName) - - nodeNamesByAddr := make(map[string]types.NodeName) - err := foreachServer(r.compute, servers.ListOpts{}, func(srv *servers.Server) (bool, error) { - addrs, err := nodeAddresses(srv) - if err != nil { - return false, err - } - - name := mapServerToNodeName(srv) - for _, addr := range addrs { - nodeNamesByAddr[addr.Address] = name - } - - return true, nil - }) - if err != nil { - return nil, err - } - - router, err := routers.Get(r.network, r.opts.RouterID).Extract() - if err != nil { - return nil, err - } - - var routes []*cloudprovider.Route - for _, item := range router.Routes { - nodeName, foundNode := nodeNamesByAddr[item.NextHop] - if !foundNode { - nodeName = types.NodeName(item.NextHop) - } - route := cloudprovider.Route{ - Name: item.DestinationCIDR, - TargetNode: nodeName, //contains the nexthop address if node was not found - Blackhole: !foundNode, - DestinationCIDR: item.DestinationCIDR, - } - routes = append(routes, &route) - } - - return routes, nil -} - -func updateRoutes(network *gophercloud.ServiceClient, router *routers.Router, newRoutes []routers.Route) (func(), error) { - origRoutes := router.Routes // shallow copy - - _, err := routers.Update(network, router.ID, routers.UpdateOpts{ - Routes: newRoutes, - }).Extract() - if err != nil { - return nil, err - } - - unwinder := func() { - klog.V(4).Infof("Reverting routes change to router %v", router.ID) - _, err := routers.Update(network, router.ID, routers.UpdateOpts{ - Routes: origRoutes, - }).Extract() - if err != nil { - klog.Warningf("Unable to reset routes during error unwind: %v", err) - } - } - - return unwinder, nil -} - -func updateAllowedAddressPairs(network *gophercloud.ServiceClient, port *neutronports.Port, newPairs []neutronports.AddressPair) (func(), error) { - origPairs := port.AllowedAddressPairs // shallow copy - - _, err := neutronports.Update(network, port.ID, neutronports.UpdateOpts{ - AllowedAddressPairs: &newPairs, - }).Extract() - if err != nil { - return nil, err - } - - unwinder := func() { - klog.V(4).Infof("Reverting allowed-address-pairs change to port %v", port.ID) - _, err := neutronports.Update(network, port.ID, neutronports.UpdateOpts{ - AllowedAddressPairs: &origPairs, - }).Extract() - if err != nil { - klog.Warningf("Unable to reset allowed-address-pairs during error unwind: %v", err) - } - } - - return unwinder, nil -} - -// CreateRoute creates the described managed route -func (r *Routes) CreateRoute(ctx context.Context, clusterName string, nameHint string, route *cloudprovider.Route) error { - klog.V(4).Infof("CreateRoute(%v, %v, %v)", clusterName, nameHint, route) - - onFailure := newCaller() - - ip, _, _ := netutils.ParseCIDRSloppy(route.DestinationCIDR) - isCIDRv6 := ip.To4() == nil - addr, err := getAddressByName(r.compute, route.TargetNode, isCIDRv6) - - if err != nil { - return err - } - - klog.V(4).Infof("Using nexthop %v for node %v", addr, route.TargetNode) - - router, err := routers.Get(r.network, r.opts.RouterID).Extract() - if err != nil { - return err - } - - routes := router.Routes - - for _, item := range routes { - if item.DestinationCIDR == route.DestinationCIDR && item.NextHop == addr { - klog.V(4).Infof("Skipping existing route: %v", route) - return nil - } - } - - routes = append(routes, routers.Route{ - DestinationCIDR: route.DestinationCIDR, - NextHop: addr, - }) - - unwind, err := updateRoutes(r.network, router, routes) - if err != nil { - return err - } - defer onFailure.call(unwind) - - // get the port of addr on target node. - portID, err := getPortIDByIP(r.compute, route.TargetNode, addr) - if err != nil { - return err - } - port, err := getPortByID(r.network, portID) - if err != nil { - return err - } - - found := false - for _, item := range port.AllowedAddressPairs { - if item.IPAddress == route.DestinationCIDR { - klog.V(4).Infof("Found existing allowed-address-pair: %v", item) - found = true - break - } - } - - if !found { - newPairs := append(port.AllowedAddressPairs, neutronports.AddressPair{ - IPAddress: route.DestinationCIDR, - }) - unwind, err := updateAllowedAddressPairs(r.network, port, newPairs) - if err != nil { - return err - } - defer onFailure.call(unwind) - } - - klog.V(4).Infof("Route created: %v", route) - onFailure.disarm() - return nil -} - -// DeleteRoute deletes the specified managed route -func (r *Routes) DeleteRoute(ctx context.Context, clusterName string, route *cloudprovider.Route) error { - klog.V(4).Infof("DeleteRoute(%v, %v)", clusterName, route) - - onFailure := newCaller() - - ip, _, _ := netutils.ParseCIDRSloppy(route.DestinationCIDR) - isCIDRv6 := ip.To4() == nil - - var addr string - - // Blackhole routes are orphaned and have no counterpart in OpenStack - if !route.Blackhole { - var err error - addr, err = getAddressByName(r.compute, route.TargetNode, isCIDRv6) - if err != nil { - return err - } - } - - router, err := routers.Get(r.network, r.opts.RouterID).Extract() - if err != nil { - return err - } - - routes := router.Routes - index := -1 - for i, item := range routes { - if item.DestinationCIDR == route.DestinationCIDR && (item.NextHop == addr || route.Blackhole && item.NextHop == string(route.TargetNode)) { - index = i - break - } - } - - if index == -1 { - klog.V(4).Infof("Skipping non-existent route: %v", route) - return nil - } - - // Delete element `index` - routes[index] = routes[len(routes)-1] - routes = routes[:len(routes)-1] - - unwind, err := updateRoutes(r.network, router, routes) - // If this was a blackhole route we are done, there are no ports to update - if err != nil || route.Blackhole { - return err - } - defer onFailure.call(unwind) - - // get the port of addr on target node. - portID, err := getPortIDByIP(r.compute, route.TargetNode, addr) - if err != nil { - return err - } - port, err := getPortByID(r.network, portID) - if err != nil { - return err - } - - addrPairs := port.AllowedAddressPairs - index = -1 - for i, item := range addrPairs { - if item.IPAddress == route.DestinationCIDR { - index = i - break - } - } - - if index != -1 { - // Delete element `index` - addrPairs[index] = addrPairs[len(addrPairs)-1] - addrPairs = addrPairs[:len(addrPairs)-1] - - unwind, err := updateAllowedAddressPairs(r.network, port, addrPairs) - if err != nil { - return err - } - defer onFailure.call(unwind) - } - - klog.V(4).Infof("Route deleted: %v", route) - onFailure.disarm() - return nil -} - -func getPortIDByIP(compute *gophercloud.ServiceClient, targetNode types.NodeName, ipAddress string) (string, error) { - srv, err := getServerByName(compute, targetNode) - if err != nil { - return "", err - } - - interfaces, err := getAttachedInterfacesByID(compute, srv.ID) - if err != nil { - return "", err - } - - for _, intf := range interfaces { - for _, fixedIP := range intf.FixedIPs { - if fixedIP.IPAddress == ipAddress { - return intf.PortID, nil - } - } - } - - return "", ErrNotFound -} - -func getPortByID(client *gophercloud.ServiceClient, portID string) (*neutronports.Port, error) { - targetPort, err := neutronports.Get(client, portID).Extract() - if err != nil { - return nil, err - } - - if targetPort == nil { - return nil, ErrNotFound - } - - return targetPort, nil -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go deleted file mode 100644 index 56752b5b6cf..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_routes_test.go +++ /dev/null @@ -1,128 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "testing" - - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" - "k8s.io/apimachinery/pkg/types" - cloudprovider "k8s.io/cloud-provider" - netutils "k8s.io/utils/net" -) - -func TestRoutes(t *testing.T) { - const clusterName = "ignored" - - cfg, ok := configFromEnv() - if !ok { - t.Skipf("No config found in environment") - } - - os, err := newOpenStack(cfg) - if err != nil { - t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) - } - - vms := getServers(os) - _, err = os.InstanceID() - if err != nil || len(vms) == 0 { - t.Skipf("Please run this test in an OpenStack vm or create at least one VM in OpenStack before you run this test.") - } - - // We know we have at least one vm. - servername := vms[0].Name - - // Pick the first router and server to try a test with - os.routeOpts.RouterID = getRouters(os)[0].ID - - r, ok := os.Routes() - if !ok { - t.Skip("Routes() returned false - perhaps your stack does not support Neutron extraroute extension?") - } - - newroute := cloudprovider.Route{ - DestinationCIDR: "10.164.2.0/24", - TargetNode: types.NodeName(servername), - } - err = r.CreateRoute(context.TODO(), clusterName, "myhint", &newroute) - if err != nil { - t.Fatalf("CreateRoute error: %v", err) - } - - routelist, err := r.ListRoutes(context.TODO(), clusterName) - if err != nil { - t.Fatalf("ListRoutes() error: %v", err) - } - for _, route := range routelist { - _, cidr, err := netutils.ParseCIDRSloppy(route.DestinationCIDR) - if err != nil { - t.Logf("Ignoring route %s, unparsable CIDR: %v", route.Name, err) - continue - } - t.Logf("%s via %s", cidr, route.TargetNode) - } - - err = r.DeleteRoute(context.TODO(), clusterName, &newroute) - if err != nil { - t.Fatalf("DeleteRoute error: %v", err) - } -} - -func getServers(os *OpenStack) []servers.Server { - c, err := os.NewComputeV2() - if err != nil { - panic(err) - } - allPages, err := servers.List(c, servers.ListOpts{}).AllPages() - if err != nil { - panic(err) - } - allServers, err := servers.ExtractServers(allPages) - if err != nil { - panic(err) - } - if len(allServers) == 0 { - panic("No servers to test with") - } - return allServers -} - -func getRouters(os *OpenStack) []routers.Router { - listOpts := routers.ListOpts{} - n, err := os.NewNetworkV2() - if err != nil { - panic(err) - } - allPages, err := routers.List(n, listOpts).AllPages() - if err != nil { - panic(err) - } - allRouters, err := routers.ExtractRouters(allPages) - if err != nil { - panic(err) - } - if len(allRouters) == 0 { - panic("No routers to test with") - } - return allRouters -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go deleted file mode 100644 index 40fdf5bc2d9..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_test.go +++ /dev/null @@ -1,733 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2014 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "fmt" - "os" - "reflect" - "regexp" - "sort" - "strings" - "testing" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - v1 "k8s.io/api/core/v1" - - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/rand" - "k8s.io/apimachinery/pkg/util/wait" -) - -const ( - testClusterName = "testCluster" - - volumeStatusTimeoutSeconds = 30 - // volumeStatus* is configuration of exponential backoff for - // waiting for specified volume status. Starting with 1 - // seconds, multiplying by 1.2 with each step and taking 13 steps at maximum - // it will time out after 32s, which roughly corresponds to 30s - volumeStatusInitDelay = 1 * time.Second - volumeStatusFactor = 1.2 - volumeStatusSteps = 13 -) - -func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string) { - backoff := wait.Backoff{ - Duration: volumeStatusInitDelay, - Factor: volumeStatusFactor, - Steps: volumeStatusSteps, - } - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - getVol, err := os.getVolume(volumeName) - if err != nil { - return false, err - } - if getVol.Status == status { - t.Logf("Volume (%s) status changed to %s after %v seconds\n", - volumeName, - status, - volumeStatusTimeoutSeconds) - return true, nil - } - return false, nil - }) - if err == wait.ErrWaitTimeout { - t.Logf("Volume (%s) status did not change to %s after %v seconds\n", - volumeName, - status, - volumeStatusTimeoutSeconds) - return - } - if err != nil { - t.Fatalf("Cannot get existing Cinder volume (%s): %v", volumeName, err) - } -} - -func TestReadConfig(t *testing.T) { - _, err := readConfig(nil) - if err == nil { - t.Errorf("Should fail when no config is provided: %s", err) - } - - // Since we are setting env vars, we need to reset old - // values for other tests to succeed. - env := clearEnviron(t) - defer resetEnviron(t, env) - - os.Setenv("OS_PASSWORD", "mypass") // Fake value for testing. - defer os.Unsetenv("OS_PASSWORD") - - os.Setenv("OS_TENANT_NAME", "admin") - defer os.Unsetenv("OS_TENANT_NAME") - - cfg, err := readConfig(strings.NewReader(` - [Global] - auth-url = http://auth.url - user-id = user - tenant-name = demo - region = RegionOne - [LoadBalancer] - create-monitor = yes - monitor-delay = 1m - monitor-timeout = 30s - monitor-max-retries = 3 - [BlockStorage] - bs-version = auto - trust-device-path = yes - ignore-volume-az = yes - [Metadata] - search-order = configDrive, metadataService - `)) - cfg.Global.Password = os.Getenv("OS_PASSWORD") - - if err != nil { - t.Fatalf("Should succeed when a valid config is provided: %s", err) - } - if cfg.Global.AuthURL != "http://auth.url" { - t.Errorf("incorrect authurl: %s", cfg.Global.AuthURL) - } - - if cfg.Global.UserID != "user" { - t.Errorf("incorrect userid: %s", cfg.Global.UserID) - } - - if cfg.Global.Password != "mypass" { - t.Errorf("incorrect password: %s", cfg.Global.Password) - } - - // config file wins over environment variable - if cfg.Global.TenantName != "demo" { - t.Errorf("incorrect tenant name: %s", cfg.Global.TenantName) - } - - if cfg.Global.Region != "RegionOne" { - t.Errorf("incorrect region: %s", cfg.Global.Region) - } - - if !cfg.LoadBalancer.CreateMonitor { - t.Errorf("incorrect lb.createmonitor: %t", cfg.LoadBalancer.CreateMonitor) - } - if cfg.LoadBalancer.MonitorDelay.Duration != 1*time.Minute { - t.Errorf("incorrect lb.monitordelay: %s", cfg.LoadBalancer.MonitorDelay) - } - if cfg.LoadBalancer.MonitorTimeout.Duration != 30*time.Second { - t.Errorf("incorrect lb.monitortimeout: %s", cfg.LoadBalancer.MonitorTimeout) - } - if cfg.LoadBalancer.MonitorMaxRetries != 3 { - t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries) - } - if cfg.BlockStorage.TrustDevicePath != true { - t.Errorf("incorrect bs.trustdevicepath: %v", cfg.BlockStorage.TrustDevicePath) - } - if cfg.BlockStorage.BSVersion != "auto" { - t.Errorf("incorrect bs.bs-version: %v", cfg.BlockStorage.BSVersion) - } - if cfg.BlockStorage.IgnoreVolumeAZ != true { - t.Errorf("incorrect bs.IgnoreVolumeAZ: %v", cfg.BlockStorage.IgnoreVolumeAZ) - } - if cfg.Metadata.SearchOrder != "configDrive, metadataService" { - t.Errorf("incorrect md.search-order: %v", cfg.Metadata.SearchOrder) - } -} - -func TestToAuthOptions(t *testing.T) { - cfg := Config{} - cfg.Global.Username = "user" - cfg.Global.Password = "pass" // Fake value for testing. - cfg.Global.DomainID = "2a73b8f597c04551a0fdc8e95544be8a" - cfg.Global.DomainName = "local" - cfg.Global.AuthURL = "http://auth.url" - cfg.Global.UserID = "user" - - ao := cfg.toAuthOptions() - - if !ao.AllowReauth { - t.Errorf("Will need to be able to reauthenticate") - } - if ao.Username != cfg.Global.Username { - t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username) - } - if ao.Password != cfg.Global.Password { - t.Errorf("Password %s != %s", ao.Password, cfg.Global.Password) - } - if ao.DomainID != cfg.Global.DomainID { - t.Errorf("DomainID %s != %s", ao.DomainID, cfg.Global.DomainID) - } - if ao.IdentityEndpoint != cfg.Global.AuthURL { - t.Errorf("IdentityEndpoint %s != %s", ao.IdentityEndpoint, cfg.Global.AuthURL) - } - if ao.UserID != cfg.Global.UserID { - t.Errorf("UserID %s != %s", ao.UserID, cfg.Global.UserID) - } - if ao.DomainName != cfg.Global.DomainName { - t.Errorf("DomainName %s != %s", ao.DomainName, cfg.Global.DomainName) - } - if ao.TenantID != cfg.Global.TenantID { - t.Errorf("TenantID %s != %s", ao.TenantID, cfg.Global.TenantID) - } -} - -func TestCheckOpenStackOpts(t *testing.T) { - delay := MyDuration{60 * time.Second} - timeout := MyDuration{30 * time.Second} - tests := []struct { - name string - openstackOpts *OpenStack - expectedError error - }{ - { - name: "test1", - openstackOpts: &OpenStack{ - provider: nil, - lbOpts: LoadBalancerOpts{ - LBVersion: "v2", - SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", - FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", - LBMethod: "ROUND_ROBIN", - LBProvider: "haproxy", - CreateMonitor: true, - MonitorDelay: delay, - MonitorTimeout: timeout, - MonitorMaxRetries: uint(3), - ManageSecurityGroups: true, - }, - metadataOpts: MetadataOpts{ - SearchOrder: configDriveID, - }, - }, - expectedError: nil, - }, - { - name: "test2", - openstackOpts: &OpenStack{ - provider: nil, - lbOpts: LoadBalancerOpts{ - LBVersion: "v2", - FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", - LBMethod: "ROUND_ROBIN", - CreateMonitor: true, - MonitorDelay: delay, - MonitorTimeout: timeout, - MonitorMaxRetries: uint(3), - ManageSecurityGroups: true, - }, - metadataOpts: MetadataOpts{ - SearchOrder: configDriveID, - }, - }, - expectedError: nil, - }, - { - name: "test3", - openstackOpts: &OpenStack{ - provider: nil, - lbOpts: LoadBalancerOpts{ - LBVersion: "v2", - SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", - FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", - LBMethod: "ROUND_ROBIN", - CreateMonitor: true, - MonitorTimeout: timeout, - MonitorMaxRetries: uint(3), - ManageSecurityGroups: true, - }, - metadataOpts: MetadataOpts{ - SearchOrder: configDriveID, - }, - }, - expectedError: fmt.Errorf("monitor-delay not set in cloud provider config"), - }, - { - name: "test4", - openstackOpts: &OpenStack{ - provider: nil, - metadataOpts: MetadataOpts{ - SearchOrder: "", - }, - }, - expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot be empty"), - }, - { - name: "test5", - openstackOpts: &OpenStack{ - provider: nil, - metadataOpts: MetadataOpts{ - SearchOrder: "value1,value2,value3", - }, - }, - expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements"), - }, - { - name: "test6", - openstackOpts: &OpenStack{ - provider: nil, - metadataOpts: MetadataOpts{ - SearchOrder: "value1", - }, - }, - expectedError: fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+ - "Supported elements include %q and %q", "value1", configDriveID, metadataID), - }, - { - name: "test7", - openstackOpts: &OpenStack{ - provider: nil, - lbOpts: LoadBalancerOpts{ - LBVersion: "v2", - SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", - FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", - LBMethod: "ROUND_ROBIN", - CreateMonitor: true, - MonitorDelay: delay, - MonitorTimeout: timeout, - ManageSecurityGroups: true, - }, - metadataOpts: MetadataOpts{ - SearchOrder: configDriveID, - }, - }, - expectedError: fmt.Errorf("monitor-max-retries not set in cloud provider config"), - }, - { - name: "test8", - openstackOpts: &OpenStack{ - provider: nil, - lbOpts: LoadBalancerOpts{ - LBVersion: "v2", - SubnetID: "6261548e-ffde-4bc7-bd22-59c83578c5ef", - FloatingNetworkID: "38b8b5f9-64dc-4424-bf86-679595714786", - LBMethod: "ROUND_ROBIN", - CreateMonitor: true, - MonitorDelay: delay, - MonitorMaxRetries: uint(3), - ManageSecurityGroups: true, - }, - metadataOpts: MetadataOpts{ - SearchOrder: configDriveID, - }, - }, - expectedError: fmt.Errorf("monitor-timeout not set in cloud provider config"), - }, - } - - for _, testcase := range tests { - err := checkOpenStackOpts(testcase.openstackOpts) - - if err == nil && testcase.expectedError == nil { - continue - } - if (err != nil && testcase.expectedError == nil) || (err == nil && testcase.expectedError != nil) || err.Error() != testcase.expectedError.Error() { - t.Errorf("%s failed: expected err=%q, got %q", - testcase.name, testcase.expectedError, err) - } - } -} - -func TestCaller(t *testing.T) { - called := false - myFunc := func() { called = true } - - c := newCaller() - c.call(myFunc) - - if !called { - t.Errorf("caller failed to call function in default case") - } - - c.disarm() - called = false - c.call(myFunc) - - if called { - t.Error("caller still called function when disarmed") - } - - // Confirm the "usual" deferred caller pattern works as expected - - called = false - successCase := func() { - c := newCaller() - defer c.call(func() { called = true }) - c.disarm() - } - if successCase(); called { - t.Error("Deferred success case still invoked unwind") - } - - called = false - failureCase := func() { - c := newCaller() - defer c.call(func() { called = true }) - } - if failureCase(); !called { - t.Error("Deferred failure case failed to invoke unwind") - } -} - -// An arbitrary sort.Interface, just for easier comparison -type AddressSlice []v1.NodeAddress - -func (a AddressSlice) Len() int { return len(a) } -func (a AddressSlice) Less(i, j int) bool { return a[i].Address < a[j].Address } -func (a AddressSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -func TestNodeAddresses(t *testing.T) { - srv := servers.Server{ - Status: "ACTIVE", - HostID: "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", - AccessIPv4: "50.56.176.99", - AccessIPv6: "2001:4800:790e:510:be76:4eff:fe04:82a8", - Addresses: map[string]interface{}{ - "private": []interface{}{ - map[string]interface{}{ - "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b", - "version": float64(4), - "addr": "10.0.0.32", - "OS-EXT-IPS:type": "fixed", - }, - map[string]interface{}{ - "version": float64(4), - "addr": "50.56.176.36", - "OS-EXT-IPS:type": "floating", - }, - map[string]interface{}{ - "version": float64(4), - "addr": "10.0.0.31", - // No OS-EXT-IPS:type - }, - }, - "public": []interface{}{ - map[string]interface{}{ - "version": float64(4), - "addr": "50.56.176.35", - }, - map[string]interface{}{ - "version": float64(6), - "addr": "2001:4800:780e:510:be76:4eff:fe04:84a8", - }, - }, - }, - Metadata: map[string]string{ - "name": "a1-yinvcez57-0-bvynoyawrhcg-kube-minion-fg5i4jwcc2yy", - TypeHostName: "a1-yinvcez57-0-bvynoyawrhcg-kube-minion-fg5i4jwcc2yy.novalocal", - }, - } - - addrs, err := nodeAddresses(&srv) - if err != nil { - t.Fatalf("nodeAddresses returned error: %v", err) - } - - sort.Sort(AddressSlice(addrs)) - t.Logf("addresses is %v", addrs) - - want := []v1.NodeAddress{ - {Type: v1.NodeInternalIP, Address: "10.0.0.31"}, - {Type: v1.NodeInternalIP, Address: "10.0.0.32"}, - {Type: v1.NodeExternalIP, Address: "2001:4800:780e:510:be76:4eff:fe04:84a8"}, - {Type: v1.NodeExternalIP, Address: "2001:4800:790e:510:be76:4eff:fe04:82a8"}, - {Type: v1.NodeExternalIP, Address: "50.56.176.35"}, - {Type: v1.NodeExternalIP, Address: "50.56.176.36"}, - {Type: v1.NodeExternalIP, Address: "50.56.176.99"}, - {Type: v1.NodeHostName, Address: "a1-yinvcez57-0-bvynoyawrhcg-kube-minion-fg5i4jwcc2yy.novalocal"}, - } - - if !reflect.DeepEqual(want, addrs) { - t.Errorf("nodeAddresses returned incorrect value %v", addrs) - } -} - -func configFromEnvWithPasswd() (cfg Config, ok bool) { - cfg, ok = configFromEnv() - if !ok { - return cfg, ok - } - cfg.Global.Password = os.Getenv("OS_PASSWORD") - return cfg, ok -} - -func TestNewOpenStack(t *testing.T) { - cfg, ok := configFromEnvWithPasswd() - if !ok { - t.Skip("No config found in environment") - } - - _, err := newOpenStack(cfg) - if err != nil { - t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) - } -} - -func TestLoadBalancer(t *testing.T) { - cfg, ok := configFromEnvWithPasswd() - if !ok { - t.Skip("No config found in environment") - } - - versions := []string{"v2", ""} - - for _, v := range versions { - t.Logf("Trying LBVersion = '%s'\n", v) - cfg.LoadBalancer.LBVersion = v - - os, err := newOpenStack(cfg) - if err != nil { - t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) - } - - lb, ok := os.LoadBalancer() - if !ok { - t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?") - } - - _, exists, err := lb.GetLoadBalancer(context.TODO(), testClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "noexist"}}) - if err != nil { - t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err) - } - if exists { - t.Fatalf("GetLoadBalancer(\"noexist\") returned exists") - } - } -} - -func TestZones(t *testing.T) { - SetMetadataFixture(&FakeMetadata) - defer ClearMetadata() - - os := OpenStack{ - provider: &gophercloud.ProviderClient{ - IdentityBase: "http://auth.url/", - }, - region: "myRegion", - } - - z, ok := os.Zones() - if !ok { - t.Fatalf("Zones() returned false") - } - - zone, err := z.GetZone(context.TODO()) - if err != nil { - t.Fatalf("GetZone() returned error: %s", err) - } - - if zone.Region != "myRegion" { - t.Fatalf("GetZone() returned wrong region (%s)", zone.Region) - } - - if zone.FailureDomain != "nova" { - t.Fatalf("GetZone() returned wrong failure domain (%s)", zone.FailureDomain) - } -} - -var diskPathRegexp = regexp.MustCompile("/dev/disk/(?:by-id|by-path)/") - -func TestVolumes(t *testing.T) { - cfg, ok := configFromEnvWithPasswd() - if !ok { - t.Skip("No config found in environment") - } - - os, err := newOpenStack(cfg) - if err != nil { - t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) - } - - tags := map[string]string{ - "test": "value", - } - vol, _, _, _, err := os.CreateVolume("kubernetes-test-volume-"+rand.String(10), 1, "", "", &tags) - if err != nil { - t.Fatalf("Cannot create a new Cinder volume: %v", err) - } - t.Logf("Volume (%s) created\n", vol) - - WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) - - id, err := os.InstanceID() - if err != nil { - t.Logf("Cannot find instance id: %v - perhaps you are running this test outside a VM launched by OpenStack", err) - } else { - diskID, err := os.AttachDisk(id, vol) - if err != nil { - t.Fatalf("Cannot AttachDisk Cinder volume %s: %v", vol, err) - } - t.Logf("Volume (%s) attached, disk ID: %s\n", vol, diskID) - - WaitForVolumeStatus(t, os, vol, volumeInUseStatus) - - devicePath := os.GetDevicePath(diskID) - if diskPathRegexp.FindString(devicePath) == "" { - t.Fatalf("GetDevicePath returned and unexpected path for Cinder volume %s, returned %s", vol, devicePath) - } - t.Logf("Volume (%s) found at path: %s\n", vol, devicePath) - - err = os.DetachDisk(id, vol) - if err != nil { - t.Fatalf("Cannot DetachDisk Cinder volume %s: %v", vol, err) - } - t.Logf("Volume (%s) detached\n", vol) - - WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) - } - - expectedVolSize := resource.MustParse("2Gi") - newVolSize, err := os.ExpandVolume(vol, resource.MustParse("1Gi"), expectedVolSize) - if err != nil { - t.Fatalf("Cannot expand a Cinder volume: %v", err) - } - if newVolSize != expectedVolSize { - t.Logf("Expected: %v but got: %v ", expectedVolSize, newVolSize) - } - t.Logf("Volume expanded to (%v) \n", newVolSize) - - WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) - - err = os.DeleteVolume(vol) - if err != nil { - t.Fatalf("Cannot delete Cinder volume %s: %v", vol, err) - } - t.Logf("Volume (%s) deleted\n", vol) - -} - -func TestInstanceIDFromProviderID(t *testing.T) { - testCases := []struct { - providerID string - instanceID string - fail bool - }{ - { - providerID: ProviderName + "://" + "/" + "7b9cf879-7146-417c-abfd-cb4272f0c935", - instanceID: "7b9cf879-7146-417c-abfd-cb4272f0c935", - fail: false, - }, - { - providerID: "openstack://7b9cf879-7146-417c-abfd-cb4272f0c935", - instanceID: "", - fail: true, - }, - { - providerID: "7b9cf879-7146-417c-abfd-cb4272f0c935", - instanceID: "", - fail: true, - }, - { - providerID: "other-provider:///7b9cf879-7146-417c-abfd-cb4272f0c935", - instanceID: "", - fail: true, - }, - } - - for _, test := range testCases { - instanceID, err := instanceIDFromProviderID(test.providerID) - if (err != nil) != test.fail { - t.Errorf("%s yielded `err != nil` as %t. expected %t", test.providerID, (err != nil), test.fail) - } - - if test.fail { - continue - } - - if instanceID != test.instanceID { - t.Errorf("%s yielded %s. expected %s", test.providerID, instanceID, test.instanceID) - } - } -} - -func TestToAuth3Options(t *testing.T) { - cfg := Config{} - cfg.Global.Username = "user" - cfg.Global.Password = "pass" // Fake value for testing. - cfg.Global.DomainID = "2a73b8f597c04551a0fdc8e95544be8a" - cfg.Global.DomainName = "local" - cfg.Global.AuthURL = "http://auth.url" - cfg.Global.UserID = "user" - - ao := cfg.toAuth3Options() - - if !ao.AllowReauth { - t.Errorf("Will need to be able to reauthenticate") - } - if ao.Username != cfg.Global.Username { - t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username) - } - if ao.Password != cfg.Global.Password { - t.Errorf("Password %s != %s", ao.Password, cfg.Global.Password) - } - if ao.DomainID != cfg.Global.DomainID { - t.Errorf("DomainID %s != %s", ao.DomainID, cfg.Global.DomainID) - } - if ao.IdentityEndpoint != cfg.Global.AuthURL { - t.Errorf("IdentityEndpoint %s != %s", ao.IdentityEndpoint, cfg.Global.AuthURL) - } - if ao.UserID != cfg.Global.UserID { - t.Errorf("UserID %s != %s", ao.UserID, cfg.Global.UserID) - } - if ao.DomainName != cfg.Global.DomainName { - t.Errorf("DomainName %s != %s", ao.DomainName, cfg.Global.DomainName) - } -} - -func clearEnviron(t *testing.T) []string { - env := os.Environ() - for _, pair := range env { - if strings.HasPrefix(pair, "OS_") { - i := strings.Index(pair, "=") + 1 - os.Unsetenv(pair[:i-1]) - } - } - return env -} -func resetEnviron(t *testing.T, items []string) { - for _, pair := range items { - if strings.HasPrefix(pair, "OS_") { - i := strings.Index(pair, "=") + 1 - if err := os.Setenv(pair[:i-1], pair[i:]); err != nil { - t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i-1], pair[i:], err) - } - } - } -} diff --git a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go b/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go deleted file mode 100644 index 3c4dda06811..00000000000 --- a/staging/src/k8s.io/legacy-cloud-providers/openstack/openstack_volumes.go +++ /dev/null @@ -1,769 +0,0 @@ -//go:build !providerless -// +build !providerless - -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package openstack - -import ( - "context" - "errors" - "fmt" - "io/ioutil" - "path" - "path/filepath" - "strings" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/types" - cloudprovider "k8s.io/cloud-provider" - cloudvolume "k8s.io/cloud-provider/volume" - volerr "k8s.io/cloud-provider/volume/errors" - volumehelpers "k8s.io/cloud-provider/volume/helpers" - "k8s.io/component-base/metrics" - - "github.com/gophercloud/gophercloud" - volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" - volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" - volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" - volumes_v3 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" - "k8s.io/klog/v2" -) - -type volumeService interface { - createVolume(opts volumeCreateOpts) (string, string, error) - getVolume(volumeID string) (Volume, error) - deleteVolume(volumeName string) error - expandVolume(volumeID string, newSize int) error -} - -// VolumesV1 is a Volumes implementation for cinder v1 -type VolumesV1 struct { - blockstorage *gophercloud.ServiceClient - opts BlockStorageOpts -} - -// VolumesV2 is a Volumes implementation for cinder v2 -type VolumesV2 struct { - blockstorage *gophercloud.ServiceClient - opts BlockStorageOpts -} - -// VolumesV3 is a Volumes implementation for cinder v3 -type VolumesV3 struct { - blockstorage *gophercloud.ServiceClient - opts BlockStorageOpts -} - -// Volume stores information about a single volume -type Volume struct { - // ID of the instance, to which this volume is attached. "" if not attached - AttachedServerID string - // Device file path - AttachedDevice string - // availabilityZone is which availability zone the volume is in - AvailabilityZone string - // Unique identifier for the volume. - ID string - // Human-readable display name for the volume. - Name string - // Current status of the volume. - Status string - // Volume size in GB - Size int -} - -type volumeCreateOpts struct { - Size int - Availability string - Name string - VolumeType string - Metadata map[string]string -} - -// implements PVLabeler. -var _ cloudprovider.PVLabeler = (*OpenStack)(nil) - -const ( - volumeAvailableStatus = "available" - volumeInUseStatus = "in-use" - volumeDeletedStatus = "deleted" - volumeErrorStatus = "error" - - // On some environments, we need to query the metadata service in order - // to locate disks. We'll use the Newton version, which includes device - // metadata. - newtonMetadataVersion = "2016-06-30" -) - -func (volumes *VolumesV1) createVolume(opts volumeCreateOpts) (string, string, error) { - startTime := time.Now() - - createOpts := volumes_v1.CreateOpts{ - Name: opts.Name, - Size: opts.Size, - VolumeType: opts.VolumeType, - AvailabilityZone: opts.Availability, - Metadata: opts.Metadata, - } - - vol, err := volumes_v1.Create(volumes.blockstorage, createOpts).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("create_v1_volume", timeTaken, err) - if err != nil { - return "", "", err - } - return vol.ID, vol.AvailabilityZone, nil -} - -func (volumes *VolumesV2) createVolume(opts volumeCreateOpts) (string, string, error) { - startTime := time.Now() - - createOpts := volumes_v2.CreateOpts{ - Name: opts.Name, - Size: opts.Size, - VolumeType: opts.VolumeType, - AvailabilityZone: opts.Availability, - Metadata: opts.Metadata, - } - - vol, err := volumes_v2.Create(volumes.blockstorage, createOpts).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("create_v2_volume", timeTaken, err) - if err != nil { - return "", "", err - } - return vol.ID, vol.AvailabilityZone, nil -} - -func (volumes *VolumesV3) createVolume(opts volumeCreateOpts) (string, string, error) { - startTime := time.Now() - - createOpts := volumes_v3.CreateOpts{ - Name: opts.Name, - Size: opts.Size, - VolumeType: opts.VolumeType, - AvailabilityZone: opts.Availability, - Metadata: opts.Metadata, - } - - vol, err := volumes_v3.Create(volumes.blockstorage, createOpts).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("create_v3_volume", timeTaken, err) - if err != nil { - return "", "", err - } - return vol.ID, vol.AvailabilityZone, nil -} - -func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) { - startTime := time.Now() - volumeV1, err := volumes_v1.Get(volumes.blockstorage, volumeID).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("get_v1_volume", timeTaken, err) - if err != nil { - if isNotFound(err) { - return Volume{}, ErrNotFound - } - return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) - } - - volume := Volume{ - AvailabilityZone: volumeV1.AvailabilityZone, - ID: volumeV1.ID, - Name: volumeV1.Name, - Status: volumeV1.Status, - Size: volumeV1.Size, - } - - if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil { - volume.AttachedServerID = volumeV1.Attachments[0]["server_id"].(string) - volume.AttachedDevice = volumeV1.Attachments[0]["device"].(string) - } - - return volume, nil -} - -func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) { - startTime := time.Now() - volumeV2, err := volumes_v2.Get(volumes.blockstorage, volumeID).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("get_v2_volume", timeTaken, err) - if err != nil { - if isNotFound(err) { - return Volume{}, ErrNotFound - } - return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) - } - - volume := Volume{ - AvailabilityZone: volumeV2.AvailabilityZone, - ID: volumeV2.ID, - Name: volumeV2.Name, - Status: volumeV2.Status, - Size: volumeV2.Size, - } - - if len(volumeV2.Attachments) > 0 { - volume.AttachedServerID = volumeV2.Attachments[0].ServerID - volume.AttachedDevice = volumeV2.Attachments[0].Device - } - - return volume, nil -} - -func (volumes *VolumesV3) getVolume(volumeID string) (Volume, error) { - startTime := time.Now() - volumeV3, err := volumes_v3.Get(volumes.blockstorage, volumeID).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("get_v3_volume", timeTaken, err) - if err != nil { - if isNotFound(err) { - return Volume{}, ErrNotFound - } - return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) - } - - volume := Volume{ - AvailabilityZone: volumeV3.AvailabilityZone, - ID: volumeV3.ID, - Name: volumeV3.Name, - Status: volumeV3.Status, - Size: volumeV3.Size, - } - - if len(volumeV3.Attachments) > 0 { - volume.AttachedServerID = volumeV3.Attachments[0].ServerID - volume.AttachedDevice = volumeV3.Attachments[0].Device - } - - return volume, nil -} - -func (volumes *VolumesV1) deleteVolume(volumeID string) error { - startTime := time.Now() - err := volumes_v1.Delete(volumes.blockstorage, volumeID).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("delete_v1_volume", timeTaken, err) - return err -} - -func (volumes *VolumesV2) deleteVolume(volumeID string) error { - startTime := time.Now() - err := volumes_v2.Delete(volumes.blockstorage, volumeID, nil).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("delete_v2_volume", timeTaken, err) - return err -} - -func (volumes *VolumesV3) deleteVolume(volumeID string) error { - startTime := time.Now() - err := volumes_v3.Delete(volumes.blockstorage, volumeID, nil).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("delete_v3_volume", timeTaken, err) - return err -} - -func (volumes *VolumesV1) expandVolume(volumeID string, newSize int) error { - startTime := time.Now() - createOpts := volumeexpand.ExtendSizeOpts{ - NewSize: newSize, - } - err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, createOpts).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("expand_volume", timeTaken, err) - return err -} - -func (volumes *VolumesV2) expandVolume(volumeID string, newSize int) error { - startTime := time.Now() - createOpts := volumeexpand.ExtendSizeOpts{ - NewSize: newSize, - } - err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, createOpts).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("expand_volume", timeTaken, err) - return err -} - -func (volumes *VolumesV3) expandVolume(volumeID string, newSize int) error { - startTime := time.Now() - createOpts := volumeexpand.ExtendSizeOpts{ - NewSize: newSize, - } - err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, createOpts).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("expand_volume", timeTaken, err) - return err -} - -// OperationPending checks if there is an operation pending on a volume -func (os *OpenStack) OperationPending(diskName string) (bool, string, error) { - volume, err := os.getVolume(diskName) - if err != nil { - return false, "", err - } - volumeStatus := volume.Status - if volumeStatus == volumeErrorStatus { - err = fmt.Errorf("status of volume %s is %s", diskName, volumeStatus) - return false, volumeStatus, err - } - if volumeStatus == volumeAvailableStatus || volumeStatus == volumeInUseStatus || volumeStatus == volumeDeletedStatus { - return false, volume.Status, nil - } - return true, volumeStatus, nil -} - -// AttachDisk attaches given cinder volume to the compute running kubelet -func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) { - volume, err := os.getVolume(volumeID) - if err != nil { - return "", err - } - - cClient, err := os.NewComputeV2() - if err != nil { - return "", err - } - - if volume.AttachedServerID != "" { - if instanceID == volume.AttachedServerID { - klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID) - return volume.ID, nil - } - nodeName, err := os.GetNodeNameByID(volume.AttachedServerID) - attachErr := fmt.Sprintf("disk %s path %s is attached to a different instance (%s)", volumeID, volume.AttachedDevice, volume.AttachedServerID) - if err != nil { - klog.Error(attachErr) - return "", errors.New(attachErr) - } - // using volume.AttachedDevice may cause problems because cinder does not report device path correctly see issue #33128 - devicePath := volume.AttachedDevice - danglingErr := volerr.NewDanglingError(attachErr, nodeName, devicePath) - klog.V(2).Infof("Found dangling volume %s attached to node %s", volumeID, nodeName) - return "", danglingErr - } - - startTime := time.Now() - // add read only flag here if possible spothanis - _, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{ - VolumeID: volume.ID, - }).Extract() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("attach_disk", timeTaken, err) - if err != nil { - return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err) - } - klog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID) - return volume.ID, nil -} - -// DetachDisk detaches given cinder volume from the compute running kubelet -func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { - volume, err := os.getVolume(volumeID) - if err != nil { - return err - } - if volume.Status == volumeAvailableStatus { - // "available" is fine since that means the volume is detached from instance already. - klog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID) - return nil - } - - if volume.Status != volumeInUseStatus { - return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status) - } - cClient, err := os.NewComputeV2() - if err != nil { - return err - } - if volume.AttachedServerID != instanceID { - return fmt.Errorf("disk: %s has no attachments or is not attached to compute: %s", volume.Name, instanceID) - } - - startTime := time.Now() - // This is a blocking call and effects kubelet's performance directly. - // We should consider kicking it out into a separate routine, if it is bad. - err = volumeattach.Delete(cClient, instanceID, volume.ID).ExtractErr() - timeTaken := time.Since(startTime).Seconds() - recordOpenstackOperationMetric("detach_disk", timeTaken, err) - if err != nil { - return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err) - } - klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID) - - return nil -} - -// ExpandVolume expands the size of specific cinder volume (in GiB) -func (os *OpenStack) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { - volume, err := os.getVolume(volumeID) - if err != nil { - return oldSize, err - } - if volume.Status != volumeAvailableStatus { - // cinder volume can not be expanded if its status is not available - if volume.Status == volumeInUseStatus { - // Send a nice event when the volume is used - return oldSize, fmt.Errorf("PVC used by a Pod can not be expanded, please ensure the PVC is not used by any Pod and is fully detached from a node") - } - // Send not so nice event when the volume is in any other state (deleted, error) - return oldSize, fmt.Errorf("volume in state %q can not be expanded, it must be \"available\"", volume.Status) - } - - // Cinder works with gigabytes, convert to GiB with rounding up - volSizeGiB, err := volumehelpers.RoundUpToGiBInt(newSize) - if err != nil { - return oldSize, err - } - newSizeQuant := resource.MustParse(fmt.Sprintf("%dGi", volSizeGiB)) - - // if volume size equals to or greater than the newSize, return nil - if volume.Size >= volSizeGiB { - return newSizeQuant, nil - } - - volumes, err := os.volumeService("") - if err != nil { - return oldSize, err - } - - err = volumes.expandVolume(volumeID, volSizeGiB) - if err != nil { - return oldSize, err - } - return newSizeQuant, nil -} - -// getVolume retrieves Volume by its ID. -func (os *OpenStack) getVolume(volumeID string) (Volume, error) { - volumes, err := os.volumeService("") - if err != nil { - return Volume{}, fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) - } - return volumes.getVolume(volumeID) -} - -// CreateVolume creates a volume of given size (in GiB) -func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, string, bool, error) { - volumes, err := os.volumeService("") - if err != nil { - return "", "", "", os.bsOpts.IgnoreVolumeAZ, fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) - } - - opts := volumeCreateOpts{ - Name: name, - Size: size, - VolumeType: vtype, - Availability: availability, - } - if tags != nil { - opts.Metadata = *tags - } - - volumeID, volumeAZ, err := volumes.createVolume(opts) - - if err != nil { - return "", "", "", os.bsOpts.IgnoreVolumeAZ, fmt.Errorf("failed to create a %d GB volume: %v", size, err) - } - - klog.Infof("Created volume %v in Availability Zone: %v Region: %v Ignore volume AZ: %v", volumeID, volumeAZ, os.region, os.bsOpts.IgnoreVolumeAZ) - return volumeID, volumeAZ, os.region, os.bsOpts.IgnoreVolumeAZ, nil -} - -// GetDevicePathBySerialID returns the path of an attached block storage volume, specified by its id. -func (os *OpenStack) GetDevicePathBySerialID(volumeID string) string { - // Build a list of candidate device paths. - // Certain Nova drivers will set the disk serial ID, including the Cinder volume id. - // Newer OpenStacks may not truncate the volumeID to 20 chars. - candidateDeviceNodes := []string{ - // KVM - fmt.Sprintf("virtio-%s", volumeID[:20]), - fmt.Sprintf("virtio-%s", volumeID), - // KVM virtio-scsi - fmt.Sprintf("scsi-0QEMU_QEMU_HARDDISK_%s", volumeID[:20]), - fmt.Sprintf("scsi-0QEMU_QEMU_HARDDISK_%s", volumeID), - // ESXi - fmt.Sprintf("wwn-0x%s", strings.Replace(volumeID, "-", "", -1)), - } - - files, _ := ioutil.ReadDir("/dev/disk/by-id/") - - for _, f := range files { - for _, c := range candidateDeviceNodes { - if c == f.Name() { - klog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name())) - return path.Join("/dev/disk/by-id/", f.Name()) - } - } - } - - klog.V(4).Infof("Failed to find device for the volumeID: %q by serial ID", volumeID) - return "" -} - -func (os *OpenStack) getDevicePathFromInstanceMetadata(volumeID string) string { - // Nova Hyper-V hosts cannot override disk SCSI IDs. In order to locate - // volumes, we're querying the metadata service. Note that the Hyper-V - // driver will include device metadata for untagged volumes as well. - // - // We're avoiding using cached metadata (or the configdrive), - // relying on the metadata service. - instanceMetadata, err := getMetadataFromMetadataService( - newtonMetadataVersion) - - if err != nil { - klog.V(4).Infof( - "Could not retrieve instance metadata. Error: %v", err) - return "" - } - - for _, device := range instanceMetadata.Devices { - if device.Type == "disk" && device.Serial == volumeID { - klog.V(4).Infof( - "Found disk metadata for volumeID %q. Bus: %q, Address: %q", - volumeID, device.Bus, device.Address) - - diskPattern := fmt.Sprintf( - "/dev/disk/by-path/*-%s-%s", - device.Bus, device.Address) - diskPaths, err := filepath.Glob(diskPattern) - if err != nil { - klog.Errorf( - "could not retrieve disk path for volumeID: %q. Error filepath.Glob(%q): %v", - volumeID, diskPattern, err) - return "" - } - - if len(diskPaths) == 1 { - return diskPaths[0] - } - - klog.Errorf( - "expecting to find one disk path for volumeID %q, found %d: %v", - volumeID, len(diskPaths), diskPaths) - return "" - } - } - - klog.V(4).Infof( - "Could not retrieve device metadata for volumeID: %q", volumeID) - return "" -} - -// GetDevicePath returns the path of an attached block storage volume, specified by its id. -func (os *OpenStack) GetDevicePath(volumeID string) string { - devicePath := os.GetDevicePathBySerialID(volumeID) - - if devicePath == "" { - devicePath = os.getDevicePathFromInstanceMetadata(volumeID) - } - - if devicePath == "" { - klog.Warningf("Failed to find device for the volumeID: %q", volumeID) - } - - return devicePath -} - -// DeleteVolume deletes a volume given volume name. -func (os *OpenStack) DeleteVolume(volumeID string) error { - used, err := os.diskIsUsed(volumeID) - if err != nil { - return err - } - if used { - msg := fmt.Sprintf("Cannot delete the volume %q, it's still attached to a node", volumeID) - return volerr.NewDeletedVolumeInUseError(msg) - } - - volumes, err := os.volumeService("") - if err != nil { - return fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) - } - - err = volumes.deleteVolume(volumeID) - return err - -} - -// GetAttachmentDiskPath gets device path of attached volume to the compute running kubelet, as known by cinder -func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) { - // See issue #33128 - Cinder does not always tell you the right device path, as such - // we must only use this value as a last resort. - volume, err := os.getVolume(volumeID) - if err != nil { - return "", err - } - if volume.Status != volumeInUseStatus { - return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status) - } - if volume.AttachedServerID != "" { - if instanceID == volume.AttachedServerID { - // Attachment[0]["device"] points to the device path - // see http://developer.openstack.org/api-ref-blockstorage-v1.html - return volume.AttachedDevice, nil - } - return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.AttachedServerID) - } - return "", fmt.Errorf("volume %s has no ServerId", volumeID) -} - -// DiskIsAttached queries if a volume is attached to a compute instance -func (os *OpenStack) DiskIsAttached(instanceID, volumeID string) (bool, error) { - if instanceID == "" { - klog.Warningf("calling DiskIsAttached with empty instanceid: %s %s", instanceID, volumeID) - } - volume, err := os.getVolume(volumeID) - if err != nil { - if err == ErrNotFound { - // Volume does not exists, it can't be attached. - return false, nil - } - return false, err - } - - return instanceID == volume.AttachedServerID, nil -} - -// DiskIsAttachedByName queries if a volume is attached to a compute instance by name -func (os *OpenStack) DiskIsAttachedByName(nodeName types.NodeName, volumeID string) (bool, string, error) { - cClient, err := os.NewComputeV2() - if err != nil { - return false, "", err - } - srv, err := getServerByName(cClient, nodeName) - if err != nil { - if err == ErrNotFound { - // instance not found anymore in cloudprovider, assume that cinder is detached - return false, "", nil - } - return false, "", err - } - instanceID := "/" + srv.ID - if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { - instanceID = instanceID[(ind + 1):] - } - attached, err := os.DiskIsAttached(instanceID, volumeID) - return attached, instanceID, err -} - -// DisksAreAttached queries if a list of volumes are attached to a compute instance -func (os *OpenStack) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) { - attached := make(map[string]bool) - for _, volumeID := range volumeIDs { - isAttached, err := os.DiskIsAttached(instanceID, volumeID) - if err != nil && err != ErrNotFound { - attached[volumeID] = true - continue - } - attached[volumeID] = isAttached - } - return attached, nil -} - -// DisksAreAttachedByName queries if a list of volumes are attached to a compute instance by name -func (os *OpenStack) DisksAreAttachedByName(nodeName types.NodeName, volumeIDs []string) (map[string]bool, error) { - attached := make(map[string]bool) - cClient, err := os.NewComputeV2() - if err != nil { - return attached, err - } - srv, err := getServerByName(cClient, nodeName) - if err != nil { - if err == ErrNotFound { - // instance not found anymore, mark all volumes as detached - for _, volumeID := range volumeIDs { - attached[volumeID] = false - } - return attached, nil - } - return attached, err - } - instanceID := "/" + srv.ID - if ind := strings.LastIndex(instanceID, "/"); ind >= 0 { - instanceID = instanceID[(ind + 1):] - } - return os.DisksAreAttached(instanceID, volumeIDs) -} - -// diskIsUsed returns true a disk is attached to any node. -func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) { - volume, err := os.getVolume(volumeID) - if err != nil { - return false, err - } - return volume.AttachedServerID != "", nil -} - -// ShouldTrustDevicePath queries if we should trust the cinder provide deviceName, See issue #33128 -func (os *OpenStack) ShouldTrustDevicePath() bool { - return os.bsOpts.TrustDevicePath -} - -// NodeVolumeAttachLimit specifies number of cinder volumes that can be attached to this node. -func (os *OpenStack) NodeVolumeAttachLimit() int { - return os.bsOpts.NodeVolumeAttachLimit -} - -// GetLabelsForVolume implements PVLabeler.GetLabelsForVolume -func (os *OpenStack) GetLabelsForVolume(ctx context.Context, pv *v1.PersistentVolume) (map[string]string, error) { - // Ignore if not Cinder. - if pv.Spec.Cinder == nil { - return nil, nil - } - - // Ignore any volumes that are being provisioned - if pv.Spec.Cinder.VolumeID == cloudvolume.ProvisionedVolumeName { - return nil, nil - } - - // if volume az is to be ignored we should return nil from here - if os.bsOpts.IgnoreVolumeAZ { - return nil, nil - } - - // Get Volume - volume, err := os.getVolume(pv.Spec.Cinder.VolumeID) - if err != nil { - return nil, err - } - - // Construct Volume Labels - labels := make(map[string]string) - if volume.AvailabilityZone != "" { - labels[v1.LabelTopologyZone] = volume.AvailabilityZone - } - if os.region != "" { - labels[v1.LabelTopologyRegion] = os.region - } - klog.V(4).Infof("The Volume %s has labels %v", pv.Spec.Cinder.VolumeID, labels) - - return labels, nil -} - -// recordOpenstackOperationMetric records openstack operation metrics -func recordOpenstackOperationMetric(operation string, timeTaken float64, err error) { - if err != nil { - openstackAPIRequestErrors.With(metrics.Labels{"request": operation}).Inc() - } else { - openstackOperationsLatency.With(metrics.Labels{"request": operation}).Observe(timeTaken) - } -} diff --git a/test/e2e/common/storage/volumes.go b/test/e2e/common/storage/volumes.go index 79204cf4a42..9e8dc0e0c89 100644 --- a/test/e2e/common/storage/volumes.go +++ b/test/e2e/common/storage/volumes.go @@ -31,8 +31,8 @@ limitations under the License. * Note that the server containers are for testing purposes only and should not * be used in production. * - * 2) With server outside of Kubernetes (Cinder, ...) - * Appropriate server (e.g. OpenStack Cinder) must exist somewhere outside + * 2) With server outside of Kubernetes + * Appropriate server must exist somewhere outside * the tested Kubernetes cluster. The test itself creates a new volume, * and checks, that Kubernetes can use it as a volume. */ diff --git a/test/e2e/framework/providers/openstack/openstack.go b/test/e2e/framework/providers/openstack/openstack.go index 784c2f709f6..5e98a58f33d 100644 --- a/test/e2e/framework/providers/openstack/openstack.go +++ b/test/e2e/framework/providers/openstack/openstack.go @@ -29,6 +29,9 @@ func newProvider() (framework.ProviderInterface, error) { } // Provider is a structure to handle OpenStack clouds for e2e testing +// It does not do anything useful, it's there only to provide valid +// --provider=openstack cmdline option to allow testing of CSI migration +// tests of kubernetes.io/cinder volume plugin. type Provider struct { framework.NullProvider } diff --git a/test/e2e/framework/test_context.go b/test/e2e/framework/test_context.go index 6600c953e2a..6108cddf740 100644 --- a/test/e2e/framework/test_context.go +++ b/test/e2e/framework/test_context.go @@ -260,7 +260,7 @@ type CloudConfig struct { ClusterIPRange string ClusterTag string Network string - ConfigFile string // for azure and openstack + ConfigFile string // for azure NodeTag string MasterTag string diff --git a/test/e2e/framework/volume/fixtures.go b/test/e2e/framework/volume/fixtures.go index 2ed2341994c..8afc4128dd6 100644 --- a/test/e2e/framework/volume/fixtures.go +++ b/test/e2e/framework/volume/fixtures.go @@ -31,8 +31,8 @@ limitations under the License. * Note that the server containers are for testing purposes only and should not * be used in production. * - * 2) With server outside of Kubernetes (Cinder, ...) - * Appropriate server (e.g. OpenStack Cinder) must exist somewhere outside + * 2) With server outside of Kubernetes + * Appropriate server must exist somewhere outside * the tested Kubernetes cluster. The test itself creates a new volume, * and checks, that Kubernetes can use it as a volume. */ diff --git a/test/e2e/storage/drivers/in_tree.go b/test/e2e/storage/drivers/in_tree.go index f11becafd71..52e402d7891 100644 --- a/test/e2e/storage/drivers/in_tree.go +++ b/test/e2e/storage/drivers/in_tree.go @@ -38,7 +38,6 @@ package drivers import ( "context" "fmt" - "os/exec" "strconv" "strings" "time" @@ -930,23 +929,12 @@ func (e *emptydirDriver) PrepareTest(f *framework.Framework) *storageframework.P } // Cinder -// This driver assumes that OpenStack client tools are installed -// (/usr/bin/nova, /usr/bin/cinder and /usr/bin/keystone) -// and that the usual OpenStack authentication env. variables are set -// (OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME at least). +// This tests only CSI migration with dynamically provisioned volumes. type cinderDriver struct { driverInfo storageframework.DriverInfo } -type cinderVolume struct { - volumeName string - volumeID string -} - var _ storageframework.TestDriver = &cinderDriver{} -var _ storageframework.PreprovisionedVolumeTestDriver = &cinderDriver{} -var _ storageframework.InlineVolumeTestDriver = &cinderDriver{} -var _ storageframework.PreprovisionedPVTestDriver = &cinderDriver{} var _ storageframework.DynamicPVTestDriver = &cinderDriver{} // InitCinderDriver returns cinderDriver that implements TestDriver interface @@ -985,38 +973,6 @@ func (c *cinderDriver) SkipUnsupportedTest(pattern storageframework.TestPattern) e2eskipper.SkipUnlessProviderIs("openstack") } -func (c *cinderDriver) GetVolumeSource(readOnly bool, fsType string, e2evolume storageframework.TestVolume) *v1.VolumeSource { - cv, ok := e2evolume.(*cinderVolume) - framework.ExpectEqual(ok, true, "Failed to cast test volume to Cinder test volume") - - volSource := v1.VolumeSource{ - Cinder: &v1.CinderVolumeSource{ - VolumeID: cv.volumeID, - ReadOnly: readOnly, - }, - } - if fsType != "" { - volSource.Cinder.FSType = fsType - } - return &volSource -} - -func (c *cinderDriver) GetPersistentVolumeSource(readOnly bool, fsType string, e2evolume storageframework.TestVolume) (*v1.PersistentVolumeSource, *v1.VolumeNodeAffinity) { - cv, ok := e2evolume.(*cinderVolume) - framework.ExpectEqual(ok, true, "Failed to cast test volume to Cinder test volume") - - pvSource := v1.PersistentVolumeSource{ - Cinder: &v1.CinderPersistentVolumeSource{ - VolumeID: cv.volumeID, - ReadOnly: readOnly, - }, - } - if fsType != "" { - pvSource.Cinder.FSType = fsType - } - return &pvSource, nil -} - func (c *cinderDriver) GetDynamicProvisionStorageClass(config *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass { provisioner := "kubernetes.io/cinder" parameters := map[string]string{} @@ -1028,78 +984,12 @@ func (c *cinderDriver) GetDynamicProvisionStorageClass(config *storageframework. return storageframework.GetStorageClass(provisioner, parameters, nil, ns) } -func (c *cinderDriver) PrepareTest(f *framework.Framework) (*storageframework.PerTestConfig, func()) { +func (c *cinderDriver) PrepareTest(f *framework.Framework) *storageframework.PerTestConfig { return &storageframework.PerTestConfig{ Driver: c, Prefix: "cinder", Framework: f, - }, func() {} -} - -func (c *cinderDriver) CreateVolume(config *storageframework.PerTestConfig, volType storageframework.TestVolType) storageframework.TestVolume { - f := config.Framework - ns := f.Namespace - - // We assume that namespace.Name is a random string - volumeName := ns.Name - ginkgo.By("creating a test Cinder volume") - output, err := exec.Command("cinder", "create", "--display-name="+volumeName, "1").CombinedOutput() - outputString := string(output[:]) - framework.Logf("cinder output:\n%s", outputString) - framework.ExpectNoError(err) - - // Parse 'id'' from stdout. Expected format: - // | attachments | [] | - // | availability_zone | nova | - // ... - // | id | 1d6ff08f-5d1c-41a4-ad72-4ef872cae685 | - volumeID := "" - for _, line := range strings.Split(outputString, "\n") { - fields := strings.Fields(line) - if len(fields) != 5 { - continue - } - if fields[1] != "id" { - continue - } - volumeID = fields[3] - break } - framework.Logf("Volume ID: %s", volumeID) - framework.ExpectNotEqual(volumeID, "") - return &cinderVolume{ - volumeName: volumeName, - volumeID: volumeID, - } -} - -func (v *cinderVolume) DeleteVolume() { - id := v.volumeID - name := v.volumeName - - // Try to delete the volume for several seconds - it takes - // a while for the plugin to detach it. - var output []byte - var err error - timeout := time.Second * 120 - - framework.Logf("Waiting up to %v for removal of cinder volume %s / %s", timeout, id, name) - for start := time.Now(); time.Since(start) < timeout; time.Sleep(5 * time.Second) { - output, err = exec.Command("cinder", "delete", id).CombinedOutput() - if err == nil { - framework.Logf("Cinder volume %s deleted", id) - return - } - framework.Logf("Failed to delete volume %s / %s: %v\n%s", id, name, err, string(output)) - } - // Timed out, try to get "cinder show " output for easier debugging - showOutput, showErr := exec.Command("cinder", "show", id).CombinedOutput() - if showErr != nil { - framework.Logf("Failed to show volume %s / %s: %v\n%s", id, name, showErr, string(showOutput)) - } else { - framework.Logf("Volume %s / %s:\n%s", id, name, string(showOutput)) - } - framework.Failf("Failed to delete pre-provisioned volume %s / %s: %v\n%s", id, name, err, string(output[:])) } // GCE diff --git a/test/e2e/storage/volume_provisioning.go b/test/e2e/storage/volume_provisioning.go index 892a035a4a3..8af3f17c2e1 100644 --- a/test/e2e/storage/volume_provisioning.go +++ b/test/e2e/storage/volume_provisioning.go @@ -294,7 +294,7 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { ClaimSize: "1.5Gi", ExpectedSize: "2Gi", PvCheck: func(claim *v1.PersistentVolumeClaim) { - testsuites.PVWriteReadSingleNodeCheck(c, f.Timeouts, claim, e2epod.NodeSelection{}) + testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) }, }, { @@ -309,7 +309,7 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { ClaimSize: "1.5Gi", ExpectedSize: "2Gi", PvCheck: func(claim *v1.PersistentVolumeClaim) { - testsuites.PVWriteReadSingleNodeCheck(c, f.Timeouts, claim, e2epod.NodeSelection{}) + testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) }, }, // vSphere generic test diff --git a/vendor/github.com/gophercloud/gophercloud/.gitignore b/vendor/github.com/gophercloud/gophercloud/.gitignore deleted file mode 100644 index dd91ed20559..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -**/*.swp -.idea -.vscode diff --git a/vendor/github.com/gophercloud/gophercloud/.travis.yml b/vendor/github.com/gophercloud/gophercloud/.travis.yml deleted file mode 100644 index 9153a00fc55..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: go -sudo: false -install: -- 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.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 deleted file mode 100644 index 135e3b203a8..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml +++ /dev/null @@ -1,114 +0,0 @@ -- 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/CHANGELOG.md b/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/vendor/github.com/gophercloud/gophercloud/LICENSE b/vendor/github.com/gophercloud/gophercloud/LICENSE deleted file mode 100644 index fbbbc9e4cba..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md deleted file mode 100644 index ad29041d9bf..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# Gophercloud: an OpenStack SDK for Go -[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud) -[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master) - -Gophercloud is an OpenStack Go SDK. - -## Useful links - -* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud) -* [Effective Go](https://golang.org/doc/effective_go.html) - -## How to install - -Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH) -is pointing to an appropriate directory where you want to install Gophercloud: - -```bash -mkdir $HOME/go -export GOPATH=$HOME/go -``` - -To protect yourself against changes in your dependencies, we highly recommend choosing a -[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for -your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install -Gophercloud as a dependency like so: - -```bash -go get github.com/gophercloud/gophercloud - -# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud" - -godep save ./... -``` - -This will install all the source files you need into a `Godeps/_workspace` directory, which is -referenceable from your own source files when you use the `godep go` command. - -## Getting started - -### Credentials - -Because you'll be hitting an API, you will need to retrieve your OpenStack -credentials and either store them as environment variables or in your local Go -files. The first method is recommended because it decouples credential -information from source code, allowing you to push the latter to your version -control system without any security risk. - -You will need to retrieve the following: - -* username -* password -* a valid Keystone identity URL - -For users that have the OpenStack dashboard installed, there's a shortcut. If -you visit the `project/access_and_security` path in Horizon and click on the -"Download OpenStack RC File" button at the top right hand corner, you will -download a bash file that exports all of your access details to environment -variables. To execute the file, run `source admin-openrc.sh` and you will be -prompted for your password. - -### Authentication - -Once you have access to your credentials, you can begin plugging them into -Gophercloud. The next step is authentication, and this is handled by a base -"Provider" struct. To get one, you can either pass in your credentials -explicitly, or tell Gophercloud to use environment variables: - -```go -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" - "github.com/gophercloud/gophercloud/openstack/utils" -) - -// Option 1: Pass in the values yourself -opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://openstack.example.com:5000/v2.0", - Username: "{username}", - Password: "{password}", -} - -// Option 2: Use a utility function to retrieve all your environment variables -opts, err := openstack.AuthOptionsFromEnv() -``` - -Once you have the `opts` variable, you can pass it in and get back a -`ProviderClient` struct: - -```go -provider, err := openstack.AuthenticatedClient(opts) -``` - -The `ProviderClient` is the top-level client that all of your OpenStack services -derive from. The provider contains all of the authentication details that allow -your Go code to access the API - such as the base URL and token ID. - -### Provision a server - -Once we have a base Provider, we inject it as a dependency into each OpenStack -service. In order to work with the Compute API, we need a Compute service -client; which can be created like so: - -```go -client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), -}) -``` - -We then use this `client` for any Compute API operation we want. In our case, -we want to provision a new server - so we invoke the `Create` method and pass -in the flavor ID (hardware specification) and image ID (operating system) we're -interested in: - -```go -import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - -server, err := servers.Create(client, servers.CreateOpts{ - Name: "My new server!", - FlavorRef: "flavor_id", - ImageRef: "image_id", -}).Extract() -``` - -The above code sample creates a new server with the parameters, and embodies the -new resource in the `server` variable (a -[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct). - -## Advanced Usage - -Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works. - -## Backwards-Compatibility Guarantees - -None. Vendor it and write tests covering the parts you use. - -## Contributing - -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](https://github.com/gophercloud/gophercloud/issues). - -## Thank You - -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/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go deleted file mode 100644 index 5ffa8d1e0a7..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ /dev/null @@ -1,437 +0,0 @@ -package gophercloud - -/* -AuthOptions stores information needed to authenticate to an OpenStack Cloud. -You can populate one manually, or use a provider's AuthOptionsFromEnv() function -to read relevant information from the standard environment variables. Pass one -to a provider's AuthenticatedClient function to authenticate and obtain a -ProviderClient representing an active session on that provider. - -Its fields are the union of those recognized by each identity implementation and -provider. - -An example of manually providing authentication information: - - opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://openstack.example.com:5000/v2.0", - Username: "{username}", - Password: "{password}", - TenantID: "{tenant_id}", - } - - provider, err := openstack.AuthenticatedClient(opts) - -An example of using AuthOptionsFromEnv(), where the environment variables can -be read from a file, such as a standard openrc file: - - opts, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(opts) -*/ -type AuthOptions struct { - // IdentityEndpoint specifies the HTTP endpoint that is required to work with - // the Identity API of the appropriate version. While it's ultimately needed by - // all of the identity services, it will often be populated by a provider-level - // function. - // - // The IdentityEndpoint is typically referred to as the "auth_url" or - // "OS_AUTH_URL" in the information provided by the cloud operator. - IdentityEndpoint string `json:"-"` - - // Username is required if using Identity V2 API. Consult with your provider's - // control panel to discover your account's username. In Identity V3, either - // UserID or a combination of Username and DomainID or DomainName are needed. - Username string `json:"username,omitempty"` - UserID string `json:"-"` - - Password string `json:"password,omitempty"` - - // At most one of DomainID and DomainName must be provided if using Username - // with Identity V3. Otherwise, either are optional. - DomainID string `json:"-"` - DomainName string `json:"name,omitempty"` - - // The TenantID and TenantName fields are optional for the Identity V2 API. - // The same fields are known as project_id and project_name in the Identity - // V3 API, but are collected as TenantID and TenantName here in both cases. - // Some providers allow you to specify a TenantName instead of the TenantId. - // Some require both. Your provider's authentication policies will determine - // how these fields influence authentication. - // If DomainID or DomainName are provided, they will also apply to TenantName. - // It is not currently possible to authenticate with Username and a Domain - // and scope to a Project in a different Domain by using TenantName. To - // accomplish that, the ProjectID will need to be provided as the TenantID - // option. - TenantID string `json:"tenantId,omitempty"` - TenantName string `json:"tenantName,omitempty"` - - // AllowReauth should be set to true if you grant permission for Gophercloud to - // cache your credentials in memory, and to allow Gophercloud to attempt to - // re-authenticate automatically if/when your token expires. If you set it to - // false, it will not cache these settings, but re-authentication will not be - // possible. This setting defaults to false. - // - // NOTE: The reauth function will try to re-authenticate endlessly if left - // unchecked. The way to limit the number of attempts is to provide a custom - // HTTP client to the provider client and provide a transport that implements - // the RoundTripper interface and stores the number of failed retries. For an - // example of this, see here: - // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 - AllowReauth bool `json:"-"` - - // 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 -// interface in the v2 tokens package -func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { - // Populate the request map. - authMap := make(map[string]interface{}) - - if opts.Username != "" { - if opts.Password != "" { - authMap["passwordCredentials"] = map[string]interface{}{ - "username": opts.Username, - "password": opts.Password, - } - } else { - return nil, ErrMissingInput{Argument: "Password"} - } - } else if opts.TokenID != "" { - authMap["token"] = map[string]interface{}{ - "id": opts.TokenID, - } - } else { - return nil, ErrMissingInput{Argument: "Username"} - } - - if opts.TenantID != "" { - authMap["tenantId"] = opts.TenantID - } - if opts.TenantName != "" { - authMap["tenantName"] = opts.TenantName - } - - return map[string]interface{}{"auth": authMap}, nil -} - -func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { - type domainReq struct { - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - } - - type projectReq struct { - Domain *domainReq `json:"domain,omitempty"` - Name *string `json:"name,omitempty"` - ID *string `json:"id,omitempty"` - } - - type userReq struct { - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Password string `json:"password,omitempty"` - Domain *domainReq `json:"domain,omitempty"` - } - - type passwordReq struct { - User userReq `json:"user"` - } - - type tokenReq struct { - 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"` - ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` - } - - type authReq struct { - Identity identityReq `json:"identity"` - } - - type request struct { - Auth authReq `json:"auth"` - } - - // Populate the request structure based on the provided arguments. Create and return an error - // if insufficient or incompatible information is present. - var req request - - if opts.Password == "" { - if opts.TokenID != "" { - // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication - // parameters. - if opts.Username != "" { - return nil, ErrUsernameWithToken{} - } - if opts.UserID != "" { - return nil, ErrUserIDWithToken{} - } - if opts.DomainID != "" { - return nil, ErrDomainIDWithToken{} - } - if opts.DomainName != "" { - return nil, ErrDomainNameWithToken{} - } - - // Configure the request for Token authentication. - req.Auth.Identity.Methods = []string{"token"} - 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 or ApplicationCredential are available, authentication can't continue. - return nil, ErrMissingPassword{} - } - } else { - // Password authentication. - req.Auth.Identity.Methods = []string{"password"} - - // At least one of Username and UserID must be specified. - if opts.Username == "" && opts.UserID == "" { - return nil, ErrUsernameOrUserID{} - } - - if opts.Username != "" { - // If Username is provided, UserID may not be provided. - if opts.UserID != "" { - return nil, ErrUsernameOrUserID{} - } - - // Either DomainID or DomainName must also be specified. - if opts.DomainID == "" && opts.DomainName == "" { - return nil, ErrDomainIDOrDomainName{} - } - - if opts.DomainID != "" { - if opts.DomainName != "" { - return nil, ErrDomainIDOrDomainName{} - } - - // Configure the request for Username and Password authentication with a DomainID. - req.Auth.Identity.Password = &passwordReq{ - User: userReq{ - Name: &opts.Username, - Password: opts.Password, - Domain: &domainReq{ID: &opts.DomainID}, - }, - } - } - - if opts.DomainName != "" { - // Configure the request for Username and Password authentication with a DomainName. - req.Auth.Identity.Password = &passwordReq{ - User: userReq{ - Name: &opts.Username, - Password: opts.Password, - Domain: &domainReq{Name: &opts.DomainName}, - }, - } - } - } - - if opts.UserID != "" { - // If UserID is specified, neither DomainID nor DomainName may be. - if opts.DomainID != "" { - return nil, ErrDomainIDWithUserID{} - } - if opts.DomainName != "" { - return nil, ErrDomainNameWithUserID{} - } - - // Configure the request for UserID and Password authentication. - req.Auth.Identity.Password = &passwordReq{ - User: userReq{ID: &opts.UserID, Password: opts.Password}, - } - } - } - - b, err := BuildRequestBody(req, "") - if err != nil { - return nil, err - } - - if len(scope) != 0 { - b["auth"].(map[string]interface{})["scope"] = scope - } - - return b, nil -} - -func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - // 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 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, ErrScopeDomainIDOrDomainName{} - } - if opts.Scope.ProjectID != "" { - return nil, 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, ErrScopeProjectIDAlone{} - } - if opts.Scope.DomainName != "" { - return nil, 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, 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 - } - - return nil, nil -} - -func (opts AuthOptions) CanReauth() bool { - return opts.AllowReauth -} diff --git a/vendor/github.com/gophercloud/gophercloud/auth_result.go b/vendor/github.com/gophercloud/gophercloud/auth_result.go deleted file mode 100644 index 2e4699b978c..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/auth_result.go +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 953ca822a97..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/doc.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Package gophercloud provides a multi-vendor interface to OpenStack-compatible -clouds. The library has a three-level hierarchy: providers, services, and -resources. - -Authenticating with Providers - -Provider structs represent the cloud providers that offer and manage a -collection of services. You will generally want to create one Provider -client per OpenStack cloud. - - It is now recommended to use the `clientconfig` package found at - https://github.com/gophercloud/utils/tree/master/openstack/clientconfig - for all authentication purposes. - - The below documentation is still relevant. clientconfig simply implements - the below and presents it in an easier and more flexible way. - -Use your OpenStack credentials to create a Provider client. The -IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in -information provided by the cloud operator. Additionally, the cloud may refer to -TenantID or TenantName as project_id and project_name. Credentials are -specified like so: - - opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://openstack.example.com:5000/v2.0", - Username: "{username}", - Password: "{password}", - TenantID: "{tenant_id}", - } - - provider, err := openstack.AuthenticatedClient(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 -OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant" -instead of "project". - - opts, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(opts) - -Service Clients - -Service structs are specific to a provider and handle all of the logic and -operations for a particular OpenStack service. Examples of services include: -Compute, Object Storage, Block Storage. In order to define one, you need to -pass in the parent provider, like so: - - opts := gophercloud.EndpointOpts{Region: "RegionOne"} - - client, err := openstack.NewComputeV2(provider, opts) - -Resources - -Resource structs are the domain models that services make use of in order -to work with and represent the state of API resources: - - server, err := servers.Get(client, "{serverId}").Extract() - -Intermediate Result structs are returned for API operations, which allow -generic access to the HTTP headers, response body, and any errors associated -with the network transaction. To turn a result into a usable resource struct, -you must call the Extract method which is chained to the response, or an -Extract function from an applicable extension: - - result := servers.Get(client, "{serverId}") - - // Attempt to extract the disk configuration from the OS-DCF disk config - // extension: - config, err := diskconfig.ExtractGet(result) - -All requests that enumerate a collection return a Pager struct that is used to -iterate through the results one page at a time. Use the EachPage method on that -Pager to handle each successive Page in a closure, then use the appropriate -extraction method from that request's package to interpret that Page as a slice -of results: - - err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) { - s, err := servers.ExtractServers(page) - if err != nil { - return false, err - } - - // Handle the []servers.Server slice. - - // Return "false" or an error to prematurely stop fetching new pages. - return true, nil - }) - -If you want to obtain the entire collection of pages without doing any -intermediary processing on each page, you can use the AllPages method: - - allPages, err := servers.List(client, nil).AllPages() - allServers, err := servers.ExtractServers(allPages) - -This top-level package contains utility functions and data types that are used -throughout the provider and service packages. Of particular note for end users -are the AuthOptions and EndpointOpts structs. -*/ -package gophercloud diff --git a/vendor/github.com/gophercloud/gophercloud/endpoint_search.go b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go deleted file mode 100644 index 2fbc3c97f14..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/endpoint_search.go +++ /dev/null @@ -1,76 +0,0 @@ -package gophercloud - -// Availability indicates to whom a specific service endpoint is accessible: -// the internet at large, internal networks only, or only to administrators. -// Different identity services use different terminology for these. Identity v2 -// lists them as different kinds of URLs within the service catalog ("adminURL", -// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an -// endpoint's response. -type Availability string - -const ( - // AvailabilityAdmin indicates that an endpoint is only available to - // administrators. - AvailabilityAdmin Availability = "admin" - - // AvailabilityPublic indicates that an endpoint is available to everyone on - // the internet. - AvailabilityPublic Availability = "public" - - // AvailabilityInternal indicates that an endpoint is only available within - // the cluster's internal network. - AvailabilityInternal Availability = "internal" -) - -// EndpointOpts specifies search criteria used by queries against an -// OpenStack service catalog. The options must contain enough information to -// unambiguously identify one, and only one, endpoint within the catalog. -// -// Usually, these are passed to service client factory functions in a provider -// package, like "openstack.NewComputeV2()". -type EndpointOpts struct { - // Type [required] is the service type for the client (e.g., "compute", - // "object-store"). Generally, this will be supplied by the service client - // function, but a user-given value will be honored if provided. - Type string - - // Name [optional] is the service name for the client (e.g., "nova") as it - // appears in the service catalog. Services can have the same Type but a - // different Name, which is why both Type and Name are sometimes needed. - Name string - - // Region [required] is the geographic region in which the endpoint resides, - // generally specifying which datacenter should house your resources. - // Required only for services that span multiple regions. - Region string - - // Availability [optional] is the visibility of the endpoint to be returned. - // Valid types include the constants AvailabilityPublic, AvailabilityInternal, - // or AvailabilityAdmin from this package. - // - // Availability is not required, and defaults to AvailabilityPublic. Not all - // providers or services offer all Availability options. - Availability Availability -} - -/* -EndpointLocator is an internal function to be used by provider implementations. - -It provides an implementation that locates a single endpoint from a service -catalog for a specific ProviderClient based on user-provided EndpointOpts. The -provider then uses it to discover related ServiceClients. -*/ -type EndpointLocator func(EndpointOpts) (string, error) - -// ApplyDefaults is an internal method to be used by provider implementations. -// -// It sets EndpointOpts fields if not already set, including a default type. -// Currently, EndpointOpts.Availability defaults to the public endpoint. -func (eo *EndpointOpts) ApplyDefaults(t string) { - if eo.Type == "" { - eo.Type = t - } - if eo.Availability == "" { - eo.Availability = AvailabilityPublic - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go deleted file mode 100644 index 0bcb3af7f00..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/errors.go +++ /dev/null @@ -1,471 +0,0 @@ -package gophercloud - -import ( - "fmt" - "strings" -) - -// BaseError is an error type that all other error types embed. -type BaseError struct { - DefaultErrString string - Info string -} - -func (e BaseError) Error() string { - e.DefaultErrString = "An error occurred while executing a Gophercloud request." - return e.choseErrString() -} - -func (e BaseError) choseErrString() string { - if e.Info != "" { - return e.Info - } - return e.DefaultErrString -} - -// ErrMissingInput is the error when input is required in a particular -// situation but not provided by the user -type ErrMissingInput struct { - BaseError - Argument string -} - -func (e ErrMissingInput) Error() string { - e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument) - return e.choseErrString() -} - -// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors. -type ErrInvalidInput struct { - ErrMissingInput - Value interface{} -} - -func (e ErrInvalidInput) Error() string { - e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value) - 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 { - BaseError - URL string - Method string - Expected []int - Actual int - Body []byte -} - -func (e ErrUnexpectedResponseCode) Error() string { - e.DefaultErrString = fmt.Sprintf( - "Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s", - e.Expected, e.Method, e.URL, e.Actual, e.Body, - ) - return e.choseErrString() -} - -// ErrDefault400 is the default error type returned on a 400 HTTP response code. -type ErrDefault400 struct { - ErrUnexpectedResponseCode -} - -// ErrDefault401 is the default error type returned on a 401 HTTP response code. -type ErrDefault401 struct { - ErrUnexpectedResponseCode -} - -// ErrDefault403 is the default error type returned on a 403 HTTP response code. -type ErrDefault403 struct { - ErrUnexpectedResponseCode -} - -// ErrDefault404 is the default error type returned on a 404 HTTP response code. -type ErrDefault404 struct { - ErrUnexpectedResponseCode -} - -// ErrDefault405 is the default error type returned on a 405 HTTP response code. -type ErrDefault405 struct { - ErrUnexpectedResponseCode -} - -// ErrDefault408 is the default error type returned on a 408 HTTP response code. -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 -} - -// ErrDefault500 is the default error type returned on a 500 HTTP response code. -type ErrDefault500 struct { - ErrUnexpectedResponseCode -} - -// ErrDefault503 is the default error type returned on a 503 HTTP response code. -type ErrDefault503 struct { - ErrUnexpectedResponseCode -} - -func (e ErrDefault400) Error() string { - 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" -} -func (e ErrDefault403) Error() string { - e.DefaultErrString = fmt.Sprintf( - "Request forbidden: [%s %s], error message: %s", - e.Method, e.URL, e.Body, - ) - return e.choseErrString() -} -func (e ErrDefault404) Error() string { - return "Resource not found" -} -func (e ErrDefault405) Error() string { - return "Method not allowed" -} -func (e ErrDefault408) Error() string { - return "The server timed out waiting for the request" -} -func (e ErrDefault429) Error() string { - return "Too many requests have been sent in a given amount of time. Pause" + - " requests, wait up to one minute, and try again." -} -func (e ErrDefault500) Error() string { - return "Internal Server Error" -} -func (e ErrDefault503) Error() string { - return "The service is currently unable to handle the request due to a temporary" + - " overloading or maintenance. This is a temporary condition. Try again later." -} - -// Err400er is the interface resource error types implement to override the error message -// from a 400 error. -type Err400er interface { - Error400(ErrUnexpectedResponseCode) error -} - -// Err401er is the interface resource error types implement to override the error message -// from a 401 error. -type Err401er interface { - Error401(ErrUnexpectedResponseCode) error -} - -// Err403er is the interface resource error types implement to override the error message -// from a 403 error. -type Err403er interface { - Error403(ErrUnexpectedResponseCode) error -} - -// Err404er is the interface resource error types implement to override the error message -// from a 404 error. -type Err404er interface { - Error404(ErrUnexpectedResponseCode) error -} - -// Err405er is the interface resource error types implement to override the error message -// from a 405 error. -type Err405er interface { - Error405(ErrUnexpectedResponseCode) error -} - -// Err408er is the interface resource error types implement to override the error message -// from a 408 error. -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 { - Error429(ErrUnexpectedResponseCode) error -} - -// Err500er is the interface resource error types implement to override the error message -// from a 500 error. -type Err500er interface { - Error500(ErrUnexpectedResponseCode) error -} - -// Err503er is the interface resource error types implement to override the error message -// from a 503 error. -type Err503er interface { - Error503(ErrUnexpectedResponseCode) error -} - -// ErrTimeOut is the error type returned when an operations times out. -type ErrTimeOut struct { - BaseError -} - -func (e ErrTimeOut) Error() string { - e.DefaultErrString = "A time out occurred" - return e.choseErrString() -} - -// ErrUnableToReauthenticate is the error type returned when reauthentication fails. -type ErrUnableToReauthenticate struct { - BaseError - ErrOriginal error -} - -func (e ErrUnableToReauthenticate) Error() string { - e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal) - return e.choseErrString() -} - -// ErrErrorAfterReauthentication is the error type returned when reauthentication -// succeeds, but an error occurs afterword (usually an HTTP error). -type ErrErrorAfterReauthentication struct { - BaseError - ErrOriginal error -} - -func (e ErrErrorAfterReauthentication) Error() string { - e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal) - return e.choseErrString() -} - -// ErrServiceNotFound is returned when no service in a service catalog matches -// the provided EndpointOpts. This is generally returned by provider service -// factory methods like "NewComputeV2()" and can mean that a service is not -// enabled for your account. -type ErrServiceNotFound struct { - BaseError -} - -func (e ErrServiceNotFound) Error() string { - e.DefaultErrString = "No suitable service could be found in the service catalog." - return e.choseErrString() -} - -// ErrEndpointNotFound is returned when no available endpoints match the -// provided EndpointOpts. This is also generally returned by provider service -// factory methods, and usually indicates that a region was specified -// incorrectly. -type ErrEndpointNotFound struct { - BaseError -} - -func (e ErrEndpointNotFound) Error() string { - e.DefaultErrString = "No suitable endpoint could be found in the service catalog." - return e.choseErrString() -} - -// ErrResourceNotFound is the error when trying to retrieve a resource's -// ID by name and the resource doesn't exist. -type ErrResourceNotFound struct { - BaseError - Name string - ResourceType string -} - -func (e ErrResourceNotFound) Error() string { - e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name) - return e.choseErrString() -} - -// ErrMultipleResourcesFound is the error when trying to retrieve a resource's -// ID by name and multiple resources have the user-provided name. -type ErrMultipleResourcesFound struct { - BaseError - Name string - Count int - ResourceType string -} - -func (e ErrMultipleResourcesFound) Error() string { - e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name) - return e.choseErrString() -} - -// ErrUnexpectedType is the error when an unexpected type is encountered -type ErrUnexpectedType struct { - BaseError - Expected string - Actual string -} - -func (e ErrUnexpectedType) Error() string { - e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual) - return e.choseErrString() -} - -func unacceptedAttributeErr(attribute string) string { - return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute) -} - -func redundantWithTokenErr(attribute string) string { - return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute) -} - -func redundantWithUserID(attribute string) string { - return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute) -} - -// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used. -type ErrAPIKeyProvided struct{ BaseError } - -func (e ErrAPIKeyProvided) Error() string { - return unacceptedAttributeErr("APIKey") -} - -// ErrTenantIDProvided indicates that a TenantID was provided but can't be used. -type ErrTenantIDProvided struct{ BaseError } - -func (e ErrTenantIDProvided) Error() string { - return unacceptedAttributeErr("TenantID") -} - -// ErrTenantNameProvided indicates that a TenantName was provided but can't be used. -type ErrTenantNameProvided struct{ BaseError } - -func (e ErrTenantNameProvided) Error() string { - return unacceptedAttributeErr("TenantName") -} - -// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead. -type ErrUsernameWithToken struct{ BaseError } - -func (e ErrUsernameWithToken) Error() string { - return redundantWithTokenErr("Username") -} - -// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead. -type ErrUserIDWithToken struct{ BaseError } - -func (e ErrUserIDWithToken) Error() string { - return redundantWithTokenErr("UserID") -} - -// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead. -type ErrDomainIDWithToken struct{ BaseError } - -func (e ErrDomainIDWithToken) Error() string { - return redundantWithTokenErr("DomainID") -} - -// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s -type ErrDomainNameWithToken struct{ BaseError } - -func (e ErrDomainNameWithToken) Error() string { - return redundantWithTokenErr("DomainName") -} - -// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once. -type ErrUsernameOrUserID struct{ BaseError } - -func (e ErrUsernameOrUserID) Error() string { - return "Exactly one of Username and UserID must be provided for password authentication" -} - -// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used. -type ErrDomainIDWithUserID struct{ BaseError } - -func (e ErrDomainIDWithUserID) Error() string { - return redundantWithUserID("DomainID") -} - -// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used. -type ErrDomainNameWithUserID struct{ BaseError } - -func (e ErrDomainNameWithUserID) Error() string { - return redundantWithUserID("DomainName") -} - -// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it. -// It may also indicate that both a DomainID and a DomainName were provided at once. -type ErrDomainIDOrDomainName struct{ BaseError } - -func (e ErrDomainIDOrDomainName) Error() string { - return "You must provide exactly one of DomainID or DomainName to authenticate by Username" -} - -// ErrMissingPassword indicates that no password was provided and no token is available. -type ErrMissingPassword struct{ BaseError } - -func (e ErrMissingPassword) Error() string { - return "You must provide a password to authenticate" -} - -// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present. -type ErrScopeDomainIDOrDomainName struct{ BaseError } - -func (e ErrScopeDomainIDOrDomainName) Error() string { - return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName" -} - -// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope. -type ErrScopeProjectIDOrProjectName struct{ BaseError } - -func (e ErrScopeProjectIDOrProjectName) Error() string { - return "You must provide at most one of ProjectID or ProjectName in a Scope" -} - -// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope. -type ErrScopeProjectIDAlone struct{ BaseError } - -func (e ErrScopeProjectIDAlone) Error() string { - return "ProjectID must be supplied alone in a Scope" -} - -// ErrScopeEmpty indicates that no credentials were provided in a Scope. -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/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go deleted file mode 100644 index 0e8d90ff826..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go +++ /dev/null @@ -1,125 +0,0 @@ -package openstack - -import ( - "os" - - "github.com/gophercloud/gophercloud" -) - -var nilOptions = gophercloud.AuthOptions{} - -/* -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 and OS_PROJECT_ID. - -Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings, -or an error will result. OS_PROJECT_ID, is optional. - -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: - - opts, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(opts) -*/ -func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { - authURL := os.Getenv("OS_AUTH_URL") - username := os.Getenv("OS_USERNAME") - userID := os.Getenv("OS_USERID") - password := os.Getenv("OS_PASSWORD") - tenantID := os.Getenv("OS_TENANT_ID") - 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 != "" { - tenantID = v - } - - // If OS_PROJECT_NAME is set, overwrite tenantName with the value. - if v := os.Getenv("OS_PROJECT_NAME"); v != "" { - tenantName = v - } - - if authURL == "" { - err := gophercloud.ErrMissingEnvironmentVariable{ - EnvironmentVariable: "OS_AUTH_URL", - } - return nilOptions, err - } - - 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 (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, - ApplicationCredentialID: applicationCredentialID, - ApplicationCredentialName: applicationCredentialName, - ApplicationCredentialSecret: applicationCredentialSecret, - } - - return ao, nil -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go deleted file mode 100644 index a78d3d0482c..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -Package volumeactions provides information and interaction with volumes in the -OpenStack Block Storage service. A volume is a detachable block storage -device, akin to a USB hard drive. - -Example of Attaching a Volume to an Instance - - attachOpts := volumeactions.AttachOpts{ - MountPoint: "/mnt", - Mode: "rw", - InstanceUUID: server.ID, - } - - err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr() - if err != nil { - panic(err) - } - - detachOpts := volumeactions.DetachOpts{ - AttachmentID: volume.Attachments[0].AttachmentID, - } - - err = volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr() - if err != nil { - panic(err) - } - - -Example of Creating an Image from a Volume - - uploadImageOpts := volumeactions.UploadImageOpts{ - ImageName: "my_vol", - Force: true, - } - - volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract() - if err != nil { - panic(err) - } - - fmt.Printf("%+v\n", volumeImage) - -Example of Extending a Volume's Size - - extendOpts := volumeactions.ExtendSizeOpts{ - NewSize: 100, - } - - err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr() - if err != nil { - panic(err) - } - -Example of Initializing a Volume Connection - - connectOpts := &volumeactions.InitializeConnectionOpts{ - IP: "127.0.0.1", - Host: "stack", - Initiator: "iqn.1994-05.com.redhat:17cf566367d2", - Multipath: gophercloud.Disabled, - Platform: "x86_64", - OSType: "linux2", - } - - connectionInfo, err := volumeactions.InitializeConnection(client, volume.ID, connectOpts).Extract() - if err != nil { - panic(err) - } - - fmt.Printf("%+v\n", connectionInfo["data"]) - - terminateOpts := &volumeactions.InitializeConnectionOpts{ - IP: "127.0.0.1", - Host: "stack", - Initiator: "iqn.1994-05.com.redhat:17cf566367d2", - Multipath: gophercloud.Disabled, - Platform: "x86_64", - OSType: "linux2", - } - - err = volumeactions.TerminateConnection(client, volume.ID, terminateOpts).ExtractErr() - if err != nil { - panic(err) - } -*/ -package volumeactions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go deleted file mode 100644 index d18bff555b5..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go +++ /dev/null @@ -1,269 +0,0 @@ -package volumeactions - -import ( - "github.com/gophercloud/gophercloud" -) - -// AttachOptsBuilder allows extensions to add additional parameters to the -// Attach request. -type AttachOptsBuilder interface { - ToVolumeAttachMap() (map[string]interface{}, error) -} - -// AttachMode describes the attachment mode for volumes. -type AttachMode string - -// These constants determine how a volume is attached. -const ( - ReadOnly AttachMode = "ro" - ReadWrite AttachMode = "rw" -) - -// AttachOpts contains options for attaching a Volume. -type AttachOpts struct { - // The mountpoint of this volume. - MountPoint string `json:"mountpoint,omitempty"` - - // The nova instance ID, can't set simultaneously with HostName. - InstanceUUID string `json:"instance_uuid,omitempty"` - - // The hostname of baremetal host, can't set simultaneously with InstanceUUID. - HostName string `json:"host_name,omitempty"` - - // Mount mode of this volume. - Mode AttachMode `json:"mode,omitempty"` -} - -// ToVolumeAttachMap assembles a request body based on the contents of a -// AttachOpts. -func (opts AttachOpts) ToVolumeAttachMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "os-attach") -} - -// Attach will attach a volume based on the values in AttachOpts. -func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder) (r AttachResult) { - b, err := opts.ToVolumeAttachMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// BeginDetach will mark the volume as detaching. -func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) { - b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})} - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// DetachOptsBuilder allows extensions to add additional parameters to the -// Detach request. -type DetachOptsBuilder interface { - ToVolumeDetachMap() (map[string]interface{}, error) -} - -// DetachOpts contains options for detaching a Volume. -type DetachOpts struct { - // AttachmentID is the ID of the attachment between a volume and instance. - AttachmentID string `json:"attachment_id,omitempty"` -} - -// ToVolumeDetachMap assembles a request body based on the contents of a -// DetachOpts. -func (opts DetachOpts) ToVolumeDetachMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "os-detach") -} - -// Detach will detach a volume based on volume ID. -func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder) (r DetachResult) { - b, err := opts.ToVolumeDetachMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// Reserve will reserve a volume based on volume ID. -func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) { - b := map[string]interface{}{"os-reserve": make(map[string]interface{})} - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201, 202}, - }) - return -} - -// Unreserve will unreserve a volume based on volume ID. -func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) { - b := map[string]interface{}{"os-unreserve": make(map[string]interface{})} - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201, 202}, - }) - return -} - -// InitializeConnectionOptsBuilder allows extensions to add additional parameters to the -// InitializeConnection request. -type InitializeConnectionOptsBuilder interface { - ToVolumeInitializeConnectionMap() (map[string]interface{}, error) -} - -// InitializeConnectionOpts hosts options for InitializeConnection. -// The fields are specific to the storage driver in use and the destination -// attachment. -type InitializeConnectionOpts struct { - IP string `json:"ip,omitempty"` - Host string `json:"host,omitempty"` - Initiator string `json:"initiator,omitempty"` - Wwpns []string `json:"wwpns,omitempty"` - Wwnns string `json:"wwnns,omitempty"` - Multipath *bool `json:"multipath,omitempty"` - Platform string `json:"platform,omitempty"` - OSType string `json:"os_type,omitempty"` -} - -// ToVolumeInitializeConnectionMap assembles a request body based on the contents of a -// InitializeConnectionOpts. -func (opts InitializeConnectionOpts) ToVolumeInitializeConnectionMap() (map[string]interface{}, error) { - b, err := gophercloud.BuildRequestBody(opts, "connector") - return map[string]interface{}{"os-initialize_connection": b}, err -} - -// InitializeConnection initializes an iSCSI connection by volume ID. -func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) { - b, err := opts.ToVolumeInitializeConnectionMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201, 202}, - }) - return -} - -// TerminateConnectionOptsBuilder allows extensions to add additional parameters to the -// TerminateConnection request. -type TerminateConnectionOptsBuilder interface { - ToVolumeTerminateConnectionMap() (map[string]interface{}, error) -} - -// TerminateConnectionOpts hosts options for TerminateConnection. -type TerminateConnectionOpts struct { - IP string `json:"ip,omitempty"` - Host string `json:"host,omitempty"` - Initiator string `json:"initiator,omitempty"` - Wwpns []string `json:"wwpns,omitempty"` - Wwnns string `json:"wwnns,omitempty"` - Multipath *bool `json:"multipath,omitempty"` - Platform string `json:"platform,omitempty"` - OSType string `json:"os_type,omitempty"` -} - -// ToVolumeTerminateConnectionMap assembles a request body based on the contents of a -// TerminateConnectionOpts. -func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string]interface{}, error) { - b, err := gophercloud.BuildRequestBody(opts, "connector") - return map[string]interface{}{"os-terminate_connection": b}, err -} - -// TerminateConnection terminates an iSCSI connection by volume ID. -func TerminateConnection(client *gophercloud.ServiceClient, id string, opts TerminateConnectionOptsBuilder) (r TerminateConnectionResult) { - b, err := opts.ToVolumeTerminateConnectionMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// ExtendSizeOptsBuilder allows extensions to add additional parameters to the -// ExtendSize request. -type ExtendSizeOptsBuilder interface { - ToVolumeExtendSizeMap() (map[string]interface{}, error) -} - -// ExtendSizeOpts contains options for extending the size of an existing Volume. -// This object is passed to the volumes.ExtendSize function. -type ExtendSizeOpts struct { - // NewSize is the new size of the volume, in GB. - NewSize int `json:"new_size" required:"true"` -} - -// ToVolumeExtendSizeMap assembles a request body based on the contents of an -// ExtendSizeOpts. -func (opts ExtendSizeOpts) ToVolumeExtendSizeMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "os-extend") -} - -// ExtendSize will extend the size of the volume based on the provided information. -// This operation does not return a response body. -func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOptsBuilder) (r ExtendSizeResult) { - b, err := opts.ToVolumeExtendSizeMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// UploadImageOptsBuilder allows extensions to add additional parameters to the -// UploadImage request. -type UploadImageOptsBuilder interface { - ToVolumeUploadImageMap() (map[string]interface{}, error) -} - -// UploadImageOpts contains options for uploading a Volume to image storage. -type UploadImageOpts struct { - // Container format, may be bare, ofv, ova, etc. - ContainerFormat string `json:"container_format,omitempty"` - - // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. - DiskFormat string `json:"disk_format,omitempty"` - - // The name of image that will be stored in glance. - ImageName string `json:"image_name,omitempty"` - - // Force image creation, usable if volume attached to instance. - Force bool `json:"force,omitempty"` -} - -// ToVolumeUploadImageMap assembles a request body based on the contents of a -// UploadImageOpts. -func (opts UploadImageOpts) ToVolumeUploadImageMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "os-volume_upload_image") -} - -// UploadImage will upload an image based on the values in UploadImageOptsBuilder. -func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageOptsBuilder) (r UploadImageResult) { - b, err := opts.ToVolumeUploadImageMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// ForceDelete will delete the volume regardless of state. -func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-force_delete": ""}, nil, nil) - return -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go deleted file mode 100644 index 5cadd360f20..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go +++ /dev/null @@ -1,191 +0,0 @@ -package volumeactions - -import ( - "encoding/json" - "time" - - "github.com/gophercloud/gophercloud" -) - -// AttachResult contains the response body and error from an Attach request. -type AttachResult struct { - gophercloud.ErrResult -} - -// BeginDetachingResult contains the response body and error from a BeginDetach -// request. -type BeginDetachingResult struct { - gophercloud.ErrResult -} - -// DetachResult contains the response body and error from a Detach request. -type DetachResult struct { - gophercloud.ErrResult -} - -// UploadImageResult contains the response body and error from an UploadImage -// request. -type UploadImageResult struct { - gophercloud.Result -} - -// ReserveResult contains the response body and error from a Reserve request. -type ReserveResult struct { - gophercloud.ErrResult -} - -// UnreserveResult contains the response body and error from an Unreserve -// request. -type UnreserveResult struct { - gophercloud.ErrResult -} - -// TerminateConnectionResult contains the response body and error from a -// TerminateConnection request. -type TerminateConnectionResult struct { - gophercloud.ErrResult -} - -// InitializeConnectionResult contains the response body and error from an -// InitializeConnection request. -type InitializeConnectionResult struct { - gophercloud.Result -} - -// ExtendSizeResult contains the response body and error from an ExtendSize request. -type ExtendSizeResult struct { - gophercloud.ErrResult -} - -// Extract will get the connection information out of the -// InitializeConnectionResult object. -// -// This will be a generic map[string]interface{} and the results will be -// dependent on the type of connection made. -func (r InitializeConnectionResult) Extract() (map[string]interface{}, error) { - var s struct { - ConnectionInfo map[string]interface{} `json:"connection_info"` - } - err := r.ExtractInto(&s) - return s.ConnectionInfo, err -} - -// ImageVolumeType contains volume type information obtained from UploadImage -// action. -type ImageVolumeType struct { - // The ID of a volume type. - ID string `json:"id"` - - // Human-readable display name for the volume type. - Name string `json:"name"` - - // Human-readable description for the volume type. - Description string `json:"display_description"` - - // Flag for public access. - IsPublic bool `json:"is_public"` - - // Extra specifications for volume type. - ExtraSpecs map[string]interface{} `json:"extra_specs"` - - // ID of quality of service specs. - QosSpecsID string `json:"qos_specs_id"` - - // Flag for deletion status of volume type. - Deleted bool `json:"deleted"` - - // The date when volume type was deleted. - DeletedAt time.Time `json:"-"` - - // The date when volume type was created. - CreatedAt time.Time `json:"-"` - - // The date when this volume was last updated. - UpdatedAt time.Time `json:"-"` -} - -func (r *ImageVolumeType) UnmarshalJSON(b []byte) error { - type tmp ImageVolumeType - var s struct { - tmp - CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` - DeletedAt gophercloud.JSONRFC3339MilliNoZ `json:"deleted_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = ImageVolumeType(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - r.DeletedAt = time.Time(s.DeletedAt) - - return err -} - -// VolumeImage contains information about volume uploaded to an image service. -type VolumeImage struct { - // The ID of a volume an image is created from. - VolumeID string `json:"id"` - - // Container format, may be bare, ofv, ova, etc. - ContainerFormat string `json:"container_format"` - - // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. - DiskFormat string `json:"disk_format"` - - // Human-readable description for the volume. - Description string `json:"display_description"` - - // The ID of the created image. - ImageID string `json:"image_id"` - - // Human-readable display name for the image. - ImageName string `json:"image_name"` - - // Size of the volume in GB. - Size int `json:"size"` - - // Current status of the volume. - Status string `json:"status"` - - // The date when this volume was last updated. - UpdatedAt time.Time `json:"-"` - - // Volume type object of used volume. - VolumeType ImageVolumeType `json:"volume_type"` -} - -func (r *VolumeImage) UnmarshalJSON(b []byte) error { - type tmp VolumeImage - var s struct { - tmp - UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = VolumeImage(s.tmp) - - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -// Extract will get an object with info about the uploaded image out of the -// UploadImageResult object. -func (r UploadImageResult) Extract() (VolumeImage, error) { - var s struct { - VolumeImage VolumeImage `json:"os-volume_upload_image"` - } - err := r.ExtractInto(&s) - return s.VolumeImage, err -} - -// ForceDeleteResult contains the response body and error from a ForceDelete request. -type ForceDeleteResult struct { - gophercloud.ErrResult -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go deleted file mode 100644 index 20486ed7194..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go +++ /dev/null @@ -1,7 +0,0 @@ -package volumeactions - -import "github.com/gophercloud/gophercloud" - -func actionURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL("volumes", id, "action") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go deleted file mode 100644 index 307b8b12d2f..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package volumes provides information and interaction with volumes in the -// OpenStack Block Storage service. A volume is a detachable block storage -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. -package volumes 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 deleted file mode 100644 index 1da94238b9c..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go +++ /dev/null @@ -1,172 +0,0 @@ -package volumes - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToVolumeCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Volume. This object is passed to -// the volumes.Create function. For more information about these parameters, -// see the Volume object. -type CreateOpts struct { - Size int `json:"size" required:"true"` - AvailabilityZone string `json:"availability_zone,omitempty"` - Description string `json:"display_description,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - Name string `json:"display_name,omitempty"` - SnapshotID string `json:"snapshot_id,omitempty"` - SourceVolID string `json:"source_volid,omitempty"` - ImageID string `json:"imageRef,omitempty"` - VolumeType string `json:"volume_type,omitempty"` -} - -// ToVolumeCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volume") -} - -// Create will create a new Volume based on the values in CreateOpts. To extract -// the Volume object from the response, call the Extract method on the -// CreateResult. -func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201}, - }) - return -} - -// 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) - return -} - -// Get retrieves the Volume with the provided ID. To extract the Volume 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 { - ToVolumeListQuery() (string, error) -} - -// ListOpts holds options for listing Volumes. It is passed to the volumes.List -// function. -type ListOpts struct { - // admin-only option. Set it to true to see all tenant volumes. - AllTenants bool `q:"all_tenants"` - // List only volumes that contain Metadata. - Metadata map[string]string `q:"metadata"` - // List only volumes that have Name as the display name. - Name string `q:"display_name"` - // List only volumes that have a status of Status. - Status string `q:"status"` -} - -// ToVolumeListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToVolumeListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// List returns Volumes 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.ToVolumeListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return VolumePage{pagination.SinglePageBase(r)} - }) -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToVolumeUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contain options for updating an existing Volume. This object is passed -// 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"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToVolumeUpdateMap assembles a request body based on the contents of an -// UpdateOpts. -func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volume") -} - -// Update will update the Volume with provided information. To extract the updated -// Volume from the response, call the Extract method on the UpdateResult. -func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToVolumeUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a server's ID given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractVolumes(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go deleted file mode 100644 index 7f68d148639..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go +++ /dev/null @@ -1,109 +0,0 @@ -package volumes - -import ( - "encoding/json" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// Volume contains all the information associated with an OpenStack Volume. -type Volume struct { - // Current status of the volume. - Status string `json:"status"` - // Human-readable display name for the volume. - Name string `json:"display_name"` - // Instances onto which the volume is attached. - Attachments []map[string]interface{} `json:"attachments"` - // This parameter is no longer used. - AvailabilityZone string `json:"availability_zone"` - // Indicates whether this is a bootable volume. - Bootable string `json:"bootable"` - // The date when this volume was created. - CreatedAt time.Time `json:"-"` - // Human-readable description for the volume. - Description string `json:"display_description"` - // The type of volume to create, either SATA or SSD. - VolumeType string `json:"volume_type"` - // The ID of the snapshot from which the volume was created - SnapshotID string `json:"snapshot_id"` - // The ID of another block storage volume from which the current volume was created - SourceVolID string `json:"source_volid"` - // Arbitrary key-value pairs defined by the user. - Metadata map[string]string `json:"metadata"` - // Unique identifier for the volume. - ID string `json:"id"` - // Size of the volume in GB. - Size int `json:"size"` -} - -func (r *Volume) UnmarshalJSON(b []byte) error { - type tmp Volume - var s struct { - tmp - CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Volume(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - - return err -} - -// 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 -} - -// VolumePage is a pagination.pager that is returned from a call to the List function. -type VolumePage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if a VolumePage contains no Volumes. -func (r VolumePage) IsEmpty() (bool, error) { - volumes, err := ExtractVolumes(r) - return len(volumes) == 0, err -} - -// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. -func ExtractVolumes(r pagination.Page) ([]Volume, error) { - var s struct { - Volumes []Volume `json:"volumes"` - } - err := (r.(VolumePage)).ExtractInto(&s) - return s.Volumes, 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 Volume object out of the commonResult object. -func (r commonResult) Extract() (*Volume, error) { - var s struct { - Volume *Volume `json:"volume"` - } - err := r.ExtractInto(&s) - return s.Volume, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go deleted file mode 100644 index 8a00f97e98c..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package volumes - -import "github.com/gophercloud/gophercloud" - -func createURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("volumes") -} - -func listURL(c *gophercloud.ServiceClient) string { - return createURL(c) -} - -func deleteURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL("volumes", id) -} - -func getURL(c *gophercloud.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func updateURL(c *gophercloud.ServiceClient, id string) string { - return deleteURL(c, id) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go deleted file mode 100644 index e86c1b4b4ee..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package volumes - -import ( - "github.com/gophercloud/gophercloud" -) - -// WaitForStatus will continually poll the resource, checking for a particular -// status. It will do this for the amount of seconds defined. -func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { - return gophercloud.WaitFor(secs, func() (bool, error) { - current, err := Get(c, 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/openstack/blockstorage/v2/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go deleted file mode 100644 index 307b8b12d2f..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package volumes provides information and interaction with volumes in the -// OpenStack Block Storage service. A volume is a detachable block storage -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. -package volumes 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 deleted file mode 100644 index c27ddbf67c3..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go +++ /dev/null @@ -1,235 +0,0 @@ -package volumes - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToVolumeCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Volume. This object is passed to -// the volumes.Create function. For more information about these parameters, -// see the Volume object. -type CreateOpts struct { - // The size of the volume, in GB - Size int `json:"size" required:"true"` - // The availability zone - AvailabilityZone string `json:"availability_zone,omitempty"` - // ConsistencyGroupID is the ID of a consistency group - ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` - // The volume description - Description string `json:"description,omitempty"` - // One or more metadata key and value pairs to associate with the volume - Metadata map[string]string `json:"metadata,omitempty"` - // The volume name - Name string `json:"name,omitempty"` - // the ID of the existing volume snapshot - SnapshotID string `json:"snapshot_id,omitempty"` - // SourceReplica is a UUID of an existing volume to replicate with - SourceReplica string `json:"source_replica,omitempty"` - // the ID of the existing volume - SourceVolID string `json:"source_volid,omitempty"` - // The ID of the image from which you want to create the volume. - // Required to create a bootable volume. - ImageID string `json:"imageRef,omitempty"` - // The associated volume type - VolumeType string `json:"volume_type,omitempty"` -} - -// ToVolumeCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volume") -} - -// Create will create a new Volume based on the values in CreateOpts. To extract -// the Volume object from the response, call the Extract method on the -// CreateResult. -func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - 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, 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 -} - -// Get retrieves the Volume with the provided ID. To extract the Volume 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 { - ToVolumeListQuery() (string, error) -} - -// ListOpts holds options for listing Volumes. It is passed to the volumes.List -// function. -type ListOpts struct { - // AllTenants will retrieve volumes of all tenants/projects. - AllTenants bool `q:"all_tenants"` - - // Metadata will filter results based on specified metadata. - Metadata map[string]string `q:"metadata"` - - // Name will filter by the specified volume name. - Name string `q:"name"` - - // Status will filter by the specified status. - Status string `q:"status"` - - // 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. -func (opts ListOpts) ToVolumeListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// List returns Volumes 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.ToVolumeListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return VolumePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToVolumeUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contain options for updating an existing Volume. This object is passed -// 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"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToVolumeUpdateMap assembles a request body based on the contents of an -// UpdateOpts. -func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volume") -} - -// Update will update the Volume with provided information. To extract the updated -// Volume from the response, call the Extract method on the UpdateResult. -func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToVolumeUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a server's ID given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractVolumes(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} - } -} 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 deleted file mode 100644 index 96572b01b45..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go +++ /dev/null @@ -1,167 +0,0 @@ -package volumes - -import ( - "encoding/json" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -type Attachment struct { - AttachedAt time.Time `json:"-"` - AttachmentID string `json:"attachment_id"` - Device string `json:"device"` - HostName string `json:"host_name"` - ID string `json:"id"` - ServerID string `json:"server_id"` - VolumeID string `json:"volume_id"` -} - -func (r *Attachment) UnmarshalJSON(b []byte) error { - type tmp Attachment - var s struct { - tmp - AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Attachment(s.tmp) - - r.AttachedAt = time.Time(s.AttachedAt) - - return err -} - -// Volume contains all the information associated with an OpenStack Volume. -type Volume struct { - // Unique identifier for the volume. - ID string `json:"id"` - // Current status of the volume. - Status string `json:"status"` - // Size of the volume in GB. - Size int `json:"size"` - // AvailabilityZone is which availability zone the volume is in. - AvailabilityZone string `json:"availability_zone"` - // The date when this volume was created. - CreatedAt time.Time `json:"-"` - // The date when this volume was last updated - UpdatedAt time.Time `json:"-"` - // Instances onto which the volume is attached. - Attachments []Attachment `json:"attachments"` - // Human-readable display name for the volume. - Name string `json:"name"` - // Human-readable description for the volume. - Description string `json:"description"` - // The type of volume to create, either SATA or SSD. - VolumeType string `json:"volume_type"` - // The ID of the snapshot from which the volume was created - SnapshotID string `json:"snapshot_id"` - // The ID of another block storage volume from which the current volume was created - SourceVolID string `json:"source_volid"` - // Arbitrary key-value pairs defined by the user. - Metadata map[string]string `json:"metadata"` - // UserID is the id of the user who created the volume. - UserID string `json:"user_id"` - // Indicates whether this is a bootable volume. - Bootable string `json:"bootable"` - // Encrypted denotes if the volume is encrypted. - Encrypted bool `json:"encrypted"` - // ReplicationStatus is the status of replication. - ReplicationStatus string `json:"replication_status"` - // ConsistencyGroupID is the consistency group ID. - ConsistencyGroupID string `json:"consistencygroup_id"` - // Multiattach denotes if the volume is multi-attach capable. - Multiattach bool `json:"multiattach"` -} - -func (r *Volume) UnmarshalJSON(b []byte) error { - type tmp Volume - var s struct { - tmp - CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Volume(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -// VolumePage is a pagination.pager that is returned from a call to the List function. -type VolumePage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if a ListResult contains no Volumes. -func (r VolumePage) IsEmpty() (bool, error) { - volumes, err := ExtractVolumes(r) - 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 - err := ExtractVolumesInto(r, &s) - return s, err -} - -type commonResult struct { - gophercloud.Result -} - -// Extract will get the Volume object out of the commonResult object. -func (r commonResult) Extract() (*Volume, error) { - var s Volume - err := r.ExtractInto(&s) - return &s, err -} - -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "volume") -} - -func ExtractVolumesInto(r pagination.Page, v interface{}) error { - return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") -} - -// 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 -} - -// UpdateResult contains the response body and error from an Update request. -type UpdateResult struct { - commonResult -} - -// DeleteResult contains the response body and error from a Delete request. -type DeleteResult struct { - gophercloud.ErrResult -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go deleted file mode 100644 index 170724905ab..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package volumes - -import "github.com/gophercloud/gophercloud" - -func createURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("volumes") -} - -func listURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("volumes", "detail") -} - -func deleteURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL("volumes", id) -} - -func getURL(c *gophercloud.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func updateURL(c *gophercloud.ServiceClient, id string) string { - return deleteURL(c, id) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go deleted file mode 100644 index e86c1b4b4ee..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package volumes - -import ( - "github.com/gophercloud/gophercloud" -) - -// WaitForStatus will continually poll the resource, checking for a particular -// status. It will do this for the amount of seconds defined. -func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { - return gophercloud.WaitFor(secs, func() (bool, error) { - current, err := Get(c, 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/openstack/blockstorage/v3/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go deleted file mode 100644 index 307b8b12d2f..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package volumes provides information and interaction with volumes in the -// OpenStack Block Storage service. A volume is a detachable block storage -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. -package volumes 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 deleted file mode 100644 index 25f70b27c1a..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go +++ /dev/null @@ -1,237 +0,0 @@ -package volumes - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToVolumeCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Volume. This object is passed to -// the volumes.Create function. For more information about these parameters, -// see the Volume object. -type CreateOpts struct { - // The size of the volume, in GB - Size int `json:"size" required:"true"` - // The availability zone - AvailabilityZone string `json:"availability_zone,omitempty"` - // ConsistencyGroupID is the ID of a consistency group - ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` - // The volume description - Description string `json:"description,omitempty"` - // One or more metadata key and value pairs to associate with the volume - Metadata map[string]string `json:"metadata,omitempty"` - // The volume name - Name string `json:"name,omitempty"` - // the ID of the existing volume snapshot - SnapshotID string `json:"snapshot_id,omitempty"` - // SourceReplica is a UUID of an existing volume to replicate with - SourceReplica string `json:"source_replica,omitempty"` - // the ID of the existing volume - SourceVolID string `json:"source_volid,omitempty"` - // The ID of the image from which you want to create the volume. - // Required to create a bootable volume. - 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 -// CreateOpts. -func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volume") -} - -// Create will create a new Volume based on the values in CreateOpts. To extract -// the Volume object from the response, call the Extract method on the -// CreateResult. -func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - 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, 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 -} - -// Get retrieves the Volume with the provided ID. To extract the Volume 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 { - ToVolumeListQuery() (string, error) -} - -// ListOpts holds options for listing Volumes. It is passed to the volumes.List -// function. -type ListOpts struct { - // AllTenants will retrieve volumes of all tenants/projects. - AllTenants bool `q:"all_tenants"` - - // Metadata will filter results based on specified metadata. - Metadata map[string]string `q:"metadata"` - - // Name will filter by the specified volume name. - Name string `q:"name"` - - // Status will filter by the specified status. - Status string `q:"status"` - - // 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. -func (opts ListOpts) ToVolumeListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// List returns Volumes 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.ToVolumeListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return VolumePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToVolumeUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contain options for updating an existing Volume. This object is passed -// 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"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToVolumeUpdateMap assembles a request body based on the contents of an -// UpdateOpts. -func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volume") -} - -// Update will update the Volume with provided information. To extract the updated -// Volume from the response, call the Extract method on the UpdateResult. -func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToVolumeUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a server's ID given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractVolumes(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} - } -} 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 deleted file mode 100644 index 3a33b5864bb..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go +++ /dev/null @@ -1,172 +0,0 @@ -package volumes - -import ( - "encoding/json" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// Attachment represents a Volume Attachment record -type Attachment struct { - AttachedAt time.Time `json:"-"` - AttachmentID string `json:"attachment_id"` - Device string `json:"device"` - HostName string `json:"host_name"` - ID string `json:"id"` - ServerID string `json:"server_id"` - VolumeID string `json:"volume_id"` -} - -// UnmarshalJSON is our unmarshalling helper -func (r *Attachment) UnmarshalJSON(b []byte) error { - type tmp Attachment - var s struct { - tmp - AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Attachment(s.tmp) - - r.AttachedAt = time.Time(s.AttachedAt) - - return err -} - -// Volume contains all the information associated with an OpenStack Volume. -type Volume struct { - // Unique identifier for the volume. - ID string `json:"id"` - // Current status of the volume. - Status string `json:"status"` - // Size of the volume in GB. - Size int `json:"size"` - // AvailabilityZone is which availability zone the volume is in. - AvailabilityZone string `json:"availability_zone"` - // The date when this volume was created. - CreatedAt time.Time `json:"-"` - // The date when this volume was last updated - UpdatedAt time.Time `json:"-"` - // Instances onto which the volume is attached. - Attachments []Attachment `json:"attachments"` - // Human-readable display name for the volume. - Name string `json:"name"` - // Human-readable description for the volume. - Description string `json:"description"` - // The type of volume to create, either SATA or SSD. - VolumeType string `json:"volume_type"` - // The ID of the snapshot from which the volume was created - SnapshotID string `json:"snapshot_id"` - // The ID of another block storage volume from which the current volume was created - SourceVolID string `json:"source_volid"` - // Arbitrary key-value pairs defined by the user. - Metadata map[string]string `json:"metadata"` - // UserID is the id of the user who created the volume. - UserID string `json:"user_id"` - // Indicates whether this is a bootable volume. - Bootable string `json:"bootable"` - // Encrypted denotes if the volume is encrypted. - Encrypted bool `json:"encrypted"` - // ReplicationStatus is the status of replication. - ReplicationStatus string `json:"replication_status"` - // ConsistencyGroupID is the consistency group ID. - 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 -func (r *Volume) UnmarshalJSON(b []byte) error { - type tmp Volume - var s struct { - tmp - CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Volume(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -// VolumePage is a pagination.pager that is returned from a call to the List function. -type VolumePage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if a ListResult contains no Volumes. -func (r VolumePage) IsEmpty() (bool, error) { - volumes, err := ExtractVolumes(r) - return len(volumes) == 0, err -} - -func (page VolumePage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"volumes_links"` - } - err := page.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 - err := ExtractVolumesInto(r, &s) - return s, err -} - -type commonResult struct { - gophercloud.Result -} - -// Extract will get the Volume object out of the commonResult object. -func (r commonResult) Extract() (*Volume, error) { - var s Volume - err := r.ExtractInto(&s) - return &s, err -} - -// ExtractInto converts our response data into a volume struct -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "volume") -} - -// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes -func ExtractVolumesInto(r pagination.Page, v interface{}) error { - return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") -} - -// 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 -} - -// UpdateResult contains the response body and error from an Update request. -type UpdateResult struct { - commonResult -} - -// DeleteResult contains the response body and error from a Delete request. -type DeleteResult struct { - gophercloud.ErrResult -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go deleted file mode 100644 index 170724905ab..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package volumes - -import "github.com/gophercloud/gophercloud" - -func createURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("volumes") -} - -func listURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("volumes", "detail") -} - -func deleteURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL("volumes", id) -} - -func getURL(c *gophercloud.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func updateURL(c *gophercloud.ServiceClient, id string) string { - return deleteURL(c, id) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go deleted file mode 100644 index e86c1b4b4ee..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package volumes - -import ( - "github.com/gophercloud/gophercloud" -) - -// WaitForStatus will continually poll the resource, checking for a particular -// status. It will do this for the amount of seconds defined. -func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { - return gophercloud.WaitFor(secs, func() (bool, error) { - current, err := Get(c, 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/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go deleted file mode 100644 index 50f239711e7..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ /dev/null @@ -1,438 +0,0 @@ -package openstack - -import ( - "fmt" - "reflect" - - "github.com/gophercloud/gophercloud" - tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" - tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" - "github.com/gophercloud/gophercloud/openstack/utils" -) - -const ( - // v2 represents Keystone v2. - // It should never increase beyond 2.0. - v2 = "v2.0" - - // v3 represents Keystone v3. - // The version can be anything from v3 to v3.x. - v3 = "v3" -) - -/* -NewClient prepares an unauthenticated ProviderClient instance. -Most users will probably prefer using the AuthenticatedClient function -instead. - -This is useful if you wish to explicitly control the version of the identity -service that's used for authentication explicitly, for example. - -A basic example of using this would be: - - ao, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.NewClient(ao.IdentityEndpoint) - client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) -*/ -func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { - base, err := utils.BaseEndpoint(endpoint) - if err != nil { - return nil, err - } - - endpoint = gophercloud.NormalizeURL(endpoint) - base = gophercloud.NormalizeURL(base) - - p := new(gophercloud.ProviderClient) - p.IdentityBase = base - p.IdentityEndpoint = endpoint - p.UseTokenLock() - - return p, nil -} - -/* -AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint -specified by the options, acquires a token, and returns a Provider Client -instance that's ready to operate. - -If the full path to a versioned identity endpoint was specified (example: -http://example.com:5000/v3), that path will be used as the endpoint to query. - -If a versionless endpoint was specified (example: http://example.com:5000/), -the endpoint will be queried to determine which versions of the identity service -are available, then chooses the most recent or most supported version. - -Example: - - ao, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(ao) - client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) -*/ -func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { - client, err := NewClient(options.IdentityEndpoint) - if err != nil { - return nil, err - } - - err = Authenticate(client, options) - if err != nil { - return nil, err - } - return client, nil -} - -// Authenticate or re-authenticate against the most recent identity service -// supported at the provided endpoint. -func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error { - versions := []*utils.Version{ - {ID: v2, Priority: 20, Suffix: "/v2.0/"}, - {ID: v3, Priority: 30, Suffix: "/v3/"}, - } - - chosen, endpoint, err := utils.ChooseVersion(client, versions) - if err != nil { - return err - } - - switch chosen.ID { - case v2: - return v2auth(client, endpoint, options, gophercloud.EndpointOpts{}) - case v3: - return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{}) - default: - // The switch statement must be out of date from the versions list. - return fmt.Errorf("Unrecognized identity version: %s", chosen.ID) - } -} - -// AuthenticateV2 explicitly authenticates against the identity v2 endpoint. -func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error { - return v2auth(client, "", options, eo) -} - -func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error { - v2Client, err := NewIdentityV2(client, eo) - if err != nil { - return err - } - - if endpoint != "" { - v2Client.Endpoint = endpoint - } - - v2Opts := tokens2.AuthOptions{ - IdentityEndpoint: options.IdentityEndpoint, - Username: options.Username, - Password: options.Password, - TenantID: options.TenantID, - TenantName: options.TenantName, - AllowReauth: options.AllowReauth, - TokenID: options.TokenID, - } - - result := tokens2.Create(v2Client, v2Opts) - - err = client.SetTokenAndAuthResult(result) - if err != nil { - return err - } - - catalog, err := result.ExtractServiceCatalog() - if err != nil { - return err - } - - if options.AllowReauth { - // 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.SetTokenAndAuthResult(nil) - tao := options - tao.AllowReauth = false - client.ReauthFunc = func() error { - err := v2auth(&tac, endpoint, tao, eo) - if err != nil { - return err - } - client.CopyTokenFrom(&tac) - return nil - } - } - client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { - return V2EndpointURL(catalog, opts) - } - - return nil -} - -// AuthenticateV3 explicitly authenticates against the identity v3 service. -func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { - return v3auth(client, "", options, eo) -} - -func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { - // Override the generated service endpoint with the one returned by the version endpoint. - v3Client, err := NewIdentityV3(client, eo) - if err != nil { - return err - } - - if endpoint != "" { - v3Client.Endpoint = endpoint - } - - result := tokens3.Create(v3Client, opts) - - err = client.SetTokenAndAuthResult(result) - if err != nil { - return err - } - - catalog, err := result.ExtractServiceCatalog() - if err != nil { - return err - } - - 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.SetTokenAndAuthResult(nil) - var tao tokens3.AuthOptionsBuilder - switch ot := opts.(type) { - case *gophercloud.AuthOptions: - o := *ot - o.AllowReauth = false - tao = &o - case *tokens3.AuthOptions: - o := *ot - o.AllowReauth = false - tao = &o - default: - tao = opts - } - client.ReauthFunc = func() error { - err := v3auth(&tac, endpoint, tao, eo) - if err != nil { - return err - } - client.CopyTokenFrom(&tac) - return nil - } - } - client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { - return V3EndpointURL(catalog, opts) - } - - return nil -} - -// NewIdentityV2 creates a ServiceClient that may be used to interact with the -// v2 identity service. -func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - endpoint := client.IdentityBase + "v2.0/" - clientType := "identity" - var err error - if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { - eo.ApplyDefaults(clientType) - endpoint, err = client.EndpointLocator(eo) - if err != nil { - return nil, err - } - } - - return &gophercloud.ServiceClient{ - ProviderClient: client, - Endpoint: endpoint, - Type: clientType, - }, nil -} - -// NewIdentityV3 creates a ServiceClient that may be used to access the v3 -// identity service. -func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - endpoint := client.IdentityBase + "v3/" - clientType := "identity" - var err error - if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { - eo.ApplyDefaults(clientType) - endpoint, err = client.EndpointLocator(eo) - if err != nil { - return nil, err - } - } - - // Ensure endpoint still has a suffix of v3. - // This is because EndpointLocator might have found a versionless - // 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, - Type: clientType, - }, nil -} - -func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { - sc := new(gophercloud.ServiceClient) - eo.ApplyDefaults(clientType) - url, err := client.EndpointLocator(eo) - if err != nil { - return sc, err - } - sc.ProviderClient = client - sc.Endpoint = url - sc.Type = clientType - 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) { - return initClientOpts(client, eo, "object-store") -} - -// NewComputeV2 creates a ServiceClient that may be used with the v2 compute -// package. -func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "compute") -} - -// NewNetworkV2 creates a ServiceClient that may be used with the v2 network -// package. -func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "network") - sc.ResourceBase = sc.Endpoint + "v2.0/" - return sc, err -} - -// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 -// block storage service. -func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volume") -} - -// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 -// block storage service. -func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volumev2") -} - -// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service. -func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volumev3") -} - -// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. -func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "sharev2") -} - -// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 -// CDN service. -func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "cdn") -} - -// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 -// orchestration service. -func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "orchestration") -} - -// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. -func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "database") -} - -// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS -// service. -func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "dns") - sc.ResourceBase = sc.Endpoint + "v2/" - return sc, err -} - -// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 -// image service. -func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "image") - sc.ResourceBase = sc.Endpoint + "v2/" - return sc, err -} - -// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2 -// load balancer service. -func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "load-balancer") - sc.ResourceBase = sc.Endpoint + "v2.0/" - return sc, err -} - -// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering -// package. -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/common/extensions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go deleted file mode 100644 index 5510f265395..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Package extensions provides information and interaction with the different -extensions available for an OpenStack service. - -The purpose of OpenStack API extensions is to: - -- Introduce new features in the API without requiring a version change. -- Introduce vendor-specific niche functionality. -- Act as a proving ground for experimental functionalities that might be -included in a future version of the API. - -Extensions usually have tags that prevent conflicts with other extensions that -define attributes or resources with the same names, and with core resources and -attributes. Because an extension might not be supported by all plug-ins, its -availability varies with deployments and the specific plug-in. - -The results of this package vary depending on the type of Service Client used. -In the following examples, note how the only difference is the creation of the -Service Client. - -Example of Retrieving Compute Extensions - - ao, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(ao) - computeClient, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) - - allPages, err := extensions.List(computeClient).Allpages() - allExtensions, err := extensions.ExtractExtensions(allPages) - - for _, extension := range allExtensions{ - fmt.Println("%+v\n", extension) - } - - -Example of Retrieving Network Extensions - - ao, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(ao) - networkClient, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) - - allPages, err := extensions.List(networkClient).Allpages() - allExtensions, err := extensions.ExtractExtensions(allPages) - - for _, extension := range allExtensions{ - fmt.Println("%+v\n", extension) - } -*/ -package extensions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go deleted file mode 100644 index 46b7d60cd69..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go +++ /dev/null @@ -1,20 +0,0 @@ -package extensions - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// Get retrieves information for a specific extension using its alias. -func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) { - _, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil) - return -} - -// List returns a Pager which allows you to iterate over the full collection of extensions. -// It does not accept query parameters. -func List(c *gophercloud.ServiceClient) pagination.Pager { - return pagination.NewPager(c, ListExtensionURL(c), func(r pagination.PageResult) pagination.Page { - return ExtensionPage{pagination.SinglePageBase(r)} - }) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go deleted file mode 100644 index 8a26edd1c59..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go +++ /dev/null @@ -1,53 +0,0 @@ -package extensions - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// GetResult temporarily stores the result of a Get call. -// Use its Extract() method to interpret it as an Extension. -type GetResult struct { - gophercloud.Result -} - -// Extract interprets a GetResult as an Extension. -func (r GetResult) Extract() (*Extension, error) { - var s struct { - Extension *Extension `json:"extension"` - } - err := r.ExtractInto(&s) - return s.Extension, err -} - -// Extension is a struct that represents an OpenStack extension. -type Extension struct { - Updated string `json:"updated"` - Name string `json:"name"` - Links []interface{} `json:"links"` - Namespace string `json:"namespace"` - Alias string `json:"alias"` - Description string `json:"description"` -} - -// ExtensionPage is the page returned by a pager when traversing over a collection of extensions. -type ExtensionPage struct { - pagination.SinglePageBase -} - -// IsEmpty checks whether an ExtensionPage struct is empty. -func (r ExtensionPage) IsEmpty() (bool, error) { - is, err := ExtractExtensions(r) - return len(is) == 0, err -} - -// ExtractExtensions accepts a Page struct, specifically an ExtensionPage -// struct, and extracts the elements into a slice of Extension structs. -// In other words, a generic collection is mapped into a relevant slice. -func ExtractExtensions(r pagination.Page) ([]Extension, error) { - var s struct { - Extensions []Extension `json:"extensions"` - } - err := (r.(ExtensionPage)).ExtractInto(&s) - return s.Extensions, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go deleted file mode 100644 index eaf38b2d191..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package extensions - -import "github.com/gophercloud/gophercloud" - -// ExtensionURL generates the URL for an extension resource by name. -func ExtensionURL(c *gophercloud.ServiceClient, name string) string { - return c.ServiceURL("extensions", name) -} - -// ListExtensionURL generates the URL for the extensions resource collection. -func ListExtensionURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("extensions") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go deleted file mode 100644 index 3653122bf30..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Package attachinterfaces provides the ability to retrieve and manage network -interfaces through Nova. - -Example of Listing a Server's Interfaces - - serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" - allPages, err := attachinterfaces.List(computeClient, serverID).AllPages() - if err != nil { - panic(err) - } - - allInterfaces, err := attachinterfaces.ExtractInterfaces(allPages) - if err != nil { - panic(err) - } - - for _, interface := range allInterfaces { - fmt.Printf("%+v\n", interface) - } - -Example to Get a Server's Interface - - portID = "0dde1598-b374-474e-986f-5b8dd1df1d4e" - serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" - interface, err := attachinterfaces.Get(computeClient, serverID, portID).Extract() - if err != nil { - panic(err) - } - -Example to Create a new Interface attachment on the Server - - networkID := "8a5fe506-7e9f-4091-899b-96336909d93c" - serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" - attachOpts := attachinterfaces.CreateOpts{ - NetworkID: networkID, - } - interface, err := attachinterfaces.Create(computeClient, serverID, attachOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete an Interface attachment from the Server - - portID = "0dde1598-b374-474e-986f-5b8dd1df1d4e" - serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" - err := attachinterfaces.Delete(computeClient, serverID, portID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package attachinterfaces 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 deleted file mode 100644 index 874f7a61ec4..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go +++ /dev/null @@ -1,72 +0,0 @@ -package attachinterfaces - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// List makes a request against the nova API to list the server's interfaces. -func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager { - return pagination.NewPager(client, listInterfaceURL(client, serverID), func(r pagination.PageResult) pagination.Page { - return InterfacePage{pagination.SinglePageBase(r)} - }) -} - -// Get requests details on a single interface attachment by the server and port IDs. -func Get(client *gophercloud.ServiceClient, serverID, portID string) (r GetResult) { - _, r.Err = client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToAttachInterfacesCreateMap() (map[string]interface{}, error) -} - -// 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 - // v2.0 allocates a port and creates an interface for it on the network. - PortID string `json:"port_id,omitempty"` - - // NetworkID is the ID of the network for which you want to create an interface. - // The NetworkID and PortID parameters are mutually exclusive. - // If you do not specify the NetworkID parameter, the OpenStack Networking - // API v2.0 uses the network information cache that is associated with the instance. - NetworkID string `json:"net_id,omitempty"` - - // 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"` -} - -// ToAttachInterfacesCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToAttachInterfacesCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "interfaceAttachment") -} - -// Create requests the creation of a new interface attachment on the server. -func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToAttachInterfacesCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Delete makes a request against the nova API to detach a single interface from the server. -// It needs server and port IDs to make a such request. -func Delete(client *gophercloud.ServiceClient, serverID, portID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteInterfaceURL(client, serverID, portID), nil) - return -} 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 deleted file mode 100644 index 7d15e1ecb4b..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go +++ /dev/null @@ -1,80 +0,0 @@ -package attachinterfaces - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -type attachInterfaceResult struct { - gophercloud.Result -} - -// Extract interprets any attachInterfaceResult as an Interface, if possible. -func (r attachInterfaceResult) Extract() (*Interface, error) { - var s struct { - Interface *Interface `json:"interfaceAttachment"` - } - err := r.ExtractInto(&s) - return s.Interface, err -} - -// GetResult is the response from a Get operation. Call its Extract -// method to interpret it as an Interface. -type GetResult struct { - attachInterfaceResult -} - -// CreateResult is the response from a Create operation. Call its Extract -// method to interpret it as an Interface. -type CreateResult struct { - attachInterfaceResult -} - -// 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 -} - -// 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,omitempty"` - IPAddress string `json:"ip_address"` -} - -// Interface represents a network interface on a server. -type Interface struct { - PortState string `json:"port_state"` - FixedIPs []FixedIP `json:"fixed_ips"` - PortID string `json:"port_id"` - NetID string `json:"net_id"` - MACAddr string `json:"mac_addr"` -} - -// InterfacePage 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 ExtractInterfaces call. -type InterfacePage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if an InterfacePage contains no interfaces. -func (r InterfacePage) IsEmpty() (bool, error) { - interfaces, err := ExtractInterfaces(r) - return len(interfaces) == 0, err -} - -// ExtractInterfaces interprets the results of a single page from a List() call, -// producing a slice of Interface structs. -func ExtractInterfaces(r pagination.Page) ([]Interface, error) { - var s struct { - Interfaces []Interface `json:"interfaceAttachments"` - } - err := (r.(InterfacePage)).ExtractInto(&s) - return s.Interfaces, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go deleted file mode 100644 index 50292e8b5a5..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go +++ /dev/null @@ -1,18 +0,0 @@ -package attachinterfaces - -import "github.com/gophercloud/gophercloud" - -func listInterfaceURL(client *gophercloud.ServiceClient, serverID string) string { - return client.ServiceURL("servers", serverID, "os-interface") -} - -func getInterfaceURL(client *gophercloud.ServiceClient, serverID, portID string) string { - return client.ServiceURL("servers", serverID, "os-interface", portID) -} - -func createInterfaceURL(client *gophercloud.ServiceClient, serverID string) string { - return client.ServiceURL("servers", serverID, "os-interface") -} -func deleteInterfaceURL(client *gophercloud.ServiceClient, serverID, portID string) string { - return client.ServiceURL("servers", serverID, "os-interface", portID) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go deleted file mode 100644 index 484eb20000c..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Package volumeattach provides the ability to attach and detach volumes -from servers. - -Example to Attach a Volume - - serverID := "7ac8686c-de71-4acb-9600-ec18b1a1ed6d" - volumeID := "87463836-f0e2-4029-abf6-20c8892a3103" - - createOpts := volumeattach.CreateOpts{ - Device: "/dev/vdc", - VolumeID: volumeID, - } - - result, err := volumeattach.Create(computeClient, serverID, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Detach a Volume - - serverID := "7ac8686c-de71-4acb-9600-ec18b1a1ed6d" - attachmentID := "ed081613-1c9b-4231-aa5e-ebfd4d87f983" - - err := volumeattach.Delete(computeClient, serverID, attachmentID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package volumeattach diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go deleted file mode 100644 index 6a262c212e1..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go +++ /dev/null @@ -1,60 +0,0 @@ -package volumeattach - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// List returns a Pager that allows you to iterate over a collection of -// VolumeAttachments. -func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager { - return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page { - return VolumeAttachmentPage{pagination.SinglePageBase(r)} - }) -} - -// CreateOptsBuilder allows extensions to add parameters to the Create request. -type CreateOptsBuilder interface { - ToVolumeAttachmentCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies volume attachment creation or import parameters. -type CreateOpts struct { - // Device is the device that the volume will attach to the instance as. - // Omit for "auto". - Device string `json:"device,omitempty"` - - // VolumeID is the ID of the volume to attach to the instance. - VolumeID string `json:"volumeId" required:"true"` -} - -// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "volumeAttachment") -} - -// Create requests the creation of a new volume attachment on the server. -func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeAttachmentCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get returns public data about a previously created VolumeAttachment. -func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r GetResult) { - _, r.Err = client.Get(getURL(client, serverID, attachmentID), &r.Body, nil) - return -} - -// Delete requests the deletion of a previous stored VolumeAttachment from -// the server. -func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil) - return -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go deleted file mode 100644 index 56d50347291..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go +++ /dev/null @@ -1,77 +0,0 @@ -package volumeattach - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// VolumeAttachment contains attachment information between a volume -// and server. -type VolumeAttachment struct { - // ID is a unique id of the attachment. - ID string `json:"id"` - - // Device is what device the volume is attached as. - Device string `json:"device"` - - // VolumeID is the ID of the attached volume. - VolumeID string `json:"volumeId"` - - // ServerID is the ID of the instance that has the volume attached. - ServerID string `json:"serverId"` -} - -// VolumeAttachmentPage stores a single page all of VolumeAttachment -// results from a List call. -type VolumeAttachmentPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a VolumeAttachmentPage is empty. -func (page VolumeAttachmentPage) IsEmpty() (bool, error) { - va, err := ExtractVolumeAttachments(page) - return len(va) == 0, err -} - -// ExtractVolumeAttachments interprets a page of results as a slice of -// VolumeAttachment. -func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) { - var s struct { - VolumeAttachments []VolumeAttachment `json:"volumeAttachments"` - } - err := (r.(VolumeAttachmentPage)).ExtractInto(&s) - return s.VolumeAttachments, err -} - -// VolumeAttachmentResult is the result from a volume attachment operation. -type VolumeAttachmentResult struct { - gophercloud.Result -} - -// Extract is a method that attempts to interpret any VolumeAttachment resource -// response as a VolumeAttachment struct. -func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) { - var s struct { - VolumeAttachment *VolumeAttachment `json:"volumeAttachment"` - } - err := r.ExtractInto(&s) - return s.VolumeAttachment, err -} - -// CreateResult is the response from a Create operation. Call its Extract method -// to interpret it as a VolumeAttachment. -type CreateResult struct { - VolumeAttachmentResult -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a VolumeAttachment. -type GetResult struct { - VolumeAttachmentResult -} - -// 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/compute/v2/extensions/volumeattach/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go deleted file mode 100644 index 083f8dc4554..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/urls.go +++ /dev/null @@ -1,25 +0,0 @@ -package volumeattach - -import "github.com/gophercloud/gophercloud" - -const resourcePath = "os-volume_attachments" - -func resourceURL(c *gophercloud.ServiceClient, serverID string) string { - return c.ServiceURL("servers", serverID, resourcePath) -} - -func listURL(c *gophercloud.ServiceClient, serverID string) string { - return resourceURL(c, serverID) -} - -func createURL(c *gophercloud.ServiceClient, serverID string) string { - return resourceURL(c, serverID) -} - -func getURL(c *gophercloud.ServiceClient, serverID, aID string) string { - return c.ServiceURL("servers", serverID, resourcePath, aID) -} - -func deleteURL(c *gophercloud.ServiceClient, serverID, aID string) string { - return getURL(c, serverID, aID) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go deleted file mode 100644 index 34d8764fadb..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go +++ /dev/null @@ -1,137 +0,0 @@ -/* -Package flavors provides information and interaction with the flavor API -in the OpenStack Compute service. - -A flavor is an available hardware configuration for a server. Each flavor -has a unique combination of disk space, memory capacity and priority for CPU -time. - -Example to List Flavors - - listOpts := flavors.ListOpts{ - AccessType: flavors.PublicAccess, - } - - allPages, err := flavors.ListDetail(computeClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allFlavors, err := flavors.ExtractFlavors(allPages) - if err != nil { - panic(err) - } - - for _, flavor := range allFlavors { - fmt.Printf("%+v\n", flavor) - } - -Example to Create a Flavor - - createOpts := flavors.CreateOpts{ - ID: "1", - Name: "m1.tiny", - Disk: gophercloud.IntToPointer(1), - RAM: 512, - VCPUs: 1, - RxTxFactor: 1.0, - } - - flavor, err := flavors.Create(computeClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to List Flavor Access - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - - allPages, err := flavors.ListAccesses(computeClient, flavorID).AllPages() - if err != nil { - panic(err) - } - - allAccesses, err := flavors.ExtractAccesses(allPages) - if err != nil { - panic(err) - } - - for _, access := range allAccesses { - fmt.Printf("%+v", access) - } - -Example to Grant Access to a Flavor - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - - accessOpts := flavors.AddAccessOpts{ - Tenant: "15153a0979884b59b0592248ef947921", - } - - accessList, err := flavors.AddAccess(computeClient, flavor.ID, accessOpts).Extract() - if err != nil { - panic(err) - } - -Example to Remove/Revoke Access to a Flavor - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - - accessOpts := flavors.RemoveAccessOpts{ - Tenant: "15153a0979884b59b0592248ef947921", - } - - accessList, err := flavors.RemoveAccess(computeClient, flavor.ID, accessOpts).Extract() - if err != nil { - panic(err) - } - -Example to Create Extra Specs for a Flavor - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - - createOpts := flavors.ExtraSpecsOpts{ - "hw:cpu_policy": "CPU-POLICY", - "hw:cpu_thread_policy": "CPU-THREAD-POLICY", - } - createdExtraSpecs, err := flavors.CreateExtraSpecs(computeClient, flavorID, createOpts).Extract() - if err != nil { - panic(err) - } - - fmt.Printf("%+v", createdExtraSpecs) - -Example to Get Extra Specs for a Flavor - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - - extraSpecs, err := flavors.ListExtraSpecs(computeClient, flavorID).Extract() - if err != nil { - panic(err) - } - - fmt.Printf("%+v", extraSpecs) - -Example to Update Extra Specs for a Flavor - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - - updateOpts := flavors.ExtraSpecsOpts{ - "hw:cpu_thread_policy": "CPU-THREAD-POLICY-UPDATED", - } - updatedExtraSpec, err := flavors.UpdateExtraSpec(computeClient, flavorID, updateOpts).Extract() - if err != nil { - panic(err) - } - - fmt.Printf("%+v", updatedExtraSpec) - -Example to Delete an Extra Spec for a Flavor - - flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b" - err := flavors.DeleteExtraSpec(computeClient, flavorID, "hw:cpu_thread_policy").ExtractErr() - if err != nil { - panic(err) - } -*/ -package flavors 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 deleted file mode 100644 index 753024a18b7..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go +++ /dev/null @@ -1,357 +0,0 @@ -package flavors - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// List request. -type ListOptsBuilder interface { - ToFlavorListQuery() (string, error) -} - -/* - AccessType maps to OpenStack's Flavor.is_public field. Although the is_public - field is boolean, the request options are ternary, which is why AccessType is - a string. The following values are allowed: - - The AccessType arguement is optional, and if it is not supplied, OpenStack - returns the PublicAccess flavors. -*/ -type AccessType string - -const ( - // PublicAccess returns public flavors and private flavors associated with - // that project. - PublicAccess AccessType = "true" - - // PrivateAccess (admin only) returns private flavors, across all projects. - PrivateAccess AccessType = "false" - - // AllAccess (admin only) returns public and private flavors across all - // projects. - AllAccess AccessType = "None" -) - -/* - ListOpts filters the results returned by the List() function. - For example, a flavor with a minDisk field of 10 will not be returned if you - specify MinDisk set to 20. - - Typically, software will use the last ID of the previous call to List to set - the Marker for the current call. -*/ -type ListOpts struct { - // ChangesSince, if provided, instructs List to return only those things which - // have changed since the timestamp provided. - ChangesSince string `q:"changes-since"` - - // MinDisk and MinRAM, if provided, elides flavors which do not meet your - // criteria. - 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"` - - // Limit instructs List to refrain from sending excessively large lists of - // flavors. - Limit int `q:"limit"` - - // AccessType, if provided, instructs List which set of flavors to return. - // If IsPublic not provided, flavors for the current project are returned. - AccessType AccessType `q:"is_public"` -} - -// ToFlavorListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToFlavorListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// ListDetail instructs OpenStack to provide a list of flavors. -// You may provide criteria by which List curtails its results for easier -// processing. -func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) - if opts != nil { - query, err := opts.ToFlavorListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return FlavorPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -type CreateOptsBuilder interface { - ToFlavorCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies parameters used for creating a flavor. -type CreateOpts struct { - // Name is the name of the flavor. - Name string `json:"name" required:"true"` - - // RAM is the memory of the flavor, measured in MB. - RAM int `json:"ram" required:"true"` - - // VCPUs is the number of vcpus for the flavor. - VCPUs int `json:"vcpus" required:"true"` - - // Disk the amount of root disk space, measured in GB. - Disk *int `json:"disk" required:"true"` - - // ID is a unique ID for the flavor. - ID string `json:"id,omitempty"` - - // Swap is the amount of swap space for the flavor, measured in MB. - Swap *int `json:"swap,omitempty"` - - // RxTxFactor alters the network bandwidth of a flavor. - RxTxFactor float64 `json:"rxtx_factor,omitempty"` - - // IsPublic flags a flavor as being available to all projects or not. - IsPublic *bool `json:"os-flavor-access:is_public,omitempty"` - - // Ephemeral is the amount of ephemeral disk space, measured in GB. - Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` -} - -// ToFlavorCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "flavor") -} - -// Create requests the creation of a new flavor. -func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToFlavorCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201}, - }) - return -} - -// 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) - return -} - -// Delete deletes the specified flavor ID. -func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// ListAccesses retrieves the tenants which have access to a flavor. -func ListAccesses(client *gophercloud.ServiceClient, id string) pagination.Pager { - url := accessURL(client, id) - - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return AccessPage{pagination.SinglePageBase(r)} - }) -} - -// AddAccessOptsBuilder allows extensions to add additional parameters to the -// AddAccess requests. -type AddAccessOptsBuilder interface { - ToFlavorAddAccessMap() (map[string]interface{}, error) -} - -// AddAccessOpts represents options for adding access to a flavor. -type AddAccessOpts struct { - // Tenant is the project/tenant ID to grant access. - Tenant string `json:"tenant"` -} - -// ToFlavorAddAccessMap constructs a request body from AddAccessOpts. -func (opts AddAccessOpts) ToFlavorAddAccessMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "addTenantAccess") -} - -// AddAccess grants a tenant/project access to a flavor. -func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsBuilder) (r AddAccessResult) { - b, err := opts.ToFlavorAddAccessMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// RemoveAccessOptsBuilder allows extensions to add additional parameters to the -// RemoveAccess requests. -type RemoveAccessOptsBuilder interface { - ToFlavorRemoveAccessMap() (map[string]interface{}, error) -} - -// RemoveAccessOpts represents options for removing access to a flavor. -type RemoveAccessOpts struct { - // Tenant is the project/tenant ID to grant access. - Tenant string `json:"tenant"` -} - -// ToFlavorRemoveAccessMap constructs a request body from RemoveAccessOpts. -func (opts RemoveAccessOpts) ToFlavorRemoveAccessMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "removeTenantAccess") -} - -// RemoveAccess removes/revokes a tenant/project access to a flavor. -func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAccessOptsBuilder) (r RemoveAccessResult) { - b, err := opts.ToFlavorRemoveAccessMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// ExtraSpecs requests all the extra-specs for the given flavor ID. -func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) { - _, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil) - return -} - -func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) { - _, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil) - return -} - -// CreateExtraSpecsOptsBuilder allows extensions to add additional parameters to the -// CreateExtraSpecs requests. -type CreateExtraSpecsOptsBuilder interface { - ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) -} - -// ExtraSpecsOpts is a map that contains key-value pairs. -type ExtraSpecsOpts map[string]string - -// ToFlavorExtraSpecsCreateMap assembles a body for a Create request based on -// the contents of ExtraSpecsOpts. -func (opts ExtraSpecsOpts) ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) { - return map[string]interface{}{"extra_specs": opts}, nil -} - -// CreateExtraSpecs will create or update the extra-specs key-value pairs for -// the specified Flavor. -func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts CreateExtraSpecsOptsBuilder) (r CreateExtraSpecsResult) { - b, err := opts.ToFlavorExtraSpecsCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// UpdateExtraSpecOptsBuilder allows extensions to add additional parameters to -// the Update request. -type UpdateExtraSpecOptsBuilder interface { - ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) -} - -// ToFlavorExtraSpecUpdateMap assembles a body for an Update request based on -// the contents of a ExtraSpecOpts. -func (opts ExtraSpecsOpts) ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) { - if len(opts) != 1 { - err := gophercloud.ErrInvalidInput{} - err.Argument = "flavors.ExtraSpecOpts" - err.Info = "Must have 1 and only one key-value pair" - return nil, "", err - } - - var key string - for k := range opts { - key = k - } - - return opts, key, nil -} - -// UpdateExtraSpec will updates the value of the specified flavor's extra spec -// for the key in opts. -func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts UpdateExtraSpecOptsBuilder) (r UpdateExtraSpecResult) { - b, key, err := opts.ToFlavorExtraSpecUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// DeleteExtraSpec will delete the key-value pair with the given key for the given -// flavor ID. -func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) { - _, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a flavor's ID given its -// name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - allPages, err := ListDetail(client, nil).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractFlavors(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - err := &gophercloud.ErrResourceNotFound{} - err.ResourceType = "flavor" - err.Name = name - return "", err - case 1: - return id, nil - default: - err := &gophercloud.ErrMultipleResourcesFound{} - err.ResourceType = "flavor" - err.Name = name - err.Count = count - return "", err - } -} 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 deleted file mode 100644 index 92fe1b1809d..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go +++ /dev/null @@ -1,252 +0,0 @@ -package flavors - -import ( - "encoding/json" - "strconv" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -type commonResult struct { - gophercloud.Result -} - -// CreateResult is the response of a Get operations. Call its Extract method to -// interpret it as a Flavor. -type CreateResult struct { - commonResult -} - -// GetResult is the response of a Get operations. Call its Extract method to -// interpret it as a Flavor. -type GetResult 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 -} - -// Extract provides access to the individual Flavor returned by the Get and -// Create functions. -func (r commonResult) Extract() (*Flavor, error) { - var s struct { - Flavor *Flavor `json:"flavor"` - } - err := r.ExtractInto(&s) - return s.Flavor, err -} - -// Flavor represent (virtual) hardware configurations for server resources -// in a region. -type Flavor struct { - // ID is the flavor's unique ID. - ID string `json:"id"` - - // Disk is the amount of root disk, measured in GB. - Disk int `json:"disk"` - - // RAM is the amount of memory, measured in MB. - RAM int `json:"ram"` - - // Name is the name of the flavor. - Name string `json:"name"` - - // RxTxFactor describes bandwidth alterations of the flavor. - RxTxFactor float64 `json:"rxtx_factor"` - - // Swap is the amount of swap space, measured in MB. - Swap int `json:"-"` - - // VCPUs indicates how many (virtual) CPUs are available for this flavor. - VCPUs int `json:"vcpus"` - - // IsPublic indicates whether the flavor is public. - IsPublic bool `json:"os-flavor-access:is_public"` - - // Ephemeral is the amount of ephemeral disk space, measured in GB. - Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"` -} - -func (r *Flavor) UnmarshalJSON(b []byte) error { - type tmp Flavor - var s struct { - tmp - Swap interface{} `json:"swap"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - - *r = Flavor(s.tmp) - - switch t := s.Swap.(type) { - case float64: - r.Swap = int(t) - case string: - switch t { - case "": - r.Swap = 0 - default: - swap, err := strconv.ParseFloat(t, 64) - if err != nil { - return err - } - r.Swap = int(swap) - } - } - - return nil -} - -// FlavorPage contains a single page of all flavors from a ListDetails call. -type FlavorPage struct { - pagination.LinkedPageBase -} - -// IsEmpty determines if a FlavorPage contains any results. -func (page FlavorPage) IsEmpty() (bool, error) { - flavors, err := ExtractFlavors(page) - return len(flavors) == 0, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (page FlavorPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"flavors_links"` - } - err := page.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// ExtractFlavors provides access to the list of flavors in a page acquired -// from the ListDetail operation. -func ExtractFlavors(r pagination.Page) ([]Flavor, error) { - var s struct { - Flavors []Flavor `json:"flavors"` - } - err := (r.(FlavorPage)).ExtractInto(&s) - return s.Flavors, err -} - -// AccessPage contains a single page of all FlavorAccess entries for a flavor. -type AccessPage struct { - pagination.SinglePageBase -} - -// IsEmpty indicates whether an AccessPage is empty. -func (page AccessPage) IsEmpty() (bool, error) { - v, err := ExtractAccesses(page) - return len(v) == 0, err -} - -// ExtractAccesses interprets a page of results as a slice of FlavorAccess. -func ExtractAccesses(r pagination.Page) ([]FlavorAccess, error) { - var s struct { - FlavorAccesses []FlavorAccess `json:"flavor_access"` - } - err := (r.(AccessPage)).ExtractInto(&s) - return s.FlavorAccesses, err -} - -type accessResult struct { - gophercloud.Result -} - -// AddAccessResult is the response of an AddAccess operation. Call its -// Extract method to interpret it as a slice of FlavorAccess. -type AddAccessResult struct { - accessResult -} - -// RemoveAccessResult is the response of a RemoveAccess operation. Call its -// Extract method to interpret it as a slice of FlavorAccess. -type RemoveAccessResult struct { - accessResult -} - -// Extract provides access to the result of an access create or delete. -// The result will be all accesses that the flavor has. -func (r accessResult) Extract() ([]FlavorAccess, error) { - var s struct { - FlavorAccesses []FlavorAccess `json:"flavor_access"` - } - err := r.ExtractInto(&s) - return s.FlavorAccesses, err -} - -// FlavorAccess represents an ACL of tenant access to a specific Flavor. -type FlavorAccess struct { - // FlavorID is the unique ID of the flavor. - FlavorID string `json:"flavor_id"` - - // TenantID is the unique ID of the tenant. - TenantID string `json:"tenant_id"` -} - -// Extract interprets any extraSpecsResult as ExtraSpecs, if possible. -func (r extraSpecsResult) Extract() (map[string]string, error) { - var s struct { - ExtraSpecs map[string]string `json:"extra_specs"` - } - err := r.ExtractInto(&s) - return s.ExtraSpecs, err -} - -// extraSpecsResult contains the result of a call for (potentially) multiple -// key-value pairs. Call its Extract method to interpret it as a -// map[string]interface. -type extraSpecsResult struct { - gophercloud.Result -} - -// ListExtraSpecsResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type ListExtraSpecsResult struct { - extraSpecsResult -} - -// CreateExtraSpecResult contains the result of a Create operation. Call its -// Extract method to interpret it as a map[string]interface. -type CreateExtraSpecsResult struct { - extraSpecsResult -} - -// extraSpecResult contains the result of a call for individual a single -// key-value pair. -type extraSpecResult struct { - gophercloud.Result -} - -// GetExtraSpecResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type GetExtraSpecResult struct { - extraSpecResult -} - -// UpdateExtraSpecResult contains the result of an Update operation. Call its -// Extract method to interpret it as a map[string]interface. -type UpdateExtraSpecResult struct { - extraSpecResult -} - -// DeleteExtraSpecResult contains the result of a Delete operation. Call its -// ExtractErr method to determine if the call succeeded or failed. -type DeleteExtraSpecResult struct { - gophercloud.ErrResult -} - -// Extract interprets any extraSpecResult as an ExtraSpec, if possible. -func (r extraSpecResult) Extract() (map[string]string, error) { - var s map[string]string - err := r.ExtractInto(&s) - return s, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go deleted file mode 100644 index 8620dd78ad0..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go +++ /dev/null @@ -1,49 +0,0 @@ -package flavors - -import ( - "github.com/gophercloud/gophercloud" -) - -func getURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("flavors", id) -} - -func listURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("flavors", "detail") -} - -func createURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("flavors") -} - -func deleteURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("flavors", id) -} - -func accessURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "os-flavor-access") -} - -func accessActionURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "action") -} - -func extraSpecsListURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "os-extra_specs") -} - -func extraSpecsGetURL(client *gophercloud.ServiceClient, id, key string) string { - return client.ServiceURL("flavors", id, "os-extra_specs", key) -} - -func extraSpecsCreateURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "os-extra_specs") -} - -func extraSpecUpdateURL(client *gophercloud.ServiceClient, id, key string) string { - return client.ServiceURL("flavors", id, "os-extra_specs", key) -} - -func extraSpecDeleteURL(client *gophercloud.ServiceClient, id, key string) string { - return client.ServiceURL("flavors", id, "os-extra_specs", key) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go deleted file mode 100644 index 22410a79a27..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Package images provides information and interaction with the images through -the OpenStack Compute service. - -This API is deprecated and will be removed from a future version of the Nova -API service. - -An image is a collection of files used to create or rebuild a server. -Operators provide a number of pre-built OS images by default. You may also -create custom images from cloud servers you have launched. - -Example to List Images - - listOpts := images.ListOpts{ - Limit: 2, - } - - allPages, err := images.ListDetail(computeClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allImages, err := images.ExtractImages(allPages) - if err != nil { - panic(err) - } - - for _, image := range allImages { - fmt.Printf("%+v\n", image) - } -*/ -package images diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go deleted file mode 100644 index 558b481b9e7..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go +++ /dev/null @@ -1,109 +0,0 @@ -package images - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// ListDetail request. -type ListOptsBuilder interface { - ToImageListQuery() (string, error) -} - -// ListOpts contain options filtering Images returned from a call to ListDetail. -type ListOpts struct { - // ChangesSince filters Images based on the last changed status (in date-time - // format). - ChangesSince string `q:"changes-since"` - - // Limit limits the number of Images to return. - Limit int `q:"limit"` - - // Mark is an Image UUID at which to set a marker. - Marker string `q:"marker"` - - // Name is the name of the Image. - Name string `q:"name"` - - // Server is the name of the Server (in URL format). - Server string `q:"server"` - - // Status is the current status of the Image. - Status string `q:"status"` - - // Type is the type of image (e.g. BASE, SERVER, ALL). - Type string `q:"type"` -} - -// ToImageListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToImageListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// ListDetail enumerates the available images. -func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listDetailURL(client) - if opts != nil { - query, err := opts.ToImageListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return ImagePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// Get returns data about a specific image by its ID. -func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// Delete deletes the specified image ID. -func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// IDFromName is a convienience function that returns an image's ID given its -// name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - allPages, err := ListDetail(client, nil).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractImages(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - err := &gophercloud.ErrResourceNotFound{} - err.ResourceType = "image" - err.Name = name - return "", err - case 1: - return id, nil - default: - err := &gophercloud.ErrMultipleResourcesFound{} - err.ResourceType = "image" - err.Name = name - err.Count = count - return "", err - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go deleted file mode 100644 index 70d1018c721..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go +++ /dev/null @@ -1,95 +0,0 @@ -package images - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as an Image. -type GetResult 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 -} - -// Extract interprets a GetResult as an Image. -func (r GetResult) Extract() (*Image, error) { - var s struct { - Image *Image `json:"image"` - } - err := r.ExtractInto(&s) - return s.Image, err -} - -// Image represents an Image returned by the Compute API. -type Image struct { - // ID is the unique ID of an image. - ID string - - // Created is the date when the image was created. - Created string - - // MinDisk is the minimum amount of disk a flavor must have to be able - // to create a server based on the image, measured in GB. - MinDisk int - - // MinRAM is the minimum amount of RAM a flavor must have to be able - // to create a server based on the image, measured in MB. - MinRAM int - - // Name provides a human-readable moniker for the OS image. - Name string - - // The Progress and Status fields indicate image-creation status. - Progress int - - // Status is the current status of the image. - Status string - - // Update is the date when the image was updated. - Updated string - - // Metadata provides free-form key/value pairs that further describe the - // image. - Metadata map[string]interface{} -} - -// ImagePage contains a single page of all Images returne from a ListDetail -// operation. Use ExtractImages to convert it into a slice of usable structs. -type ImagePage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if an ImagePage contains no Image results. -func (page ImagePage) IsEmpty() (bool, error) { - images, err := ExtractImages(page) - return len(images) == 0, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (page ImagePage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"images_links"` - } - err := page.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// ExtractImages converts a page of List results into a slice of usable Image -// structs. -func ExtractImages(r pagination.Page) ([]Image, error) { - var s struct { - Images []Image `json:"images"` - } - err := (r.(ImagePage)).ExtractInto(&s) - return s.Images, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go deleted file mode 100644 index 57787fb725e..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go +++ /dev/null @@ -1,15 +0,0 @@ -package images - -import "github.com/gophercloud/gophercloud" - -func listDetailURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("images", "detail") -} - -func getURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("images", id) -} - -func deleteURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("images", id) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go deleted file mode 100644 index 3b0ab783626..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Package servers provides information and interaction with the server API -resource in the OpenStack Compute service. - -A server is a virtual machine instance in the compute system. In order for -one to be provisioned, a valid flavor and image are required. - -Example to List Servers - - listOpts := servers.ListOpts{ - AllTenants: true, - } - - allPages, err := servers.List(computeClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allServers, err := servers.ExtractServers(allPages) - if err != nil { - panic(err) - } - - for _, server := range allServers { - fmt.Printf("%+v\n", server) - } - -Example to Create a Server - - createOpts := servers.CreateOpts{ - Name: "server_name", - ImageRef: "image-uuid", - FlavorRef: "flavor-uuid", - } - - server, err := servers.Create(computeClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Server - - serverID := "d9072956-1560-487c-97f2-18bdf65ec749" - err := servers.Delete(computeClient, serverID).ExtractErr() - if err != nil { - panic(err) - } - -Example to Force Delete a Server - - serverID := "d9072956-1560-487c-97f2-18bdf65ec749" - err := servers.ForceDelete(computeClient, serverID).ExtractErr() - if err != nil { - panic(err) - } - -Example to Reboot a Server - - rebootOpts := servers.RebootOpts{ - Type: servers.SoftReboot, - } - - serverID := "d9072956-1560-487c-97f2-18bdf65ec749" - - err := servers.Reboot(computeClient, serverID, rebootOpts).ExtractErr() - if err != nil { - panic(err) - } - -Example to Rebuild a Server - - rebuildOpts := servers.RebuildOpts{ - Name: "new_name", - ImageID: "image-uuid", - } - - serverID := "d9072956-1560-487c-97f2-18bdf65ec749" - - server, err := servers.Rebuilt(computeClient, serverID, rebuildOpts).Extract() - if err != nil { - panic(err) - } - -Example to Resize a Server - - resizeOpts := servers.ResizeOpts{ - FlavorRef: "flavor-uuid", - } - - serverID := "d9072956-1560-487c-97f2-18bdf65ec749" - - err := servers.Resize(computeClient, serverID, resizeOpts).ExtractErr() - if err != nil { - panic(err) - } - - err = servers.ConfirmResize(computeClient, serverID).ExtractErr() - if err != nil { - panic(err) - } - -Example to Snapshot a Server - - snapshotOpts := servers.CreateImageOpts{ - Name: "snapshot_name", - } - - serverID := "d9072956-1560-487c-97f2-18bdf65ec749" - - image, err := servers.CreateImage(computeClient, serverID, snapshotOpts).ExtractImageID() - if err != nil { - panic(err) - } -*/ -package servers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go deleted file mode 100644 index c9f0e3c20b5..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go +++ /dev/null @@ -1,71 +0,0 @@ -package servers - -import ( - "fmt" - - "github.com/gophercloud/gophercloud" -) - -// ErrNeitherImageIDNorImageNameProvided is the error when neither the image -// ID nor the image name is provided for a server operation -type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput } - -func (e ErrNeitherImageIDNorImageNameProvided) Error() string { - return "One and only one of the image ID and the image name must be provided." -} - -// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor -// ID nor the flavor name is provided for a server operation -type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput } - -func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string { - return "One and only one of the flavor ID and the flavor name must be provided." -} - -type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput } - -func (e ErrNoClientProvidedForIDByName) Error() string { - return "A service client must be provided to find a resource ID by name." -} - -// ErrInvalidHowParameterProvided is the error when an unknown value is given -// for the `how` argument -type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput } - -// ErrNoAdminPassProvided is the error when an administrative password isn't -// provided for a server operation -type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput } - -// ErrNoImageIDProvided is the error when an image ID isn't provided for a server -// operation -type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput } - -// ErrNoIDProvided is the error when a server ID isn't provided for a server -// operation -type ErrNoIDProvided struct{ gophercloud.ErrMissingInput } - -// ErrServer is a generic error type for servers HTTP operations. -type ErrServer struct { - gophercloud.ErrUnexpectedResponseCode - ID string -} - -func (se ErrServer) Error() string { - return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID) -} - -// Error404 overrides the generic 404 error message. -func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error { - se.ErrUnexpectedResponseCode = e - return &ErrServerNotFound{se} -} - -// ErrServerNotFound is the error when a 404 is received during server HTTP -// operations. -type ErrServerNotFound struct { - ErrServer -} - -func (e ErrServerNotFound) Error() string { - return fmt.Sprintf("I couldn't find server [%s]", e.ID) -} 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 deleted file mode 100644 index 84ec9f31d35..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index ee8e93b1dcb..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go +++ /dev/null @@ -1,812 +0,0 @@ -package servers - -import ( - "encoding/base64" - "encoding/json" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" - "github.com/gophercloud/gophercloud/openstack/compute/v2/images" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// List request. -type ListOptsBuilder interface { - ToServerListQuery() (string, error) -} - -// ListOpts allows the filtering and sorting of paginated collections through -// the API. Filtering is achieved by passing in struct field values that map to -// the server attributes you want to see returned. Marker and Limit are used -// for pagination. -type ListOpts struct { - // ChangesSince is a time/date stamp for when the server last changed status. - ChangesSince string `q:"changes-since"` - - // Image is the name of the image in URL format. - Image string `q:"image"` - - // Flavor is the name of the flavor in URL format. - Flavor string `q:"flavor"` - - // Name of the server as a string; can be queried with regular expressions. - // Realize that ?name=bob returns both bob and bobb. If you need to match bob - // only, you can use a regular expression matching the syntax of the - // underlying database server implemented for Compute. - Name string `q:"name"` - - // Status is the value of the status of the server so that you can filter on - // "ACTIVE" for example. - Status string `q:"status"` - - // Host is the name of the host as a string. - Host string `q:"host"` - - // Marker is a UUID of the server at which you want to set a marker. - Marker string `q:"marker"` - - // Limit is an integer value for the limit of values to return. - Limit int `q:"limit"` - - // AllTenants is a bool to show all tenants. - AllTenants bool `q:"all_tenants"` - - // TenantID lists servers for a particular tenant. - // Setting "AllTenants = true" is required. - TenantID string `q:"tenant_id"` -} - -// ToServerListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToServerListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// List makes a request against the API to list servers accessible to you. -func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listDetailURL(client) - if opts != nil { - query, err := opts.ToServerListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return ServerPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToServerCreateMap() (map[string]interface{}, error) -} - -// Network is used within CreateOpts to control a new server's network -// attachments. -type Network struct { - // UUID of a network to attach to the newly provisioned server. - // Required unless Port is provided. - UUID string - - // Port of a neutron network to attach to the newly provisioned server. - // Required unless UUID is provided. - Port string - - // FixedIP specifies a fixed IPv4 address to be used on this network. - FixedIP string -} - -// Personality is an array of files that are injected into the server at launch. -type Personality []*File - -// File is used within CreateOpts and RebuildOpts to inject a file into the -// server at launch. -// File implements the json.Marshaler interface, so when a Create or Rebuild -// operation is requested, json.Marshal will call File's MarshalJSON method. -type File struct { - // Path of the file. - Path string - - // Contents of the file. Maximum content size is 255 bytes. - Contents []byte -} - -// MarshalJSON marshals the escaped file, base64 encoding the contents. -func (f *File) MarshalJSON() ([]byte, error) { - file := struct { - Path string `json:"path"` - Contents string `json:"contents"` - }{ - Path: f.Path, - Contents: base64.StdEncoding.EncodeToString(f.Contents), - } - return json.Marshal(file) -} - -// CreateOpts specifies server creation parameters. -type CreateOpts struct { - // Name is the name to assign to the newly launched server. - Name string `json:"name" required:"true"` - - // ImageRef [optional; required if ImageName is not provided] is the ID or - // full URL to the image that contains the server's OS and initial state. - // Also optional if using the boot-from-volume extension. - ImageRef string `json:"imageRef"` - - // ImageName [optional; required if ImageRef is not provided] is the name of - // the image that contains the server's OS and initial state. - // Also optional if using the boot-from-volume extension. - ImageName string `json:"-"` - - // FlavorRef [optional; required if FlavorName is not provided] is the ID or - // full URL to the flavor that describes the server's specs. - FlavorRef string `json:"flavorRef"` - - // FlavorName [optional; required if FlavorRef is not provided] is the name of - // the flavor that describes the server's specs. - FlavorName string `json:"-"` - - // SecurityGroups lists the names of the security groups to which this server - // should belong. - SecurityGroups []string `json:"-"` - - // UserData contains configuration information or scripts to use upon launch. - // Create will base64-encode it for you, if it isn't already. - UserData []byte `json:"-"` - - // AvailabilityZone in which to launch the server. - AvailabilityZone string `json:"availability_zone,omitempty"` - - // Networks dictates how this server will be attached to available networks. - // By default, the server will be attached to all isolated networks for the - // tenant. - Networks []Network `json:"-"` - - // Metadata contains key-value pairs (up to 255 bytes each) to attach to the - // server. - Metadata map[string]string `json:"metadata,omitempty"` - - // Personality includes files to inject into the server at launch. - // Create will base64-encode file contents for you. - Personality Personality `json:"personality,omitempty"` - - // ConfigDrive enables metadata injection through a configuration drive. - ConfigDrive *bool `json:"config_drive,omitempty"` - - // AdminPass sets the root user password. If not set, a randomly-generated - // password will be created and returned in the response. - AdminPass string `json:"adminPass,omitempty"` - - // AccessIPv4 specifies an IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` - - // 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 -// CreateOpts. -func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { - sc := opts.ServiceClient - opts.ServiceClient = nil - b, err := gophercloud.BuildRequestBody(opts, "") - if err != nil { - return nil, err - } - - if opts.UserData != nil { - var userData string - if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil { - userData = base64.StdEncoding.EncodeToString(opts.UserData) - } else { - userData = string(opts.UserData) - } - b["user_data"] = &userData - } - - if len(opts.SecurityGroups) > 0 { - securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups)) - for i, groupName := range opts.SecurityGroups { - securityGroups[i] = map[string]interface{}{"name": groupName} - } - b["security_groups"] = securityGroups - } - - if len(opts.Networks) > 0 { - networks := make([]map[string]interface{}, len(opts.Networks)) - for i, net := range opts.Networks { - networks[i] = make(map[string]interface{}) - if net.UUID != "" { - networks[i]["uuid"] = net.UUID - } - if net.Port != "" { - networks[i]["port"] = net.Port - } - if net.FixedIP != "" { - networks[i]["fixed_ip"] = net.FixedIP - } - } - b["networks"] = networks - } - - // If ImageRef isn't provided, check if ImageName was provided to ascertain - // the image ID. - if opts.ImageRef == "" { - if opts.ImageName != "" { - if sc == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - imageID, err := images.IDFromName(sc, opts.ImageName) - if err != nil { - return nil, err - } - b["imageRef"] = imageID - } - } - - // If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID. - if opts.FlavorRef == "" { - if opts.FlavorName == "" { - err := ErrNeitherFlavorIDNorFlavorNameProvided{} - err.Argument = "FlavorRef/FlavorName" - return nil, err - } - if sc == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - flavorID, err := flavors.IDFromName(sc, opts.FlavorName) - if err != nil { - return nil, err - } - 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 -} - -// Create requests a server to be provisioned to the user in the current tenant. -func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - reqBody, err := opts.ToServerCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil) - return -} - -// Delete requests that a server previously provisioned be removed from your -// account. -func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// ForceDelete forces the deletion of a server. -func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) - return -} - -// Get requests details on a single server, 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 -} - -// UpdateOptsBuilder allows extensions to add additional attributes to the -// Update request. -type UpdateOptsBuilder interface { - ToServerUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts specifies the base attributes that may be updated on an existing -// server. -type UpdateOpts struct { - // Name changes the displayed name of the server. - // The server host name will *not* change. - // Server names are not constrained to be unique, even within the same tenant. - Name string `json:"name,omitempty"` - - // AccessIPv4 provides a new IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` - - // AccessIPv6 provides a new IPv6 address for the instance. - AccessIPv6 string `json:"accessIPv6,omitempty"` -} - -// ToServerUpdateMap formats an UpdateOpts structure into a request body. -func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "server") -} - -// Update requests that various attributes of the indicated server be changed. -func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToServerUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// ChangeAdminPassword alters the administrator or root password for a specified -// server. -func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) { - b := map[string]interface{}{ - "changePassword": map[string]string{ - "adminPass": newPassword, - }, - } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) - return -} - -// RebootMethod describes the mechanisms by which a server reboot can be requested. -type RebootMethod string - -// These constants determine how a server should be rebooted. -// See the Reboot() function for further details. -const ( - SoftReboot RebootMethod = "SOFT" - HardReboot RebootMethod = "HARD" - OSReboot = SoftReboot - PowerCycle = HardReboot -) - -// RebootOptsBuilder allows extensions to add additional parameters to the -// reboot request. -type RebootOptsBuilder interface { - ToServerRebootMap() (map[string]interface{}, error) -} - -// RebootOpts provides options to the reboot request. -type RebootOpts struct { - // Type is the type of reboot to perform on the server. - Type RebootMethod `json:"type" required:"true"` -} - -// ToServerRebootMap builds a body for the reboot request. -func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "reboot") -} - -/* - Reboot requests that a given server reboot. - - Two methods exist for rebooting a server: - - HardReboot (aka PowerCycle) starts the server instance by physically cutting - power to the machine, or if a VM, terminating it at the hypervisor level. - It's done. Caput. Full stop. - Then, after a brief while, power is rtored or the VM instance restarted. - - SoftReboot (aka OSReboot) simply tells the OS to restart under its own - procedure. - E.g., in Linux, asking it to enter runlevel 6, or executing - "sudo shutdown -r now", or by asking Windows to rtart the machine. -*/ -func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) { - b, err := opts.ToServerRebootMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) - return -} - -// RebuildOptsBuilder allows extensions to provide additional parameters to the -// rebuild request. -type RebuildOptsBuilder interface { - ToServerRebuildMap() (map[string]interface{}, error) -} - -// RebuildOpts represents the configuration options used in a server rebuild -// operation. -type RebuildOpts struct { - // AdminPass is the server's admin password - AdminPass string `json:"adminPass,omitempty"` - - // ImageID is the ID of the image you want your server to be provisioned on. - ImageID string `json:"imageRef"` - - // ImageName is readable name of an image. - ImageName string `json:"-"` - - // Name to set the server to - Name string `json:"name,omitempty"` - - // AccessIPv4 [optional] provides a new IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` - - // AccessIPv6 [optional] provides a new IPv6 address for the instance. - AccessIPv6 string `json:"accessIPv6,omitempty"` - - // Metadata [optional] contains key-value pairs (up to 255 bytes each) - // to attach to the server. - Metadata map[string]string `json:"metadata,omitempty"` - - // Personality [optional] includes files to inject into the server at launch. - // Rebuild will base64-encode file contents for you. - Personality Personality `json:"personality,omitempty"` - - // ServiceClient will allow calls to be made to retrieve an image or - // flavor ID by name. - ServiceClient *gophercloud.ServiceClient `json:"-"` -} - -// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON -func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { - b, err := gophercloud.BuildRequestBody(opts, "") - if err != nil { - return nil, err - } - - // If ImageRef isn't provided, check if ImageName was provided to ascertain - // the image ID. - if opts.ImageID == "" { - if opts.ImageName != "" { - if opts.ServiceClient == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName) - if err != nil { - return nil, err - } - b["imageRef"] = imageID - } - } - - return map[string]interface{}{"rebuild": b}, nil -} - -// Rebuild will reprovision the server according to the configuration options -// provided in the RebuildOpts struct. -func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) { - b, err := opts.ToServerRebuildMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil) - return -} - -// ResizeOptsBuilder allows extensions to add additional parameters to the -// resize request. -type ResizeOptsBuilder interface { - ToServerResizeMap() (map[string]interface{}, error) -} - -// ResizeOpts represents the configuration options used to control a Resize -// operation. -type ResizeOpts struct { - // FlavorRef is the ID of the flavor you wish your server to become. - FlavorRef string `json:"flavorRef" required:"true"` -} - -// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON -// request body for the Resize request. -func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "resize") -} - -// Resize instructs the provider to change the flavor of the server. -// -// Note that this implies rebuilding it. -// -// Unfortunately, one cannot pass rebuild parameters to the resize function. -// When the resize completes, the server will be in VERIFY_RESIZE state. -// While in this state, you can explore the use of the new server's -// configuration. If you like it, call ConfirmResize() to commit the resize -// permanently. Otherwise, call RevertResize() to restore the old configuration. -func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { - b, err := opts.ToServerResizeMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) - return -} - -// ConfirmResize confirms a previous resize operation on a server. -// See Resize() for more details. -func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{ - OkCodes: []int{201, 202, 204}, - }) - return -} - -// RevertResize cancels a previous resize operation on a server. -// See Resize() for more details. -func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) - return -} - -// ResetMetadataOptsBuilder allows extensions to add additional parameters to -// the Reset request. -type ResetMetadataOptsBuilder interface { - ToMetadataResetMap() (map[string]interface{}, error) -} - -// MetadataOpts is a map that contains key-value pairs. -type MetadataOpts map[string]string - -// ToMetadataResetMap assembles a body for a Reset request based on the contents -// of a MetadataOpts. -func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) { - return map[string]interface{}{"metadata": opts}, nil -} - -// ToMetadataUpdateMap assembles a body for an Update request based on the -// contents of a MetadataOpts. -func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) { - return map[string]interface{}{"metadata": opts}, nil -} - -// ResetMetadata will create multiple new key-value pairs for the given server -// ID. -// Note: Using this operation will erase any already-existing metadata and -// create the new metadata provided. To keep any already-existing metadata, -// use the UpdateMetadatas or UpdateMetadata function. -func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) { - b, err := opts.ToMetadataResetMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Metadata requests all the metadata for the given server ID. -func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) { - _, r.Err = client.Get(metadataURL(client, id), &r.Body, nil) - return -} - -// UpdateMetadataOptsBuilder allows extensions to add additional parameters to -// the Create request. -type UpdateMetadataOptsBuilder interface { - ToMetadataUpdateMap() (map[string]interface{}, error) -} - -// UpdateMetadata updates (or creates) all the metadata specified by opts for -// the given server ID. This operation does not affect already-existing metadata -// that is not specified by opts. -func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { - b, err := opts.ToMetadataUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// MetadatumOptsBuilder allows extensions to add additional parameters to the -// Create request. -type MetadatumOptsBuilder interface { - ToMetadatumCreateMap() (map[string]interface{}, string, error) -} - -// MetadatumOpts is a map of length one that contains a key-value pair. -type MetadatumOpts map[string]string - -// ToMetadatumCreateMap assembles a body for a Create request based on the -// contents of a MetadataumOpts. -func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) { - if len(opts) != 1 { - err := gophercloud.ErrInvalidInput{} - err.Argument = "servers.MetadatumOpts" - err.Info = "Must have 1 and only 1 key-value pair" - return nil, "", err - } - metadatum := map[string]interface{}{"meta": opts} - var key string - for k := range metadatum["meta"].(MetadatumOpts) { - key = k - } - return metadatum, key, nil -} - -// CreateMetadatum will create or update the key-value pair with the given key -// for the given server ID. -func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) { - b, key, err := opts.ToMetadatumCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Metadatum requests the key-value pair with the given key for the given -// server ID. -func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) { - _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) - return -} - -// DeleteMetadatum will delete the key-value pair with the given key for the -// given server ID. -func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) { - _, r.Err = client.Delete(metadatumURL(client, id, key), nil) - return -} - -// ListAddresses makes a request against the API to list the servers IP -// addresses. -func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager { - return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page { - return AddressPage{pagination.SinglePageBase(r)} - }) -} - -// ListAddressesByNetwork makes a request against the API to list the servers IP -// addresses for the given network. -func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager { - return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page { - return NetworkAddressPage{pagination.SinglePageBase(r)} - }) -} - -// CreateImageOptsBuilder allows extensions to add additional parameters to the -// CreateImage request. -type CreateImageOptsBuilder interface { - ToServerCreateImageMap() (map[string]interface{}, error) -} - -// CreateImageOpts provides options to pass to the CreateImage request. -type CreateImageOpts struct { - // Name of the image/snapshot. - Name string `json:"name" required:"true"` - - // Metadata contains key-value pairs (up to 255 bytes each) to attach to - // the created image. - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToServerCreateImageMap formats a CreateImageOpts structure into a request -// body. -func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "createImage") -} - -// CreateImage makes a request against the nova API to schedule an image to be -// created of the server -func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) { - b, err := opts.ToServerCreateImageMap() - if err != nil { - r.Err = err - return - } - resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, - }) - r.Err = err - r.Header = resp.Header - return -} - -// IDFromName is a convienience function that returns a server's ID given its -// name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - allPages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractServers(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} - } -} - -// GetPassword makes a request against the nova API to get the encrypted -// administrative password. -func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) { - _, 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 deleted file mode 100644 index f973d1ea0e1..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go +++ /dev/null @@ -1,414 +0,0 @@ -package servers - -import ( - "crypto/rsa" - "encoding/base64" - "encoding/json" - "fmt" - "net/url" - "path" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -type serverResult struct { - gophercloud.Result -} - -// Extract interprets any serverResult as a Server, if possible. -func (r serverResult) Extract() (*Server, error) { - var s Server - err := r.ExtractInto(&s) - return &s, err -} - -func (r serverResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "server") -} - -func ExtractServersInto(r pagination.Page, v interface{}) error { - return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") -} - -// CreateResult is the response from a Create operation. Call its Extract -// method to interpret it as a Server. -type CreateResult struct { - serverResult -} - -// GetResult is the response from a Get operation. Call its Extract -// method to interpret it as a Server. -type GetResult struct { - serverResult -} - -// UpdateResult is the response from an Update operation. Call its Extract -// method to interpret it as a Server. -type UpdateResult struct { - serverResult -} - -// 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 -} - -// RebuildResult is the response from a Rebuild operation. Call its Extract -// method to interpret it as a Server. -type RebuildResult struct { - serverResult -} - -// ActionResult represents the result of server action operations, like reboot. -// Call its ExtractErr method to determine if the action succeeded or failed. -type ActionResult struct { - gophercloud.ErrResult -} - -// 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 { - gophercloud.Result -} - -// ExtractPassword gets the encrypted password. -// If privateKey != nil the password is decrypted with the private key. -// If privateKey == nil the encrypted password is returned and can be decrypted -// with: -// echo '' | base64 -D | openssl rsautl -decrypt -inkey -func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { - var s struct { - Password string `json:"password"` - } - err := r.ExtractInto(&s) - if err == nil && privateKey != nil && s.Password != "" { - return decryptPassword(s.Password, privateKey) - } - return s.Password, err -} - -func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) { - b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword))) - - n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) - if err != nil { - return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err) - } - password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) - if err != nil { - return "", fmt.Errorf("Failed to decrypt password: %s", err) - } - - return string(password), nil -} - -// ExtractImageID gets the ID of the newly created server image from the header. -func (r CreateImageResult) ExtractImageID() (string, error) { - if r.Err != nil { - return "", r.Err - } - // Get the image id from the header - u, err := url.ParseRequestURI(r.Header.Get("Location")) - if err != nil { - return "", err - } - imageID := path.Base(u.Path) - if imageID == "." || imageID == "/" { - return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u) - } - return imageID, nil -} - -// Server represents a server/instance in the OpenStack cloud. -type Server struct { - // ID uniquely identifies this server amongst all other servers, - // including those not accessible to the current tenant. - ID string `json:"id"` - - // TenantID identifies the tenant owning this server resource. - TenantID string `json:"tenant_id"` - - // UserID uniquely identifies the user account owning the tenant. - UserID string `json:"user_id"` - - // Name contains the human-readable name for the server. - Name string `json:"name"` - - // Updated and Created contain ISO-8601 timestamps of when the state of the - // server last changed, and when it was created. - Updated time.Time `json:"updated"` - Created time.Time `json:"created"` - - // HostID is the host where the server is located in the cloud. - HostID string `json:"hostid"` - - // Status contains the current operational status of the server, - // such as IN_PROGRESS or ACTIVE. - Status string `json:"status"` - - // Progress ranges from 0..100. - // A request made against the server completes only once Progress reaches 100. - Progress int `json:"progress"` - - // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, - // suitable for remote access for administration. - AccessIPv4 string `json:"accessIPv4"` - AccessIPv6 string `json:"accessIPv6"` - - // Image refers to a JSON object, which itself indicates the OS image used to - // deploy the server. - Image map[string]interface{} `json:"-"` - - // Flavor refers to a JSON object, which itself indicates the hardware - // configuration of the deployed server. - Flavor map[string]interface{} `json:"flavor"` - - // Addresses includes a list of all IP addresses assigned to the server, - // keyed by pool. - Addresses map[string]interface{} `json:"addresses"` - - // Metadata includes a list of all user-specified key-value pairs attached - // to the server. - Metadata map[string]string `json:"metadata"` - - // Links includes HTTP references to the itself, useful for passing along to - // other APIs that might want a server reference. - Links []interface{} `json:"links"` - - // KeyName indicates which public key was injected into the server on launch. - KeyName string `json:"key_name"` - - // AdminPass will generally be empty (""). However, it will contain the - // administrative password chosen when provisioning a new server without a - // set AdminPass setting in the first place. - // Note that this is the ONLY time this field will be valid. - AdminPass string `json:"adminPass"` - - // SecurityGroups includes the security groups that this instance has applied - // to it. - SecurityGroups []map[string]interface{} `json:"security_groups"` - - // Fault contains failure information about a server. - Fault Fault `json:"fault"` -} - -type Fault struct { - Code int `json:"code"` - Created time.Time `json:"created"` - Details string `json:"details"` - Message string `json:"message"` -} - -func (r *Server) UnmarshalJSON(b []byte) error { - type tmp Server - var s struct { - tmp - Image interface{} `json:"image"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - - *r = Server(s.tmp) - - switch t := s.Image.(type) { - case map[string]interface{}: - r.Image = t - case string: - switch t { - case "": - r.Image = nil - } - } - - return err -} - -// ServerPage 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 ExtractServers call. -type ServerPage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if a page contains no Server results. -func (r ServerPage) IsEmpty() (bool, error) { - s, err := ExtractServers(r) - return len(s) == 0, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (r ServerPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"servers_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// ExtractServers interprets the results of a single page from a List() call, -// producing a slice of Server entities. -func ExtractServers(r pagination.Page) ([]Server, error) { - var s []Server - err := ExtractServersInto(r, &s) - return s, err -} - -// MetadataResult contains the result of a call for (potentially) multiple -// key-value pairs. Call its Extract method to interpret it as a -// map[string]interface. -type MetadataResult struct { - gophercloud.Result -} - -// GetMetadataResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type GetMetadataResult struct { - MetadataResult -} - -// ResetMetadataResult contains the result of a Reset operation. Call its -// Extract method to interpret it as a map[string]interface. -type ResetMetadataResult struct { - MetadataResult -} - -// UpdateMetadataResult contains the result of an Update operation. Call its -// Extract method to interpret it as a map[string]interface. -type UpdateMetadataResult struct { - MetadataResult -} - -// MetadatumResult contains the result of a call for individual a single -// key-value pair. -type MetadatumResult struct { - gophercloud.Result -} - -// GetMetadatumResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type GetMetadatumResult struct { - MetadatumResult -} - -// CreateMetadatumResult contains the result of a Create operation. Call its -// Extract method to interpret it as a map[string]interface. -type CreateMetadatumResult struct { - MetadatumResult -} - -// DeleteMetadatumResult contains the result of a Delete operation. Call its -// ExtractErr method to determine if the call succeeded or failed. -type DeleteMetadatumResult struct { - gophercloud.ErrResult -} - -// Extract interprets any MetadataResult as a Metadata, if possible. -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 -} - -// Extract interprets any MetadatumResult as a Metadatum, if possible. -func (r MetadatumResult) Extract() (map[string]string, error) { - var s struct { - Metadatum map[string]string `json:"meta"` - } - err := r.ExtractInto(&s) - return s.Metadatum, err -} - -// Address represents an IP address. -type Address struct { - Version int `json:"version"` - Address string `json:"addr"` -} - -// AddressPage abstracts the raw results of making a ListAddresses() 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 ExtractAddresses call. -type AddressPage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if an AddressPage contains no networks. -func (r AddressPage) IsEmpty() (bool, error) { - addresses, err := ExtractAddresses(r) - return len(addresses) == 0, err -} - -// ExtractAddresses interprets the results of a single page from a -// ListAddresses() call, producing a map of addresses. -func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { - var s struct { - Addresses map[string][]Address `json:"addresses"` - } - err := (r.(AddressPage)).ExtractInto(&s) - return s.Addresses, err -} - -// NetworkAddressPage abstracts the raw results of making a -// ListAddressesByNetwork() 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 ExtractAddresses call. -type NetworkAddressPage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if a NetworkAddressPage contains no addresses. -func (r NetworkAddressPage) IsEmpty() (bool, error) { - addresses, err := ExtractNetworkAddresses(r) - return len(addresses) == 0, err -} - -// ExtractNetworkAddresses interprets the results of a single page from a -// ListAddressesByNetwork() call, producing a slice of addresses. -func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { - var s map[string][]Address - err := (r.(NetworkAddressPage)).ExtractInto(&s) - if err != nil { - return nil, err - } - - var key string - for k := range s { - key = k - } - - return s[key], err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go deleted file mode 100644 index e892e8d9259..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go +++ /dev/null @@ -1,51 +0,0 @@ -package servers - -import "github.com/gophercloud/gophercloud" - -func createURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("servers") -} - -func listURL(client *gophercloud.ServiceClient) string { - return createURL(client) -} - -func listDetailURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("servers", "detail") -} - -func deleteURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("servers", 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 actionURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "action") -} - -func metadatumURL(client *gophercloud.ServiceClient, id, key string) string { - return client.ServiceURL("servers", id, "metadata", key) -} - -func metadataURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "metadata") -} - -func listAddressesURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "ips") -} - -func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string { - return client.ServiceURL("servers", id, "ips", network) -} - -func passwordURL(client *gophercloud.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "os-server-password") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go deleted file mode 100644 index cadef054506..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go +++ /dev/null @@ -1,21 +0,0 @@ -package servers - -import "github.com/gophercloud/gophercloud" - -// WaitForStatus will continually poll a server until it successfully -// transitions to a specified status. It will do this for at most the number -// of seconds specified. -func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { - return gophercloud.WaitFor(secs, func() (bool, error) { - current, err := Get(c, 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/openstack/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go deleted file mode 100644 index cedf1f4d3a3..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/doc.go +++ /dev/null @@ -1,14 +0,0 @@ -/* -Package openstack contains resources for the individual OpenStack projects -supported in Gophercloud. It also includes functions to authenticate to an -OpenStack cloud and for provisioning various service-level clients. - -Example of Creating a Service Client - - ao, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(ao) - client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) -*/ -package openstack diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go deleted file mode 100644 index 12c8aebcf77..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go +++ /dev/null @@ -1,107 +0,0 @@ -package openstack - -import ( - "github.com/gophercloud/gophercloud" - tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" - tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" -) - -/* -V2EndpointURL discovers the endpoint URL for a specific service from a -ServiceCatalog acquired during the v2 identity service. - -The specified EndpointOpts are used to identify a unique, unambiguous endpoint -to return. It's an error both when multiple endpoints match the provided -criteria and when none do. The minimum that can be specified is a Type, but you -will also often need to specify a Name and/or a Region depending on what's -available on your OpenStack deployment. -*/ -func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { - // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. - var endpoints = make([]tokens2.Endpoint, 0, 1) - for _, entry := range catalog.Entries { - if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { - for _, endpoint := range entry.Endpoints { - if opts.Region == "" || endpoint.Region == opts.Region { - endpoints = append(endpoints, endpoint) - } - } - } - } - - // Report an error if the options were ambiguous. - if len(endpoints) > 1 { - err := &ErrMultipleMatchingEndpointsV2{} - err.Endpoints = endpoints - return "", err - } - - // Extract the appropriate URL from the matching Endpoint. - for _, endpoint := range endpoints { - switch opts.Availability { - case gophercloud.AvailabilityPublic: - return gophercloud.NormalizeURL(endpoint.PublicURL), nil - case gophercloud.AvailabilityInternal: - return gophercloud.NormalizeURL(endpoint.InternalURL), nil - case gophercloud.AvailabilityAdmin: - return gophercloud.NormalizeURL(endpoint.AdminURL), nil - default: - err := &ErrInvalidAvailabilityProvided{} - err.Argument = "Availability" - err.Value = opts.Availability - return "", err - } - } - - // Report an error if there were no matching endpoints. - err := &gophercloud.ErrEndpointNotFound{} - return "", err -} - -/* -V3EndpointURL discovers the endpoint URL for a specific service from a Catalog -acquired during the v3 identity service. - -The specified EndpointOpts are used to identify a unique, unambiguous endpoint -to return. It's an error both when multiple endpoints match the provided -criteria and when none do. The minimum that can be specified is a Type, but you -will also often need to specify a Name and/or a Region depending on what's -available on your OpenStack deployment. -*/ -func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { - // Extract Endpoints from the catalog entries that match the requested Type, Interface, - // Name if provided, and Region if provided. - var endpoints = make([]tokens3.Endpoint, 0, 1) - for _, entry := range catalog.Entries { - if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { - for _, endpoint := range entry.Endpoints { - if opts.Availability != gophercloud.AvailabilityAdmin && - opts.Availability != gophercloud.AvailabilityPublic && - opts.Availability != gophercloud.AvailabilityInternal { - err := &ErrInvalidAvailabilityProvided{} - err.Argument = "Availability" - err.Value = opts.Availability - return "", err - } - if (opts.Availability == gophercloud.Availability(endpoint.Interface)) && - (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) { - endpoints = append(endpoints, endpoint) - } - } - } - } - - // Report an error if the options were ambiguous. - if len(endpoints) > 1 { - return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints} - } - - // Extract the URL from the matching Endpoint. - for _, endpoint := range endpoints { - return gophercloud.NormalizeURL(endpoint.URL), nil - } - - // Report an error if there were no matching endpoints. - err := &gophercloud.ErrEndpointNotFound{} - return "", err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go deleted file mode 100644 index df410b1c611..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/errors.go +++ /dev/null @@ -1,71 +0,0 @@ -package openstack - -import ( - "fmt" - - "github.com/gophercloud/gophercloud" - tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" - tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" -) - -// ErrEndpointNotFound is the error when no suitable endpoint can be found -// in the user's catalog -type ErrEndpointNotFound struct{ gophercloud.BaseError } - -func (e ErrEndpointNotFound) Error() string { - return "No suitable endpoint could be found in the service catalog." -} - -// ErrInvalidAvailabilityProvided is the error when an invalid endpoint -// availability is provided -type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput } - -func (e ErrInvalidAvailabilityProvided) Error() string { - return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value) -} - -// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint -// for the given options is found in the v2 catalog -type ErrMultipleMatchingEndpointsV2 struct { - gophercloud.BaseError - Endpoints []tokens2.Endpoint -} - -func (e ErrMultipleMatchingEndpointsV2) Error() string { - return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints) -} - -// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint -// for the given options is found in the v3 catalog -type ErrMultipleMatchingEndpointsV3 struct { - gophercloud.BaseError - Endpoints []tokens3.Endpoint -} - -func (e ErrMultipleMatchingEndpointsV3) Error() string { - return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints) -} - -// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not -// found -type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput } - -func (e ErrNoAuthURL) Error() string { - return "Environment variable OS_AUTH_URL needs to be set." -} - -// ErrNoUsername is the error when the OS_USERNAME environment variable is not -// found -type ErrNoUsername struct{ gophercloud.ErrInvalidInput } - -func (e ErrNoUsername) Error() string { - return "Environment variable OS_USERNAME needs to be set." -} - -// ErrNoPassword is the error when the OS_PASSWORD environment variable is not -// found -type ErrNoPassword struct{ gophercloud.ErrInvalidInput } - -func (e ErrNoPassword) Error() string { - return "Environment variable OS_PASSWORD needs to be set." -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go deleted file mode 100644 index 45623369e18..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Package tenants provides information and interaction with the -tenants API resource for the OpenStack Identity service. - -See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 -and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants -for more information. - -Example to List Tenants - - listOpts := tenants.ListOpts{ - Limit: 2, - } - - allPages, err := tenants.List(identityClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allTenants, err := tenants.ExtractTenants(allPages) - if err != nil { - panic(err) - } - - for _, tenant := range allTenants { - fmt.Printf("%+v\n", tenant) - } - -Example to Create a Tenant - - createOpts := tenants.CreateOpts{ - Name: "tenant_name", - Description: "this is a tenant", - Enabled: gophercloud.Enabled, - } - - tenant, err := tenants.Create(identityClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Tenant - - tenantID := "e6db6ed6277c461a853458589063b295" - - updateOpts := tenants.UpdateOpts{ - Description: "this is a new description", - Enabled: gophercloud.Disabled, - } - - tenant, err := tenants.Update(identityClient, tenantID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Tenant - - tenantID := "e6db6ed6277c461a853458589063b295" - - err := tenants.Delete(identitYClient, tenantID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package tenants 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 deleted file mode 100644 index f21a58f10c8..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go +++ /dev/null @@ -1,116 +0,0 @@ -package tenants - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOpts filters the Tenants that are returned by the List call. -type ListOpts struct { - // Marker is the ID of the last Tenant on the previous page. - Marker string `q:"marker"` - - // Limit specifies the page size. - Limit int `q:"limit"` -} - -// List enumerates the Tenants to which the current token has access. -func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager { - url := listURL(client) - if opts != nil { - q, err := gophercloud.BuildQueryString(opts) - if err != nil { - return pagination.Pager{Err: err} - } - url += q.String() - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return TenantPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// CreateOpts represents the options needed when creating new tenant. -type CreateOpts struct { - // Name is the name of the tenant. - Name string `json:"name" required:"true"` - - // Description is the description of the tenant. - Description string `json:"description,omitempty"` - - // Enabled sets the tenant status to enabled or disabled. - Enabled *bool `json:"enabled,omitempty"` -} - -// CreateOptsBuilder enables extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToTenantCreateMap() (map[string]interface{}, error) -} - -// ToTenantCreateMap assembles a request body based on the contents of -// a CreateOpts. -func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "tenant") -} - -// Create is the operation responsible for creating new tenant. -func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToTenantCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201}, - }) - return -} - -// Get requests details on a single tenant by ID. -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 { - ToTenantUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts specifies the base attributes that may be updated on an existing -// tenant. -type UpdateOpts struct { - // Name is the name of the tenant. - Name string `json:"name,omitempty"` - - // Description is the description of the tenant. - Description *string `json:"description,omitempty"` - - // Enabled sets the tenant status to enabled or disabled. - Enabled *bool `json:"enabled,omitempty"` -} - -// ToTenantUpdateMap formats an UpdateOpts structure into a request body. -func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "tenant") -} - -// Update is the operation responsible for updating exist tenants by their TenantID. -func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToTenantUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Delete is the operation responsible for permanently deleting a tenant. -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/identity/v2/tenants/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go deleted file mode 100644 index bb6c2c6b08a..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go +++ /dev/null @@ -1,91 +0,0 @@ -package tenants - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// Tenant is a grouping of users in the identity service. -type Tenant struct { - // ID is a unique identifier for this tenant. - ID string `json:"id"` - - // Name is a friendlier user-facing name for this tenant. - Name string `json:"name"` - - // Description is a human-readable explanation of this Tenant's purpose. - Description string `json:"description"` - - // Enabled indicates whether or not a tenant is active. - Enabled bool `json:"enabled"` -} - -// TenantPage is a single page of Tenant results. -type TenantPage struct { - pagination.LinkedPageBase -} - -// IsEmpty determines whether or not a page of Tenants contains any results. -func (r TenantPage) IsEmpty() (bool, error) { - tenants, err := ExtractTenants(r) - return len(tenants) == 0, err -} - -// NextPageURL extracts the "next" link from the tenants_links section of the result. -func (r TenantPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"tenants_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// ExtractTenants returns a slice of Tenants contained in a single page of -// results. -func ExtractTenants(r pagination.Page) ([]Tenant, error) { - var s struct { - Tenants []Tenant `json:"tenants"` - } - err := (r.(TenantPage)).ExtractInto(&s) - return s.Tenants, err -} - -type tenantResult struct { - gophercloud.Result -} - -// Extract interprets any tenantResults as a Tenant. -func (r tenantResult) Extract() (*Tenant, error) { - var s struct { - Tenant *Tenant `json:"tenant"` - } - err := r.ExtractInto(&s) - return s.Tenant, err -} - -// GetResult is the response from a Get request. Call its Extract method to -// interpret it as a Tenant. -type GetResult struct { - tenantResult -} - -// CreateResult is the response from a Create request. Call its Extract method -// to interpret it as a Tenant. -type CreateResult struct { - tenantResult -} - -// DeleteResult is the response from a Get request. Call its ExtractErr method -// to determine if the call succeeded or failed. -type DeleteResult struct { - gophercloud.ErrResult -} - -// UpdateResult is the response from a Update request. Call its Extract method -// to interpret it as a Tenant. -type UpdateResult struct { - tenantResult -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go deleted file mode 100644 index 0f026690790..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package tenants - -import "github.com/gophercloud/gophercloud" - -func listURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("tenants") -} - -func getURL(client *gophercloud.ServiceClient, tenantID string) string { - return client.ServiceURL("tenants", tenantID) -} - -func createURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("tenants") -} - -func deleteURL(client *gophercloud.ServiceClient, tenantID string) string { - return client.ServiceURL("tenants", tenantID) -} - -func updateURL(client *gophercloud.ServiceClient, tenantID string) string { - return client.ServiceURL("tenants", tenantID) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go deleted file mode 100644 index 5375eea8726..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Package tokens provides information and interaction with the token API -resource for the OpenStack Identity service. - -For more information, see: -http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 - -Example to Create an Unscoped Token from a Password - - authOpts := gophercloud.AuthOptions{ - Username: "user", - Password: "pass" - } - - token, err := tokens.Create(identityClient, authOpts).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token from a Tenant ID and Password - - authOpts := gophercloud.AuthOptions{ - Username: "user", - Password: "password", - TenantID: "fc394f2ab2df4114bde39905f800dc57" - } - - token, err := tokens.Create(identityClient, authOpts).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token from a Tenant Name and Password - - authOpts := gophercloud.AuthOptions{ - Username: "user", - Password: "password", - TenantName: "tenantname" - } - - token, err := tokens.Create(identityClient, authOpts).ExtractToken() - if err != nil { - panic(err) - } -*/ -package tokens diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go deleted file mode 100644 index ab32368cc6e..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go +++ /dev/null @@ -1,103 +0,0 @@ -package tokens - -import "github.com/gophercloud/gophercloud" - -// PasswordCredentialsV2 represents the required options to authenticate -// with a username and password. -type PasswordCredentialsV2 struct { - Username string `json:"username" required:"true"` - Password string `json:"password" required:"true"` -} - -// TokenCredentialsV2 represents the required options to authenticate -// with a token. -type TokenCredentialsV2 struct { - ID string `json:"id,omitempty" required:"true"` -} - -// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the -// AuthOptionsBuilder interface. -type AuthOptionsV2 struct { - PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"` - - // The TenantID and TenantName fields are optional for the Identity V2 API. - // Some providers allow you to specify a TenantName instead of the TenantId. - // Some require both. Your provider's authentication policies will determine - // how these fields influence authentication. - TenantID string `json:"tenantId,omitempty"` - TenantName string `json:"tenantName,omitempty"` - - // TokenCredentials allows users to authenticate (possibly as another user) - // with an authentication token ID. - TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"` -} - -// AuthOptionsBuilder allows extensions to add additional parameters to the -// token create request. -type AuthOptionsBuilder interface { - // ToTokenCreateMap assembles the Create request body, returning an error - // if parameters are missing or inconsistent. - ToTokenV2CreateMap() (map[string]interface{}, error) -} - -// AuthOptions are the valid options for Openstack Identity v2 authentication. -// For field descriptions, see gophercloud.AuthOptions. -type AuthOptions struct { - IdentityEndpoint string `json:"-"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - TenantID string `json:"tenantId,omitempty"` - TenantName string `json:"tenantName,omitempty"` - AllowReauth bool `json:"-"` - TokenID string -} - -// ToTokenV2CreateMap builds a token request body from the given AuthOptions. -func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { - v2Opts := AuthOptionsV2{ - TenantID: opts.TenantID, - TenantName: opts.TenantName, - } - - if opts.Password != "" { - v2Opts.PasswordCredentials = &PasswordCredentialsV2{ - Username: opts.Username, - Password: opts.Password, - } - } else { - v2Opts.TokenCredentials = &TokenCredentialsV2{ - ID: opts.TokenID, - } - } - - b, err := gophercloud.BuildRequestBody(v2Opts, "auth") - if err != nil { - return nil, err - } - return b, nil -} - -// Create authenticates to the identity service and attempts to acquire a Token. -// Generally, rather than interact with this call directly, end users should -// call openstack.AuthenticatedClient(), which abstracts all of the gory details -// about navigating service catalogs and such. -func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) { - b, err := auth.ToTokenV2CreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 203}, - MoreHeaders: map[string]string{"X-Auth-Token": ""}, - }) - return -} - -// Get validates and retrieves information for user's token. -func Get(client *gophercloud.ServiceClient, token string) (r GetResult) { - _, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 203}, - }) - return -} 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 deleted file mode 100644 index ee5da37f465..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go +++ /dev/null @@ -1,174 +0,0 @@ -package tokens - -import ( - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants" -) - -// Token provides only the most basic information related to an authentication -// token. -type Token struct { - // ID provides the primary means of identifying a user to the OpenStack API. - // OpenStack defines this field as an opaque value, so do not depend on its - // content. It is safe, however, to compare for equality. - ID string - - // ExpiresAt provides a timestamp in ISO 8601 format, indicating when the - // authentication token becomes invalid. After this point in time, future - // API requests made using this authentication token will respond with - // errors. Either the caller will need to reauthenticate manually, or more - // preferably, the caller should exploit automatic re-authentication. - // See the AuthOptions structure for more details. - ExpiresAt time.Time - - // Tenant provides information about the tenant to which this token grants - // access. - Tenant tenants.Tenant -} - -// Role is a role for a user. -type Role struct { - Name string `json:"name"` -} - -// User is an OpenStack user. -type User struct { - ID string `json:"id"` - Name string `json:"name"` - UserName string `json:"username"` - Roles []Role `json:"roles"` -} - -// Endpoint represents a single API endpoint offered by a service. -// It provides the public and internal URLs, if supported, along with a region -// specifier, again if provided. -// -// The significance of the Region field will depend upon your provider. -// -// In addition, the interface offered by the service will have version -// information associated with it through the VersionId, VersionInfo, and -// VersionList fields, if provided or supported. -// -// In all cases, fields which aren't supported by the provider and service -// combined will assume a zero-value (""). -type Endpoint struct { - TenantID string `json:"tenantId"` - PublicURL string `json:"publicURL"` - InternalURL string `json:"internalURL"` - AdminURL string `json:"adminURL"` - Region string `json:"region"` - VersionID string `json:"versionId"` - VersionInfo string `json:"versionInfo"` - VersionList string `json:"versionList"` -} - -// CatalogEntry provides a type-safe interface to an Identity API V2 service -// catalog listing. -// -// Each class of service, such as cloud DNS or block storage services, will have -// a single CatalogEntry representing it. -// -// Note: when looking for the desired service, try, whenever possible, to key -// off the type field. Otherwise, you'll tie the representation of the service -// to a specific provider. -type CatalogEntry struct { - // Name will contain the provider-specified name for the service. - Name string `json:"name"` - - // Type will contain a type string if OpenStack defines a type for the - // service. Otherwise, for provider-specific services, the provider may assign - // their own type strings. - Type string `json:"type"` - - // Endpoints will let the caller iterate over all the different endpoints that - // may exist for the service. - Endpoints []Endpoint `json:"endpoints"` -} - -// ServiceCatalog provides a view into the service catalog from a previous, -// successful authentication. -type ServiceCatalog struct { - Entries []CatalogEntry -} - -// CreateResult is the response from a Create request. Use ExtractToken() to -// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a -// service catalog. -type CreateResult struct { - gophercloud.Result -} - -// GetResult is the deferred response from a Get call, which is the same with a -// Created token. Use ExtractUser() to interpret it as a User. -type GetResult struct { - CreateResult -} - -// ExtractToken returns the just-created Token from a CreateResult. -func (r CreateResult) ExtractToken() (*Token, error) { - var s struct { - Access struct { - Token struct { - Expires string `json:"expires"` - ID string `json:"id"` - Tenant tenants.Tenant `json:"tenant"` - } `json:"token"` - } `json:"access"` - } - - err := r.ExtractInto(&s) - if err != nil { - return nil, err - } - - expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires) - if err != nil { - return nil, err - } - - return &Token{ - ID: s.Access.Token.ID, - ExpiresAt: expiresTs, - Tenant: s.Access.Token.Tenant, - }, 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) { - var s struct { - Access struct { - Entries []CatalogEntry `json:"serviceCatalog"` - } `json:"access"` - } - err := r.ExtractInto(&s) - return &ServiceCatalog{Entries: s.Access.Entries}, err -} - -// ExtractUser returns the User from a GetResult. -func (r GetResult) ExtractUser() (*User, error) { - var s struct { - Access struct { - User User `json:"user"` - } `json:"access"` - } - err := r.ExtractInto(&s) - return &s.Access.User, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go deleted file mode 100644 index ee0a28f2004..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package tokens - -import "github.com/gophercloud/gophercloud" - -// CreateURL generates the URL used to create new Tokens. -func CreateURL(client *gophercloud.ServiceClient) string { - return client.ServiceURL("tokens") -} - -// GetURL generates the URL used to Validate Tokens. -func GetURL(client *gophercloud.ServiceClient, token string) string { - return client.ServiceURL("tokens", token) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go deleted file mode 100644 index 8db7724f2b0..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/doc.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -Package trusts enables management of OpenStack Identity Trusts. - -Example to Create a Token with Username, Password, and Trust ID - - var trustToken struct { - tokens.Token - trusts.TokenExt - } - - authOptions := tokens.AuthOptions{ - UserID: "username", - Password: "password", - } - - createOpts := trusts.AuthOptsExt{ - AuthOptionsBuilder: authOptions, - TrustID: "de0945a", - } - - err := tokens.Create(identityClient, createOpts).ExtractInto(&trustToken) - if err != nil { - panic(err) - } -*/ -package trusts diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go deleted file mode 100644 index 438fba61de5..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/requests.go +++ /dev/null @@ -1,39 +0,0 @@ -package trusts - -import "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" - -// AuthOptsExt extends the base Identity v3 tokens AuthOpts with a TrustID. -type AuthOptsExt struct { - tokens.AuthOptionsBuilder - - // TrustID is the ID of the trust. - TrustID string `json:"id"` -} - -// ToTokenV3CreateMap builds a create request body from the AuthOpts. -func (opts AuthOptsExt) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { - return opts.AuthOptionsBuilder.ToTokenV3CreateMap(scope) -} - -// ToTokenV3ScopeMap builds a scope from AuthOpts. -func (opts AuthOptsExt) ToTokenV3ScopeMap() (map[string]interface{}, error) { - b, err := opts.AuthOptionsBuilder.ToTokenV3ScopeMap() - if err != nil { - return nil, err - } - - if opts.TrustID != "" { - if b == nil { - b = make(map[string]interface{}) - } - b["OS-TRUST:trust"] = map[string]interface{}{ - "id": opts.TrustID, - } - } - - return b, nil -} - -func (opts AuthOptsExt) CanReauth() bool { - return opts.AuthOptionsBuilder.CanReauth() -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go deleted file mode 100644 index e6912e612c2..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts/results.go +++ /dev/null @@ -1,27 +0,0 @@ -package trusts - -// TrusteeUser represents the trusted user ID of a trust. -type TrusteeUser struct { - ID string `json:"id"` -} - -// TrustorUser represents the trusting user ID of a trust. -type TrustorUser struct { - ID string `json:"id"` -} - -// Trust represents a delegated authorization request between two -// identities. -type Trust struct { - ID string `json:"id"` - Impersonation bool `json:"impersonation"` - TrusteeUser TrusteeUser `json:"trustee_user"` - TrustorUser TrustorUser `json:"trustor_user"` - RedelegatedTrustID string `json:"redelegated_trust_id"` - RedelegationCount int `json:"redelegation_count"` -} - -// TokenExt represents an extension of the base token result. -type TokenExt struct { - Trust Trust `json:"OS-TRUST:trust"` -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go deleted file mode 100644 index 966e128f128..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go +++ /dev/null @@ -1,108 +0,0 @@ -/* -Package tokens provides information and interaction with the token API -resource for the OpenStack Identity service. - -For more information, see: -http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3 - -Example to Create a Token From a Username and Password - - authOptions := tokens.AuthOptions{ - UserID: "username", - Password: "password", - } - - token, err := tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token From a Username, Password, and Domain - - authOptions := tokens.AuthOptions{ - UserID: "username", - Password: "password", - DomainID: "default", - } - - token, err := tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - - authOptions = tokens.AuthOptions{ - UserID: "username", - Password: "password", - DomainName: "default", - } - - token, err = tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token From a Token - - authOptions := tokens.AuthOptions{ - TokenID: "token_id", - } - - token, err := tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token from a Username and Password with Project ID Scope - - scope := tokens.Scope{ - ProjectID: "0fe36e73809d46aeae6705c39077b1b3", - } - - authOptions := tokens.AuthOptions{ - Scope: &scope, - UserID: "username", - Password: "password", - } - - token, err = tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token from a Username and Password with Domain ID Scope - - scope := tokens.Scope{ - DomainID: "default", - } - - authOptions := tokens.AuthOptions{ - Scope: &scope, - UserID: "username", - Password: "password", - } - - token, err = tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - -Example to Create a Token from a Username and Password with Project Name Scope - - scope := tokens.Scope{ - ProjectName: "project_name", - DomainID: "default", - } - - authOptions := tokens.AuthOptions{ - Scope: &scope, - UserID: "username", - Password: "password", - } - - token, err = tokens.Create(identityClient, authOptions).ExtractToken() - if err != nil { - panic(err) - } - -*/ -package tokens 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 deleted file mode 100644 index e4d766b2327..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go +++ /dev/null @@ -1,162 +0,0 @@ -package tokens - -import "github.com/gophercloud/gophercloud" - -// Scope allows a created token to be limited to a specific domain or project. -type Scope struct { - ProjectID string - ProjectName string - DomainID string - DomainName string -} - -// AuthOptionsBuilder provides the ability for extensions to add additional -// parameters to AuthOptions. Extensions must satisfy all required methods. -type AuthOptionsBuilder interface { - // ToTokenV3CreateMap assembles the Create request body, returning an error - // if parameters are missing or inconsistent. - ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) - ToTokenV3ScopeMap() (map[string]interface{}, error) - CanReauth() bool -} - -// AuthOptions represents options for authenticating a user. -type AuthOptions struct { - // IdentityEndpoint specifies the HTTP endpoint that is required to work with - // the Identity API of the appropriate version. While it's ultimately needed - // by all of the identity services, it will often be populated by a - // provider-level function. - IdentityEndpoint string `json:"-"` - - // Username is required if using Identity V2 API. Consult with your provider's - // control panel to discover your account's username. In Identity V3, either - // UserID or a combination of Username and DomainID or DomainName are needed. - Username string `json:"username,omitempty"` - UserID string `json:"id,omitempty"` - - Password string `json:"password,omitempty"` - - // At most one of DomainID and DomainName must be provided if using Username - // with Identity V3. Otherwise, either are optional. - DomainID string `json:"-"` - DomainName string `json:"name,omitempty"` - - // AllowReauth should be set to true if you grant permission for Gophercloud - // to cache your credentials in memory, and to allow Gophercloud to attempt - // to re-authenticate automatically if/when your token expires. If you set - // it to false, it will not cache these settings, but re-authentication will - // not be possible. This setting defaults to false. - AllowReauth bool `json:"-"` - - // TokenID allows users to authenticate (possibly as another user) with an - // 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, - ApplicationCredentialID: opts.ApplicationCredentialID, - ApplicationCredentialName: opts.ApplicationCredentialName, - ApplicationCredentialSecret: opts.ApplicationCredentialSecret, - } - - return gophercloudAuthOpts.ToTokenV3CreateMap(scope) -} - -// ToTokenV3CreateMap builds a scope request body from AuthOptions. -func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - scope := gophercloud.AuthScope(opts.Scope) - - gophercloudAuthOpts := gophercloud.AuthOptions{ - Scope: &scope, - DomainID: opts.DomainID, - DomainName: opts.DomainName, - } - - return gophercloudAuthOpts.ToTokenV3ScopeMap() -} - -func (opts *AuthOptions) CanReauth() bool { - return opts.AllowReauth -} - -func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string { - return map[string]string{ - "X-Subject-Token": subjectToken, - } -} - -// Create authenticates and either generates a new token, or changes the Scope -// of an existing token. -func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) { - scope, err := opts.ToTokenV3ScopeMap() - if err != nil { - r.Err = err - return - } - - b, err := opts.ToTokenV3CreateMap(scope) - if err != nil { - r.Err = err - return - } - - resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{ - MoreHeaders: map[string]string{"X-Auth-Token": ""}, - }) - r.Err = err - if resp != nil { - r.Header = resp.Header - } - return -} - -// Get validates and retrieves information about another token. -func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { - resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(c, token), - OkCodes: []int{200, 203}, - }) - if resp != nil { - 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.Head(tokenURL(c), &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(c, token), - OkCodes: []int{200, 204, 404}, - }) - if err != nil { - return false, err - } - - return resp.StatusCode == 200 || resp.StatusCode == 204, nil -} - -// Revoke immediately makes specified token invalid. -func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) { - _, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(c, token), - }) - return -} 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 deleted file mode 100644 index 6f26c96bcdc..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go +++ /dev/null @@ -1,178 +0,0 @@ -package tokens - -import ( - "time" - - "github.com/gophercloud/gophercloud" -) - -// Endpoint represents a single API endpoint offered by a service. -// It matches either a public, internal or admin URL. -// If supported, it contains a region specifier, again if provided. -// The significance of the Region field will depend upon your provider. -type Endpoint struct { - ID string `json:"id"` - Region string `json:"region"` - RegionID string `json:"region_id"` - Interface string `json:"interface"` - URL string `json:"url"` -} - -// CatalogEntry provides a type-safe interface to an Identity API V3 service -// catalog listing. Each class of service, such as cloud DNS or block storage -// services, could have multiple CatalogEntry representing it (one by interface -// type, e.g public, admin or internal). -// -// Note: when looking for the desired service, try, whenever possible, to key -// off the type field. Otherwise, you'll tie the representation of the service -// to a specific provider. -type CatalogEntry struct { - // Service ID - ID string `json:"id"` - - // Name will contain the provider-specified name for the service. - Name string `json:"name"` - - // Type will contain a type string if OpenStack defines a type for the - // service. Otherwise, for provider-specific services, the provider may - // assign their own type strings. - Type string `json:"type"` - - // Endpoints will let the caller iterate over all the different endpoints that - // may exist for the service. - Endpoints []Endpoint `json:"endpoints"` -} - -// ServiceCatalog provides a view into the service catalog from a previous, -// successful authentication. -type ServiceCatalog struct { - Entries []CatalogEntry `json:"catalog"` -} - -// Domain provides information about the domain to which this token grants -// access. -type Domain struct { - ID string `json:"id"` - Name string `json:"name"` -} - -// User represents a user resource that exists in the Identity Service. -type User struct { - Domain Domain `json:"domain"` - ID string `json:"id"` - Name string `json:"name"` -} - -// Role provides information about roles to which User is authorized. -type Role struct { - ID string `json:"id"` - Name string `json:"name"` -} - -// Project provides information about project to which User is authorized. -type Project struct { - Domain Domain `json:"domain"` - ID string `json:"id"` - Name string `json:"name"` -} - -// commonResult is the response from a request. A commonResult has various -// methods which can be used to extract different details about the result. -type commonResult struct { - gophercloud.Result -} - -// Extract is a shortcut for ExtractToken. -// This function is deprecated and still present for backward compatibility. -func (r commonResult) Extract() (*Token, error) { - return r.ExtractToken() -} - -// ExtractToken interprets a commonResult as a Token. -func (r commonResult) ExtractToken() (*Token, error) { - var s Token - err := r.ExtractInto(&s) - if err != nil { - return nil, err - } - - // Parse the token itself from the stored headers. - s.ID = r.Header.Get("X-Subject-Token") - - 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) { - var s ServiceCatalog - err := r.ExtractInto(&s) - return &s, err -} - -// ExtractUser returns the User that is the owner of the Token. -func (r commonResult) ExtractUser() (*User, error) { - var s struct { - User *User `json:"user"` - } - err := r.ExtractInto(&s) - return s.User, err -} - -// ExtractRoles returns Roles to which User is authorized. -func (r commonResult) ExtractRoles() ([]Role, error) { - var s struct { - Roles []Role `json:"roles"` - } - err := r.ExtractInto(&s) - return s.Roles, err -} - -// ExtractProject returns Project to which User is authorized. -func (r commonResult) ExtractProject() (*Project, error) { - var s struct { - Project *Project `json:"project"` - } - err := r.ExtractInto(&s) - return s.Project, err -} - -// CreateResult is the response from a Create request. Use ExtractToken() -// to interpret it as a Token, or ExtractServiceCatalog() to interpret it -// as a service catalog. -type CreateResult struct { - commonResult -} - -// GetResult is the response from a Get request. Use ExtractToken() -// to interpret it as a Token, or ExtractServiceCatalog() to interpret it -// as a service catalog. -type GetResult struct { - commonResult -} - -// RevokeResult is response from a Revoke request. -type RevokeResult struct { - commonResult -} - -// Token is a string that grants a user access to a controlled set of services -// in an OpenStack provider. Each Token is valid for a set length of time. -type Token struct { - // ID is the issued token. - ID string `json:"id"` - - // ExpiresAt is the timestamp at which this token will no longer be accepted. - ExpiresAt time.Time `json:"expires_at"` -} - -func (r commonResult) ExtractInto(v interface{}) error { - return r.ExtractIntoStructPtr(v, "token") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go deleted file mode 100644 index 2f864a31c8b..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go +++ /dev/null @@ -1,7 +0,0 @@ -package tokens - -import "github.com/gophercloud/gophercloud" - -func tokenURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("auth", "tokens") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go deleted file mode 100644 index 0c43689bb80..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/delegate.go +++ /dev/null @@ -1,41 +0,0 @@ -package extensions - -import ( - "github.com/gophercloud/gophercloud" - common "github.com/gophercloud/gophercloud/openstack/common/extensions" - "github.com/gophercloud/gophercloud/pagination" -) - -// Extension is a single OpenStack extension. -type Extension struct { - common.Extension -} - -// GetResult wraps a GetResult from common. -type GetResult struct { - common.GetResult -} - -// ExtractExtensions interprets a Page as a slice of Extensions. -func ExtractExtensions(page pagination.Page) ([]Extension, error) { - inner, err := common.ExtractExtensions(page) - if err != nil { - return nil, err - } - outer := make([]Extension, len(inner)) - for index, ext := range inner { - outer[index] = Extension{ext} - } - return outer, nil -} - -// Get retrieves information for a specific extension using its alias. -func Get(c *gophercloud.ServiceClient, alias string) GetResult { - return GetResult{common.Get(c, alias)} -} - -// List returns a Pager which allows you to iterate over the full collection of extensions. -// It does not accept query parameters. -func List(c *gophercloud.ServiceClient) pagination.Pager { - return common.List(c) -} 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 deleted file mode 100644 index eda010cb0c8..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Package external provides information and interaction with the external -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 - } - - var allNetworks []NetworkWithExternalExt - - 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 to Create a Network with External Information - - iTrue := true - networkCreateOpts := networks.CreateOpts{ - Name: "private", - AdminStateUp: &iTrue, - } - - createOpts := external.CreateOptsExt{ - networkCreateOpts, - &iTrue, - } - - network, err := networks.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } -*/ -package external 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 deleted file mode 100644 index ced5efed8d9..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go +++ /dev/null @@ -1,84 +0,0 @@ -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. -type CreateOptsExt struct { - networks.CreateOptsBuilder - External *bool `json:"router:external,omitempty"` -} - -// ToNetworkCreateMap adds the router:external options 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.External == nil { - return base, nil - } - - networkMap := base["network"].(map[string]interface{}) - networkMap["router:external"] = opts.External - - return base, nil -} - -// UpdateOptsExt is the structure used when updating existing external network -// resources. It embeds networks.UpdateOpts and so inherits all of its required -// and optional fields, with the addition of the External field. -type UpdateOptsExt struct { - networks.UpdateOptsBuilder - External *bool `json:"router:external,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.External == nil { - return base, nil - } - - networkMap := base["network"].(map[string]interface{}) - networkMap["router:external"] = opts.External - - return base, nil -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go deleted file mode 100644 index 7cbbffdcf8a..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go +++ /dev/null @@ -1,8 +0,0 @@ -package external - -// NetworkExternalExt represents a decorated form of a Network with based on the -// "external-net" extension. -type NetworkExternalExt struct { - // Specifies whether the network is an external network or not. - External bool `json:"router:external"` -} 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 deleted file mode 100644 index a71a3ec88a9..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -package floatingips enables management and retrieval of Floating IPs from the -OpenStack Networking service. - -Example to List Floating IPs - - listOpts := floatingips.ListOpts{ - FloatingNetworkID: "a6917946-38ab-4ffd-a55a-26c0980ce5ee", - } - - allPages, err := floatingips.List(networkClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allFIPs, err := floatingips.ExtractFloatingIPs(allPages) - if err != nil { - panic(err) - } - - for _, fip := range allFIPs { - fmt.Printf("%+v\n", fip) - } - -Example to Create a Floating IP - - createOpts := floatingips.CreateOpts{ - FloatingNetworkID: "a6917946-38ab-4ffd-a55a-26c0980ce5ee", - } - - fip, err := floatingips.Create(networkingClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Floating IP - - fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" - portID := "76d0a61b-b8e5-490c-9892-4cf674f2bec8" - - updateOpts := floatingips.UpdateOpts{ - PortID: &portID, - } - - fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Disassociate a Floating IP with a Port - - fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" - - updateOpts := floatingips.UpdateOpts{ - PortID: new(string), - } - - fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Floating IP - - fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" - err := floatingips.Delete(networkClient, fipID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package floatingips 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 deleted file mode 100644 index 0c0db64d8d3..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go +++ /dev/null @@ -1,182 +0,0 @@ -package floatingips - -import ( - "github.com/gophercloud/gophercloud" - "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 -// 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"` - Description string `q:"description"` - FloatingNetworkID string `q:"floating_network_id"` - PortID string `q:"port_id"` - FixedIP string `q:"fixed_ip_address"` - FloatingIP string `q:"floating_ip_address"` - 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"` - 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 ListOptsBuilder) pagination.Pager { - url := rootURL(c) - if opts != nil { - query, err := opts.ToFloatingIPListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - return FloatingIPPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToFloatingIPCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains all the values needed to create a new floating IP -// 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"` - FixedIP string `json:"fixed_ip_address,omitempty"` - SubnetID string `json:"subnet_id,omitempty"` - TenantID string `json:"tenant_id,omitempty"` - ProjectID string `json:"project_id,omitempty"` -} - -// ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder -// interface -func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "floatingip") -} - -// Create accepts a CreateOpts struct and uses the values provided to create a -// new floating IP resource. You can create floating IPs on external networks -// only. If you provide a FloatingNetworkID which refers to a network that is -// not external (i.e. its `router:external' attribute is False), the operation -// will fail and return a 400 error. -// -// If you do not specify a FloatingIP address value, the operation will -// automatically allocate an available address for the new resource. If you do -// choose to specify one, it must fall within the subnet range for the external -// network - otherwise the operation returns a 400 error. If the FloatingIP -// address is already in use, the operation returns a 409 error code. -// -// You can associate the new resource with an internal port by using the PortID -// field. If you specify a PortID that is not valid, the operation will fail and -// return 404 error code. -// -// You must also configure an IP address for the port associated with the PortID -// you have provided - this is what the FixedIP refers to: an IP fixed to a -// port. Because a port might be associated with multiple IP addresses, you can -// use the FixedIP field to associate a particular IP address rather than have -// the API assume for you. If you specify an IP address that is not valid, the -// operation will fail and return a 400 error code. If the PortID and FixedIP -// are already associated with another resource, the operation will fail and -// returns a 409 error code. -func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToFloatingIPCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) - return -} - -// Get retrieves a particular floating IP resource 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 { - ToFloatingIPUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contains the values used when updating a floating IP resource. The -// only value that can be updated is which internal port the floating IP is -// 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 { - 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) { - 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 -// "update" a floating IP is to associate it with a new internal port, or -// disassociated it from all ports. See UpdateOpts for instructions of how to -// do this. -func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToFloatingIPUpdateMap() - 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 floating IP resource. Please -// ensure this is what you want - you can also disassociate the IP from existing -// internal ports. -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/networking/v2/extensions/layer3/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go deleted file mode 100644 index a9709ccec3f..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go +++ /dev/null @@ -1,131 +0,0 @@ -package floatingips - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// FloatingIP represents a floating IP resource. A floating IP is an external -// IP address that is mapped to an internal port and, optionally, a specific -// IP address on a private network. In other words, it enables access to an -// instance on a private network from an external network. For this reason, -// floating IPs can only be defined on networks where the `router:external' -// attribute (provided by the external network extension) is set to True. -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"` - - // FloatingIP is the address of the floating IP on the external network. - FloatingIP string `json:"floating_ip_address"` - - // PortID is the UUID of the port on an internal network that is associated - // with the floating IP. - PortID string `json:"port_id"` - - // FixedIP is the specific IP address of the internal port which should be - // associated with the floating IP. - FixedIP string `json:"fixed_ip_address"` - - // TenantID is the project owner of the floating IP. Only admin users can - // specify a project identifier other than its own. - TenantID string `json:"tenant_id"` - - // ProjectID is the project owner of the floating IP. - ProjectID string `json:"project_id"` - - // Status is the condition of the API resource. - Status string `json:"status"` - - // 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 { - gophercloud.Result -} - -// Extract will extract a FloatingIP resource from a result. -func (r commonResult) Extract() (*FloatingIP, error) { - var s FloatingIP - err := r.ExtractInto(&s) - 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 -// method to interpret it as a FloatingIP. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret it as a FloatingIP. -type GetResult struct { - commonResult -} - -// UpdateResult represents the result of an update operation. Call its Extract -// method to interpret it as a FloatingIP. -type UpdateResult struct { - commonResult -} - -// DeleteResult represents the result of an update operation. Call its -// ExtractErr method to determine if the request succeeded or failed. -type DeleteResult struct { - gophercloud.ErrResult -} - -// FloatingIPPage is the page returned by a pager when traversing over a -// collection of floating IPs. -type FloatingIPPage struct { - pagination.LinkedPageBase -} - -// NextPageURL is invoked when a paginated collection of floating IPs has -// reached the end of a page and the pager seeks to traverse over a new one. -// In order to do this, it needs to construct the next page's URL. -func (r FloatingIPPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"floatingips_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// IsEmpty checks whether a FloatingIPPage struct is empty. -func (r FloatingIPPage) IsEmpty() (bool, error) { - is, err := ExtractFloatingIPs(r) - return len(is) == 0, err -} - -// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage -// struct, and extracts the elements into a slice of FloatingIP structs. In -// other words, a generic collection is mapped into a relevant slice. -func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { - var s struct { - FloatingIPs []FloatingIP `json:"floatingips"` - } - 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/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go deleted file mode 100644 index 1318a184caa..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package floatingips - -import "github.com/gophercloud/gophercloud" - -const resourcePath = "floatingips" - -func rootURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func resourceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go deleted file mode 100644 index 6ede7f5e171..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go +++ /dev/null @@ -1,108 +0,0 @@ -/* -Package routers enables management and retrieval of Routers from the OpenStack -Networking service. - -Example to List Routers - - listOpts := routers.ListOpts{} - allPages, err := routers.List(networkClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allRouters, err := routers.ExtractRouters(allPages) - if err != nil { - panic(err) - } - - for _, router := range allRoutes { - fmt.Printf("%+v\n", router) - } - -Example to Create a Router - - iTrue := true - gwi := routers.GatewayInfo{ - NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", - } - - createOpts := routers.CreateOpts{ - Name: "router_1", - AdminStateUp: &iTrue, - GatewayInfo: &gwi, - } - - router, err := routers.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Router - - routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - - routes := []routers.Route{{ - DestinationCIDR: "40.0.1.0/24", - NextHop: "10.1.0.10", - }} - - updateOpts := routers.UpdateOpts{ - Name: "new_name", - Routes: routes, - } - - router, err := routers.Update(networkClient, routerID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Remove all Routes from a Router - - routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - - routes := []routers.Route{} - - updateOpts := routers.UpdateOpts{ - Routes: routes, - } - - router, err := routers.Update(networkClient, routerID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Router - - routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - err := routers.Delete(networkClient, routerID).ExtractErr() - if err != nil { - panic(err) - } - -Example to Add an Interface to a Router - - routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - - intOpts := routers.AddInterfaceOpts{ - SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1", - } - - interface, err := routers.AddInterface(networkClient, routerID, intOpts).Extract() - if err != nil { - panic(err) - } - -Example to Remove an Interface from a Router - - routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - - intOpts := routers.RemoveInterfaceOpts{ - SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1", - } - - interface, err := routers.RemoveInterface(networkClient, routerID, intOpts).Extract() - if err != nil { - panic(err) - } -*/ -package routers 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 deleted file mode 100644 index cf499f9873d..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go +++ /dev/null @@ -1,233 +0,0 @@ -package routers - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOpts allows the filtering and sorting of paginated collections through -// the API. Filtering is achieved by passing in struct field values that map to -// the floating IP attributes you want to see returned. SortKey allows you to -// sort by a particular network attribute. SortDir sets the direction, and is -// either `asc' or `desc'. Marker and Limit are used for pagination. -type ListOpts struct { - ID string `q:"id"` - Name string `q:"name"` - Description string `q:"description"` - AdminStateUp *bool `q:"admin_state_up"` - Distributed *bool `q:"distributed"` - Status string `q:"status"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` - Tags string `q:"tags"` - TagsAny string `q:"tags-any"` - NotTags string `q:"not-tags"` - NotTagsAny string `q:"not-tags-any"` -} - -// List returns a Pager which allows you to iterate over a collection of -// routers. It accepts a ListOpts struct, which allows you to filter and sort -// the returned collection for greater efficiency. -// -// Default policy settings return only those routers that are owned by the -// tenant who submits the request, unless an admin user submits the request. -func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { - q, err := gophercloud.BuildQueryString(&opts) - if err != nil { - return pagination.Pager{Err: err} - } - u := rootURL(c) + q.String() - return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { - return RouterPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToRouterCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains all the values needed to create a new router. There are -// 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"` - ProjectID string `json:"project_id,omitempty"` - GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` - AvailabilityZoneHints []string `json:"availability_zone_hints,omitempty"` -} - -// ToRouterCreateMap builds a create request body from CreateOpts. -func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "router") -} - -// Create accepts a CreateOpts struct and uses the values to create a new -// logical router. When it is created, the router does not have an internal -// interface - it is not associated to any subnet. -// -// You can optionally specify an external gateway for a router using the -// GatewayInfo struct. The external gateway for the router must be plugged into -// an external network (it is external if its `router:external' field is set to -// true). -func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToRouterCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) - return -} - -// Get retrieves a particular router 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 { - ToRouterUpdateMap() (map[string]interface{}, error) -} - -// 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"` - Routes []Route `json:"routes"` -} - -// ToRouterUpdateMap builds an update body based on UpdateOpts. -func (opts UpdateOpts) ToRouterUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "router") -} - -// Update allows routers to be updated. You can update the name, administrative -// state, and the external gateway. For more information about how to set the -// external gateway for a router, see Create. This operation does not enable -// the update of router interfaces. To do this, use the AddInterface and -// RemoveInterface functions. -func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToRouterUpdateMap() - 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 router based on its unique ID. -func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(resourceURL(c, id), nil) - return -} - -// AddInterfaceOptsBuilder allows extensions to add additional parameters to -// the AddInterface request. -type AddInterfaceOptsBuilder interface { - ToRouterAddInterfaceMap() (map[string]interface{}, error) -} - -// AddInterfaceOpts represents the options for adding an interface to a router. -type AddInterfaceOpts struct { - SubnetID string `json:"subnet_id,omitempty" xor:"PortID"` - PortID string `json:"port_id,omitempty" xor:"SubnetID"` -} - -// ToRouterAddInterfaceMap builds a request body from AddInterfaceOpts. -func (opts AddInterfaceOpts) ToRouterAddInterfaceMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "") -} - -// AddInterface attaches a subnet to an internal router interface. You must -// specify either a SubnetID or PortID in the request body. If you specify both, -// the operation will fail and an error will be returned. -// -// If you specify a SubnetID, the gateway IP address for that particular subnet -// is used to create the router interface. Alternatively, if you specify a -// PortID, the IP address associated with the port is used to create the router -// interface. -// -// If you reference a port that is associated with multiple IP addresses, or -// if the port is associated with zero IP addresses, the operation will fail and -// a 400 Bad Request error will be returned. -// -// If you reference a port already in use, the operation will fail and a 409 -// Conflict error will be returned. -// -// The PortID that is returned after using Extract() on the result of this -// operation can either be the same PortID passed in or, on the other hand, the -// identifier of a new port created by this operation. After the operation -// completes, the device ID of the port is set to the router ID, and the -// device owner attribute is set to `network:router_interface'. -func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOptsBuilder) (r InterfaceResult) { - b, err := opts.ToRouterAddInterfaceMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// RemoveInterfaceOptsBuilder allows extensions to add additional parameters to -// the RemoveInterface request. -type RemoveInterfaceOptsBuilder interface { - ToRouterRemoveInterfaceMap() (map[string]interface{}, error) -} - -// RemoveInterfaceOpts represents options for removing an interface from -// a router. -type RemoveInterfaceOpts struct { - SubnetID string `json:"subnet_id,omitempty" or:"PortID"` - PortID string `json:"port_id,omitempty" or:"SubnetID"` -} - -// ToRouterRemoveInterfaceMap builds a request body based on -// RemoveInterfaceOpts. -func (opts RemoveInterfaceOpts) ToRouterRemoveInterfaceMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "") -} - -// RemoveInterface removes an internal router interface, which detaches a -// subnet from the router. You must specify either a SubnetID or PortID, since -// these values are used to identify the router interface to remove. -// -// Unlike AddInterface, you can also specify both a SubnetID and PortID. If you -// choose to specify both, the subnet ID must correspond to the subnet ID of -// the first IP address on the port specified by the port ID. Otherwise, the -// operation will fail and return a 409 Conflict error. -// -// If the router, subnet or port which are referenced do not exist or are not -// visible to you, the operation will fail and a 404 Not Found error will be -// returned. After this operation completes, the port connecting the router -// with the subnet is removed from the subnet for the network. -func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfaceOptsBuilder) (r InterfaceResult) { - b, err := opts.ToRouterRemoveInterfaceMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} 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 deleted file mode 100644 index 857e1947e1f..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go +++ /dev/null @@ -1,181 +0,0 @@ -package routers - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// GatewayInfo represents the information of an external gateway for any -// particular network router. -type GatewayInfo struct { - NetworkID string `json:"network_id,omitempty"` - EnableSNAT *bool `json:"enable_snat,omitempty"` - ExternalFixedIPs []ExternalFixedIP `json:"external_fixed_ips,omitempty"` -} - -// ExternalFixedIP is the IP address and subnet ID of the external gateway of a -// router. -type ExternalFixedIP struct { - IPAddress string `json:"ip_address,omitempty"` - SubnetID string `json:"subnet_id"` -} - -// Route is a possible route in a router. -type Route struct { - NextHop string `json:"nexthop"` - DestinationCIDR string `json:"destination"` -} - -// Router represents a Neutron router. A router is a logical entity that -// forwards packets across internal subnets and NATs (network address -// translation) them on external networks through an appropriate gateway. -// -// A router has an interface for each subnet with which it is associated. By -// default, the IP address of such interface is the subnet's gateway IP. Also, -// whenever a router is associated with a subnet, a port for that router -// interface is added to the subnet's network. -type Router struct { - // Status indicates whether or not a router is currently operational. - Status string `json:"status"` - - // GateayInfo provides information on external gateway for the router. - GatewayInfo GatewayInfo `json:"external_gateway_info"` - - // AdminStateUp is the administrative state of the router. - AdminStateUp bool `json:"admin_state_up"` - - // Distributed is whether router is disitrubted or not. - Distributed bool `json:"distributed"` - - // Name is the human readable name for the router. It does not have to be - // 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"` - - // TenantID is the project owner of the router. Only admin users can - // specify a project identifier other than its own. - TenantID string `json:"tenant_id"` - - // ProjectID is the project owner of the router. - ProjectID string `json:"project_id"` - - // Routes are a collection of static routes that the router will host. - Routes []Route `json:"routes"` - - // 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 -// collection of routers. -type RouterPage struct { - pagination.LinkedPageBase -} - -// NextPageURL is invoked when a paginated collection of routers has reached -// the end of a page and the pager seeks to traverse over a new one. In order -// to do this, it needs to construct the next page's URL. -func (r RouterPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"routers_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// IsEmpty checks whether a RouterPage struct is empty. -func (r RouterPage) IsEmpty() (bool, error) { - is, err := ExtractRouters(r) - return len(is) == 0, err -} - -// ExtractRouters accepts a Page struct, specifically a RouterPage struct, -// and extracts the elements into a slice of Router structs. In other words, -// a generic collection is mapped into a relevant slice. -func ExtractRouters(r pagination.Page) ([]Router, error) { - var s struct { - Routers []Router `json:"routers"` - } - err := (r.(RouterPage)).ExtractInto(&s) - return s.Routers, err -} - -type commonResult struct { - gophercloud.Result -} - -// Extract is a function that accepts a result and extracts a router. -func (r commonResult) Extract() (*Router, error) { - var s struct { - Router *Router `json:"router"` - } - err := r.ExtractInto(&s) - return s.Router, err -} - -// CreateResult represents the result of a create operation. Call its Extract -// method to interpret it as a Router. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret it as a Router. -type GetResult struct { - commonResult -} - -// UpdateResult represents the result of an update operation. Call its Extract -// method to interpret it as a Router. -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 -} - -// InterfaceInfo represents information about a particular router interface. As -// mentioned above, in order for a router to forward to a subnet, it needs an -// interface. -type InterfaceInfo struct { - // SubnetID is the ID of the subnet which this interface is associated with. - SubnetID string `json:"subnet_id"` - - // PortID is the ID of the port that is a part of the subnet. - PortID string `json:"port_id"` - - // ID is the UUID of the interface. - ID string `json:"id"` - - // TenantID is the owner of the interface. - TenantID string `json:"tenant_id"` -} - -// InterfaceResult represents the result of interface operations, such as -// AddInterface() and RemoveInterface(). Call its Extract method to interpret -// the result as a InterfaceInfo. -type InterfaceResult struct { - gophercloud.Result -} - -// Extract is a function that accepts a result and extracts an information struct. -func (r InterfaceResult) Extract() (*InterfaceInfo, error) { - var s InterfaceInfo - err := r.ExtractInto(&s) - return &s, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go deleted file mode 100644 index f9e9da32117..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go +++ /dev/null @@ -1,21 +0,0 @@ -package routers - -import "github.com/gophercloud/gophercloud" - -const resourcePath = "routers" - -func rootURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func resourceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id) -} - -func addInterfaceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id, "add_router_interface") -} - -func removeInterfaceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id, "remove_router_interface") -} 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 deleted file mode 100644 index 813579905c2..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -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 deleted file mode 100644 index 9d2b3a0d351..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go +++ /dev/null @@ -1,376 +0,0 @@ -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 deleted file mode 100644 index 5153b1b90c8..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go +++ /dev/null @@ -1,245 +0,0 @@ -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/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go deleted file mode 100644 index ecb607a8e89..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go +++ /dev/null @@ -1,25 +0,0 @@ -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/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go deleted file mode 100644 index 108cdb03d8b..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -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 - updateOpts := listeners.UpdateOpts{ - ConnLimit: &i1001, - } - - 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) - } -*/ -package listeners 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 deleted file mode 100644 index f2966b6c44a..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go +++ /dev/null @@ -1,212 +0,0 @@ -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" - ProtocolHTTP Protocol = "HTTP" - ProtocolHTTPS Protocol = "HTTPS" - ProtocolTerminatedHTTPS Protocol = "TERMINATED_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"` - TenantID string `q:"tenant_id"` - 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"` -} - -// 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 -// 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.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"` - - // TenantID is only required if the caller has an admin role and wants - // to create a pool for another project. - TenantID string `json:"tenant_id,omitempty"` - - // 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"` -} - -// 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 tenants by -// specifying a TenantID 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"` -} - -// 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 -} 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 deleted file mode 100644 index ae105793225..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go +++ /dev/null @@ -1,141 +0,0 @@ -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" -) - -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. - TenantID string `json:"tenant_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. - // 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 -// 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 -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go deleted file mode 100644 index 02fb1eb39ec..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go +++ /dev/null @@ -1,16 +0,0 @@ -package listeners - -import "github.com/gophercloud/gophercloud" - -const ( - rootPath = "lbaas" - resourcePath = "listeners" -) - -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/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go deleted file mode 100644 index c6d53a7b052..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -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", - } - - lb, err := loadbalancers.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Load Balancer - - lbID := "d67d56a6-4a86-4688-a282-f46444705c64" - - i1001 := 1001 - updateOpts := loadbalancers.UpdateOpts{ - Name: "new-name", - } - - lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Load Balancers - - lbID := "d67d56a6-4a86-4688-a282-f46444705c64" - err := loadbalancers.Delete(networkClient, lbID).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) - } -*/ -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 deleted file mode 100644 index f5b14134821..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go +++ /dev/null @@ -1,204 +0,0 @@ -package loadbalancers - -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 { - 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"` - TenantID string `q:"tenant_id"` - 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"` - 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"` -} - -// 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 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.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"` - - // 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). - VipSubnetID string `json:"vip_subnet_id" required:"true"` - - // TenantID is the UUID of the project who owns the Loadbalancer. - // 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 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"` -} - -// 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"` -} - -// 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 -} - -// Delete will permanently delete a particular LoadBalancer based on its -// unique ID. -func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(resourceURL(c, id), nil) - return -} - -// CascadingDelete is like `Delete`, but will also delete any of the load balancer's -// children (listener, monitor, etc). -// NOTE: This function will only work with Octavia load balancers; Neutron does not -// support this. -func CascadingDelete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - if c.Type != "load-balancer" { - r.Err = fmt.Errorf("error prior to running cascade delete: only Octavia LBs supported") - return - } - u := fmt.Sprintf("%s?cascade=true", resourceURL(c, id)) - _, r.Err = c.Delete(u, 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 -} 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 deleted file mode 100644 index 7f423c933dd..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go +++ /dev/null @@ -1,186 +0,0 @@ -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" -) - -// 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. - TenantID string `json:"tenant_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 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"` -} - -// 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 -} 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 deleted file mode 100644 index 2d2a99b7797..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go +++ /dev/null @@ -1,26 +0,0 @@ -package loadbalancers - -import "github.com/gophercloud/gophercloud" - -const ( - rootPath = "lbaas" - resourcePath = "loadbalancers" - statusPath = "statuses" - 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 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/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go deleted file mode 100644 index 6ed8c8fb5ff..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -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/networking/v2/extensions/lbaas_v2/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go deleted file mode 100644 index f728f5a8237..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go +++ /dev/null @@ -1,257 +0,0 @@ -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/networking/v2/extensions/lbaas_v2/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go deleted file mode 100644 index a78f7aeb0ff..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go +++ /dev/null @@ -1,153 +0,0 @@ -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"` - - // TenantID is the owner of the Monitor. - TenantID string `json:"tenant_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"` -} - -// 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/networking/v2/extensions/lbaas_v2/monitors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go deleted file mode 100644 index a222e52a93d..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go +++ /dev/null @@ -1,16 +0,0 @@ -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/networking/v2/extensions/lbaas_v2/pools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go deleted file mode 100644 index 06971486806..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -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.ExtractPools(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) - } -*/ -package pools 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 deleted file mode 100644 index f427ae7bf57..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go +++ /dev/null @@ -1,356 +0,0 @@ -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"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - AdminStateUp *bool `q:"admin_state_up"` - Name string `q:"name"` - ID string `q:"id"` - LoadbalancerID string `q:"loadbalancer_id"` - ListenerID string `q:"listener_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 -// 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.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" - 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, 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"` - - // TenantID is the UUID of the project who owns the Pool. - // 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 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"` - TenantID string `q:"tenant_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 -// tenant 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"` - - // TenantID is the UUID of the project who owns the Member. - // 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 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 -} - -// 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/networking/v2/extensions/lbaas_v2/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go deleted file mode 100644 index fba0d3a8782..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go +++ /dev/null @@ -1,291 +0,0 @@ -package pools - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_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. - TenantID string `json:"tenant_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. - // 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 -// 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. - TenantID string `json:"tenant_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 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 -// 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 -} - -// 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 -} - -// 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/networking/v2/extensions/lbaas_v2/pools/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go deleted file mode 100644 index bceca67707f..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go +++ /dev/null @@ -1,25 +0,0 @@ -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/networking/v2/extensions/security/groups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go deleted file mode 100644 index 7d8bbcaacba..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Package groups provides information and interaction with Security Groups -for the OpenStack Networking service. - -Example to List Security Groups - - listOpts := groups.ListOpts{ - TenantID: "966b3c7d36a24facaf20b7e458bf2192", - } - - allPages, err := groups.List(networkClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allGroups, err := groups.ExtractGroups(allPages) - if err != nil { - panic(err) - } - - for _, group := range allGroups { - fmt.Printf("%+v\n", group) - } - -Example to Create a Security Group - - createOpts := groups.CreateOpts{ - Name: "group_name", - Description: "A Security Group", - } - - group, err := groups.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Security Group - - groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" - - updateOpts := groups.UpdateOpts{ - Name: "new_name", - } - - group, err := groups.Update(networkClient, groupID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Security Group - - groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" - err := groups.Delete(networkClient, groupID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package groups 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 deleted file mode 100644 index a22cd306e8a..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go +++ /dev/null @@ -1,166 +0,0 @@ -package groups - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// 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 group attributes you want to see returned. SortKey allows you to -// sort by a particular network attribute. SortDir sets the direction, and is -// either `asc' or `desc'. Marker and Limit are used for pagination. -type ListOpts struct { - ID string `q:"id"` - Name string `q:"name"` - Description string `q:"description"` - 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 -// security groups. It accepts a ListOpts struct, which allows you to filter -// and sort the returned collection for greater efficiency. -func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { - q, err := gophercloud.BuildQueryString(&opts) - if err != nil { - return pagination.Pager{Err: err} - } - u := rootURL(c) + q.String() - return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { - return SecGroupPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToSecGroupCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains all the values needed to create a new security group. -type CreateOpts struct { - // Human-readable name for the Security Group. Does not have to be unique. - Name string `json:"name" required:"true"` - - // TenantID is the UUID of the project who owns the Group. - // Only administrative users can specify a tenant UUID other than their own. - TenantID string `json:"tenant_id,omitempty"` - - // ProjectID is the UUID of the project who owns the Group. - // Only administrative users can specify a tenant UUID other than their own. - ProjectID string `json:"project_id,omitempty"` - - // Describes the security group. - Description string `json:"description,omitempty"` -} - -// ToSecGroupCreateMap builds a request body from CreateOpts. -func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "security_group") -} - -// Create is an operation which provisions a new security group with default -// security group rules for the IPv4 and IPv6 ether types. -func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToSecGroupCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) - return -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToSecGroupUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contains all the values needed to update an existing security -// group. -type UpdateOpts struct { - // Human-readable name for the Security Group. Does not have to be unique. - Name string `json:"name,omitempty"` - - // Describes the security group. - Description *string `json:"description,omitempty"` -} - -// ToSecGroupUpdateMap builds a request body from UpdateOpts. -func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "security_group") -} - -// Update is an operation which updates an existing security group. -func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToSecGroupUpdateMap() - if err != nil { - r.Err = err - return - } - - _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get retrieves a particular security group 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 security group based on its -// unique ID. -func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(resourceURL(c, id), nil) - return -} - -// IDFromName is a convenience function that returns a security group's ID, -// given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractGroups(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "security group"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"} - } -} 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 deleted file mode 100644 index 468952b3e4e..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go +++ /dev/null @@ -1,108 +0,0 @@ -package groups - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" - "github.com/gophercloud/gophercloud/pagination" -) - -// SecGroup represents a container for security group rules. -type SecGroup struct { - // The UUID for the security group. - ID string - - // Human-readable name for the security group. Might not be unique. - // Cannot be named "default" as that is automatically created for a tenant. - Name string - - // The security group description. - Description string - - // A slice of security group rules that dictate the permitted behaviour for - // traffic entering and leaving the group. - Rules []rules.SecGroupRule `json:"security_group_rules"` - - // TenantID is the project owner of the security group. - TenantID string `json:"tenant_id"` - - // 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 -// collection of security groups. -type SecGroupPage struct { - pagination.LinkedPageBase -} - -// NextPageURL is invoked when a paginated collection of security groups has -// reached the end of a page and the pager seeks to traverse over a new one. In -// order to do this, it needs to construct the next page's URL. -func (r SecGroupPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"security_groups_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - - return gophercloud.ExtractNextURL(s.Links) -} - -// IsEmpty checks whether a SecGroupPage struct is empty. -func (r SecGroupPage) IsEmpty() (bool, error) { - is, err := ExtractGroups(r) - return len(is) == 0, err -} - -// ExtractGroups accepts a Page struct, specifically a SecGroupPage struct, -// and extracts the elements into a slice of SecGroup structs. In other words, -// a generic collection is mapped into a relevant slice. -func ExtractGroups(r pagination.Page) ([]SecGroup, error) { - var s struct { - SecGroups []SecGroup `json:"security_groups"` - } - err := (r.(SecGroupPage)).ExtractInto(&s) - return s.SecGroups, err -} - -type commonResult struct { - gophercloud.Result -} - -// Extract is a function that accepts a result and extracts a security group. -func (r commonResult) Extract() (*SecGroup, error) { - var s struct { - SecGroup *SecGroup `json:"security_group"` - } - err := r.ExtractInto(&s) - return s.SecGroup, err -} - -// CreateResult represents the result of a create operation. Call its Extract -// method to interpret it as a SecGroup. -type CreateResult struct { - commonResult -} - -// UpdateResult represents the result of an update operation. Call its Extract -// method to interpret it as a SecGroup. -type UpdateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret it as a SecGroup. -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 -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go deleted file mode 100644 index 104cbcc558d..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package groups - -import "github.com/gophercloud/gophercloud" - -const rootPath = "security-groups" - -func rootURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL(rootPath) -} - -func resourceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL(rootPath, id) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go deleted file mode 100644 index bf66dc8b40e..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Package rules provides information and interaction with Security Group Rules -for the OpenStack Networking service. - -Example to List Security Groups Rules - - listOpts := rules.ListOpts{ - Protocol: "tcp", - } - - allPages, err := rules.List(networkClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allRules, err := rules.ExtractRules(allPages) - if err != nil { - panic(err) - } - - for _, rule := range allRules { - fmt.Printf("%+v\n", rule) - } - -Example to Create a Security Group Rule - - createOpts := rules.CreateOpts{ - Direction: "ingress", - PortRangeMin: 80, - EtherType: rules.EtherType4, - PortRangeMax: 80, - Protocol: "tcp", - RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", - SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", - } - - rule, err := rules.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Security Group Rule - - ruleID := "37d94f8a-d136-465c-ae46-144f0d8ef141" - err := rules.Delete(networkClient, ruleID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package rules 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 deleted file mode 100644 index c7741ffcd2c..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go +++ /dev/null @@ -1,159 +0,0 @@ -package rules - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOpts allows the filtering and sorting of paginated collections through -// the API. Filtering is achieved by passing in struct field values that map to -// the security group rule attributes you want to see returned. SortKey allows -// you to sort by a particular network attribute. SortDir sets the direction, -// and is either `asc' or `desc'. Marker and Limit are used for pagination. -type ListOpts struct { - Direction string `q:"direction"` - EtherType string `q:"ethertype"` - ID string `q:"id"` - Description string `q:"description"` - PortRangeMax int `q:"port_range_max"` - PortRangeMin int `q:"port_range_min"` - Protocol string `q:"protocol"` - RemoteGroupID string `q:"remote_group_id"` - RemoteIPPrefix string `q:"remote_ip_prefix"` - SecGroupID string `q:"security_group_id"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` -} - -// List returns a Pager which allows you to iterate over a collection of -// security group rules. It accepts a ListOpts struct, which allows you to filter -// and sort the returned collection for greater efficiency. -func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { - q, err := gophercloud.BuildQueryString(&opts) - if err != nil { - return pagination.Pager{Err: err} - } - u := rootURL(c) + q.String() - return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { - return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -type RuleDirection string -type RuleProtocol string -type RuleEtherType string - -// Constants useful for CreateOpts -const ( - DirIngress RuleDirection = "ingress" - DirEgress RuleDirection = "egress" - EtherType4 RuleEtherType = "IPv4" - EtherType6 RuleEtherType = "IPv6" - ProtocolAH RuleProtocol = "ah" - ProtocolDCCP RuleProtocol = "dccp" - ProtocolEGP RuleProtocol = "egp" - ProtocolESP RuleProtocol = "esp" - ProtocolGRE RuleProtocol = "gre" - ProtocolICMP RuleProtocol = "icmp" - ProtocolIGMP RuleProtocol = "igmp" - ProtocolIPv6Encap RuleProtocol = "ipv6-encap" - ProtocolIPv6Frag RuleProtocol = "ipv6-frag" - ProtocolIPv6ICMP RuleProtocol = "ipv6-icmp" - ProtocolIPv6NoNxt RuleProtocol = "ipv6-nonxt" - ProtocolIPv6Opts RuleProtocol = "ipv6-opts" - ProtocolIPv6Route RuleProtocol = "ipv6-route" - ProtocolOSPF RuleProtocol = "ospf" - ProtocolPGM RuleProtocol = "pgm" - ProtocolRSVP RuleProtocol = "rsvp" - ProtocolSCTP RuleProtocol = "sctp" - ProtocolTCP RuleProtocol = "tcp" - ProtocolUDP RuleProtocol = "udp" - ProtocolUDPLite RuleProtocol = "udplite" - ProtocolVRRP RuleProtocol = "vrrp" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToSecGroupRuleCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains all the values needed to create a new security group -// rule. -type CreateOpts struct { - // Must be either "ingress" or "egress": the direction in which the security - // 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"` - - // The security group ID to associate with this security group rule. - SecGroupID string `json:"security_group_id" required:"true"` - - // The maximum port number in the range that is matched by the security group - // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If - // the protocol is ICMP, this value must be an ICMP type. - PortRangeMax int `json:"port_range_max,omitempty"` - - // The minimum port number in the range that is matched by the security group - // rule. If the protocol is TCP or UDP, this value must be less than or equal - // to the value of the PortRangeMax attribute. If the protocol is ICMP, this - // value must be an ICMP type. - PortRangeMin int `json:"port_range_min,omitempty"` - - // The protocol that is matched by the security group rule. Valid values are - // "tcp", "udp", "icmp" or an empty string. - Protocol RuleProtocol `json:"protocol,omitempty"` - - // The remote group ID to be associated with this security group rule. You can - // specify either RemoteGroupID or RemoteIPPrefix. - RemoteGroupID string `json:"remote_group_id,omitempty"` - - // The remote IP prefix to be associated with this security group rule. You can - // specify either RemoteGroupID or RemoteIPPrefix. This attribute matches the - // specified IP prefix as the source IP address of the IP packet. - RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` - - // TenantID is the UUID of the project who owns the Rule. - // Only administrative users can specify a project UUID other than their own. - ProjectID string `json:"project_id,omitempty"` -} - -// ToSecGroupRuleCreateMap builds a request body from CreateOpts. -func (opts CreateOpts) ToSecGroupRuleCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "security_group_rule") -} - -// Create is an operation which adds a new security group rule and associates it -// with an existing security group (whose ID is specified in CreateOpts). -func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToSecGroupRuleCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) - return -} - -// Get retrieves a particular security group rule 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 security group rule 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/networking/v2/extensions/security/rules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go deleted file mode 100644 index 3bf5501d922..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go +++ /dev/null @@ -1,127 +0,0 @@ -package rules - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// SecGroupRule represents a rule to dictate the behaviour of incoming or -// outgoing traffic for a particular security group. -type SecGroupRule struct { - // The UUID for this security group rule. - ID string - - // The direction in which the security group rule is applied. The only values - // allowed are "ingress" or "egress". For a compute instance, an ingress - // security group rule is applied to incoming (ingress) traffic for that - // 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"` - - // The security group ID to associate with this security group rule. - SecGroupID string `json:"security_group_id"` - - // The minimum port number in the range that is matched by the security group - // rule. If the protocol is TCP or UDP, this value must be less than or equal - // to the value of the PortRangeMax attribute. If the protocol is ICMP, this - // value must be an ICMP type. - PortRangeMin int `json:"port_range_min"` - - // The maximum port number in the range that is matched by the security group - // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If - // the protocol is ICMP, this value must be an ICMP type. - PortRangeMax int `json:"port_range_max"` - - // The protocol that is matched by the security group rule. Valid values are - // "tcp", "udp", "icmp" or an empty string. - Protocol string - - // The remote group ID to be associated with this security group rule. You - // can specify either RemoteGroupID or RemoteIPPrefix. - RemoteGroupID string `json:"remote_group_id"` - - // The remote IP prefix to be associated with this security group rule. You - // can specify either RemoteGroupID or RemoteIPPrefix . This attribute - // matches the specified IP prefix as the source IP address of the IP packet. - RemoteIPPrefix string `json:"remote_ip_prefix"` - - // TenantID is the project owner of this security group rule. - TenantID string `json:"tenant_id"` - - // ProjectID is the project owner of this security group rule. - ProjectID string `json:"project_id"` -} - -// SecGroupRulePage is the page returned by a pager when traversing over a -// collection of security group rules. -type SecGroupRulePage struct { - pagination.LinkedPageBase -} - -// NextPageURL is invoked when a paginated collection of security group rules has -// reached the end of a page and the pager seeks to traverse over a new one. In -// order to do this, it needs to construct the next page's URL. -func (r SecGroupRulePage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"security_group_rules_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// IsEmpty checks whether a SecGroupRulePage struct is empty. -func (r SecGroupRulePage) IsEmpty() (bool, error) { - is, err := ExtractRules(r) - return len(is) == 0, err -} - -// ExtractRules accepts a Page struct, specifically a SecGroupRulePage struct, -// and extracts the elements into a slice of SecGroupRule structs. In other words, -// a generic collection is mapped into a relevant slice. -func ExtractRules(r pagination.Page) ([]SecGroupRule, error) { - var s struct { - SecGroupRules []SecGroupRule `json:"security_group_rules"` - } - err := (r.(SecGroupRulePage)).ExtractInto(&s) - return s.SecGroupRules, err -} - -type commonResult struct { - gophercloud.Result -} - -// Extract is a function that accepts a result and extracts a security rule. -func (r commonResult) Extract() (*SecGroupRule, error) { - var s struct { - SecGroupRule *SecGroupRule `json:"security_group_rule"` - } - err := r.ExtractInto(&s) - return s.SecGroupRule, err -} - -// CreateResult represents the result of a create operation. Call its Extract -// method to interpret it as a SecGroupRule. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret it as a SecGroupRule. -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 -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go deleted file mode 100644 index a5ede0e89b9..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package rules - -import "github.com/gophercloud/gophercloud" - -const rootPath = "security-group-rules" - -func rootURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL(rootPath) -} - -func resourceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL(rootPath, id) -} 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 deleted file mode 100644 index 9d1dd5a7ea4..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Package networks contains functionality for working with Neutron network -resources. A network is an isolated virtual layer-2 broadcast domain that is -typically reserved for the tenant who created it (unless you configure the -network to be shared). Tenants can create multiple networks until the -thresholds per-tenant quota is reached. - -In the v2.0 Networking API, the network is the main entity. Ports and subnets -are always associated with a network. - -Example to List Networks - - listOpts := networks.ListOpts{ - TenantID: "a99e9b4e620e4db09a2dfb6e42a01e66", - } - - allPages, err := networks.List(networkClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allNetworks, err := networks.ExtractNetworks(allPages) - if err != nil { - panic(err) - } - - for _, network := range allNetworks { - fmt.Printf("%+v", network) - } - -Example to Create a Network - - iTrue := true - createOpts := networks.CreateOpts{ - Name: "network_1", - AdminStateUp: &iTrue, - } - - network, err := networks.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Network - - networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" - - name := "new_name" - updateOpts := networks.UpdateOpts{ - Name: &name, - } - - network, err := networks.Update(networkClient, networkID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Network - - networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" - err := networks.Delete(networkClient, networkID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package networks 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 deleted file mode 100644 index 8006c481679..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go +++ /dev/null @@ -1,180 +0,0 @@ -package networks - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// List request. -type ListOptsBuilder interface { - ToNetworkListQuery() (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 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. -type ListOpts struct { - Status string `q:"status"` - Name string `q:"name"` - Description string `q:"description"` - AdminStateUp *bool `q:"admin_state_up"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Shared *bool `q:"shared"` - ID string `q:"id"` - Marker string `q:"marker"` - Limit int `q:"limit"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` - Tags string `q:"tags"` - TagsAny string `q:"tags-any"` - NotTags string `q:"not-tags"` - NotTagsAny string `q:"not-tags-any"` -} - -// ToNetworkListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToNetworkListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// List returns a Pager which allows you to iterate over a collection of -// networks. 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 := listURL(c) - if opts != nil { - query, err := opts.ToNetworkListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - return NetworkPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// Get retrieves a specific network 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 -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToNetworkCreateMap() (map[string]interface{}, error) -} - -// CreateOpts represents options used to create a network. -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"` - AvailabilityZoneHints []string `json:"availability_zone_hints,omitempty"` -} - -// ToNetworkCreateMap builds a request body from CreateOpts. -func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "network") -} - -// Create accepts a CreateOpts struct and creates a new network using the values -// provided. This operation does not actually require a request body, i.e. the -// CreateOpts struct argument can be empty. -// -// The tenant ID that is contained in the URI is the tenant that creates the -// network. An admin user, however, has the option of specifying another tenant -// ID in the CreateOpts struct. -func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToNetworkCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Post(createURL(c), b, &r.Body, nil) - return -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToNetworkUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts represents options used to update a network. -type UpdateOpts struct { - 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. -func (opts UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "network") -} - -// Update accepts a UpdateOpts struct and updates an existing network using the -// values provided. For more information, see the Create function. -func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToNetworkUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201}, - }) - return -} - -// Delete accepts a unique ID and deletes the network associated with it. -func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) { - _, r.Err = c.Delete(deleteURL(c, networkID), nil) - return -} - -// IDFromName is a convenience function that returns a network's ID, given -// its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractNetworks(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "network"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "network"} - } -} 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 deleted file mode 100644 index f03067415fb..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go +++ /dev/null @@ -1,124 +0,0 @@ -package networks - -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 a network resource. -func (r commonResult) Extract() (*Network, error) { - var s Network - err := r.ExtractInto(&s) - return &s, err -} - -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "network") -} - -// CreateResult represents the result of a create operation. Call its Extract -// method to interpret it as a Network. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret it as a Network. -type GetResult struct { - commonResult -} - -// UpdateResult represents the result of an update operation. Call its Extract -// method to interpret it as a Network. -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 -} - -// Network represents, well, a network. -type Network struct { - // UUID for the network - ID string `json:"id"` - - // 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"` - - // Indicates whether network is currently operational. Possible values include - // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional - // values. - Status string `json:"status"` - - // Subnets associated with this network. - Subnets []string `json:"subnets"` - - // TenantID is the project owner of the network. - TenantID string `json:"tenant_id"` - - // ProjectID is the project owner of the network. - ProjectID string `json:"project_id"` - - // Specifies whether the network resource can be accessed by any tenant. - Shared bool `json:"shared"` - - // 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 -// collection of networks. -type NetworkPage struct { - pagination.LinkedPageBase -} - -// NextPageURL is invoked when a paginated collection of networks has reached -// the end of a page and the pager seeks to traverse over a new one. In order -// to do this, it needs to construct the next page's URL. -func (r NetworkPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"networks_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// IsEmpty checks whether a NetworkPage struct is empty. -func (r NetworkPage) IsEmpty() (bool, error) { - is, err := ExtractNetworks(r) - return len(is) == 0, err -} - -// ExtractNetworks accepts a Page struct, specifically a NetworkPage struct, -// and extracts the elements into a slice of Network structs. In other words, -// a generic collection is mapped into a relevant slice. -func ExtractNetworks(r pagination.Page) ([]Network, error) { - var s []Network - err := ExtractNetworksInto(r, &s) - return s, err -} - -func ExtractNetworksInto(r pagination.Page, v interface{}) error { - return r.(NetworkPage).Result.ExtractIntoSlicePtr(v, "networks") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go deleted file mode 100644 index 4a8fb1dc7d3..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/urls.go +++ /dev/null @@ -1,31 +0,0 @@ -package networks - -import "github.com/gophercloud/gophercloud" - -func resourceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL("networks", id) -} - -func rootURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("networks") -} - -func getURL(c *gophercloud.ServiceClient, id string) string { - return resourceURL(c, id) -} - -func listURL(c *gophercloud.ServiceClient) string { - return rootURL(c) -} - -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/ports/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go deleted file mode 100644 index cfb1774fb4b..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Package ports contains functionality for working with Neutron port resources. - -A port represents a virtual switch port on a logical network switch. Virtual -instances attach their interfaces into ports. The logical port also defines -the MAC address and the IP address(es) to be assigned to the interfaces -plugged into them. When IP addresses are associated to a port, this also -implies the port is associated with a subnet, as the IP address was taken -from the allocation pool for a specific subnet. - -Example to List Ports - - listOpts := ports.ListOpts{ - DeviceID: "b0b89efe-82f8-461d-958b-adbf80f50c7d", - } - - allPages, err := ports.List(networkClient, listOpts).AllPages() - if err != nil { - panic(err) - } - - allPorts, err := ports.ExtractPorts(allPages) - if err != nil { - panic(err) - } - - for _, port := range allPorts { - fmt.Printf("%+v\n", port) - } - -Example to Create a Port - - createOtps := 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"}, - AllowedAddressPairs: []ports.AddressPair{ - {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }, - } - - port, err := ports.Create(networkClient, createOpts).Extract() - if err != nil { - panic(err) - } - -Example to Update a Port - - portID := "c34bae2b-7641-49b6-bf6d-d8e473620ed8" - - updateOpts := ports.UpdateOpts{ - Name: "new_name", - SecurityGroups: &[]string{}, - } - - port, err := ports.Update(networkClient, portID, updateOpts).Extract() - if err != nil { - panic(err) - } - -Example to Delete a Port - - portID := "c34bae2b-7641-49b6-bf6d-d8e473620ed8" - err := ports.Delete(networkClient, portID).ExtractErr() - if err != nil { - panic(err) - } -*/ -package ports 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 deleted file mode 100644 index f5f7d761ce8..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go +++ /dev/null @@ -1,191 +0,0 @@ -package ports - -import ( - "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) -} - -// 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 port attributes you want to see returned. SortKey allows you to sort -// by a particular port attribute. SortDir sets the direction, and is either -// `asc' or `desc'. Marker and Limit are used for pagination. -type ListOpts struct { - Status string `q:"status"` - Name string `q:"name"` - Description string `q:"description"` - AdminStateUp *bool `q:"admin_state_up"` - NetworkID string `q:"network_id"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - DeviceOwner string `q:"device_owner"` - MACAddress string `q:"mac_address"` - ID string `q:"id"` - DeviceID string `q:"device_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"` -} - -// ToPortListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToPortListQuery() (string, error) { - q, err := gophercloud.BuildQueryString(opts) - return q.String(), err -} - -// List returns a Pager which allows you to iterate over a collection of -// ports. It accepts a ListOpts struct, which allows you to filter and sort -// the returned collection for greater efficiency. -// -// Default policy settings return only those ports 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.ToPortListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - return PortPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// Get retrieves a specific port 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 -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToPortCreateMap() (map[string]interface{}, error) -} - -// CreateOpts represents the attributes used when creating a new port. -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"` - DeviceID string `json:"device_id,omitempty"` - DeviceOwner string `json:"device_owner,omitempty"` - TenantID string `json:"tenant_id,omitempty"` - ProjectID string `json:"project_id,omitempty"` - SecurityGroups *[]string `json:"security_groups,omitempty"` - AllowedAddressPairs []AddressPair `json:"allowed_address_pairs,omitempty"` -} - -// ToPortCreateMap builds a request body from CreateOpts. -func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "port") -} - -// Create accepts a CreateOpts struct and creates a new network using the values -// provided. You must remember to provide a NetworkID value. -func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToPortCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Post(createURL(c), b, &r.Body, nil) - return -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToPortUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts represents the attributes used when updating an existing port. -type UpdateOpts struct { - 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"` - SecurityGroups *[]string `json:"security_groups,omitempty"` - AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"` -} - -// ToPortUpdateMap builds a request body from UpdateOpts. -func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "port") -} - -// Update accepts a UpdateOpts struct and updates an existing port using the -// values provided. -func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToPortUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 201}, - }) - return -} - -// Delete accepts a unique ID and deletes the port associated with it. -func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(deleteURL(c, id), nil) - return -} - -// IDFromName is a convenience function that returns a port's ID, -// given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractPorts(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "port"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "port"} - } -} 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 deleted file mode 100644 index 3941b62300d..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go +++ /dev/null @@ -1,149 +0,0 @@ -package ports - -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 a port resource. -func (r commonResult) Extract() (*Port, error) { - var s Port - err := r.ExtractInto(&s) - return &s, err -} - -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "port") -} - -// CreateResult represents the result of a create operation. Call its Extract -// method to interpret it as a Port. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret it as a Port. -type GetResult struct { - commonResult -} - -// UpdateResult represents the result of an update operation. Call its Extract -// method to interpret it as a Port. -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 -} - -// IP is a sub-struct that represents an individual IP. -type IP struct { - SubnetID string `json:"subnet_id"` - IPAddress string `json:"ip_address,omitempty"` -} - -// AddressPair contains the IP Address and the MAC address. -type AddressPair struct { - IPAddress string `json:"ip_address,omitempty"` - MACAddress string `json:"mac_address,omitempty"` -} - -// Port represents a Neutron port. See package documentation for a top-level -// description of what this is. -type Port struct { - // UUID for the port. - ID string `json:"id"` - - // Network that this port is associated with. - NetworkID string `json:"network_id"` - - // 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"` - - // Indicates whether network is currently operational. Possible values include - // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional - // values. - Status string `json:"status"` - - // Mac address to use on this port. - MACAddress string `json:"mac_address"` - - // Specifies IP addresses for the port thus associating the port itself with - // the subnets where the IP addresses are picked from - FixedIPs []IP `json:"fixed_ips"` - - // TenantID is the project owner of the port. - TenantID string `json:"tenant_id"` - - // ProjectID is the project owner of the port. - ProjectID string `json:"project_id"` - - // Identifies the entity (e.g.: dhcp agent) using this port. - DeviceOwner string `json:"device_owner"` - - // Specifies the IDs of any security groups associated with a port. - SecurityGroups []string `json:"security_groups"` - - // Identifies the device (e.g., virtual server) using this port. - DeviceID string `json:"device_id"` - - // 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 -// of network ports. -type PortPage struct { - pagination.LinkedPageBase -} - -// NextPageURL is invoked when a paginated collection of ports has reached -// the end of a page and the pager seeks to traverse over a new one. In order -// to do this, it needs to construct the next page's URL. -func (r PortPage) NextPageURL() (string, error) { - var s struct { - Links []gophercloud.Link `json:"ports_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return gophercloud.ExtractNextURL(s.Links) -} - -// IsEmpty checks whether a PortPage struct is empty. -func (r PortPage) IsEmpty() (bool, error) { - is, err := ExtractPorts(r) - return len(is) == 0, err -} - -// ExtractPorts accepts a Page struct, specifically a PortPage struct, -// and extracts the elements into a slice of Port structs. In other words, -// a generic collection is mapped into a relevant slice. -func ExtractPorts(r pagination.Page) ([]Port, error) { - var s []Port - err := ExtractPortsInto(r, &s) - return s, err -} - -func ExtractPortsInto(r pagination.Page, v interface{}) error { - return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go deleted file mode 100644 index 600d6f2fd95..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/urls.go +++ /dev/null @@ -1,31 +0,0 @@ -package ports - -import "github.com/gophercloud/gophercloud" - -func resourceURL(c *gophercloud.ServiceClient, id string) string { - return c.ServiceURL("ports", id) -} - -func rootURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("ports") -} - -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/utils/base_endpoint.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go deleted file mode 100644 index 40080f7af20..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go +++ /dev/null @@ -1,28 +0,0 @@ -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/choose_version.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go deleted file mode 100644 index 27da19f91a8..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go +++ /dev/null @@ -1,111 +0,0 @@ -package utils - -import ( - "fmt" - "strings" - - "github.com/gophercloud/gophercloud" -) - -// Version is a supported API version, corresponding to a vN package within the appropriate service. -type Version struct { - ID string - Suffix string - Priority int -} - -var goodStatus = map[string]bool{ - "current": true, - "supported": true, - "stable": true, -} - -// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's -// published versions. -// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint. -func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) { - type linkResp struct { - Href string `json:"href"` - Rel string `json:"rel"` - } - - type valueResp struct { - ID string `json:"id"` - Status string `json:"status"` - Links []linkResp `json:"links"` - } - - type versionsResp struct { - Values []valueResp `json:"values"` - } - - type response struct { - Versions versionsResp `json:"versions"` - } - - normalize := func(endpoint string) string { - if !strings.HasSuffix(endpoint, "/") { - return endpoint + "/" - } - return endpoint - } - identityEndpoint := normalize(client.IdentityEndpoint) - - // If a full endpoint is specified, check version suffixes for a match first. - for _, v := range recognized { - if strings.HasSuffix(identityEndpoint, v.Suffix) { - return v, identityEndpoint, nil - } - } - - var resp response - _, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{ - JSONResponse: &resp, - OkCodes: []int{200, 300}, - }) - - if err != nil { - return nil, "", err - } - - var highest *Version - var endpoint string - - for _, value := range resp.Versions.Values { - href := "" - for _, link := range value.Links { - if link.Rel == "self" { - href = normalize(link.Href) - } - } - - for _, version := range recognized { - if strings.Contains(value.ID, version.ID) { - // Prefer a version that exactly matches the provided endpoint. - if href == identityEndpoint { - if href == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) - } - return version, href, nil - } - - // Otherwise, find the highest-priority version with a whitelisted status. - if goodStatus[strings.ToLower(value.Status)] { - if highest == nil || version.Priority > highest.Priority { - highest = version - endpoint = href - } - } - } - } - } - - if highest == nil { - return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase) - } - if endpoint == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase) - } - - return highest, endpoint, nil -} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/pagination/http.go deleted file mode 100644 index 757295c423a..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/pagination/http.go +++ /dev/null @@ -1,60 +0,0 @@ -package pagination - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "strings" - - "github.com/gophercloud/gophercloud" -) - -// PageResult stores the HTTP response that returned the current page of results. -type PageResult struct { - gophercloud.Result - url.URL -} - -// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the -// results, interpreting it as JSON if the content type indicates. -func PageResultFrom(resp *http.Response) (PageResult, error) { - var parsedBody interface{} - - defer resp.Body.Close() - rawBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - return PageResult{}, err - } - - if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { - err = json.Unmarshal(rawBody, &parsedBody) - if err != nil { - return PageResult{}, err - } - } else { - parsedBody = rawBody - } - - return PageResultFromParsed(resp, parsedBody), err -} - -// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its -// body parsed as JSON (and closed). -func PageResultFromParsed(resp *http.Response, body interface{}) PageResult { - return PageResult{ - Result: gophercloud.Result{ - Body: body, - Header: resp.Header, - }, - URL: *resp.Request.URL, - } -} - -// Request performs an HTTP request and extracts the http.Response from the result. -func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { - return client.Get(url, nil, &gophercloud.RequestOpts{ - MoreHeaders: headers, - OkCodes: []int{200, 204, 300}, - }) -} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/linked.go b/vendor/github.com/gophercloud/gophercloud/pagination/linked.go deleted file mode 100644 index 3656fb7f8f4..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/pagination/linked.go +++ /dev/null @@ -1,92 +0,0 @@ -package pagination - -import ( - "fmt" - "reflect" - - "github.com/gophercloud/gophercloud" -) - -// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result. -type LinkedPageBase struct { - PageResult - - // LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer. - // If any link along the path is missing, an empty URL will be returned. - // If any link results in an unexpected value type, an error will be returned. - // When left as "nil", []string{"links", "next"} will be used as a default. - LinkPath []string -} - -// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present. -// It assumes that the links are available in a "links" element of the top-level response object. -// If this is not the case, override NextPageURL on your result type. -func (current LinkedPageBase) NextPageURL() (string, error) { - var path []string - var key string - - if current.LinkPath == nil { - path = []string{"links", "next"} - } else { - path = current.LinkPath - } - - submap, ok := current.Body.(map[string]interface{}) - if !ok { - err := gophercloud.ErrUnexpectedType{} - err.Expected = "map[string]interface{}" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) - return "", err - } - - for { - key, path = path[0], path[1:len(path)] - - value, ok := submap[key] - if !ok { - return "", nil - } - - if len(path) > 0 { - submap, ok = value.(map[string]interface{}) - if !ok { - err := gophercloud.ErrUnexpectedType{} - err.Expected = "map[string]interface{}" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) - return "", err - } - } else { - if value == nil { - // Actual null element. - return "", nil - } - - url, ok := value.(string) - if !ok { - err := gophercloud.ErrUnexpectedType{} - err.Expected = "string" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) - return "", err - } - - return url, nil - } - } -} - -// IsEmpty satisifies the IsEmpty method of the Page interface -func (current LinkedPageBase) IsEmpty() (bool, error) { - if b, ok := current.Body.([]interface{}); ok { - return len(b) == 0, nil - } - err := gophercloud.ErrUnexpectedType{} - err.Expected = "[]interface{}" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) - return true, err -} - -// GetBody returns the linked page's body. This method is needed to satisfy the -// Page interface. -func (current LinkedPageBase) GetBody() interface{} { - return current.Body -} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/marker.go b/vendor/github.com/gophercloud/gophercloud/pagination/marker.go deleted file mode 100644 index 52e53bae850..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/pagination/marker.go +++ /dev/null @@ -1,58 +0,0 @@ -package pagination - -import ( - "fmt" - "reflect" - - "github.com/gophercloud/gophercloud" -) - -// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager. -// For convenience, embed the MarkedPageBase struct. -type MarkerPage interface { - Page - - // LastMarker returns the last "marker" value on this page. - LastMarker() (string, error) -} - -// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters. -type MarkerPageBase struct { - PageResult - - // Owner is a reference to the embedding struct. - Owner MarkerPage -} - -// NextPageURL generates the URL for the page of results after this one. -func (current MarkerPageBase) NextPageURL() (string, error) { - currentURL := current.URL - - mark, err := current.Owner.LastMarker() - if err != nil { - return "", err - } - - q := currentURL.Query() - q.Set("marker", mark) - currentURL.RawQuery = q.Encode() - - return currentURL.String(), nil -} - -// IsEmpty satisifies the IsEmpty method of the Page interface -func (current MarkerPageBase) IsEmpty() (bool, error) { - if b, ok := current.Body.([]interface{}); ok { - return len(b) == 0, nil - } - err := gophercloud.ErrUnexpectedType{} - err.Expected = "[]interface{}" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) - return true, err -} - -// GetBody returns the linked page's body. This method is needed to satisfy the -// Page interface. -func (current MarkerPageBase) GetBody() interface{} { - return current.Body -} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go deleted file mode 100644 index 42c0b2dbe5b..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go +++ /dev/null @@ -1,251 +0,0 @@ -package pagination - -import ( - "errors" - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gophercloud/gophercloud" -) - -var ( - // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist. - ErrPageNotAvailable = errors.New("The requested page does not exist.") -) - -// Page must be satisfied by the result type of any resource collection. -// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated. -// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs, -// instead. -// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type -// will need to implement. -type Page interface { - // NextPageURL generates the URL for the page of data that follows this collection. - // Return "" if no such page exists. - NextPageURL() (string, error) - - // IsEmpty returns true if this Page has no items in it. - IsEmpty() (bool, error) - - // GetBody returns the Page Body. This is used in the `AllPages` method. - GetBody() interface{} -} - -// Pager knows how to advance through a specific resource collection, one page at a time. -type Pager struct { - client *gophercloud.ServiceClient - - initialURL string - - createPage func(r PageResult) Page - - firstPage Page - - Err error - - // Headers supplies additional HTTP headers to populate on each paged request. - Headers map[string]string -} - -// NewPager constructs a manually-configured pager. -// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page. -func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager { - return Pager{ - client: client, - initialURL: initialURL, - createPage: createPage, - } -} - -// WithPageCreator returns a new Pager that substitutes a different page creation function. This is -// useful for overriding List functions in delegation. -func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager { - return Pager{ - client: p.client, - initialURL: p.initialURL, - createPage: createPage, - } -} - -func (p Pager) fetchNextPage(url string) (Page, error) { - resp, err := Request(p.client, p.Headers, url) - if err != nil { - return nil, err - } - - remembered, err := PageResultFrom(resp) - if err != nil { - return nil, err - } - - return p.createPage(remembered), nil -} - -// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function. -// Return "false" from the handler to prematurely stop iterating. -func (p Pager) EachPage(handler func(Page) (bool, error)) error { - if p.Err != nil { - return p.Err - } - currentURL := p.initialURL - for { - 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() - if err != nil { - return err - } - if empty { - return nil - } - - ok, err := handler(currentPage) - if err != nil { - return err - } - if !ok { - return nil - } - - currentURL, err = currentPage.NextPageURL() - if err != nil { - return err - } - if currentURL == "" { - return nil - } - } -} - -// AllPages returns all the pages from a `List` operation in a single page, -// allowing the user to retrieve all the pages at once. -func (p Pager) AllPages() (Page, error) { - // pagesSlice holds all the pages until they get converted into as Page Body. - var pagesSlice []interface{} - // body will contain the final concatenated Page body. - var body reflect.Value - - // 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(firstPage) - - // if it's a single page, just return the firstPage (first page) - if _, found := pageType.FieldByName("SinglePageBase"); found { - 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 := 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 - // Iterate over the pages to concatenate the bodies. - err = p.EachPage(func(page Page) (bool, error) { - b := page.GetBody().(map[string]interface{}) - for k, v := range b { - // If it's a linked page, we don't want the `links`, we want the other one. - if !strings.HasSuffix(k, "links") { - // check the field's type. we only want []interface{} (which is really []map[string]interface{}) - switch vt := v.(type) { - case []interface{}: - key = k - pagesSlice = append(pagesSlice, vt...) - } - } - } - return true, nil - }) - if err != nil { - return nil, err - } - // Set body to value of type `map[string]interface{}` - body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice))) - body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice)) - case []byte: - // Iterate over the pages to concatenate the bodies. - err = p.EachPage(func(page Page) (bool, error) { - b := page.GetBody().([]byte) - pagesSlice = append(pagesSlice, b) - // seperate pages with a comma - pagesSlice = append(pagesSlice, []byte{10}) - return true, nil - }) - if err != nil { - return nil, err - } - if len(pagesSlice) > 0 { - // Remove the trailing comma. - pagesSlice = pagesSlice[:len(pagesSlice)-1] - } - var b []byte - // Combine the slice of slices in to a single slice. - for _, slice := range pagesSlice { - b = append(b, slice.([]byte)...) - } - // Set body to value of type `bytes`. - body = reflect.New(reflect.TypeOf(b)).Elem() - body.SetBytes(b) - case []interface{}: - // Iterate over the pages to concatenate the bodies. - err = p.EachPage(func(page Page) (bool, error) { - b := page.GetBody().([]interface{}) - pagesSlice = append(pagesSlice, b...) - return true, nil - }) - if err != nil { - return nil, err - } - // Set body to value of type `[]interface{}` - body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice)) - for i, s := range pagesSlice { - body.Index(i).Set(reflect.ValueOf(s)) - } - default: - err := gophercloud.ErrUnexpectedType{} - err.Expected = "map[string]interface{}/[]byte/[]interface{}" - err.Actual = fmt.Sprintf("%T", pb) - return nil, err - } - - // Each `Extract*` function is expecting a specific type of page coming back, - // otherwise the type assertion in those functions will fail. pageType is needed - // to create a type in this method that has the same type that the `Extract*` - // function is expecting and set the Body of that object to the concatenated - // pages. - page := reflect.New(pageType) - // Set the page body to be the concatenated pages. - page.Elem().FieldByName("Body").Set(body) - // Set any additional headers that were pass along. The `objectstorage` pacakge, - // for example, passes a Content-Type header. - h := make(http.Header) - for k, v := range p.Headers { - h.Add(k, v) - } - page.Elem().FieldByName("Header").Set(reflect.ValueOf(h)) - // Type assert the page to a Page interface so that the type assertion in the - // `Extract*` methods will work. - return page.Elem().Interface().(Page), err -} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go b/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go deleted file mode 100644 index 912daea3642..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go +++ /dev/null @@ -1,4 +0,0 @@ -/* -Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs. -*/ -package pagination diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/single.go b/vendor/github.com/gophercloud/gophercloud/pagination/single.go deleted file mode 100644 index 4251d6491ef..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/pagination/single.go +++ /dev/null @@ -1,33 +0,0 @@ -package pagination - -import ( - "fmt" - "reflect" - - "github.com/gophercloud/gophercloud" -) - -// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once. -type SinglePageBase PageResult - -// NextPageURL always returns "" to indicate that there are no more pages to return. -func (current SinglePageBase) NextPageURL() (string, error) { - return "", nil -} - -// IsEmpty satisifies the IsEmpty method of the Page interface -func (current SinglePageBase) IsEmpty() (bool, error) { - if b, ok := current.Body.([]interface{}); ok { - return len(b) == 0, nil - } - err := gophercloud.ErrUnexpectedType{} - err.Expected = "[]interface{}" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) - return true, err -} - -// GetBody returns the single page's body. This method is needed to satisfy the -// Page interface. -func (current SinglePageBase) GetBody() interface{} { - return current.Body -} diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go deleted file mode 100644 index b9986660cbd..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/params.go +++ /dev/null @@ -1,491 +0,0 @@ -package gophercloud - -import ( - "encoding/json" - "fmt" - "net/url" - "reflect" - "strconv" - "strings" - "time" -) - -/* -BuildRequestBody builds a map[string]interface from the given `struct`. If -parent is not an empty string, the final map[string]interface returned will -encapsulate the built one. For example: - - disk := 1 - createOpts := flavors.CreateOpts{ - ID: "1", - Name: "m1.tiny", - Disk: &disk, - RAM: 512, - VCPUs: 1, - RxTxFactor: 1.0, - } - - body, err := gophercloud.BuildRequestBody(createOpts, "flavor") - -The above example can be run as-is, however it is recommended to look at how -BuildRequestBody is used within Gophercloud to more fully understand how it -fits within the request process as a whole rather than use it directly as shown -above. -*/ -func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) { - optsValue := reflect.ValueOf(opts) - if optsValue.Kind() == reflect.Ptr { - optsValue = optsValue.Elem() - } - - optsType := reflect.TypeOf(opts) - if optsType.Kind() == reflect.Ptr { - optsType = optsType.Elem() - } - - optsMap := make(map[string]interface{}) - if optsValue.Kind() == reflect.Struct { - //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind()) - for i := 0; i < optsValue.NumField(); i++ { - v := optsValue.Field(i) - f := optsType.Field(i) - - if f.Name != strings.Title(f.Name) { - //fmt.Printf("Skipping field: %s...\n", f.Name) - continue - } - - //fmt.Printf("Starting on field: %s...\n", f.Name) - - zero := isZero(v) - //fmt.Printf("v is zero?: %v\n", zero) - - // if the field has a required tag that's set to "true" - if requiredTag := f.Tag.Get("required"); requiredTag == "true" { - //fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero) - // if the field's value is zero, return a missing-argument error - if zero { - // if the field has a 'required' tag, it can't have a zero-value - err := ErrMissingInput{} - err.Argument = f.Name - return nil, err - } - } - - if xorTag := f.Tag.Get("xor"); xorTag != "" { - //fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag) - xorField := optsValue.FieldByName(xorTag) - var xorFieldIsZero bool - if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) { - xorFieldIsZero = true - } else { - if xorField.Kind() == reflect.Ptr { - xorField = xorField.Elem() - } - xorFieldIsZero = isZero(xorField) - } - if !(zero != xorFieldIsZero) { - err := ErrMissingInput{} - err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag) - err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag) - return nil, err - } - } - - if orTag := f.Tag.Get("or"); orTag != "" { - //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag) - //fmt.Printf("field is zero?: %v\n", zero) - if zero { - orField := optsValue.FieldByName(orTag) - var orFieldIsZero bool - if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) { - orFieldIsZero = true - } else { - if orField.Kind() == reflect.Ptr { - orField = orField.Elem() - } - orFieldIsZero = isZero(orField) - } - if orFieldIsZero { - err := ErrMissingInput{} - err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag) - err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag) - return nil, err - } - } - } - - jsonTag := f.Tag.Get("json") - if jsonTag == "-" { - 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)) - if jsonTag != "" { - jsonTagPieces := strings.Split(jsonTag, ",") - if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" { - if v.CanSet() { - if !v.IsNil() { - if v.Kind() == reflect.Ptr { - v.Set(reflect.Zero(v.Type())) - } - } - //fmt.Printf("value after change: %+v\n", optsValue.Field(i)) - } - } - } - continue - } - - //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name) - _, err := BuildRequestBody(v.Interface(), f.Name) - if err != nil { - return nil, err - } - } - } - - //fmt.Printf("opts: %+v \n", opts) - - b, err := json.Marshal(opts) - if err != nil { - return nil, err - } - - //fmt.Printf("string(b): %s\n", string(b)) - - err = json.Unmarshal(b, &optsMap) - if err != nil { - return nil, err - } - - //fmt.Printf("optsMap: %+v\n", optsMap) - - if parent != "" { - optsMap = map[string]interface{}{parent: optsMap} - } - //fmt.Printf("optsMap after parent added: %+v\n", optsMap) - return optsMap, nil - } - // Return an error if the underlying type of 'opts' isn't a struct. - return nil, fmt.Errorf("Options type is not a struct.") -} - -// EnabledState is a convenience type, mostly used in Create and Update -// operations. Because the zero value of a bool is FALSE, we need to use a -// pointer instead to indicate zero-ness. -type EnabledState *bool - -// Convenience vars for EnabledState values. -var ( - iTrue = true - iFalse = false - - Enabled EnabledState = &iTrue - Disabled EnabledState = &iFalse -) - -// IPVersion is a type for the possible IP address versions. Valid instances -// are IPv4 and IPv6 -type IPVersion int - -const ( - // IPv4 is used for IP version 4 addresses - IPv4 IPVersion = 4 - // IPv6 is used for IP version 6 addresses - IPv6 IPVersion = 6 -) - -// IntToPointer is a function for converting integers into integer pointers. -// This is useful when passing in options to operations. -func IntToPointer(i int) *int { - return &i -} - -/* -MaybeString is an internal function to be used by request methods in individual -resource packages. - -It takes a string that might be a zero value and returns either a pointer to its -address or nil. This is useful for allowing users to conveniently omit values -from an options struct by leaving them zeroed, but still pass nil to the JSON -serializer so they'll be omitted from the request body. -*/ -func MaybeString(original string) *string { - if original != "" { - return &original - } - return nil -} - -/* -MaybeInt is an internal function to be used by request methods in individual -resource packages. - -Like MaybeString, it accepts an int that may or may not be a zero value, and -returns either a pointer to its address or nil. It's intended to hint that the -JSON serializer should omit its field. -*/ -func MaybeInt(original int) *int { - if original != 0 { - return &original - } - return nil -} - -/* -func isUnderlyingStructZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Ptr: - return isUnderlyingStructZero(v.Elem()) - default: - return isZero(v) - } -} -*/ - -var t time.Time - -func isZero(v reflect.Value) bool { - //fmt.Printf("\n\nchecking isZero for value: %+v\n", v) - switch v.Kind() { - case reflect.Ptr: - if v.IsNil() { - return true - } - return false - case reflect.Func, reflect.Map, reflect.Slice: - return v.IsNil() - case reflect.Array: - z := true - for i := 0; i < v.Len(); i++ { - z = z && isZero(v.Index(i)) - } - return z - case reflect.Struct: - if v.Type() == reflect.TypeOf(t) { - if v.Interface().(time.Time).IsZero() { - return true - } - return false - } - z := true - for i := 0; i < v.NumField(); i++ { - z = z && isZero(v.Field(i)) - } - return z - } - // Compare other types directly: - z := reflect.Zero(v.Type()) - //fmt.Printf("zero type for value: %+v\n\n\n", z) - return v.Interface() == z.Interface() -} - -/* -BuildQueryString is an internal function to be used by request methods in -individual resource packages. - -It accepts a tagged structure and expands it into a URL struct. Field names are -converted into query parameters based on a "q" tag. For example: - - type struct Something { - Bar string `q:"x_bar"` - Baz int `q:"lorem_ipsum"` - } - - instance := Something{ - Bar: "AAA", - Baz: "BBB", - } - -will be converted into "?x_bar=AAA&lorem_ipsum=BBB". - -The struct's fields may be strings, integers, or boolean values. Fields left at -their type's zero value will be omitted from the query. -*/ -func BuildQueryString(opts interface{}) (*url.URL, error) { - optsValue := reflect.ValueOf(opts) - if optsValue.Kind() == reflect.Ptr { - optsValue = optsValue.Elem() - } - - optsType := reflect.TypeOf(opts) - if optsType.Kind() == reflect.Ptr { - optsType = optsType.Elem() - } - - params := url.Values{} - - if optsValue.Kind() == reflect.Struct { - for i := 0; i < optsValue.NumField(); i++ { - v := optsValue.Field(i) - f := optsType.Field(i) - qTag := f.Tag.Get("q") - - // if the field has a 'q' tag, it goes in the query string - if qTag != "" { - tags := strings.Split(qTag, ",") - - // if the field is set, add it to the slice of query pieces - if !isZero(v) { - loop: - switch v.Kind() { - case reflect.Ptr: - v = v.Elem() - goto loop - case reflect.String: - params.Add(tags[0], v.String()) - case reflect.Int: - params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) - case reflect.Bool: - params.Add(tags[0], strconv.FormatBool(v.Bool())) - case reflect.Slice: - switch v.Type().Elem() { - case reflect.TypeOf(0): - for i := 0; i < v.Len(); i++ { - params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10)) - } - default: - for i := 0; i < v.Len(); i++ { - params.Add(tags[0], v.Index(i).String()) - } - } - case reflect.Map: - if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String { - var s []string - for _, k := range v.MapKeys() { - value := v.MapIndex(k).String() - s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value)) - } - params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", "))) - } - } - } else { - // if the field has a 'required' tag, it can't have a zero-value - if requiredTag := f.Tag.Get("required"); requiredTag == "true" { - return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) - } - } - } - } - - return &url.URL{RawQuery: params.Encode()}, nil - } - // Return an error if the underlying type of 'opts' isn't a struct. - return nil, fmt.Errorf("Options type is not a struct.") -} - -/* -BuildHeaders is an internal function to be used by request methods in -individual resource packages. - -It accepts an arbitrary tagged structure and produces a string map that's -suitable for use as the HTTP headers of an outgoing request. Field names are -mapped to header names based in "h" tags. - - type struct Something { - Bar string `h:"x_bar"` - Baz int `h:"lorem_ipsum"` - } - - instance := Something{ - Bar: "AAA", - Baz: "BBB", - } - -will be converted into: - - map[string]string{ - "x_bar": "AAA", - "lorem_ipsum": "BBB", - } - -Untagged fields and fields left at their zero values are skipped. Integers, -booleans and string values are supported. -*/ -func BuildHeaders(opts interface{}) (map[string]string, error) { - optsValue := reflect.ValueOf(opts) - if optsValue.Kind() == reflect.Ptr { - optsValue = optsValue.Elem() - } - - optsType := reflect.TypeOf(opts) - if optsType.Kind() == reflect.Ptr { - optsType = optsType.Elem() - } - - optsMap := make(map[string]string) - if optsValue.Kind() == reflect.Struct { - for i := 0; i < optsValue.NumField(); i++ { - v := optsValue.Field(i) - f := optsType.Field(i) - hTag := f.Tag.Get("h") - - // if the field has a 'h' tag, it goes in the header - if hTag != "" { - tags := strings.Split(hTag, ",") - - // if the field is set, add it to the slice of query pieces - if !isZero(v) { - switch v.Kind() { - case reflect.String: - optsMap[tags[0]] = v.String() - case reflect.Int: - optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) - case reflect.Bool: - optsMap[tags[0]] = strconv.FormatBool(v.Bool()) - } - } else { - // if the field has a 'required' tag, it can't have a zero-value - if requiredTag := f.Tag.Get("required"); requiredTag == "true" { - return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) - } - } - } - - } - return optsMap, nil - } - // Return an error if the underlying type of 'opts' isn't a struct. - return optsMap, fmt.Errorf("Options type is not a struct.") -} - -// IDSliceToQueryString takes a slice of elements and converts them into a query -// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the -// result would be `?name=20&name=40&name=60' -func IDSliceToQueryString(name string, ids []int) string { - str := "" - for k, v := range ids { - if k == 0 { - str += "?" - } else { - str += "&" - } - str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v)) - } - return str -} - -// IntWithinRange returns TRUE if an integer falls within a defined range, and -// FALSE if not. -func IntWithinRange(val, min, max int) bool { - return val > min && val < max -} diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go deleted file mode 100644 index fce00462fd3..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/provider_client.go +++ /dev/null @@ -1,501 +0,0 @@ -package gophercloud - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "io" - "io/ioutil" - "net/http" - "strings" - "sync" -) - -// DefaultUserAgent is the default User-Agent string set in the request header. -const DefaultUserAgent = "gophercloud/2.0.0" - -// UserAgent represents a User-Agent header. -type UserAgent struct { - // prepend is the slice of User-Agent strings to prepend to DefaultUserAgent. - // All the strings to prepend are accumulated and prepended in the Join method. - prepend []string -} - -// Prepend prepends a user-defined string to the default User-Agent string. Users -// may pass in one or more strings to prepend. -func (ua *UserAgent) Prepend(s ...string) { - ua.prepend = append(s, ua.prepend...) -} - -// Join concatenates all the user-defined User-Agend strings with the default -// Gophercloud User-Agent string. -func (ua *UserAgent) Join() string { - uaSlice := append(ua.prepend, DefaultUserAgent) - return strings.Join(uaSlice, " ") -} - -// ProviderClient stores details that are required to interact with any -// services within a specific provider's API. -// -// Generally, you acquire a ProviderClient by calling the NewClient method in -// the appropriate provider's child package, providing whatever authentication -// credentials are required. -type ProviderClient struct { - // IdentityBase is the base URL used for a particular provider's identity - // service - it will be used when issuing authenticatation requests. It - // should point to the root resource of the identity service, not a specific - // identity version. - IdentityBase string - - // IdentityEndpoint is the identity endpoint. This may be a specific version - // of the identity service. If this is the case, this endpoint is used rather - // than querying versions first. - IdentityEndpoint string - - // TokenID is the ID of the most recently issued valid token. - // NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application. - // To safely read or write this value, call `Token` or `SetToken`, respectively - TokenID string - - // EndpointLocator describes how this provider discovers the endpoints for - // its constituent services. - EndpointLocator EndpointLocator - - // HTTPClient allows users to interject arbitrary http, https, or other transit behaviors. - HTTPClient http.Client - - // UserAgent represents the User-Agent header in the HTTP request. - UserAgent UserAgent - - // ReauthFunc is the function used to re-authenticate the user if the request - // fails with a 401 HTTP response code. This a needed because there may be multiple - // 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 - reauthingErr error - done *sync.Cond -} - -// AuthenticatedHeaders returns a map of HTTP headers that are common for all -// 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.Lock() - for client.reauthmut.reauthing { - client.reauthmut.done.Wait() - } - client.reauthmut.Unlock() - } - t := client.Token() - if t == "" { - return - } - return map[string]string{"X-Auth-Token": t} -} - -// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token. -// If the application's ProviderClient is not used concurrently, this doesn't need to be called. -func (client *ProviderClient) UseTokenLock() { - client.mut = new(sync.RWMutex) - 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 { - if client.mut != nil { - client.mut.RLock() - defer client.mut.RUnlock() - } - return client.TokenID -} - -// SetToken safely sets the value of the auth token in the ProviderClient. Applications may -// 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 -} - -// 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.reauthmut == nil { - return client.ReauthFunc() - } - - 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 { - err = client.ReauthFunc() - } - - client.reauthmut.Lock() - client.reauthmut.reauthing = false - client.reauthmut.reauthingErr = err - client.reauthmut.done.Broadcast() - client.reauthmut.Unlock() - return -} - -// RequestOpts customizes the behavior of the provider.Request() method. -type RequestOpts struct { - // JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The - // content type of the request will default to "application/json" unless overridden by MoreHeaders. - // It's an error to specify both a JSONBody and a RawBody. - JSONBody interface{} - // RawBody contains an io.Reader that will be consumed by the request directly. No content-type - // will be set unless one is provided explicitly by MoreHeaders. - RawBody io.Reader - // JSONResponse, if provided, will be populated with the contents of the response body parsed as - // JSON. - JSONResponse interface{} - // OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If - // the response has a different code, an error will be returned. - OkCodes []int - // MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is - // provided with a blank value (""), that header will be *omitted* instead: use this to suppress - // the default Accept header or an inferred Content-Type, for example. - MoreHeaders map[string]string - // ErrorContext specifies the resource error type to return if an error is encountered. - // This lets resources override default error messages based on the response status code. - ErrorContext error -} - -var applicationJSON = "application/json" - -// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication -// header will automatically be provided. -func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) { - var body io.Reader - var contentType *string - - // Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided - // io.ReadSeeker as-is. Default the content-type to application/json. - if options.JSONBody != nil { - if options.RawBody != nil { - return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()") - } - - rendered, err := json.Marshal(options.JSONBody) - if err != nil { - return nil, err - } - - body = bytes.NewReader(rendered) - contentType = &applicationJSON - } - - if options.RawBody != nil { - body = options.RawBody - } - - // Construct the http.Request. - req, err := http.NewRequest(method, url, body) - 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. - if contentType != nil { - req.Header.Set("Content-Type", *contentType) - } - req.Header.Set("Accept", applicationJSON) - - // Set the User-Agent header - req.Header.Set("User-Agent", client.UserAgent.Join()) - - if options.MoreHeaders != nil { - for k, v := range options.MoreHeaders { - if v != "" { - req.Header.Set(k, v) - } else { - req.Header.Del(k) - } - } - } - - // get latest token from client - for k, v := range client.AuthenticatedHeaders() { - req.Header.Set(k, v) - } - - // Set connection parameter to close the connection immediately when we've got the response - req.Close = true - - prereqtok := req.Header.Get("X-Auth-Token") - - // Issue the request. - resp, err := client.HTTPClient.Do(req) - if err != nil { - return nil, err - } - - // Allow default OkCodes if none explicitly set - okc := options.OkCodes - if okc == nil { - okc = defaultOkCodes(method) - } - - // Validate the HTTP response status. - var ok bool - for _, code := range okc { - if resp.StatusCode == code { - ok = true - break - } - } - - if !ok { - body, _ := ioutil.ReadAll(resp.Body) - resp.Body.Close() - respErr := ErrUnexpectedResponseCode{ - URL: url, - Method: method, - Expected: options.OkCodes, - Actual: resp.StatusCode, - Body: body, - } - - errType := options.ErrorContext - switch resp.StatusCode { - case http.StatusBadRequest: - err = ErrDefault400{respErr} - if error400er, ok := errType.(Err400er); ok { - err = error400er.Error400(respErr) - } - case http.StatusUnauthorized: - if client.ReauthFunc != nil { - err = client.Reauthenticate(prereqtok) - if err != nil { - e := &ErrUnableToReauthenticate{} - e.ErrOriginal = respErr - return nil, e - } - if options.RawBody != nil { - if seeker, ok := options.RawBody.(io.Seeker); ok { - seeker.Seek(0, 0) - } - } - resp, err = client.Request(method, url, options) - if err != nil { - switch err.(type) { - case *ErrUnexpectedResponseCode: - e := &ErrErrorAfterReauthentication{} - e.ErrOriginal = err.(*ErrUnexpectedResponseCode) - return nil, e - default: - e := &ErrErrorAfterReauthentication{} - e.ErrOriginal = err - return nil, e - } - } - return resp, nil - } - err = ErrDefault401{respErr} - if error401er, ok := errType.(Err401er); ok { - err = error401er.Error401(respErr) - } - case http.StatusForbidden: - err = ErrDefault403{respErr} - if error403er, ok := errType.(Err403er); ok { - err = error403er.Error403(respErr) - } - case http.StatusNotFound: - err = ErrDefault404{respErr} - if error404er, ok := errType.(Err404er); ok { - err = error404er.Error404(respErr) - } - case http.StatusMethodNotAllowed: - err = ErrDefault405{respErr} - if error405er, ok := errType.(Err405er); ok { - err = error405er.Error405(respErr) - } - case http.StatusRequestTimeout: - err = ErrDefault408{respErr} - 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 { - err = error429er.Error429(respErr) - } - case http.StatusInternalServerError: - err = ErrDefault500{respErr} - if error500er, ok := errType.(Err500er); ok { - err = error500er.Error500(respErr) - } - case http.StatusServiceUnavailable: - err = ErrDefault503{respErr} - if error503er, ok := errType.(Err503er); ok { - err = error503er.Error503(respErr) - } - } - - if err == nil { - err = respErr - } - - return resp, err - } - - // Parse the response body as JSON, if requested to do so. - if options.JSONResponse != nil { - defer resp.Body.Close() - if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil { - return nil, err - } - } - - return resp, nil -} - -func defaultOkCodes(method string) []int { - switch { - case method == "GET": - return []int{200} - case method == "POST": - return []int{201, 202} - case method == "PUT": - return []int{201, 202} - case method == "PATCH": - return []int{200, 202, 204} - case method == "DELETE": - return []int{202, 204} - } - - return []int{} -} diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go deleted file mode 100644 index 94a16bff0b4..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/results.go +++ /dev/null @@ -1,448 +0,0 @@ -package gophercloud - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "reflect" - "strconv" - "time" -) - -/* -Result is an internal type to be used by individual resource packages, but its -methods will be available on a wide variety of user-facing embedding types. - -It acts as a base struct that other Result types, returned from request -functions, can embed for convenience. All Results capture basic information -from the HTTP transaction that was performed, including the response body, -HTTP headers, and any errors that happened. - -Generally, each Result type will have an Extract method that can be used to -further interpret the result's payload in a specific context. Extensions or -providers can then provide additional extraction functions to pull out -provider- or extension-specific information as well. -*/ -type Result struct { - // Body is the payload of the HTTP response from the server. In most cases, - // this will be the deserialized JSON structure. - Body interface{} - - // Header contains the HTTP header structure from the original response. - Header http.Header - - // Err is an error that occurred during the operation. It's deferred until - // extraction to make it easier to chain the Extract call. - Err error -} - -// ExtractInto allows users to provide an object into which `Extract` will extract -// the `Result.Body`. This would be useful for OpenStack providers that have -// different fields in the response object than OpenStack proper. -func (r Result) ExtractInto(to interface{}) error { - if r.Err != nil { - return r.Err - } - - if reader, ok := r.Body.(io.Reader); ok { - if readCloser, ok := reader.(io.Closer); ok { - defer readCloser.Close() - } - return json.NewDecoder(reader).Decode(to) - } - - b, err := json.Marshal(r.Body) - if err != nil { - return err - } - err = json.Unmarshal(b, to) - - return err -} - -func (r Result) extractIntoPtr(to interface{}, label string) error { - if label == "" { - return r.ExtractInto(&to) - } - - var m map[string]interface{} - err := r.ExtractInto(&m) - if err != nil { - return err - } - - b, err := json.Marshal(m[label]) - if err != nil { - return err - } - - toValue := reflect.ValueOf(to) - if toValue.Kind() == reflect.Ptr { - toValue = toValue.Elem() - } - - switch toValue.Kind() { - case reflect.Slice: - typeOfV := toValue.Type().Elem() - if typeOfV.Kind() == reflect.Struct { - if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { - newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) - - 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) - } - } - - // "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) - } - } - case reflect.Struct: - typeOfV := toValue.Type() - if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { - for i := 0; i < toValue.NumField(); i++ { - toField := toValue.Field(i) - if toField.Kind() == reflect.Struct { - s := toField.Addr().Interface() - err = json.NewDecoder(bytes.NewReader(b)).Decode(s) - if err != nil { - return err - } - } - } - } - } - - err = json.Unmarshal(b, &to) - return err -} - -// ExtractIntoStructPtr will unmarshal the Result (r) into the provided -// interface{} (to). -// -// NOTE: For internal use only -// -// `to` must be a pointer to an underlying struct type -// -// If provided, `label` will be filtered out of the response -// body prior to `r` being unmarshalled into `to`. -func (r Result) ExtractIntoStructPtr(to interface{}, label string) error { - if r.Err != nil { - return r.Err - } - - t := reflect.TypeOf(to) - if k := t.Kind(); k != reflect.Ptr { - return fmt.Errorf("Expected pointer, got %v", k) - } - switch t.Elem().Kind() { - case reflect.Struct: - return r.extractIntoPtr(to, label) - default: - return fmt.Errorf("Expected pointer to struct, got: %v", t) - } -} - -// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided -// interface{} (to). -// -// NOTE: For internal use only -// -// `to` must be a pointer to an underlying slice type -// -// If provided, `label` will be filtered out of the response -// body prior to `r` being unmarshalled into `to`. -func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error { - if r.Err != nil { - return r.Err - } - - t := reflect.TypeOf(to) - if k := t.Kind(); k != reflect.Ptr { - return fmt.Errorf("Expected pointer, got %v", k) - } - switch t.Elem().Kind() { - case reflect.Slice: - return r.extractIntoPtr(to, label) - default: - return fmt.Errorf("Expected pointer to slice, got: %v", t) - } -} - -// PrettyPrintJSON creates a string containing the full response body as -// pretty-printed JSON. It's useful for capturing test fixtures and for -// debugging extraction bugs. If you include its output in an issue related to -// a buggy extraction function, we will all love you forever. -func (r Result) PrettyPrintJSON() string { - pretty, err := json.MarshalIndent(r.Body, "", " ") - if err != nil { - panic(err.Error()) - } - return string(pretty) -} - -// ErrResult is an internal type to be used by individual resource packages, but -// its methods will be available on a wide variety of user-facing embedding -// types. -// -// It represents results that only contain a potential error and -// nothing else. Usually, if the operation executed successfully, the Err field -// will be nil; otherwise it will be stocked with a relevant error. Use the -// ExtractErr method -// to cleanly pull it out. -type ErrResult struct { - Result -} - -// ExtractErr is a function that extracts error information, or nil, from a result. -func (r ErrResult) ExtractErr() error { - return r.Err -} - -/* -HeaderResult is an internal type to be used by individual resource packages, but -its methods will be available on a wide variety of user-facing embedding types. - -It represents a result that only contains an error (possibly nil) and an -http.Header. This is used, for example, by the objectstorage packages in -openstack, because most of the operations don't return response bodies, but do -have relevant information in headers. -*/ -type HeaderResult struct { - Result -} - -// ExtractInto allows users to provide an object into which `Extract` will -// extract the http.Header headers of the result. -func (r HeaderResult) ExtractInto(to interface{}) error { - if r.Err != nil { - return r.Err - } - - tmpHeaderMap := map[string]string{} - for k, v := range r.Header { - if len(v) > 0 { - tmpHeaderMap[k] = v[0] - } - } - - b, err := json.Marshal(tmpHeaderMap) - if err != nil { - return err - } - err = json.Unmarshal(b, to) - - return err -} - -// RFC3339Milli describes a common time format used by some API responses. -const RFC3339Milli = "2006-01-02T15:04:05.999999Z" - -type JSONRFC3339Milli time.Time - -func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error { - b := bytes.NewBuffer(data) - dec := json.NewDecoder(b) - var s string - if err := dec.Decode(&s); err != nil { - return err - } - t, err := time.Parse(RFC3339Milli, s) - if err != nil { - return err - } - *jt = JSONRFC3339Milli(t) - return nil -} - -const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999" - -type JSONRFC3339MilliNoZ time.Time - -func (jt *JSONRFC3339MilliNoZ) 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(RFC3339MilliNoZ, s) - if err != nil { - return err - } - *jt = JSONRFC3339MilliNoZ(t) - return nil -} - -type JSONRFC1123 time.Time - -func (jt *JSONRFC1123) 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(time.RFC1123, s) - if err != nil { - return err - } - *jt = JSONRFC1123(t) - return nil -} - -type JSONUnix time.Time - -func (jt *JSONUnix) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - if s == "" { - return nil - } - unix, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return err - } - t = time.Unix(unix, 0) - *jt = JSONUnix(t) - return nil -} - -// RFC3339NoZ is the time format used in Heat (Orchestration). -const RFC3339NoZ = "2006-01-02T15:04:05" - -type JSONRFC3339NoZ time.Time - -func (jt *JSONRFC3339NoZ) 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(RFC3339NoZ, s) - if err != nil { - return err - } - *jt = JSONRFC3339NoZ(t) - 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. - -It's a response substructure common to many paginated collection results that is -used to point to related pages. Usually, the one we care about is the one with -Rel field set to "next". -*/ -type Link struct { - Href string `json:"href"` - Rel string `json:"rel"` -} - -/* -ExtractNextURL is an internal function useful for packages of collection -resources that are paginated in a certain way. - -It attempts to extract the "next" URL from slice of Link structs, or -"" if no such URL is present. -*/ -func ExtractNextURL(links []Link) (string, error) { - var url string - - for _, l := range links { - if l.Rel == "next" { - url = l.Href - } - } - - if url == "" { - return "", nil - } - - return url, nil -} diff --git a/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go deleted file mode 100644 index f222f05a66d..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/service_client.go +++ /dev/null @@ -1,154 +0,0 @@ -package gophercloud - -import ( - "io" - "net/http" - "strings" -) - -// ServiceClient stores details required to interact with a specific service API implemented by a provider. -// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient. -type ServiceClient struct { - // ProviderClient is a reference to the provider that implements this service. - *ProviderClient - - // Endpoint is the base URL of the service's API, acquired from a service catalog. - // It MUST end with a /. - Endpoint string - - // ResourceBase is the base URL shared by the resources within a service's API. It should include - // the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used - // as-is, instead. - ResourceBase string - - // This is the service client type (e.g. compute, sharev2). - // NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS. - // It is only exported because it gets set in a different package. - Type string - - // 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 /. -func (client *ServiceClient) ResourceBaseURL() string { - if client.ResourceBase != "" { - return client.ResourceBase - } - return client.Endpoint -} - -// ServiceURL constructs a URL for a resource belonging to this provider. -func (client *ServiceClient) ServiceURL(parts ...string) string { - return client.ResourceBaseURL() + strings.Join(parts, "/") -} - -func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) { - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - - if client.Microversion != "" { - client.setMicroversionHeader(opts) - } -} - -// Get calls `Request` with the "GET" HTTP verb. -func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = new(RequestOpts) - } - client.initReqOpts(url, nil, JSONResponse, opts) - return client.Request("GET", url, opts) -} - -// Post calls `Request` with the "POST" HTTP verb. -func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = new(RequestOpts) - } - client.initReqOpts(url, JSONBody, JSONResponse, opts) - return client.Request("POST", url, opts) -} - -// Put calls `Request` with the "PUT" HTTP verb. -func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = new(RequestOpts) - } - client.initReqOpts(url, JSONBody, JSONResponse, opts) - return client.Request("PUT", url, opts) -} - -// Patch calls `Request` with the "PATCH" HTTP verb. -func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = new(RequestOpts) - } - client.initReqOpts(url, JSONBody, JSONResponse, opts) - return client.Request("PATCH", url, opts) -} - -// Delete calls `Request` with the "DELETE" HTTP verb. -func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = new(RequestOpts) - } - client.initReqOpts(url, nil, nil, opts) - 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": - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - case "sharev2": - 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/util.go b/vendor/github.com/gophercloud/gophercloud/util.go deleted file mode 100644 index 68f9a5d3eca..00000000000 --- a/vendor/github.com/gophercloud/gophercloud/util.go +++ /dev/null @@ -1,102 +0,0 @@ -package gophercloud - -import ( - "fmt" - "net/url" - "path/filepath" - "strings" - "time" -) - -// WaitFor polls a predicate function, once per second, up to a timeout limit. -// This is useful to wait for a resource to transition to a certain state. -// To handle situations when the predicate might hang indefinitely, the -// predicate will be prematurely cancelled after the timeout. -// Resource packages will wrap this in a more convenient function that's -// specific to a certain resource, but it can also be useful on its own. -func WaitFor(timeout int, predicate func() (bool, error)) error { - type WaitForResult struct { - Success bool - Error error - } - - start := time.Now().Unix() - - for { - // If a timeout is set, and that's been exceeded, shut it down. - if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) { - return fmt.Errorf("A timeout occurred") - } - - time.Sleep(1 * time.Second) - - var result WaitForResult - ch := make(chan bool, 1) - go func() { - defer close(ch) - satisfied, err := predicate() - result.Success = satisfied - result.Error = err - }() - - select { - case <-ch: - if result.Error != nil { - return result.Error - } - if result.Success { - return nil - } - // If the predicate has not finished by the timeout, cancel it. - case <-time.After(time.Duration(timeout) * time.Second): - return fmt.Errorf("A timeout occurred") - } - } -} - -// NormalizeURL is an internal function to be used by provider clients. -// -// It ensures that each endpoint URL has a closing `/`, as expected by -// ServiceClient's methods. -func NormalizeURL(url string) string { - if !strings.HasSuffix(url, "/") { - return url + "/" - } - return url -} - -// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as -// a reference in the filesystem, if necessary. basePath is assumed to contain -// either '.' when first used, or the file:// type fqdn of the parent resource. -// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml -func NormalizePathURL(basePath, rawPath string) (string, error) { - u, err := url.Parse(rawPath) - if err != nil { - return "", err - } - // if a scheme is defined, it must be a fqdn already - if u.Scheme != "" { - return u.String(), nil - } - // if basePath is a url, then child resources are assumed to be relative to it - bu, err := url.Parse(basePath) - if err != nil { - return "", err - } - var basePathSys, absPathSys string - if bu.Scheme != "" { - basePathSys = filepath.FromSlash(bu.Path) - absPathSys = filepath.Join(basePathSys, rawPath) - bu.Path = filepath.ToSlash(absPathSys) - return bu.String(), nil - } - - absPathSys = filepath.Join(basePath, rawPath) - u.Path = filepath.ToSlash(absPathSys) - if err != nil { - return "", err - } - u.Scheme = "file" - return u.String(), nil - -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3cdc9753262..908aef11871 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -457,39 +457,6 @@ github.com/google/uuid github.com/googleapis/gax-go/v2 github.com/googleapis/gax-go/v2/apierror github.com/googleapis/gax-go/v2/apierror/internal/proto -# github.com/gophercloud/gophercloud v0.1.0 => github.com/gophercloud/gophercloud v0.1.0 -## explicit -github.com/gophercloud/gophercloud -github.com/gophercloud/gophercloud/openstack -github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions -github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes -github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes -github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes -github.com/gophercloud/gophercloud/openstack/common/extensions -github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces -github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach -github.com/gophercloud/gophercloud/openstack/compute/v2/flavors -github.com/gophercloud/gophercloud/openstack/compute/v2/images -github.com/gophercloud/gophercloud/openstack/compute/v2/servers -github.com/gophercloud/gophercloud/openstack/identity/v2/tenants -github.com/gophercloud/gophercloud/openstack/identity/v2/tokens -github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts -github.com/gophercloud/gophercloud/openstack/identity/v3/tokens -github.com/gophercloud/gophercloud/openstack/networking/v2/extensions -github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external -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/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 -github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups -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 -github.com/gophercloud/gophercloud/openstack/utils -github.com/gophercloud/gophercloud/pagination # github.com/gorilla/websocket v1.4.2 ## explicit; go 1.12 github.com/gorilla/websocket @@ -1915,7 +1882,6 @@ k8s.io/client-go/plugin/pkg/client/auth/azure k8s.io/client-go/plugin/pkg/client/auth/exec k8s.io/client-go/plugin/pkg/client/auth/gcp k8s.io/client-go/plugin/pkg/client/auth/oidc -k8s.io/client-go/plugin/pkg/client/auth/openstack k8s.io/client-go/rest k8s.io/client-go/rest/fake k8s.io/client-go/rest/watch @@ -2323,7 +2289,6 @@ k8s.io/legacy-cloud-providers/azure/metrics k8s.io/legacy-cloud-providers/azure/retry k8s.io/legacy-cloud-providers/gce k8s.io/legacy-cloud-providers/gce/gcpcredential -k8s.io/legacy-cloud-providers/openstack k8s.io/legacy-cloud-providers/vsphere k8s.io/legacy-cloud-providers/vsphere/testing k8s.io/legacy-cloud-providers/vsphere/vclib @@ -2523,7 +2488,6 @@ sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.3.0 ## explicit; go 1.12 sigs.k8s.io/yaml -# github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.1.0 # k8s.io/api => ./staging/src/k8s.io/api # k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver # k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery