Update to latest gophercloud

Change-Id: I0145b899576f76e7116152fee978bc04b05080f8
pull/564/head
Davanum Srinivas 2019-01-28 17:43:01 -05:00
parent aba3161f8a
commit 55a8dbcbfb
No known key found for this signature in database
GPG Key ID: 80D83A796103BF59
70 changed files with 2093 additions and 600 deletions

64
Godeps/Godeps.json generated
View File

@ -2101,123 +2101,127 @@
},
{
"ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/networks",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gorilla/websocket",

199
Godeps/LICENSES generated
View File

@ -65927,6 +65927,205 @@ specific language governing permissions and limitations under the License.
================================================================================
================================================================================
= vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies licensed under: =
Copyright 2012-2013 Rackspace, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
= vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416
================================================================================
================================================================================
= vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners licensed under: =

View File

@ -256,7 +256,7 @@ func (volumes *VolumesV1) deleteVolume(volumeID string) error {
func (volumes *VolumesV2) deleteVolume(volumeID string) error {
startTime := time.Now()
err := volumes_v2.Delete(volumes.blockstorage, volumeID).ExtractErr()
err := volumes_v2.Delete(volumes.blockstorage, volumeID, nil).ExtractErr()
timeTaken := time.Since(startTime).Seconds()
recordOpenstackOperationMetric("delete_v2_volume", timeTaken, err)
return err
@ -264,7 +264,7 @@ func (volumes *VolumesV2) deleteVolume(volumeID string) error {
func (volumes *VolumesV3) deleteVolume(volumeID string) error {
startTime := time.Now()
err := volumes_v3.Delete(volumes.blockstorage, volumeID).ExtractErr()
err := volumes_v3.Delete(volumes.blockstorage, volumeID, nil).ExtractErr()
timeTaken := time.Since(startTime).Seconds()
recordOpenstackOperationMetric("delete_v3_volume", timeTaken, err)
return err

View File

@ -112,31 +112,31 @@
},
{
"ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d"
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gregjones/httpcache",

View File

@ -1,2 +1,3 @@
**/*.swp
.idea
.vscode

View File

@ -7,8 +7,8 @@ install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/goimports
go:
- 1.8
- tip
- "1.10"
- "tip"
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="

View File

@ -1,3 +1,73 @@
- 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-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-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-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:
@ -7,6 +77,22 @@
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
periodic:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test

View File

@ -1,148 +0,0 @@
# Tips
## Implementing default logging and re-authentication attempts
You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
like the following and setting it as the provider client's HTTP Client (via the
`gophercloud.ProviderClient.HTTPClient` field):
```go
//...
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default Gophercloud RoundTripper to allow for logging.
type LogRoundTripper struct {
rt http.RoundTripper
numReauthAttempts int
}
// newHTTPClient return a custom HTTP client that allows for logging relevant
// information before and after the HTTP request.
func newHTTPClient() http.Client {
return http.Client{
Transport: &LogRoundTripper{
rt: http.DefaultTransport,
},
}
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
glog.Infof("Request URL: %s\n", request.URL)
response, err := lrt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if lrt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
lrt.numReauthAttempts++
}
glog.Debugf("Response Status: %s\n", response.Status)
return response, nil
}
endpoint := "https://127.0.0.1/auth"
pc := openstack.NewClient(endpoint)
pc.HTTPClient = newHTTPClient()
//...
```
## Implementing custom objects
OpenStack request/response objects may differ among variable names or types.
### Custom request objects
To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
example, to pass in
```go
type MyCreateServerOpts struct {
Name string
Size int
}
```
to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
```go
func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
return map[string]interface{}{
"name": o.Name,
"size": o.Size,
}, nil
}
```
create an instance of your custom options object, and pass it to `servers.Create`:
```go
// ...
myOpts := MyCreateServerOpts{
Name: "s1",
Size: "100",
}
server, err := servers.Create(computeClient, myOpts).Extract()
// ...
```
### Custom response objects
Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
combined to create a custom object:
```go
// ...
type MyVolume struct {
volumes.Volume
tenantattr.VolumeExt
}
var v struct {
MyVolume `json:"volume"`
}
err := volumes.Get(client, volID).ExtractInto(&v)
// ...
```
## Overriding default `UnmarshalJSON` method
For some response objects, a field may be a custom type or may be allowed to take on
different types. In these cases, overriding the default `UnmarshalJSON` method may be
necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
method on the type:
```go
// ...
type MyVolume struct {
ID string `json: "id"`
TimeCreated time.Time `json: "-"`
}
func (r *MyVolume) UnmarshalJSON(b []byte) error {
type tmp MyVolume
var s struct {
tmp
TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Volume(s.tmp)
r.TimeCreated = time.Time(s.CreatedAt)
return err
}
// ...
```

View File

@ -1,32 +0,0 @@
# Compute
## Floating IPs
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
## Security Groups
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
## Servers
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
```golang
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
res := servers.Reboot(client, server.ID, rebootOpts)
```
# Identity
## V3
### Tokens
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
`time.Time`

View File

@ -127,7 +127,7 @@ new resource in the `server` variable (a
## Advanced Usage
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works.
## Backwards-Compatibility Guarantees
@ -140,7 +140,7 @@ See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues).
to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues).
## Thank You
@ -148,12 +148,12 @@ We'd like to extend special thanks and appreciation to the following:
### OpenLab
<a href="http://openlabtesting.org/"><img src="assets/openlab.png" width="600px"></a>
<a href="http://openlabtesting.org/"><img src="./docs/assets/openlab.png" width="600px"></a>
OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases.
### VEXXHOST
<a href="https://vexxhost.com/"><img src="assets/vexxhost.png" width="600px"></a>
<a href="https://vexxhost.com/"><img src="./docs/assets/vexxhost.png" width="600px"></a>
VEXXHOST is providing their services to assist with the development and testing of Gophercloud.

View File

@ -1,79 +0,0 @@
## On Pull Requests
- Please make sure to read our [contributing guide](/.github/CONTRIBUTING.md).
- Before you start a PR there needs to be a Github issue and a discussion about it
on that issue with a core contributor, even if it's just a 'SGTM'.
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
- A PR's description must contain link(s) to the line(s) in the OpenStack
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
are not good enough. The link(s) should be to a non-`master` branch. For example,
a pull request implementing the creation of a Neutron v2 subnet might put the
following link in the description:
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
From that link, a reviewer (or user) can verify the fields in the request/response
objects in the PR.
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with
one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM]
prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the
[Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will
let reviewers know it is ready to review.
- A PR should be small. Even if you intend on implementing an entire
service, a PR should only be one route of that service
(e.g. create server or get server, but not both).
- Unless explicitly asked, do not squash commits in the middle of a review; only
append. It makes it difficult for the reviewer to see what's changed from one
review to the next.
- See [#583](https://github.com/gophercloud/gophercloud/issues/583) as an example of a
well-formatted issue which contains all relevant information we need to review and approve.
## On Code
- In re design: follow as closely as is reasonable the code already in the library.
Most operations (e.g. create, delete) admit the same design.
- Unit tests and acceptance (integration) tests must be written to cover each PR.
Tests for operations with several options (e.g. list, create) should include all
the options in the tests. This will allow users to verify an operation on their
own infrastructure and see an example of usage.
- If in doubt, ask in-line on the PR.
### File Structure
- The following should be used in most cases:
- `requests.go`: contains all the functions that make HTTP requests and the
types associated with the HTTP request (parameters for URL, body, etc)
- `results.go`: contains all the response objects and their methods
- `urls.go`: contains the endpoints to which the requests are made
### Naming
- For methods on a type in `results.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a
`pagination.Pager`, should be named returns of the name `r`.
- Functions in `requests.go` that accept request bodies should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Map`
(eg `ToPortCreateMap`).
- Functions in `requests.go` that accept query strings should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Query`
(eg `ToServerListQuery`).

View File

@ -81,6 +81,23 @@ type AuthOptions struct {
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
// Scope determines the scoping of the authentication request.
Scope *AuthScope `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
}
// AuthScope allows a created token to be limited to a specific domain or project.
type AuthScope struct {
ProjectID string
ProjectName string
DomainID string
DomainName string
}
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
@ -131,7 +148,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
type userReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Password string `json:"password"`
Password string `json:"password,omitempty"`
Domain *domainReq `json:"domain,omitempty"`
}
@ -143,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
ID string `json:"id"`
}
type applicationCredentialReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
User *userReq `json:"user,omitempty"`
Secret *string `json:"secret,omitempty"`
}
type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
}
type authReq struct {
@ -183,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID,
}
} else if opts.ApplicationCredentialID != "" {
// Configure the request for ApplicationCredentialID authentication.
// https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
// There are three kinds of possible application_credential requests
// 1. application_credential id + secret
// 2. application_credential name + secret + user_id
// 3. application_credential name + secret + username + domain_id / domain_name
if opts.ApplicationCredentialSecret == "" {
return nil, ErrAppCredMissingSecret{}
}
req.Auth.Identity.Methods = []string{"application_credential"}
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
ID: &opts.ApplicationCredentialID,
Secret: &opts.ApplicationCredentialSecret,
}
} else if opts.ApplicationCredentialName != "" {
if opts.ApplicationCredentialSecret == "" {
return nil, ErrAppCredMissingSecret{}
}
var userRequest *userReq
if opts.UserID != "" {
// UserID could be used without the domain information
userRequest = &userReq{
ID: &opts.UserID,
}
}
if userRequest == nil && opts.Username == "" {
// Make sure that Username or UserID are provided
return nil, ErrUsernameOrUserID{}
}
if userRequest == nil && opts.DomainID != "" {
userRequest = &userReq{
Name: &opts.Username,
Domain: &domainReq{ID: &opts.DomainID},
}
}
if userRequest == nil && opts.DomainName != "" {
userRequest = &userReq{
Name: &opts.Username,
Domain: &domainReq{Name: &opts.DomainName},
}
}
// Make sure that DomainID or DomainName are provided among Username
if userRequest == nil {
return nil, ErrDomainIDOrDomainName{}
}
req.Auth.Identity.Methods = []string{"application_credential"}
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
Name: &opts.ApplicationCredentialName,
User: userRequest,
Secret: &opts.ApplicationCredentialSecret,
}
} else {
// If no password or token ID are available, authentication can't continue.
// If no password or token ID or ApplicationCredential are available, authentication can't continue.
return nil, ErrMissingPassword{}
}
} else {
@ -263,85 +348,83 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
}
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
var scope struct {
ProjectID string
ProjectName string
DomainID string
DomainName string
}
if opts.TenantID != "" {
scope.ProjectID = opts.TenantID
} else {
if opts.TenantName != "" {
scope.ProjectName = opts.TenantName
scope.DomainID = opts.DomainID
scope.DomainName = opts.DomainName
// For backwards compatibility.
// If AuthOptions.Scope was not set, try to determine it.
// This works well for common scenarios.
if opts.Scope == nil {
opts.Scope = new(AuthScope)
if opts.TenantID != "" {
opts.Scope.ProjectID = opts.TenantID
} else {
if opts.TenantName != "" {
opts.Scope.ProjectName = opts.TenantName
opts.Scope.DomainID = opts.DomainID
opts.Scope.DomainName = opts.DomainName
}
}
}
if scope.ProjectName != "" {
if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if scope.DomainID == "" && scope.DomainName == "" {
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
if scope.ProjectID != "" {
if opts.Scope.ProjectID != "" {
return nil, ErrScopeProjectIDOrProjectName{}
}
if scope.DomainID != "" {
if opts.Scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"id": &scope.DomainID},
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
},
}, nil
}
if scope.DomainName != "" {
if opts.Scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"name": &scope.DomainName},
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
},
}, nil
}
} else if scope.ProjectID != "" {
} else if opts.Scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if scope.DomainID != "" {
if opts.Scope.DomainID != "" {
return nil, ErrScopeProjectIDAlone{}
}
if scope.DomainName != "" {
if opts.Scope.DomainName != "" {
return nil, ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &scope.ProjectID,
"id": &opts.Scope.ProjectID,
},
}, nil
} else if scope.DomainID != "" {
} else if opts.Scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if scope.DomainName != "" {
if opts.Scope.DomainName != "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &scope.DomainID,
"id": &opts.Scope.DomainID,
},
}, nil
} else if scope.DomainName != "" {
} else if opts.Scope.DomainName != "" {
// DomainName
return map[string]interface{}{
"domain": map[string]interface{}{
"name": &scope.DomainName,
"name": &opts.Scope.DomainName,
},
}, nil
}

View File

@ -41,7 +41,7 @@ pass in the parent provider, like so:
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts)
client, err := openstack.NewComputeV2(provider, opts)
Resources

View File

@ -1,6 +1,9 @@
package gophercloud
import "fmt"
import (
"fmt"
"strings"
)
// BaseError is an error type that all other error types embed.
type BaseError struct {
@ -43,6 +46,33 @@ func (e ErrInvalidInput) Error() string {
return e.choseErrString()
}
// ErrMissingEnvironmentVariable is the error when environment variable is required
// in a particular situation but not provided by the user
type ErrMissingEnvironmentVariable struct {
BaseError
EnvironmentVariable string
}
func (e ErrMissingEnvironmentVariable) Error() string {
e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable)
return e.choseErrString()
}
// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables
// is required in a particular situation but not provided by the user
type ErrMissingAnyoneOfEnvironmentVariables struct {
BaseError
EnvironmentVariables []string
}
func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
e.DefaultErrString = fmt.Sprintf(
"Missing one of the following environment variables [%s]",
strings.Join(e.EnvironmentVariables, ", "),
)
return e.choseErrString()
}
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
// those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct {
@ -108,7 +138,11 @@ type ErrDefault503 struct {
}
func (e ErrDefault400) Error() string {
return "Invalid request due to incorrect syntax or missing required parameters."
e.DefaultErrString = fmt.Sprintf(
"Bad request with: [%s %s], error message: %s",
e.Method, e.URL, e.Body,
)
return e.choseErrString()
}
func (e ErrDefault401) Error() string {
return "Authentication failed"
@ -417,3 +451,10 @@ type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope"
}
// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name
type ErrAppCredMissingSecret struct{ BaseError }
func (e ErrAppCredMissingSecret) Error() string {
return "You must provide an Application Credential Secret"
}

View File

@ -38,6 +38,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID")
applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME")
applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET")
// If OS_PROJECT_ID is set, overwrite tenantID with the value.
if v := os.Getenv("OS_PROJECT_ID"); v != "" {
@ -50,29 +53,61 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
}
if authURL == "" {
err := gophercloud.ErrMissingInput{Argument: "authURL"}
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_AUTH_URL",
}
return nilOptions, err
}
if username == "" && userID == "" {
err := gophercloud.ErrMissingInput{Argument: "username"}
if userID == "" && username == "" {
// Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set
if applicationCredentialID == "" && applicationCredentialSecret == "" {
err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
}
return nilOptions, err
}
}
if password == "" && applicationCredentialID == "" && applicationCredentialName == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_PASSWORD",
}
return nilOptions, err
}
if password == "" {
err := gophercloud.ErrMissingInput{Argument: "password"}
if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET",
}
return nilOptions, err
}
if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" {
if userID == "" && username == "" {
return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
}
}
if username != "" && domainID == "" && domainName == "" {
return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"},
}
}
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
ApplicationCredentialID: applicationCredentialID,
ApplicationCredentialName: applicationCredentialName,
ApplicationCredentialSecret: applicationCredentialSecret,
}
return ao, nil

View File

@ -110,8 +110,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"display_name,omitempty"`
Description string `json:"display_description,omitempty"`
Name *string `json:"display_name,omitempty"`
Description *string `json:"display_description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
@ -139,7 +139,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}

View File

@ -61,9 +61,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// DeleteOptsBuilder allows extensions to add additional parameters to the
// Delete request.
type DeleteOptsBuilder interface {
ToVolumeDeleteQuery() (string, error)
}
// DeleteOpts contains options for deleting a Volume. This object is passed to
// the volumes.Delete function.
type DeleteOpts struct {
// Delete all snapshots of this volume as well.
Cascade bool `q:"cascade"`
}
// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) {
url := deleteURL(client, id)
if opts != nil {
query, err := opts.ToVolumeDeleteQuery()
if err != nil {
r.Err = err
return
}
url += query
}
_, r.Err = client.Delete(url, nil)
return
}
@ -98,6 +126,19 @@ type ListOpts struct {
// TenantID will filter by a specific tenant/project ID.
// Setting AllTenants is required for this.
TenantID string `q:"project_id"`
// Comma-separated list of sort keys and optional sort directions in the
// form of <key>[:<direction>].
Sort string `q:"sort"`
// Requests a page size of items.
Limit int `q:"limit"`
// Used in conjunction with limit to return a slice of items.
Offset int `q:"offset"`
// The ID of the last-seen item.
Marker string `q:"marker"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
@ -118,7 +159,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return VolumePage{pagination.SinglePageBase(r)}
return VolumePage{pagination.LinkedPageBase{PageResult: r}}
})
}
@ -132,8 +173,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
@ -161,7 +202,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}

View File

@ -98,7 +98,7 @@ func (r *Volume) UnmarshalJSON(b []byte) error {
// VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct {
pagination.SinglePageBase
pagination.LinkedPageBase
}
// IsEmpty returns true if a ListResult contains no Volumes.
@ -107,6 +107,19 @@ func (r VolumePage) IsEmpty() (bool, error) {
return len(volumes) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (r VolumePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"volumes_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s []Volume

View File

@ -38,6 +38,8 @@ type CreateOpts struct {
ImageID string `json:"imageRef,omitempty"`
// The associated volume type
VolumeType string `json:"volume_type,omitempty"`
// Multiattach denotes if the volume is multi-attach capable.
Multiattach bool `json:"multiattach,omitempty"`
}
// ToVolumeCreateMap assembles a request body based on the contents of a
@ -61,9 +63,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// DeleteOptsBuilder allows extensions to add additional parameters to the
// Delete request.
type DeleteOptsBuilder interface {
ToVolumeDeleteQuery() (string, error)
}
// DeleteOpts contains options for deleting a Volume. This object is passed to
// the volumes.Delete function.
type DeleteOpts struct {
// Delete all snapshots of this volume as well.
Cascade bool `q:"cascade"`
}
// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) {
url := deleteURL(client, id)
if opts != nil {
query, err := opts.ToVolumeDeleteQuery()
if err != nil {
r.Err = err
return
}
url += query
}
_, r.Err = client.Delete(url, nil)
return
}
@ -145,8 +175,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
@ -174,7 +204,12 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}

View File

@ -2,10 +2,7 @@ package openstack
import (
"fmt"
"net/url"
"reflect"
"regexp"
"strings"
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
@ -38,21 +35,11 @@ A basic example of using this would be:
client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
*/
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
u, err := url.Parse(endpoint)
base, err := utils.BaseEndpoint(endpoint)
if err != nil {
return nil, err
}
u.RawQuery, u.Fragment = "", ""
var base string
versionRe := regexp.MustCompile("v[0-9.]+/?")
if version := versionRe.FindString(u.Path); version != "" {
base = strings.Replace(u.String(), version, "", -1)
} else {
base = u.String()
}
endpoint = gophercloud.NormalizeURL(endpoint)
base = gophercloud.NormalizeURL(base)
@ -163,6 +150,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once
tac := *client
tac.IsThrowaway = true
tac.ReauthFunc = nil
tac.TokenID = ""
tao := options
@ -219,6 +207,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once
tac := *client
tac.IsThrowaway = true
tac.ReauthFunc = nil
tac.TokenID = ""
var tao tokens3.AuthOptionsBuilder
@ -287,11 +276,17 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
// Ensure endpoint still has a suffix of v3.
// This is because EndpointLocator might have found a versionless
// endpoint and requests will fail unless targeted at /v3.
if !strings.HasSuffix(endpoint, "v3/") {
endpoint = endpoint + "v3/"
// endpoint or the published endpoint is still /v2.0. In both
// cases, we need to fix the endpoint to point to /v3.
base, err := utils.BaseEndpoint(endpoint)
if err != nil {
return nil, err
}
base = gophercloud.NormalizeURL(base)
endpoint = base + "v3/"
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
@ -400,3 +395,35 @@ func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "clustering")
}
// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
// service.
func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "messaging")
sc.MoreHeaders = map[string]string{"Client-ID": clientID}
return sc, err
}
// NewContainerV1 creates a ServiceClient that may be used with v1 container package
func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "container")
}
// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
// manager service.
func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "key-manager")
sc.ResourceBase = sc.Endpoint + "v1/"
return sc, err
}
// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
// package.
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "container-infra")
}
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "workflowv2")
}

View File

@ -28,7 +28,6 @@ type CreateOptsBuilder interface {
// CreateOpts specifies parameters of a new interface attachment.
type CreateOpts struct {
// PortID is the ID of the port for which you want to create an interface.
// The NetworkID and PortID parameters are mutually exclusive.
// If you do not specify the PortID parameter, the OpenStack Networking API
@ -43,6 +42,7 @@ type CreateOpts struct {
// Slice of FixedIPs. If you request a specific FixedIP address without a
// NetworkID, the request returns a Bad Request (400) response code.
// Note: this uses the FixedIP struct, but only the IPAddress field can be used.
FixedIPs []FixedIP `json:"fixed_ips,omitempty"`
}

View File

@ -37,8 +37,10 @@ type DeleteResult struct {
}
// FixedIP represents a Fixed IP Address.
// This struct is also used when creating an attachment,
// but it is not possible to specify a SubnetID.
type FixedIP struct {
SubnetID string `json:"subnet_id"`
SubnetID string `json:"subnet_id,omitempty"`
IPAddress string `json:"ip_address"`
}

View File

@ -52,6 +52,14 @@ type ListOpts struct {
MinDisk int `q:"minDisk"`
MinRAM int `q:"minRam"`
// SortDir allows to select sort direction.
// It can be "asc" or "desc" (default).
SortDir string `q:"sort_dir"`
// SortKey allows to sort by one of the flavors attributes.
// Default is flavorid.
SortKey string `q:"sort_key"`
// Marker and Limit control paging.
// Marker instructs List where to start listing from.
Marker string `q:"marker"`

View File

@ -59,7 +59,7 @@ type Flavor struct {
RxTxFactor float64 `json:"rxtx_factor"`
// Swap is the amount of swap space, measured in MB.
Swap int `json:"swap"`
Swap int `json:"-"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `json:"vcpus"`

View File

@ -383,7 +383,7 @@ type RebootOpts struct {
}
// ToServerRebootMap builds a body for the reboot request.
func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "reboot")
}
@ -545,39 +545,6 @@ func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult)
return
}
// RescueOptsBuilder is an interface that allows extensions to override the
// default structure of a Rescue request.
type RescueOptsBuilder interface {
ToServerRescueMap() (map[string]interface{}, error)
}
// RescueOpts represents the configuration options used to control a Rescue
// option.
type RescueOpts struct {
// AdminPass is the desired administrative password for the instance in
// RESCUE mode. If it's left blank, the server will generate a password.
AdminPass string `json:"adminPass,omitempty"`
}
// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
// request body for the Rescue request.
func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "rescue")
}
// Rescue instructs the provider to place the server into RESCUE mode.
func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) {
b, err := opts.ToServerRescueMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ResetMetadataOptsBuilder allows extensions to add additional parameters to
// the Reset request.
type ResetMetadataOptsBuilder interface {
@ -756,7 +723,12 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
allPages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
allPages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
@ -789,3 +761,34 @@ func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPassw
_, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil)
return
}
// ShowConsoleOutputOptsBuilder is the interface types must satisfy in order to be
// used as ShowConsoleOutput options
type ShowConsoleOutputOptsBuilder interface {
ToServerShowConsoleOutputMap() (map[string]interface{}, error)
}
// ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder
type ShowConsoleOutputOpts struct {
// The number of lines to fetch from the end of console log.
// All lines will be returned if this is not specified.
Length int `json:"length,omitempty"`
}
// ToServerShowConsoleOutputMap formats a ShowConsoleOutputOpts structure into a request body.
func (opts ShowConsoleOutputOpts) ToServerShowConsoleOutputMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-getConsoleOutput")
}
// ShowConsoleOutput makes a request against the nova API to get console log from the server
func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowConsoleOutputOptsBuilder) (r ShowConsoleOutputResult) {
b, err := opts.ToServerShowConsoleOutputMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}

View File

@ -68,18 +68,27 @@ type ActionResult struct {
gophercloud.ErrResult
}
// RescueResult is the response from a Rescue operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type RescueResult struct {
ActionResult
}
// CreateImageResult is the response from a CreateImage operation. Call its
// ExtractImageID method to retrieve the ID of the newly created image.
type CreateImageResult struct {
gophercloud.Result
}
// ShowConsoleOutputResult represents the result of console output from a server
type ShowConsoleOutputResult struct {
gophercloud.Result
}
// Extract will return the console output from a ShowConsoleOutput request.
func (r ShowConsoleOutputResult) Extract() (string, error) {
var s struct {
Output string `json:"output"`
}
err := r.ExtractInto(&s)
return s.Output, err
}
// GetPasswordResult represent the result of a get os-server-password operation.
// Call its ExtractPassword method to retrieve the password.
type GetPasswordResult struct {
@ -134,15 +143,6 @@ func (r CreateImageResult) ExtractImageID() (string, error) {
return imageID, nil
}
// Extract interprets any RescueResult as an AdminPass, if possible.
func (r RescueResult) Extract() (string, error) {
var s struct {
AdminPass string `json:"adminPass"`
}
err := r.ExtractInto(&s)
return s.AdminPass, err
}
// Server represents a server/instance in the OpenStack cloud.
type Server struct {
// ID uniquely identifies this server amongst all other servers,

View File

@ -85,7 +85,7 @@ type UpdateOpts struct {
Name string `json:"name,omitempty"`
// Description is the description of the tenant.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// Enabled sets the tenant status to enabled or disabled.
Enabled *bool `json:"enabled,omitempty"`

View File

@ -52,19 +52,28 @@ type AuthOptions struct {
// authentication token ID.
TokenID string `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
Scope Scope `json:"-"`
}
// ToTokenV3CreateMap builds a request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
gophercloudAuthOpts := gophercloud.AuthOptions{
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
ApplicationCredentialID: opts.ApplicationCredentialID,
ApplicationCredentialName: opts.ApplicationCredentialName,
ApplicationCredentialSecret: opts.ApplicationCredentialSecret,
}
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
@ -72,72 +81,15 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
// ToTokenV3CreateMap builds a scope request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
}
if opts.Scope.ProjectID != "" {
return nil, gophercloud.ErrScopeProjectIDOrProjectName{}
}
scope := gophercloud.AuthScope(opts.Scope)
if opts.Scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
},
}, nil
}
if opts.Scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
},
}, nil
}
} else if opts.Scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if opts.Scope.DomainID != "" {
return nil, gophercloud.ErrScopeProjectIDAlone{}
}
if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &opts.Scope.ProjectID,
},
}, nil
} else if opts.Scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &opts.Scope.DomainID,
},
}, nil
} else if opts.Scope.DomainName != "" {
// DomainName
return map[string]interface{}{
"domain": map[string]interface{}{
"name": &opts.Scope.DomainName,
},
}, nil
gophercloudAuthOpts := gophercloud.AuthOptions{
Scope: &scope,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
}
return nil, nil
return gophercloudAuthOpts.ToTokenV3ScopeMap()
}
func (opts *AuthOptions) CanReauth() bool {
@ -190,7 +142,7 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
// Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{200, 204, 404},
})

View File

@ -27,6 +27,7 @@ filegroup(
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors:all-srcs",

View File

@ -10,7 +10,10 @@ go_library(
importmap = "k8s.io/kubernetes/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external",
importpath = "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks:go_default_library"],
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks:go_default_library",
],
)
filegroup(

View File

@ -4,6 +4,13 @@ extension for the OpenStack Networking service.
Example to List Networks with External Information
iTrue := true
networkListOpts := networks.ListOpts{}
listOpts := external.ListOptsExt{
ListOptsBuilder: networkListOpts,
External: &iTrue,
}
type NetworkWithExternalExt struct {
networks.Network
external.NetworkExternalExt
@ -11,7 +18,7 @@ Example to List Networks with External Information
var allNetworks []NetworkWithExternalExt
allPages, err := networks.List(networkClient, nil).AllPages()
allPages, err := networks.List(networkClient, listOpts).AllPages()
if err != nil {
panic(err)
}

View File

@ -1,9 +1,37 @@
package external
import (
"net/url"
"strconv"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)
// ListOptsExt adds the external network options to the base ListOpts.
type ListOptsExt struct {
networks.ListOptsBuilder
External *bool `q:"router:external"`
}
// ToNetworkListQuery adds the router:external option to the base network
// list options.
func (opts ListOptsExt) ToNetworkListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder)
if err != nil {
return "", err
}
params := q.Query()
if opts.External != nil {
v := strconv.FormatBool(*opts.External)
params.Add("router:external", v)
}
q = &url.URL{RawQuery: params.Encode()}
return q.String(), err
}
// CreateOptsExt is the structure used when creating new external network
// resources. It embeds networks.CreateOpts and so inherits all of its required
// and optional fields, with the addition of the External field.

View File

@ -52,7 +52,7 @@ Example to Disassociate a Floating IP with a Port
fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7"
updateOpts := floatingips.UpdateOpts{
PortID: nil,
PortID: new(string),
}
fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract()

View File

@ -12,6 +12,7 @@ import (
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
ID string `q:"id"`
Description string `q:"description"`
FloatingNetworkID string `q:"floating_network_id"`
PortID string `q:"port_id"`
FixedIP string `q:"fixed_ip_address"`
@ -24,6 +25,10 @@ type ListOpts struct {
SortDir string `q:"sort_dir"`
RouterID string `q:"router_id"`
Status string `q:"status"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// List returns a Pager which allows you to iterate over a collection of
@ -50,6 +55,7 @@ type CreateOptsBuilder interface {
// resource. The only required fields are FloatingNetworkID and PortID which
// refer to the external network and internal port respectively.
type CreateOpts struct {
Description string `json:"description,omitempty"`
FloatingNetworkID string `json:"floating_network_id" required:"true"`
FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string `json:"port_id,omitempty"`
@ -116,13 +122,23 @@ type UpdateOptsBuilder interface {
// linked to. To associate the floating IP with a new internal port, provide its
// ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct {
PortID *string `json:"port_id"`
Description *string `json:"description,omitempty"`
PortID *string `json:"port_id,omitempty"`
}
// ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
// interface
func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "floatingip")
b, err := gophercloud.BuildRequestBody(opts, "floatingip")
if err != nil {
return nil, err
}
if m := b["floatingip"].(map[string]interface{}); m["port_id"] == "" {
m["port_id"] = nil
}
return b, nil
}
// Update allows floating IP resources to be updated. Currently, the only way to

View File

@ -15,6 +15,9 @@ type FloatingIP struct {
// ID is the unique identifier for the floating IP instance.
ID string `json:"id"`
// Description for the floating IP instance.
Description string `json:"description"`
// FloatingNetworkID is the UUID of the external network where the floating
// IP is to be created.
FloatingNetworkID string `json:"floating_network_id"`
@ -42,6 +45,9 @@ type FloatingIP struct {
// RouterID is the ID of the router used for this floating IP.
RouterID string `json:"router_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
type commonResult struct {

View File

@ -13,6 +13,7 @@ import (
type ListOpts struct {
ID string `q:"id"`
Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
Distributed *bool `q:"distributed"`
Status string `q:"status"`
@ -22,6 +23,10 @@ type ListOpts struct {
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// List returns a Pager which allows you to iterate over a collection of
@ -51,6 +56,7 @@ type CreateOptsBuilder interface {
// no required values.
type CreateOpts struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
@ -97,6 +103,7 @@ type UpdateOptsBuilder interface {
// UpdateOpts contains the values used when updating a router.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`

View File

@ -16,7 +16,7 @@ type GatewayInfo struct {
// ExternalFixedIP is the IP address and subnet ID of the external gateway of a
// router.
type ExternalFixedIP struct {
IPAddress string `json:"ip_address"`
IPAddress string `json:"ip_address,omitempty"`
SubnetID string `json:"subnet_id"`
}
@ -51,6 +51,9 @@ type Router struct {
// unique.
Name string `json:"name"`
// Description for the router.
Description string `json:"description"`
// ID is the unique identifier for the router.
ID string `json:"id"`
@ -67,6 +70,9 @@ type Router struct {
// Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others.
// Used to make network resources highly available.
AvailabilityZoneHints []string `json:"availability_zone_hints"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// RouterPage is the page returned by a pager when traversing over a

View File

@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies",
importpath = "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -13,6 +13,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
],

View File

@ -10,9 +10,10 @@ type Protocol string
// Supported attributes for create/update operations.
const (
ProtocolTCP Protocol = "TCP"
ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS"
ProtocolTCP Protocol = "TCP"
ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS"
ProtocolTerminatedHTTPS Protocol = "TERMINATED_HTTPS"
)
// ListOptsBuilder allows extensions to add additional parameters to the
@ -154,10 +155,13 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents options for updating a Listener.
type UpdateOpts struct {
// Human-readable name for the Listener. Does not have to be unique.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// The ID of the default pool with which the Listener is associated.
DefaultPoolID *string `json:"default_pool_id,omitempty"`
// Human-readable description for the Listener.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// The maximum number of connections allowed for the Listener.
ConnLimit *int `json:"connection_limit,omitempty"`
@ -175,7 +179,16 @@ type UpdateOpts struct {
// ToListenerUpdateMap builds a request body from UpdateOpts.
func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "listener")
b, err := gophercloud.BuildRequestBody(opts, "listener")
if err != nil {
return nil, err
}
if m := b["listener"].(map[string]interface{}); m["default_pool_id"] == "" {
m["default_pool_id"] = nil
}
return b, nil
}
// Update is an operation which modifies the attributes of the specified

View File

@ -2,6 +2,7 @@ package listeners
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/gophercloud/gophercloud/pagination"
)
@ -54,6 +55,15 @@ type Listener struct {
// Pools are the pools which are part of this listener.
Pools []pools.Pool `json:"pools"`
// L7policies are the L7 policies which are part of this listener.
// This field seems to only be returned during a call to a load balancer's /status
// see: https://github.com/gophercloud/gophercloud/issues/1352
L7Policies []l7policies.L7Policy `json:"l7policies"`
// The provisioning status of the listener.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
}
// ListenerPage is the page returned by a pager when traversing over a

View File

@ -14,6 +14,7 @@ go_library(
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
],
)

View File

@ -67,5 +67,13 @@ Example to Get the Status of a Load Balancer
if err != nil {
panic(err)
}
Example to Get the Statistics of a Load Balancer
lbID := "d67d56a6-4a86-4688-a282-f46444705c64"
stats, err := loadbalancers.GetStats(networkClient, LBID).Extract()
if err != nil {
panic(err)
}
*/
package loadbalancers

View File

@ -141,10 +141,10 @@ type UpdateOptsBuilder interface {
// operation.
type UpdateOpts struct {
// Human-readable name for the Loadbalancer. Does not have to be unique.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// Human-readable description for the Loadbalancer.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
@ -196,3 +196,9 @@ func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult)
_, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil)
return
}
// GetStats will return the shows the current statistics of a particular LoadBalancer.
func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) {
_, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil)
return
}

View File

@ -3,6 +3,7 @@ package loadbalancers
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/gophercloud/gophercloud/pagination"
)
@ -51,6 +52,9 @@ type LoadBalancer struct {
// Listeners are the listeners related to this Loadbalancer.
Listeners []listeners.Listener `json:"listeners"`
// Pools are the pools related to this Loadbalancer.
Pools []pools.Pool `json:"pools"`
}
// StatusTree represents the status of a loadbalancer.
@ -58,6 +62,23 @@ type StatusTree struct {
Loadbalancer *LoadBalancer `json:"loadbalancer"`
}
type Stats struct {
// The currently active connections.
ActiveConnections int `json:"active_connections"`
// The total bytes received.
BytesIn int `json:"bytes_in"`
// The total bytes sent.
BytesOut int `json:"bytes_out"`
// The total requests that were unable to be fulfilled.
RequestErrors int `json:"request_errors"`
// The total connections handled.
TotalConnections int `json:"total_connections"`
}
// LoadBalancerPage is the page returned by a pager when traversing over a
// collection of load balancers.
type LoadBalancerPage struct {
@ -124,6 +145,22 @@ func (r GetStatusesResult) Extract() (*StatusTree, error) {
return s.Statuses, err
}
// StatsResult represents the result of a GetStats operation.
// Call its Extract method to interpret it as a Stats.
type StatsResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts the status of
// a Loadbalancer.
func (r StatsResult) Extract() (*Stats, error) {
var s struct {
Stats *Stats `json:"stats"`
}
err := r.ExtractInto(&s)
return s.Stats, err
}
// CreateResult represents the result of a create operation. Call its Extract
// method to interpret it as a LoadBalancer.
type CreateResult struct {

View File

@ -3,9 +3,10 @@ package loadbalancers
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lbaas"
resourcePath = "loadbalancers"
statusPath = "statuses"
rootPath = "lbaas"
resourcePath = "loadbalancers"
statusPath = "statuses"
statisticsPath = "stats"
)
func rootURL(c *gophercloud.ServiceClient) string {
@ -19,3 +20,7 @@ func resourceURL(c *gophercloud.ServiceClient, id string) string {
func statusRootURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, resourcePath, id, statusPath)
}
func statisticsRootURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, resourcePath, id, statisticsPath)
}

View File

@ -223,7 +223,7 @@ type UpdateOpts struct {
ExpectedCodes string `json:"expected_codes,omitempty"`
// The Name of the Monitor.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// The administrative state of the Monitor. A valid value is true (UP)
// or false (DOWN).

View File

@ -70,6 +70,10 @@ type Monitor struct {
// List of pools that are associated with the health monitor.
Pools []PoolID `json:"pools"`
// The provisioning status of the monitor.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
}
// MonitorPage is the page returned by a pager when traversing over a

View File

@ -13,7 +13,7 @@ Example to List Pools
panic(err)
}
allPools, err := pools.ExtractMonitors(allPages)
allPools, err := pools.ExtractPools(allPages)
if err != nil {
panic(err)
}
@ -83,12 +83,13 @@ Example to Create a Member
poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
weight := 10
createOpts := pools.CreateMemberOpts{
Name: "db",
SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9",
Address: "10.0.2.11",
ProtocolPort: 80,
Weight: 10,
Weight: &weight,
}
member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract()
@ -101,9 +102,10 @@ Example to Update a Member
poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
memberID := "64dba99f-8af8-4200-8882-e32a0660f23e"
weight := 4
updateOpts := pools.UpdateMemberOpts{
Name: "new-name",
Weight: 4,
Weight: &weight,
}
member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract()

View File

@ -154,10 +154,10 @@ type UpdateOptsBuilder interface {
// operation.
type UpdateOpts struct {
// Name of the pool.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// Human-readable description for the pool.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
@ -274,7 +274,7 @@ type CreateMemberOpts struct {
// that this member should receive from the pool. For example, a member with
// a weight of 10 receives five times as much traffic as a member with a
// weight of 2.
Weight int `json:"weight,omitempty"`
Weight *int `json:"weight,omitempty"`
// If you omit this parameter, LBaaS uses the vip_subnet_id parameter value
// for the subnet UUID.
@ -317,13 +317,13 @@ type UpdateMemberOptsBuilder interface {
// operation.
type UpdateMemberOpts struct {
// Name of the Member.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// A positive integer value that indicates the relative portion of traffic
// that this member should receive from the pool. For example, a member with
// a weight of 10 receives five times as much traffic as a member with a
// weight of 2.
Weight int `json:"weight,omitempty"`
Weight *int `json:"weight,omitempty"`
// The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).

View File

@ -92,6 +92,15 @@ type Pool struct {
// The Monitor associated with this Pool.
Monitor monitors.Monitor `json:"healthmonitor"`
// The provisioning status of the pool.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The operating status of the pool.
// This field seems to only be returned during a call to a load balancer's /status
// see: https://github.com/gophercloud/gophercloud/issues/1362
OperatingStatus string `json:"operating_status"`
}
// PoolPage is the page returned by a pager when traversing over a
@ -196,6 +205,15 @@ type Member struct {
// The unique ID for the Member.
ID string `json:"id"`
// The provisioning status of the member.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The operating status of the member.
// This field seems to only be returned during a call to a load balancer's /status
// see: https://github.com/gophercloud/gophercloud/issues/1362
OperatingStatus string `json:"operating_status"`
}
// MemberPage is the page returned by a pager when traversing over a

View File

@ -11,14 +11,19 @@ import (
// sort by a particular network attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
ID string `q:"id"`
Name string `q:"name"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
ID string `q:"id"`
Name string `q:"name"`
Description string `q:"description"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// List returns a Pager which allows you to iterate over a collection of
@ -88,7 +93,7 @@ type UpdateOpts struct {
Name string `json:"name,omitempty"`
// Describes the security group.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
}
// ToSecGroupUpdateMap builds a request body from UpdateOpts.
@ -128,7 +133,12 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, ListOpts{}).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}

View File

@ -27,6 +27,9 @@ type SecGroup struct {
// ProjectID is the project owner of the security group.
ProjectID string `json:"project_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// SecGroupPage is the page returned by a pager when traversing over a

View File

@ -14,6 +14,7 @@ type ListOpts struct {
Direction string `q:"direction"`
EtherType string `q:"ethertype"`
ID string `q:"id"`
Description string `q:"description"`
PortRangeMax int `q:"port_range_max"`
PortRangeMin int `q:"port_range_min"`
Protocol string `q:"protocol"`
@ -88,6 +89,9 @@ type CreateOpts struct {
// group rule is applied.
Direction RuleDirection `json:"direction" required:"true"`
// String description of each rule, optional
Description string `json:"description,omitempty"`
// Must be "IPv4" or "IPv6", and addresses represented in CIDR must match the
// ingress or egress rules.
EtherType RuleEtherType `json:"ethertype" required:"true"`

View File

@ -17,6 +17,9 @@ type SecGroupRule struct {
// instance. An egress rule is applied to traffic leaving the instance.
Direction string
// Descripton of the rule
Description string `json:"description"`
// Must be IPv4 or IPv6, and addresses represented in CIDR must match the
// ingress or egress rules.
EtherType string `json:"ethertype"`

View File

@ -19,6 +19,7 @@ type ListOptsBuilder interface {
type ListOpts struct {
Status string `q:"status"`
Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
@ -28,6 +29,10 @@ type ListOpts struct {
Limit int `q:"limit"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// ToNetworkListQuery formats a ListOpts into a query string.
@ -69,6 +74,7 @@ type CreateOptsBuilder interface {
type CreateOpts struct {
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Shared *bool `json:"shared,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
ProjectID string `json:"project_id,omitempty"`
@ -105,9 +111,10 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents options used to update a network.
type UpdateOpts struct {
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"`
Shared *bool `json:"shared,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Shared *bool `json:"shared,omitempty"`
}
// ToNetworkUpdateMap builds a request body from UpdateOpts.
@ -140,7 +147,12 @@ func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) {
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}

View File

@ -52,6 +52,9 @@ type Network struct {
// Human-readable name for the network. Might not be unique.
Name string `json:"name"`
// Description for the network
Description string `json:"description"`
// The administrative state of network. If false (down), the network does not
// forward packets.
AdminStateUp bool `json:"admin_state_up"`
@ -76,6 +79,9 @@ type Network struct {
// Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others.
// Used to make network resources highly available.
AvailabilityZoneHints []string `json:"availability_zone_hints"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// NetworkPage is the page returned by a pager when traversing over a

View File

@ -19,6 +19,7 @@ type ListOptsBuilder interface {
type ListOpts struct {
Status string `q:"status"`
Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"`
@ -31,6 +32,10 @@ type ListOpts struct {
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// ToPortListQuery formats a ListOpts into a query string.
@ -76,6 +81,7 @@ type CreateOptsBuilder interface {
type CreateOpts struct {
NetworkID string `json:"network_id" required:"true"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
MACAddress string `json:"mac_address,omitempty"`
FixedIPs interface{} `json:"fixed_ips,omitempty"`
@ -112,11 +118,12 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
FixedIPs interface{} `json:"fixed_ips,omitempty"`
DeviceID string `json:"device_id,omitempty"`
DeviceOwner string `json:"device_owner,omitempty"`
DeviceID *string `json:"device_id,omitempty"`
DeviceOwner *string `json:"device_owner,omitempty"`
SecurityGroups *[]string `json:"security_groups,omitempty"`
AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"`
}
@ -151,7 +158,12 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}

View File

@ -68,6 +68,9 @@ type Port struct {
// Human-readable name for the port. Might not be unique.
Name string `json:"name"`
// Describes the port.
Description string `json:"description"`
// Administrative state of port. If false (down), port does not forward
// packets.
AdminStateUp bool `json:"admin_state_up"`
@ -101,6 +104,9 @@ type Port struct {
// Identifies the list of IP addresses the port will recognize/accept
AllowedAddressPairs []AddressPair `json:"allowed_address_pairs"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// PortPage is the page returned by a pager when traversing over a collection

View File

@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["choose_version.go"],
srcs = [
"base_endpoint.go",
"choose_version.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/gophercloud/gophercloud/openstack/utils",
importpath = "github.com/gophercloud/gophercloud/openstack/utils",
visibility = ["//visibility:public"],

View File

@ -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
}

View File

@ -41,6 +41,8 @@ type Pager struct {
createPage func(r PageResult) Page
firstPage Page
Err error
// Headers supplies additional HTTP headers to populate on each paged request.
@ -89,9 +91,18 @@ func (p Pager) EachPage(handler func(Page) (bool, error)) error {
}
currentURL := p.initialURL
for {
currentPage, err := p.fetchNextPage(currentURL)
if err != nil {
return err
var currentPage Page
// if first page has already been fetched, no need to fetch it again
if p.firstPage != nil {
currentPage = p.firstPage
p.firstPage = nil
} else {
var err error
currentPage, err = p.fetchNextPage(currentURL)
if err != nil {
return err
}
}
empty, err := currentPage.IsEmpty()
@ -128,23 +139,26 @@ func (p Pager) AllPages() (Page, error) {
// body will contain the final concatenated Page body.
var body reflect.Value
// Grab a test page to ascertain the page body type.
testPage, err := p.fetchNextPage(p.initialURL)
// Grab a first page to ascertain the page body type.
firstPage, err := p.fetchNextPage(p.initialURL)
if err != nil {
return nil, err
}
// Store the page type so we can use reflection to create a new mega-page of
// that type.
pageType := reflect.TypeOf(testPage)
pageType := reflect.TypeOf(firstPage)
// if it's a single page, just return the testPage (first page)
// if it's a single page, just return the firstPage (first page)
if _, found := pageType.FieldByName("SinglePageBase"); found {
return testPage, nil
return firstPage, nil
}
// store the first page to avoid getting it twice
p.firstPage = firstPage
// Switch on the page body type. Recognized types are `map[string]interface{}`,
// `[]byte`, and `[]interface{}`.
switch pb := testPage.GetBody().(type) {
switch pb := firstPage.GetBody().(type) {
case map[string]interface{}:
// key is the map key for the page body if the body type is `map[string]interface{}`.
var key string

View File

@ -120,6 +120,22 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{},
continue
}
if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
sliceValue := v
if sliceValue.Kind() == reflect.Ptr {
sliceValue = sliceValue.Elem()
}
for i := 0; i < sliceValue.Len(); i++ {
element := sliceValue.Index(i)
if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) {
_, err := BuildRequestBody(element.Interface(), "")
if err != nil {
return nil, err
}
}
}
}
if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
if zero {
//fmt.Printf("value before change: %+v\n", optsValue.Field(i))
@ -363,9 +379,8 @@ func BuildQueryString(opts interface{}) (*url.URL, error) {
}
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
// if the field has a 'required' tag, it can't have a zero-value
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
}
}
@ -439,10 +454,9 @@ func BuildHeaders(opts interface{}) (map[string]string, error) {
optsMap[tags[0]] = strconv.FormatBool(v.Bool())
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
return optsMap, fmt.Errorf("Required header not set.")
// if the field has a 'required' tag, it can't have a zero-value
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
}
}
}

View File

@ -3,6 +3,7 @@ package gophercloud
import (
"bytes"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
@ -71,6 +72,10 @@ type ProviderClient struct {
// authentication functions for different Identity service versions.
ReauthFunc func() error
// IsThrowaway 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.
IsThrowaway bool
mut *sync.RWMutex
reauthmut *reauthlock
@ -78,19 +83,23 @@ type ProviderClient struct {
type reauthlock struct {
sync.RWMutex
reauthing bool
reauthing bool
reauthingErr error
done *sync.Cond
}
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
// authenticated service requests.
// authenticated service requests. Blocks if Reauthenticate is in progress.
func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
if client.IsThrowaway {
return
}
if client.reauthmut != nil {
client.reauthmut.RLock()
if client.reauthmut.reauthing {
client.reauthmut.RUnlock()
return
client.reauthmut.Lock()
for client.reauthmut.reauthing {
client.reauthmut.done.Wait()
}
client.reauthmut.RUnlock()
client.reauthmut.Unlock()
}
t := client.Token()
if t == "" {
@ -126,11 +135,11 @@ func (client *ProviderClient) SetToken(t string) {
client.TokenID = t
}
//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.
// 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
@ -139,11 +148,25 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
if client.mut == 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.mut.Lock()
defer client.mut.Unlock()
client.reauthmut.Lock()
client.reauthmut.reauthing = true
client.reauthmut.done = sync.NewCond(client.reauthmut)
client.reauthmut.reauthingErr = nil
client.reauthmut.Unlock()
if previousToken == "" || client.TokenID == previousToken {
@ -152,6 +175,8 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
client.reauthmut.Lock()
client.reauthmut.reauthing = false
client.reauthmut.reauthingErr = err
client.reauthmut.done.Broadcast()
client.reauthmut.Unlock()
return
}
@ -192,7 +217,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
// io.ReadSeeker as-is. Default the content-type to application/json.
if options.JSONBody != nil {
if options.RawBody != nil {
panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().")
return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()")
}
rendered, err := json.Marshal(options.JSONBody)
@ -378,7 +403,7 @@ func defaultOkCodes(method string) []int {
case method == "PUT":
return []int{201, 202}
case method == "PATCH":
return []int{200, 204}
return []int{200, 202, 204}
case method == "DELETE":
return []int{202, 204}
}

View File

@ -89,23 +89,47 @@ func (r Result) extractIntoPtr(to interface{}, label string) error {
if typeOfV.Kind() == reflect.Struct {
if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
newType := reflect.New(typeOfV).Elem()
for _, v := range m[label].([]interface{}) {
b, err := json.Marshal(v)
if err != nil {
return err
}
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()
for i := 0; i < newType.NumField(); i++ {
s := newType.Field(i).Addr().Interface()
err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
b, err := json.Marshal(v)
if err != nil {
return err
}
// This is needed for structs with an UnmarshalJSON method.
// Technically this is just unmarshalling the response into
// a struct that is never used, but it's good enough to
// trigger the UnmarshalJSON method.
for i := 0; i < newType.NumField(); i++ {
s := newType.Field(i).Addr().Interface()
// Unmarshal is used rather than NewDecoder to also work
// around the above-mentioned bug.
err = json.Unmarshal(b, s)
if err != nil {
return err
}
}
newSlice = reflect.Append(newSlice, newType)
}
newSlice = reflect.Append(newSlice, newType)
}
// "to" should now be properly modeled to receive the
// JSON response body and unmarshal into all the correct
// fields of the struct or composed extension struct
// at the end of this method.
toValue.Set(newSlice)
}
}
@ -345,6 +369,48 @@ func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
return nil
}
// RFC3339ZNoT is the time format used in Zun (Containers Service).
const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
type JSONRFC3339ZNoT time.Time
func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if s == "" {
return nil
}
t, err := time.Parse(RFC3339ZNoT, s)
if err != nil {
return err
}
*jt = JSONRFC3339ZNoT(t)
return nil
}
// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service).
const RFC3339ZNoTNoZ = "2006-01-02 15:04:05"
type JSONRFC3339ZNoTNoZ time.Time
func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if s == "" {
return nil
}
t, err := time.Parse(RFC3339ZNoTNoZ, s)
if err != nil {
return err
}
*jt = JSONRFC3339ZNoTNoZ(t)
return nil
}
/*
Link is an internal type to be used in packages of collection resources that are
paginated in a certain way.

View File

@ -28,6 +28,10 @@ type ServiceClient struct {
// The microversion of the service to use. Set this to use a particular microversion.
Microversion string
// MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way,
// values set in this field will be set on all the HTTP requests the service client sends.
MoreHeaders map[string]string
}
// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
@ -108,6 +112,15 @@ func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Respon
return client.Request("DELETE", url, opts)
}
// Head calls `Request` with the "HEAD" HTTP verb.
func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = new(RequestOpts)
}
client.initReqOpts(url, nil, nil, opts)
return client.Request("HEAD", url, opts)
}
func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
switch client.Type {
case "compute":
@ -122,3 +135,16 @@ func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
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)
}