OpenStack Designate provider

This commit is contained in:
Stan Lagun 2017-08-05 00:16:24 -07:00
parent 485ede3228
commit 0cb99281ff
822 changed files with 73534 additions and 3 deletions

6
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: d1ca92323760e7bdb44d2b80d066dd21bcd49c714faa88372a87a81a9520dffc
updated: 2017-06-16T11:52:01.08864027+02:00
hash: 16145fd21935177a6d06a0570740b844c8f7d4280a48a0b1145bf3c835f58f41
updated: 2017-08-03T20:37:19.172220412-07:00
imports:
- name: bitbucket.org/ww/goautoneg
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
@ -125,6 +125,8 @@ imports:
- query
- name: github.com/google/gofuzz
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/gophercloud/gophercloud
version: 3172ddf2336933bc38b355df89cb015e0954df67
- name: github.com/howeyc/gopass
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
- name: github.com/imdario/mergo

View File

@ -54,3 +54,4 @@ import:
- package: github.com/digitalocean/godo
version: ~1.1.0
- package: github.com/coreos/go-oidc
- package: github.com/gophercloud/gophercloud

View File

@ -103,6 +103,8 @@ func main() {
p, err = provider.NewDigitalOceanProvider(domainFilter, cfg.DryRun)
case "inmemory":
p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
case "designate":
p, err = provider.NewDesignateProvider(domainFilter, cfg.DryRun)
default:
log.Fatalf("unknown dns provider: %s", cfg.Provider)
}

View File

@ -114,7 +114,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "inmemory")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, inmemory, designate)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "inmemory", "designate")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("google-project", "When using the Google provider, specify the Google project (required when --provider=google)").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private")

417
provider/designate.go Normal file
View File

@ -0,0 +1,417 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"os"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
"github.com/gophercloud/gophercloud/pagination"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
const (
// ID of the RecordSet from which endpoint was created
designateRecordSetID = "designate-recordset-id"
// Zone ID of the RecordSet
designateZoneID = "designate-record-id"
// Initial records values of the RecordSet. This label is required in order not to loose records that haven't
// changed where there are several targets per domain and only some of them changed.
// Values are joined by zero-byte to in order to get a single string
designateOriginalRecords = "designate-original-records"
)
// interface between provider and OpenStack DNS API
type designateClientInterface interface {
// ForEachZone calls handler for each zone managed by the Designate
ForEachZone(handler func(zone *zones.Zone) error) error
// ForEachRecordSet calls handler for each recordset in the given DNS zone
ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error
// CreateRecordSet creates recordset in the given DNS zone
CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error)
// UpdateRecordSet updates recordset in the given DNS zone
UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error
// DeleteRecordSet deletes recordset in the given DNS zone
DeleteRecordSet(zoneID, recordSetID string) error
}
// implementation of the designateClientInterface
type designateClient struct {
serviceClient *gophercloud.ServiceClient
}
// factory function for the designateClientInterface
func newDesignateClient() (designateClientInterface, error) {
serviceClient, err := createDesignateServiceClient()
if err != nil {
return nil, err
}
return &designateClient{serviceClient}, nil
}
// copies environment variables to new names without overwriting existing values
func remapEnv(mapping map[string]string) {
for k, v := range mapping {
currentVal := os.Getenv(k)
newVal := os.Getenv(v)
if currentVal == "" && newVal != "" {
os.Setenv(k, newVal)
}
}
}
// returns OpenStack Keystone authentication settings by obtaining values from standard environment variables.
// also fixes incompatibilities between gophercloud implementation and *-stackrc files that can be downloaded
// from OpenStack dashboard in latest versions
func getAuthSettings() (gophercloud.AuthOptions, error) {
remapEnv(map[string]string{
"OS_TENANT_NAME": "OS_PROJECT_NAME",
"OS_TENANT_ID": "OS_PROJECT_ID",
"OS_DOMAIN_NAME": "OS_USER_DOMAIN_NAME",
"OS_DOMAIN_ID": "OS_USER_DOMAIN_ID",
})
opts, err := openstack.AuthOptionsFromEnv()
if err != nil {
return gophercloud.AuthOptions{}, err
}
opts.AllowReauth = true
if !strings.HasSuffix(opts.IdentityEndpoint, "/") {
opts.IdentityEndpoint += "/"
}
if !strings.HasSuffix(opts.IdentityEndpoint, "/v2.0/") && !strings.HasSuffix(opts.IdentityEndpoint, "/v3/") {
opts.IdentityEndpoint += "v2.0/"
}
return opts, nil
}
// authenticate in OpenStack and obtain Designate service endpoint
func createDesignateServiceClient() (*gophercloud.ServiceClient, error) {
opts, err := getAuthSettings()
if err != nil {
return nil, err
}
log.Infof("Using OpenStack Keystone at %s", opts.IdentityEndpoint)
authProvider, err := openstack.AuthenticatedClient(opts)
if err != nil {
return nil, err
}
eo := gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
}
client, err := openstack.NewDNSV2(authProvider, eo)
if err != nil {
return nil, err
}
log.Infof("Found OpenStack Designate service at %s", client.Endpoint)
return client, nil
}
// ForEachZone calls handler for each zone managed by the Designate
func (c designateClient) ForEachZone(handler func(zone *zones.Zone) error) error {
pager := zones.List(c.serviceClient, zones.ListOpts{})
return pager.EachPage(
func(page pagination.Page) (bool, error) {
list, err := zones.ExtractZones(page)
if err != nil {
return false, err
}
for _, zone := range list {
err := handler(&zone)
if err != nil {
return false, err
}
}
return true, nil
},
)
}
// ForEachRecordSet calls handler for each recordset in the given DNS zone
func (c designateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error {
pager := recordsets.ListByZone(c.serviceClient, zoneID, recordsets.ListOpts{})
return pager.EachPage(
func(page pagination.Page) (bool, error) {
list, err := recordsets.ExtractRecordSets(page)
if err != nil {
return false, err
}
for _, recordSet := range list {
err := handler(&recordSet)
if err != nil {
return false, err
}
}
return true, nil
},
)
}
// CreateRecordSet creates recordset in the given DNS zone
func (c designateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) {
r, err := recordsets.Create(c.serviceClient, zoneID, opts).Extract()
if err != nil {
return "", err
}
return r.ID, nil
}
// UpdateRecordSet updates recordset in the given DNS zone
func (c designateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error {
_, err := recordsets.Update(c.serviceClient, zoneID, recordSetID, opts).Extract()
return err
}
// DeleteRecordSet deletes recordset in the given DNS zone
func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error {
return recordsets.Delete(c.serviceClient, zoneID, recordSetID).ExtractErr()
}
// designate provider type
type designateProvider struct {
client designateClientInterface
// only consider hosted zones managing domains ending in this suffix
domainFilter DomainFilter
dryRun bool
}
// NewDesignateProvider is a factory function for OpenStack designate providers
func NewDesignateProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
client, err := newDesignateClient()
if err != nil {
return nil, err
}
return &designateProvider{
client: client,
domainFilter: domainFilter,
dryRun: dryRun,
}, nil
}
// converts domain name to FQDN
func canonicalizeDomainName(domain string) string {
if !strings.HasSuffix(domain, ".") {
domain += "."
}
return strings.ToLower(domain)
}
// returns ZoneID -> ZoneName mapping for zones that are managed by the Designate and match domain filter
func (p designateProvider) getZones() (map[string]string, error) {
result := map[string]string{}
err := p.client.ForEachZone(
func(zone *zones.Zone) error {
if zone.Type != "" && strings.ToUpper(zone.Type) != "PRIMARY" || zone.Status != "ACTIVE" {
return nil
}
zoneName := canonicalizeDomainName(zone.Name)
if !p.domainFilter.Match(zoneName) {
return nil
}
result[zone.ID] = zoneName
return nil
},
)
return result, err
}
// finds best suitable DNS zone for the hostname
func (p designateProvider) getHostZoneID(hostname string, managedZones map[string]string) (string, error) {
longestZoneLength := 0
resultID := ""
for zoneID, zoneName := range managedZones {
if !strings.HasSuffix(hostname, zoneName) {
continue
}
ln := len(zoneName)
if ln > longestZoneLength {
resultID = zoneID
longestZoneLength = ln
}
}
return resultID, nil
}
// Records returns the list of records.
func (p designateProvider) Records() ([]*endpoint.Endpoint, error) {
var result []*endpoint.Endpoint
managedZones, err := p.getZones()
if err != nil {
return nil, err
}
for zoneID := range managedZones {
err = p.client.ForEachRecordSet(zoneID,
func(recordSet *recordsets.RecordSet) error {
if recordSet.Type != endpoint.RecordTypeA && recordSet.Type != endpoint.RecordTypeTXT && recordSet.Type != endpoint.RecordTypeCNAME {
return nil
}
for _, record := range recordSet.Records {
ep := endpoint.NewEndpoint(recordSet.Name, record, recordSet.Type)
ep.Labels[designateRecordSetID] = recordSet.ID
ep.Labels[designateZoneID] = recordSet.ZoneID
ep.Labels[designateOriginalRecords] = strings.Join(recordSet.Records, "\000")
result = append(result, ep)
}
return nil
},
)
if err != nil {
return nil, err
}
}
return result, nil
}
// temporary structure to hold recordset parameters so that we could aggregate endpoints into recordsets
type recordSet struct {
dnsName string
recordType string
zoneID string
recordSetID string
names map[string]bool
}
// adds endpoint into recordset aggregation, loading original values from endpoint labels first
func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete bool) {
key := fmt.Sprintf("%s/%s", ep.DNSName, ep.RecordType)
rs := recordSets[key]
if rs == nil {
rs = &recordSet{
dnsName: canonicalizeDomainName(ep.DNSName),
recordType: ep.RecordType,
names: make(map[string]bool),
}
}
if rs.zoneID == "" {
rs.zoneID = ep.Labels[designateZoneID]
}
if rs.recordSetID == "" {
rs.recordSetID = ep.Labels[designateRecordSetID]
}
for _, rec := range strings.Split(ep.Labels[designateOriginalRecords], "\000") {
if _, ok := rs.names[rec]; !ok && rec != "" {
rs.names[rec] = true
}
}
target := ep.Target
if ep.RecordType == endpoint.RecordTypeCNAME {
target = canonicalizeDomainName(target)
}
rs.names[target] = !delete
recordSets[key] = rs
}
// ApplyChanges applies a given set of changes in a given zone.
func (p designateProvider) ApplyChanges(changes *plan.Changes) error {
managedZones, err := p.getZones()
if err != nil {
return err
}
recordSets := map[string]*recordSet{}
for _, ep := range changes.Create {
addEndpoint(ep, recordSets, false)
}
for _, ep := range changes.UpdateNew {
addEndpoint(ep, recordSets, false)
}
for _, ep := range changes.UpdateOld {
addEndpoint(ep, recordSets, true)
}
for _, ep := range changes.Delete {
addEndpoint(ep, recordSets, true)
}
for _, rs := range recordSets {
if err2 := p.upsertRecordSet(rs, managedZones); err == nil {
err = err2
}
}
return err
}
// apply recordset changes by inserting/updating/deleting recordsets
func (p designateProvider) upsertRecordSet(rs *recordSet, managedZones map[string]string) error {
if rs.zoneID == "" {
var err error
rs.zoneID, err = p.getHostZoneID(rs.dnsName, managedZones)
if err != nil {
return err
}
if rs.zoneID == "" {
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected ", rs.dnsName)
return nil
}
}
var records []string
for rec, v := range rs.names {
if v {
records = append(records, rec)
}
}
if rs.recordSetID == "" && records == nil {
return nil
}
if rs.recordSetID == "" {
opts := recordsets.CreateOpts{
Name: rs.dnsName,
Type: rs.recordType,
Records: records,
}
log.Infof("Creating records: %s/%s: %s", rs.dnsName, rs.recordType, strings.Join(records, ","))
if p.dryRun {
return nil
}
_, err := p.client.CreateRecordSet(rs.zoneID, opts)
return err
} else if len(records) == 0 {
log.Infof("Deleting records for %s/%s", rs.dnsName, rs.recordType)
if p.dryRun {
return nil
}
return p.client.DeleteRecordSet(rs.zoneID, rs.recordSetID)
} else {
opts := recordsets.UpdateOpts{
Records: records,
}
log.Infof("Updating records: %s/%s: %s", rs.dnsName, rs.recordType, strings.Join(records, ","))
if p.dryRun {
return nil
}
return p.client.UpdateRecordSet(rs.zoneID, rs.recordSetID, opts)
}
}

519
provider/designate_test.go Normal file
View File

@ -0,0 +1,519 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"reflect"
"sort"
"sync/atomic"
"testing"
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
var lastGeneratedDesignateID int32
func generateDesignateID() string {
return fmt.Sprintf("id-%d", atomic.AddInt32(&lastGeneratedDesignateID, 1))
}
type fakeDesignateClient struct {
managedZones map[string]*struct {
zone *zones.Zone
recordSets map[string]*recordsets.RecordSet
}
}
func (c fakeDesignateClient) AddZone(zone zones.Zone) string {
if zone.ID == "" {
zone.ID = zone.Name
}
c.managedZones[zone.ID] = &struct {
zone *zones.Zone
recordSets map[string]*recordsets.RecordSet
}{
zone: &zone,
recordSets: make(map[string]*recordsets.RecordSet),
}
return zone.ID
}
func (c fakeDesignateClient) ForEachZone(handler func(zone *zones.Zone) error) error {
for _, zone := range c.managedZones {
if err := handler(zone.zone); err != nil {
return err
}
}
return nil
}
func (c fakeDesignateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error {
zone := c.managedZones[zoneID]
if zone == nil {
return fmt.Errorf("unknown zone %s", zoneID)
}
for _, recordSet := range zone.recordSets {
if err := handler(recordSet); err != nil {
return err
}
}
return nil
}
func (c fakeDesignateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) {
zone := c.managedZones[zoneID]
if zone == nil {
return "", fmt.Errorf("unknown zone %s", zoneID)
}
rs := &recordsets.RecordSet{
ID: generateDesignateID(),
ZoneID: zoneID,
Name: opts.Name,
Description: opts.Description,
Records: opts.Records,
TTL: opts.TTL,
Type: opts.Type,
}
zone.recordSets[rs.ID] = rs
return rs.ID, nil
}
func (c fakeDesignateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error {
zone := c.managedZones[zoneID]
if zone == nil {
return fmt.Errorf("unknown zone %s", zoneID)
}
rs := zone.recordSets[recordSetID]
if rs == nil {
return fmt.Errorf("unknown record-set %s", recordSetID)
}
rs.Description = opts.Description
rs.TTL = opts.TTL
rs.Records = opts.Records
return nil
}
func (c fakeDesignateClient) DeleteRecordSet(zoneID, recordSetID string) error {
zone := c.managedZones[zoneID]
if zone == nil {
return fmt.Errorf("unknown zone %s", zoneID)
}
delete(zone.recordSets, recordSetID)
return nil
}
func (c fakeDesignateClient) ToProvider() Provider {
return &designateProvider{client: c}
}
func newFakeDesignateClient() *fakeDesignateClient {
return &fakeDesignateClient{
make(map[string]*struct {
zone *zones.Zone
recordSets map[string]*recordsets.RecordSet
}),
}
}
func TestDesignateRecords(t *testing.T) {
client := newFakeDesignateClient()
zone1ID := client.AddZone(zones.Zone{
Name: "example.com.",
Type: "PRIMARY",
Status: "ACTIVE",
})
rs11ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
Name: "www.example.com.",
Type: endpoint.RecordTypeA,
Records: []string{"10.1.1.1"},
})
rs12ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
Name: "www.example.com.",
Type: endpoint.RecordTypeTXT,
Records: []string{"text1"},
})
client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
Name: "xxx.example.com.",
Type: "SRV",
Records: []string{"http://test.com:1234"},
})
rs14ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
Name: "ftp.example.com.",
Type: endpoint.RecordTypeA,
Records: []string{"10.1.1.2"},
})
zone2ID := client.AddZone(zones.Zone{
Name: "test.net.",
Type: "PRIMARY",
Status: "ACTIVE",
})
rs21ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{
Name: "srv.test.net.",
Type: endpoint.RecordTypeA,
Records: []string{"10.2.1.1", "10.2.1.2"},
})
rs22ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{
Name: "db.test.net.",
Type: endpoint.RecordTypeCNAME,
Records: []string{"sql.test.net."},
})
expected := []*endpoint.Endpoint{
{
DNSName: "www.example.com",
RecordType: endpoint.RecordTypeA,
Target: "10.1.1.1",
Labels: map[string]string{
designateRecordSetID: rs11ID,
designateZoneID: zone1ID,
designateOriginalRecords: "10.1.1.1",
},
},
{
DNSName: "www.example.com",
RecordType: endpoint.RecordTypeTXT,
Target: "text1",
Labels: map[string]string{
designateRecordSetID: rs12ID,
designateZoneID: zone1ID,
designateOriginalRecords: "text1",
},
},
{
DNSName: "ftp.example.com",
RecordType: endpoint.RecordTypeA,
Target: "10.1.1.2",
Labels: map[string]string{
designateRecordSetID: rs14ID,
designateZoneID: zone1ID,
designateOriginalRecords: "10.1.1.2",
},
},
{
DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA,
Target: "10.2.1.1",
Labels: map[string]string{
designateRecordSetID: rs21ID,
designateZoneID: zone2ID,
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
},
},
{
DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA,
Target: "10.2.1.2",
Labels: map[string]string{
designateRecordSetID: rs21ID,
designateZoneID: zone2ID,
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
},
},
{
DNSName: "db.test.net",
RecordType: endpoint.RecordTypeCNAME,
Target: "sql.test.net",
Labels: map[string]string{
designateRecordSetID: rs22ID,
designateZoneID: zone2ID,
designateOriginalRecords: "sql.test.net.",
},
},
}
endpoints, err := client.ToProvider().Records()
if err != nil {
t.Fatal(err)
}
out:
for _, ep := range endpoints {
for i, ex := range expected {
if reflect.DeepEqual(ep, ex) {
expected = append(expected[:i], expected[i+1:]...)
continue out
}
}
t.Errorf("unexpected endpoint %s/%s -> %s", ep.DNSName, ep.RecordType, ep.Target)
}
if len(expected) != 0 {
t.Errorf("not all expected endpoints were returned. Remained: %v", expected)
}
}
func TestDesignateCreateRecords(t *testing.T) {
client := newFakeDesignateClient()
testDesignateCreateRecords(t, client)
}
func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet {
for i, zoneName := range []string{"example.com.", "test.net."} {
client.AddZone(zones.Zone{
ID: fmt.Sprintf("zone-%d", i+1),
Name: zoneName,
Type: "PRIMARY",
Status: "ACTIVE",
})
}
endpoints := []*endpoint.Endpoint{
{
DNSName: "www.example.com",
RecordType: endpoint.RecordTypeA,
Target: "10.1.1.1",
Labels: map[string]string{},
},
{
DNSName: "www.example.com",
RecordType: endpoint.RecordTypeTXT,
Target: "text1",
Labels: map[string]string{},
},
{
DNSName: "ftp.example.com",
RecordType: endpoint.RecordTypeA,
Target: "10.1.1.2",
Labels: map[string]string{},
},
{
DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA,
Target: "10.2.1.1",
Labels: map[string]string{},
},
{
DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA,
Target: "10.2.1.2",
Labels: map[string]string{},
},
{
DNSName: "db.test.net",
RecordType: endpoint.RecordTypeCNAME,
Target: "sql.test.net",
Labels: map[string]string{},
},
}
expected := []*recordsets.RecordSet{
{
Name: "www.example.com.",
Type: endpoint.RecordTypeA,
Records: []string{"10.1.1.1"},
ZoneID: "zone-1",
},
{
Name: "www.example.com.",
Type: endpoint.RecordTypeTXT,
Records: []string{"text1"},
ZoneID: "zone-1",
},
{
Name: "ftp.example.com.",
Type: endpoint.RecordTypeA,
Records: []string{"10.1.1.2"},
ZoneID: "zone-1",
},
{
Name: "srv.test.net.",
Type: endpoint.RecordTypeA,
Records: []string{"10.2.1.1", "10.2.1.2"},
ZoneID: "zone-2",
},
{
Name: "db.test.net.",
Type: endpoint.RecordTypeCNAME,
Records: []string{"sql.test.net."},
ZoneID: "zone-2",
},
}
expectedCopy := make([]*recordsets.RecordSet, len(expected))
copy(expectedCopy, expected)
err := client.ToProvider().ApplyChanges(&plan.Changes{Create: endpoints})
if err != nil {
t.Fatal(err)
}
client.ForEachZone(func(zone *zones.Zone) error {
client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
id := recordSet.ID
recordSet.ID = ""
for i, ex := range expected {
sort.Strings(recordSet.Records)
if reflect.DeepEqual(ex, recordSet) {
ex.ID = id
recordSet.ID = id
expected = append(expected[:i], expected[i+1:]...)
return nil
}
}
t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
return nil
})
return nil
})
if len(expected) != 0 {
t.Errorf("not all expected record-sets were created. Remained: %v", expected)
}
return expectedCopy
}
func TestDesignateUpdateRecords(t *testing.T) {
client := newFakeDesignateClient()
testDesignateUpdateRecords(t, client)
}
func testDesignateUpdateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet {
expected := testDesignateCreateRecords(t, client)
updatesOld := []*endpoint.Endpoint{
{
DNSName: "ftp.example.com",
RecordType: endpoint.RecordTypeA,
Target: "10.1.1.2",
Labels: map[string]string{
designateZoneID: "zone-1",
designateRecordSetID: expected[2].ID,
designateOriginalRecords: "10.1.1.2",
},
},
{
DNSName: "srv.test.net.",
RecordType: endpoint.RecordTypeA,
Target: "10.2.1.2",
Labels: map[string]string{
designateZoneID: "zone-2",
designateRecordSetID: expected[3].ID,
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
},
},
}
updatesNew := []*endpoint.Endpoint{
{
DNSName: "ftp.example.com",
RecordType: endpoint.RecordTypeA,
Target: "10.3.3.1",
Labels: map[string]string{
designateZoneID: "zone-1",
designateRecordSetID: expected[2].ID,
designateOriginalRecords: "10.1.1.2",
},
},
{
DNSName: "srv.test.net.",
RecordType: endpoint.RecordTypeA,
Target: "10.3.3.2",
Labels: map[string]string{
designateZoneID: "zone-2",
designateRecordSetID: expected[3].ID,
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
},
},
}
expectedCopy := make([]*recordsets.RecordSet, len(expected))
copy(expectedCopy, expected)
expected[2].Records = []string{"10.3.3.1"}
expected[3].Records = []string{"10.2.1.1", "10.3.3.2"}
err := client.ToProvider().ApplyChanges(&plan.Changes{UpdateOld: updatesOld, UpdateNew: updatesNew})
if err != nil {
t.Fatal(err)
}
client.ForEachZone(func(zone *zones.Zone) error {
client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
for i, ex := range expected {
sort.Strings(recordSet.Records)
if reflect.DeepEqual(ex, recordSet) {
expected = append(expected[:i], expected[i+1:]...)
return nil
}
}
t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
return nil
})
return nil
})
if len(expected) != 0 {
t.Errorf("not all expected record-sets were updated. Remained: %v", expected)
}
return expectedCopy
}
func TestDesignateDeleteRecords(t *testing.T) {
client := newFakeDesignateClient()
testDesignateDeleteRecords(t, client)
}
func testDesignateDeleteRecords(t *testing.T, client *fakeDesignateClient) {
expected := testDesignateUpdateRecords(t, client)
deletes := []*endpoint.Endpoint{
{
DNSName: "www.example.com.",
RecordType: endpoint.RecordTypeA,
Target: "10.1.1.1",
Labels: map[string]string{
designateZoneID: "zone-1",
designateRecordSetID: expected[0].ID,
designateOriginalRecords: "10.1.1.1",
},
},
{
DNSName: "srv.test.net.",
RecordType: endpoint.RecordTypeA,
Target: "10.2.1.1",
Labels: map[string]string{
designateZoneID: "zone-2",
designateRecordSetID: expected[3].ID,
designateOriginalRecords: "10.2.1.1\00010.3.3.2",
},
},
}
expected[3].Records = []string{"10.3.3.2"}
expected = expected[1:]
err := client.ToProvider().ApplyChanges(&plan.Changes{Delete: deletes})
if err != nil {
t.Fatal(err)
}
client.ForEachZone(func(zone *zones.Zone) error {
client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
for i, ex := range expected {
sort.Strings(recordSet.Records)
if reflect.DeepEqual(ex, recordSet) {
expected = append(expected[:i], expected[i+1:]...)
return nil
}
}
t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
return nil
})
return nil
})
if len(expected) != 0 {
t.Errorf("not all expected record-sets were deleted. Remained: %v", expected)
}
}

View File

@ -0,0 +1,235 @@
# Contributing to Gophercloud
- [Getting started](#getting-started)
- [Tests](#tests)
- [Style guide](#basic-style-guide)
- [3 ways to get involved](#5-ways-to-get-involved)
## Setting up your git workspace
As a contributor you will need to setup your workspace in a slightly different
way than just downloading it. Here are the basic installation instructions:
1. Configure your `$GOPATH` and run `go get` as described in the main
[README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
get dependencies for unit and acceptance tests.
```bash
go get -tags "fixtures acceptance" github.com/gophercloud/gophercloud
```
2. Move into the directory that houses your local repository:
```bash
cd ${GOPATH}/src/github.com/gophercloud/gophercloud
```
3. Fork the `gophercloud/gophercloud` repository and update your remote refs. You
will need to rename the `origin` remote branch to `upstream`, and add your
fork as `origin` instead:
```bash
git remote rename origin upstream
git remote add origin git@github.com:<my_username>/gophercloud.git
```
4. Checkout the latest development branch:
```bash
git checkout master
```
5. If you're working on something (discussed more in detail below), you will
need to checkout a new feature branch:
```bash
git checkout -b my-new-feature
```
Another thing to bear in mind is that you will need to add a few extra
environment variables for acceptance tests - this is documented in our
[acceptance tests readme](/acceptance).
## Tests
When working on a new or existing feature, testing will be the backbone of your
work since it helps uncover and prevent regressions in the codebase. There are
two types of test we use in Gophercloud: unit tests and acceptance tests, which
are both described below.
### Unit tests
Unit tests are the fine-grained tests that establish and ensure the behavior
of individual units of functionality. We usually test on an
operation-by-operation basis (an operation typically being an API action) with
the use of mocking to set up explicit expectations. Each operation will set up
its HTTP response expectation, and then test how the system responds when fed
this controlled, pre-determined input.
To make life easier, we've introduced a bunch of test helpers to simplify the
process of testing expectations with assertions:
```go
import (
"testing"
"github.com/gophercloud/gophercloud/testhelper"
)
func TestSomething(t *testing.T) {
result, err := Operation()
testhelper.AssertEquals(t, "foo", result.Bar)
testhelper.AssertNoErr(t, err)
}
func TestSomethingElse(t *testing.T) {
testhelper.CheckEquals(t, "expected", "actual")
}
```
`AssertEquals` and `AssertNoErr` will throw a fatal error if a value does not
match an expected value or if an error has been declared, respectively. You can
also use `CheckEquals` and `CheckNoErr` for the same purpose; the only difference
being that `t.Errorf` is raised rather than `t.Fatalf`.
Here is a truncated example of mocked HTTP responses:
```go
import (
"testing"
th "github.com/gophercloud/gophercloud/testhelper"
fake "github.com/gophercloud/gophercloud/testhelper/client"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)
func TestGet(t *testing.T) {
// Setup the HTTP request multiplexer and server
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
// Test we're using the correct HTTP method
th.TestMethod(t, r, "GET")
// Test we're setting the auth token
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
// Set the appropriate headers for our mocked response
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Set the HTTP body
fmt.Fprintf(w, `
{
"network": {
"status": "ACTIVE",
"name": "private-network",
"admin_state_up": true,
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
"shared": true,
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
// Call our API operation
network, err := networks.Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
// Assert no errors and equality
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Status, "ACTIVE")
}
```
### Acceptance tests
As we've already mentioned, unit tests have a very narrow and confined focus -
they test small units of behavior. Acceptance tests on the other hand have a
far larger scope: they are fully functional tests that test the entire API of a
service in one fell swoop. They don't care about unit isolation or mocking
expectations, they instead do a full run-through and consequently test how the
entire system _integrates_ together. When an API satisfies expectations, it
proves by default that the requirements for a contract have been met.
Please be aware that acceptance tests will hit a live API - and may incur
service charges from your provider. Although most tests handle their own
teardown procedures, it is always worth manually checking that resources are
deleted after the test suite finishes.
### Running tests
To run all tests:
```bash
go test -tags fixtures ./...
```
To run all tests with verbose output:
```bash
go test -v -tags fixtures ./...
```
To run tests that match certain [build tags]():
```bash
go test -tags "fixtures foo bar" ./...
```
To run tests for a particular sub-package:
```bash
cd ./path/to/package && go test -tags fixtures .
```
## Style guide
See [here](/STYLEGUIDE.md)
## 3 ways to get involved
There are five main ways you can get involved in our open-source project, and
each is described briefly below. Once you've made up your mind and decided on
your fix, you will need to follow the same basic steps that all submissions are
required to adhere to:
1. [fork](https://help.github.com/articles/fork-a-repo/) the `gophercloud/gophercloud` repository
2. checkout a [new branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)
3. submit your branch as a [pull request](https://help.github.com/articles/creating-a-pull-request/)
### 1. Fixing bugs
If you want to start fixing open bugs, we'd really appreciate that! Bug fixing
is central to any project. The best way to get started is by heading to our
[bug tracker](https://github.com/gophercloud/gophercloud/issues) and finding open
bugs that you think nobody is working on. It might be useful to comment on the
thread to see the current state of the issue and if anybody has made any
breakthroughs on it so far.
### 2. Improving documentation
The best source of documentation is on [godoc.org](http://godoc.org). It is
automatically generated from the source code.
If you feel that a certain section could be improved - whether it's to clarify
ambiguity, correct a technical mistake, or to fix a grammatical error - please
feel entitled to do so! We welcome doc pull requests with the same childlike
enthusiasm as any other contribution!
### 3. Working on a new feature
If you've found something we've left out, definitely feel free to start work on
introducing that feature. It's always useful to open an issue or submit a pull
request early on to indicate your intent to a core contributor - this enables
quick/early feedback and can help steer you in the right direction by avoiding
known issues. It might also help you avoid losing time implementing something
that might not ever work. One tip is to prefix your Pull Request issue title
with [wip] - then people know it's a work in progress.
You must ensure that all of your work is well tested - both in terms of unit
and acceptance tests. Untested code will not be merged because it introduces
too much of a risk to end-users.
Happy hacking!

View File

@ -0,0 +1 @@
Before starting a PR, please read the [style guide](https://github.com/gophercloud/gophercloud/blob/master/STYLEGUIDE.md).

View File

@ -0,0 +1,9 @@
Prior to a PR being reviewed, there needs to be a Github issue that the PR
addresses. Replace the brackets and text below with that issue number.
For #[PUT ISSUE NUMBER HERE]
Links to the line numbers/files in the OpenStack source code that support the
code in this PR:
[PUT URLS HERE]

1
vendor/github.com/gophercloud/gophercloud/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
**/*.swp

19
vendor/github.com/gophercloud/gophercloud/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
sudo: false
install:
- go get golang.org/x/crypto/ssh
- go get -v -tags 'fixtures acceptance' ./...
- go get github.com/wadey/gocovmerge
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/goimports
go:
- 1.8
- tip
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
script:
- ./script/coverage
- ./script/format
after_success:
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out

View File

148
vendor/github.com/gophercloud/gophercloud/FAQ.md generated vendored Normal file
View File

@ -0,0 +1,148 @@
# 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
}
// ...
```

191
vendor/github.com/gophercloud/gophercloud/LICENSE generated vendored Normal file
View File

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

32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md generated vendored Normal file
View File

@ -0,0 +1,32 @@
# 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`

143
vendor/github.com/gophercloud/gophercloud/README.md generated vendored Normal file
View File

@ -0,0 +1,143 @@
# Gophercloud: an OpenStack SDK for Go
[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud)
[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master)
Gophercloud is an OpenStack Go SDK.
## Useful links
* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud)
* [Effective Go](https://golang.org/doc/effective_go.html)
## How to install
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
is pointing to an appropriate directory where you want to install Gophercloud:
```bash
mkdir $HOME/go
export GOPATH=$HOME/go
```
To protect yourself against changes in your dependencies, we highly recommend choosing a
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
Gophercloud as a dependency like so:
```bash
go get github.com/gophercloud/gophercloud
# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud"
godep save ./...
```
This will install all the source files you need into a `Godeps/_workspace` directory, which is
referenceable from your own source files when you use the `godep go` command.
## Getting started
### Credentials
Because you'll be hitting an API, you will need to retrieve your OpenStack
credentials and either store them as environment variables or in your local Go
files. The first method is recommended because it decouples credential
information from source code, allowing you to push the latter to your version
control system without any security risk.
You will need to retrieve the following:
* username
* password
* a valid Keystone identity URL
For users that have the OpenStack dashboard installed, there's a shortcut. If
you visit the `project/access_and_security` path in Horizon and click on the
"Download OpenStack RC File" button at the top right hand corner, you will
download a bash file that exports all of your access details to environment
variables. To execute the file, run `source admin-openrc.sh` and you will be
prompted for your password.
### Authentication
Once you have access to your credentials, you can begin plugging them into
Gophercloud. The next step is authentication, and this is handled by a base
"Provider" struct. To get one, you can either pass in your credentials
explicitly, or tell Gophercloud to use environment variables:
```go
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/utils"
)
// Option 1: Pass in the values yourself
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
}
// Option 2: Use a utility function to retrieve all your environment variables
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have the `opts` variable, you can pass it in and get back a
`ProviderClient` struct:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
The `ProviderClient` is the top-level client that all of your OpenStack services
derive from. The provider contains all of the authentication details that allow
your Go code to access the API - such as the base URL and token ID.
### Provision a server
Once we have a base Provider, we inject it as a dependency into each OpenStack
service. In order to work with the Compute API, we need a Compute service
client; which can be created like so:
```go
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
```
We then use this `client` for any Compute API operation we want. In our case,
we want to provision a new server - so we invoke the `Create` method and pass
in the flavor ID (hardware specification) and image ID (operating system) we're
interested in:
```go
import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
server, err := servers.Create(client, servers.CreateOpts{
Name: "My new server!",
FlavorRef: "flavor_id",
ImageRef: "image_id",
}).Extract()
```
The above code sample creates a new server with the parameters, and embodies the
new resource in the `server` variable (a
[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct).
## Advanced Usage
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
## Backwards-Compatibility Guarantees
None. Vendor it and write tests covering the parts you use.
## Contributing
See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues).

View File

@ -0,0 +1,74 @@
## On Pull Requests
- 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.
## 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

@ -0,0 +1,86 @@
# Gophercloud Acceptance tests
The purpose of these acceptance tests is to validate that SDK features meet
the requirements of a contract - to consumers, other parts of the library, and
to a remote API.
> **Note:** Because every test will be run against a real API endpoint, you
> may incur bandwidth and service charges for all the resource usage. These
> tests *should* remove their remote products automatically. However, there may
> be certain cases where this does not happen; always double-check to make sure
> you have no stragglers left behind.
### Step 1. Set environment variables
A lot of tests rely on environment variables for configuration - so you will need
to set them before running the suite. If you're testing against pure OpenStack APIs,
you can download a file that contains all of these variables for you: just visit
the `project/access_and_security` page in your control panel and click the "Download
OpenStack RC File" button at the top right. For all other providers, you will need
to set them manually.
#### Authentication
|Name|Description|
|---|---|
|`OS_USERNAME`|Your API username|
|`OS_PASSWORD`|Your API password|
|`OS_AUTH_URL`|The identity URL you need to authenticate|
|`OS_TENANT_NAME`|Your API tenant name|
|`OS_TENANT_ID`|Your API tenant ID|
#### General
|Name|Description|
|---|---|
|`OS_REGION_NAME`|The region you want your resources to reside in|
#### Compute
|Name|Description|
|---|---|
|`OS_IMAGE_ID`|The ID of the image your want your server to be based on|
|`OS_FLAVOR_ID`|The ID of the flavor you want your server to be based on|
|`OS_FLAVOR_ID_RESIZE`|The ID of the flavor you want your server to be resized to|
|`OS_POOL_NAME`|The Pool from where to obtain Floating IPs|
|`OS_NETWORK_NAME`|The network to launch instances on|
#### Shared file systems
|Name|Description|
|---|---|
|`OS_SHARE_NETWORK_ID`| The share network ID to use when creating shares|
### 2. Run the test suite
From the root directory, run:
```
./script/acceptancetest
```
Alternatively, add the following to your `.bashrc`:
```bash
gophercloudtest() {
if [[ -n $1 ]] && [[ -n $2 ]]; then
pushd $GOPATH/src/github.com/gophercloud/gophercloud
go test -v -tags "fixtures acceptance" -run "$1" github.com/gophercloud/gophercloud/acceptance/openstack/$2 | tee ~/gophercloud.log
popd
fi
}
```
Then run either groups or individual tests by doing:
```shell
$ gophercloudtest TestFlavorsList compute/v2
$ gophercloudtest TestFlavors compute/v2
$ gophercloudtest Test compute/v2
```
### 3. Notes
#### Compute Tests
* In order to run the `TestBootFromVolumeMultiEphemeral` test, a flavor with ephemeral disk space must be used.
* The `TestDefSecRules` tests require a compatible network driver and admin privileges.

View File

@ -0,0 +1,324 @@
// Package clients contains functions for creating OpenStack service clients
// for use in acceptance tests. It also manages the required environment
// variables to run the tests.
package clients
import (
"fmt"
"os"
"strings"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
)
// AcceptanceTestChoices contains image and flavor selections for use by the acceptance tests.
type AcceptanceTestChoices struct {
// ImageID contains the ID of a valid image.
ImageID string
// FlavorID contains the ID of a valid flavor.
FlavorID string
// FlavorIDResize contains the ID of a different flavor available on the same OpenStack installation, that is distinct
// from FlavorID.
FlavorIDResize string
// FloatingIPPool contains the name of the pool from where to obtain floating IPs.
FloatingIPPoolName string
// NetworkName is the name of a network to launch the instance on.
NetworkName string
// ExternalNetworkID is the network ID of the external network.
ExternalNetworkID string
// ShareNetworkID is the Manila Share network ID
ShareNetworkID string
}
// AcceptanceTestChoicesFromEnv populates a ComputeChoices struct from environment variables.
// If any required state is missing, an `error` will be returned that enumerates the missing properties.
func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) {
imageID := os.Getenv("OS_IMAGE_ID")
flavorID := os.Getenv("OS_FLAVOR_ID")
flavorIDResize := os.Getenv("OS_FLAVOR_ID_RESIZE")
networkName := os.Getenv("OS_NETWORK_NAME")
floatingIPPoolName := os.Getenv("OS_POOL_NAME")
externalNetworkID := os.Getenv("OS_EXTGW_ID")
shareNetworkID := os.Getenv("OS_SHARE_NETWORK_ID")
missing := make([]string, 0, 3)
if imageID == "" {
missing = append(missing, "OS_IMAGE_ID")
}
if flavorID == "" {
missing = append(missing, "OS_FLAVOR_ID")
}
if flavorIDResize == "" {
missing = append(missing, "OS_FLAVOR_ID_RESIZE")
}
if floatingIPPoolName == "" {
missing = append(missing, "OS_POOL_NAME")
}
if externalNetworkID == "" {
missing = append(missing, "OS_EXTGW_ID")
}
if networkName == "" {
networkName = "private"
}
if shareNetworkID == "" {
missing = append(missing, "OS_SHARE_NETWORK_ID")
}
notDistinct := ""
if flavorID == flavorIDResize {
notDistinct = "OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE must be distinct."
}
if len(missing) > 0 || notDistinct != "" {
text := "You're missing some important setup:\n"
if len(missing) > 0 {
text += " * These environment variables must be provided: " + strings.Join(missing, ", ") + "\n"
}
if notDistinct != "" {
text += " * " + notDistinct + "\n"
}
return nil, fmt.Errorf(text)
}
return &AcceptanceTestChoices{
ImageID: imageID,
FlavorID: flavorID,
FlavorIDResize: flavorIDResize,
FloatingIPPoolName: floatingIPPoolName,
NetworkName: networkName,
ExternalNetworkID: externalNetworkID,
ShareNetworkID: shareNetworkID,
}, nil
}
// NewBlockStorageV1Client returns a *ServiceClient for making calls
// to the OpenStack Block Storage v1 API. An error will be returned
// if authentication or client creation was not possible.
func NewBlockStorageV1Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewBlockStorageV2Client returns a *ServiceClient for making calls
// to the OpenStack Block Storage v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewBlockStorageV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewBlockStorageV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewSharedFileSystemV2Client returns a *ServiceClient for making calls
// to the OpenStack Shared File System v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewSharedFileSystemV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewComputeV2Client returns a *ServiceClient for making calls
// to the OpenStack Compute v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewComputeV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewComputeV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewDNSV2Client returns a *ServiceClient for making calls
// to the OpenStack Compute v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewDNSV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewDNSV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewIdentityV2Client returns a *ServiceClient for making calls
// to the OpenStack Identity v2 API. An error will be returned
// if authentication or client creation was not possible.
func NewIdentityV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewIdentityV2AdminClient returns a *ServiceClient for making calls
// to the Admin Endpoint of the OpenStack Identity v2 API. An error
// will be returned if authentication or client creation was not possible.
func NewIdentityV2AdminClient() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
Availability: gophercloud.AvailabilityAdmin,
})
}
// NewIdentityV2UnauthenticatedClient returns an unauthenticated *ServiceClient
// for the OpenStack Identity v2 API. An error will be returned if
// authentication or client creation was not possible.
func NewIdentityV2UnauthenticatedClient() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.NewClient(ao.IdentityEndpoint)
if err != nil {
return nil, err
}
return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{})
}
// NewIdentityV3Client returns a *ServiceClient for making calls
// to the OpenStack Identity v3 API. An error will be returned
// if authentication or client creation was not possible.
func NewIdentityV3Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewIdentityV3(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewIdentityV3UnauthenticatedClient returns an unauthenticated *ServiceClient
// for the OpenStack Identity v3 API. An error will be returned if
// authentication or client creation was not possible.
func NewIdentityV3UnauthenticatedClient() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.NewClient(ao.IdentityEndpoint)
if err != nil {
return nil, err
}
return openstack.NewIdentityV3(client, gophercloud.EndpointOpts{})
}
// NewImageServiceV2Client returns a *ServiceClient for making calls to the
// OpenStack Image v2 API. An error will be returned if authentication or
// client creation was not possible.
func NewImageServiceV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewImageServiceV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
// NewNetworkV2Client returns a *ServiceClient for making calls to the
// OpenStack Networking v2 API. An error will be returned if authentication
// or client creation was not possible.
func NewNetworkV2Client() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}

View File

@ -0,0 +1,153 @@
// Package extensions contains common functions for creating block storage
// resources that are extensions of the block storage API. See the `*_test.go`
// files for example usages.
package extensions
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
// CreateUploadImage will upload volume it as volume-baked image. An name of new image or err will be
// returned
func CreateUploadImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (volumeactions.VolumeImage, error) {
if testing.Short() {
t.Skip("Skipping test that requires volume-backed image uploading in short mode.")
}
imageName := tools.RandomString("ACPTTEST", 16)
uploadImageOpts := volumeactions.UploadImageOpts{
ImageName: imageName,
Force: true,
}
volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract()
if err != nil {
return volumeImage, err
}
t.Logf("Uploading volume %s as volume-backed image %s", volume.ID, imageName)
if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil {
return volumeImage, err
}
t.Logf("Uploaded volume %s as volume-backed image %s", volume.ID, imageName)
return volumeImage, nil
}
// DeleteUploadedImage deletes uploaded image. An error will be returned
// if the deletion request failed.
func DeleteUploadedImage(t *testing.T, client *gophercloud.ServiceClient, imageName string) error {
if testing.Short() {
t.Skip("Skipping test that requires volume-backed image removing in short mode.")
}
t.Logf("Getting image id for image name %s", imageName)
imageID, err := images.IDFromName(client, imageName)
if err != nil {
return err
}
t.Logf("Removing image %s", imageID)
err = images.Delete(client, imageID).ExtractErr()
if err != nil {
return err
}
return nil
}
// CreateVolumeAttach will attach a volume to an instance. An error will be
// returned if the attachment failed.
func CreateVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, server *servers.Server) error {
if testing.Short() {
t.Skip("Skipping test that requires volume attachment in short mode.")
}
attachOpts := volumeactions.AttachOpts{
MountPoint: "/mnt",
Mode: "rw",
InstanceUUID: server.ID,
}
t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
if err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr(); err != nil {
return err
}
if err := volumes.WaitForStatus(client, volume.ID, "in-use", 60); err != nil {
return err
}
t.Logf("Attached volume %s to server %s", volume.ID, server.ID)
return nil
}
// CreateVolumeReserve creates a volume reservation. An error will be returned
// if the reservation failed.
func CreateVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error {
if testing.Short() {
t.Skip("Skipping test that requires volume reservation in short mode.")
}
t.Logf("Attempting to reserve volume %s", volume.ID)
if err := volumeactions.Reserve(client, volume.ID).ExtractErr(); err != nil {
return err
}
t.Logf("Reserved volume %s", volume.ID)
return nil
}
// DeleteVolumeAttach will detach a volume from an instance. A fatal error will
// occur if the snapshot failed to be deleted. This works best when used as a
// deferred function.
func DeleteVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
t.Logf("Attepting to detach volume volume: %s", volume.ID)
detachOpts := volumeactions.DetachOpts{
AttachmentID: volume.Attachments[0].AttachmentID,
}
if err := volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr(); err != nil {
t.Fatalf("Unable to detach volume %s: %v", volume.ID, err)
}
if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil {
t.Fatalf("Volume %s failed to become unavailable in 60 seconds: %v", volume.ID, err)
}
t.Logf("Detached volume: %s", volume.ID)
}
// DeleteVolumeReserve deletes a volume reservation. A fatal error will occur
// if the deletion request failed. This works best when used as a deferred
// function.
func DeleteVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
if testing.Short() {
t.Skip("Skipping test that requires volume reservation in short mode.")
}
t.Logf("Attempting to unreserve volume %s", volume.ID)
if err := volumeactions.Unreserve(client, volume.ID).ExtractErr(); err != nil {
t.Fatalf("Unable to unreserve volume %s: %v", volume.ID, err)
}
t.Logf("Unreserved volume %s", volume.ID)
}

View File

@ -0,0 +1,3 @@
// The extensions package contains acceptance tests for the Openstack Cinder extensions service.
package extensions

View File

@ -0,0 +1,143 @@
// +build acceptance blockstorage
package extensions
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2"
compute "github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2"
)
func TestVolumeActionsUploadImageDestroy(t *testing.T) {
blockClient, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
computeClient, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
volume, err := blockstorage.CreateVolume(t, blockClient)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer blockstorage.DeleteVolume(t, blockClient, volume)
volumeImage, err := CreateUploadImage(t, blockClient, volume)
if err != nil {
t.Fatalf("Unable to upload volume-backed image: %v", err)
}
tools.PrintResource(t, volumeImage)
err = DeleteUploadedImage(t, computeClient, volumeImage.ImageName)
if err != nil {
t.Fatalf("Unable to delete volume-backed image: %v", err)
}
}
func TestVolumeActionsAttachCreateDestroy(t *testing.T) {
blockClient, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
computeClient, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := compute.CreateServer(t, computeClient)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer compute.DeleteServer(t, computeClient, server)
volume, err := blockstorage.CreateVolume(t, blockClient)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer blockstorage.DeleteVolume(t, blockClient, volume)
err = CreateVolumeAttach(t, blockClient, volume, server)
if err != nil {
t.Fatalf("Unable to attach volume: %v", err)
}
newVolume, err := volumes.Get(blockClient, volume.ID).Extract()
if err != nil {
t.Fatal("Unable to get updated volume information: %v", err)
}
DeleteVolumeAttach(t, blockClient, newVolume)
}
func TestVolumeActionsReserveUnreserve(t *testing.T) {
client, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create blockstorage client: %v", err)
}
volume, err := blockstorage.CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer blockstorage.DeleteVolume(t, client, volume)
err = CreateVolumeReserve(t, client, volume)
if err != nil {
t.Fatalf("Unable to create volume reserve: %v", err)
}
defer DeleteVolumeReserve(t, client, volume)
}
// Note(jtopjian): I plan to work on this at some point, but it requires
// setting up a server with iscsi utils.
/*
func TestVolumeConns(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
t.Logf("Creating volume")
cv, err := volumes.Create(client, &volumes.CreateOpts{
Size: 1,
Name: "blockv2-volume",
}).Extract()
th.AssertNoErr(t, err)
defer func() {
err = volumes.WaitForStatus(client, cv.ID, "available", 60)
th.AssertNoErr(t, err)
t.Logf("Deleting volume")
err = volumes.Delete(client, cv.ID).ExtractErr()
th.AssertNoErr(t, err)
}()
err = volumes.WaitForStatus(client, cv.ID, "available", 60)
th.AssertNoErr(t, err)
connOpts := &volumeactions.ConnectorOpts{
IP: "127.0.0.1",
Host: "stack",
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
Multipath: false,
Platform: "x86_64",
OSType: "linux2",
}
t.Logf("Initializing connection")
_, err = volumeactions.InitializeConnection(client, cv.ID, connOpts).Extract()
th.AssertNoErr(t, err)
t.Logf("Terminating connection")
err = volumeactions.TerminateConnection(client, cv.ID, connOpts).ExtractErr()
th.AssertNoErr(t, err)
}
*/

View File

@ -0,0 +1,142 @@
// Package v1 contains common functions for creating block storage based
// resources for use in acceptance tests. See the `*_test.go` files for
// example usages.
package v1
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumetypes"
)
// CreateSnapshot will create a volume snapshot based off of a given volume and
// with a random name. An error will be returned if the snapshot failed to be
// created.
func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) {
if testing.Short() {
t.Skip("Skipping test that requires snapshot creation in short mode.")
}
snapshotName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create snapshot %s based on volume %s", snapshotName, volume.ID)
createOpts := snapshots.CreateOpts{
Name: snapshotName,
VolumeID: volume.ID,
}
snapshot, err := snapshots.Create(client, createOpts).Extract()
if err != nil {
return snapshot, err
}
err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60)
if err != nil {
return snapshot, err
}
return snapshot, nil
}
// CreateVolume will create a volume with a random name and size of 1GB. An
// error will be returned if the volume was unable to be created.
func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) {
if testing.Short() {
t.Skip("Skipping test that requires volume creation in short mode.")
}
volumeName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create volume: %s", volumeName)
createOpts := volumes.CreateOpts{
Size: 1,
Name: volumeName,
}
volume, err := volumes.Create(client, createOpts).Extract()
if err != nil {
return volume, err
}
err = volumes.WaitForStatus(client, volume.ID, "available", 60)
if err != nil {
return volume, err
}
return volume, nil
}
// CreateVolumeType will create a volume type with a random name. An error will
// be returned if the volume type was unable to be created.
func CreateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*volumetypes.VolumeType, error) {
volumeTypeName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create volume type: %s", volumeTypeName)
createOpts := volumetypes.CreateOpts{
Name: volumeTypeName,
ExtraSpecs: map[string]interface{}{
"capabilities": "ssd",
"priority": 3,
},
}
volumeType, err := volumetypes.Create(client, createOpts).Extract()
if err != nil {
return volumeType, err
}
return volumeType, nil
}
// DeleteSnapshot will delete a snapshot. A fatal error will occur if the
// snapshot failed to be deleted. This works best when used as a deferred
// function.
func DeleteSnapshotshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) {
err := snapshots.Delete(client, snapshot.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete snapshot %s: %v", snapshot.ID, err)
}
// Volumes can't be deleted until their snapshots have been,
// so block up to 120 seconds for the snapshot to delete.
err = gophercloud.WaitFor(120, func() (bool, error) {
_, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("Unable to wait for snapshot to delete: %v", err)
}
t.Logf("Deleted snapshot: %s", snapshot.ID)
}
// DeleteVolume will delete a volume. A fatal error will occur if the volume
// failed to be deleted. This works best when used as a deferred function.
func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
err := volumes.Delete(client, volume.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete volume %s: %v", volume.ID, err)
}
t.Logf("Deleted volume: %s", volume.ID)
}
// DeleteVolumeType will delete a volume type. A fatal error will occur if the
// volume type failed to be deleted. This works best when used as a deferred
// function.
func DeleteVolumeType(t *testing.T, client *gophercloud.ServiceClient, volumeType *volumetypes.VolumeType) {
err := volumetypes.Delete(client, volumeType.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete volume type %s: %v", volumeType.ID, err)
}
t.Logf("Deleted volume type: %s", volumeType.ID)
}

View File

@ -0,0 +1,2 @@
// Package v1 contains openstack cinder acceptance tests
package v1

View File

@ -0,0 +1,58 @@
// +build acceptance blockstorage
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots"
)
func TestSnapshotsList(t *testing.T) {
client, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := snapshots.List(client, snapshots.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve snapshots: %v", err)
}
allSnapshots, err := snapshots.ExtractSnapshots(allPages)
if err != nil {
t.Fatalf("Unable to extract snapshots: %v", err)
}
for _, snapshot := range allSnapshots {
tools.PrintResource(t, snapshot)
}
}
func TestSnapshotsCreateDelete(t *testing.T) {
client, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
volume, err := CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer DeleteVolume(t, client, volume)
snapshot, err := CreateSnapshot(t, client, volume)
if err != nil {
t.Fatalf("Unable to create snapshot: %v", err)
}
defer DeleteSnapshotshot(t, client, snapshot)
newSnapshot, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve snapshot: %v", err)
}
tools.PrintResource(t, newSnapshot)
}

View File

@ -0,0 +1,52 @@
// +build acceptance blockstorage
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
)
func TestVolumesList(t *testing.T) {
client, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := volumes.List(client, volumes.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve volumes: %v", err)
}
allVolumes, err := volumes.ExtractVolumes(allPages)
if err != nil {
t.Fatalf("Unable to extract volumes: %v", err)
}
for _, volume := range allVolumes {
tools.PrintResource(t, volume)
}
}
func TestVolumesCreateDestroy(t *testing.T) {
client, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create blockstorage client: %v", err)
}
volume, err := CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer DeleteVolume(t, client, volume)
newVolume, err := volumes.Get(client, volume.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve volume: %v", err)
}
tools.PrintResource(t, newVolume)
}

View File

@ -0,0 +1,47 @@
// +build acceptance blockstorage
package v1
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumetypes"
)
func TestVolumeTypesList(t *testing.T) {
client, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := volumetypes.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve volume types: %v", err)
}
allVolumeTypes, err := volumetypes.ExtractVolumeTypes(allPages)
if err != nil {
t.Fatalf("Unable to extract volume types: %v", err)
}
for _, volumeType := range allVolumeTypes {
tools.PrintResource(t, volumeType)
}
}
func TestVolumeTypesCreateDestroy(t *testing.T) {
client, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
volumeType, err := CreateVolumeType(t, client)
if err != nil {
t.Fatalf("Unable to create volume type: %v", err)
}
defer DeleteVolumeType(t, client, volumeType)
tools.PrintResource(t, volumeType)
}

View File

@ -0,0 +1,142 @@
// Package v2 contains common functions for creating block storage based
// resources for use in acceptance tests. See the `*_test.go` files for
// example usages.
package v2
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
)
// CreateVolume will create a volume with a random name and size of 1GB. An
// error will be returned if the volume was unable to be created.
func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) {
if testing.Short() {
t.Skip("Skipping test that requires volume creation in short mode.")
}
volumeName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create volume: %s", volumeName)
createOpts := volumes.CreateOpts{
Size: 1,
Name: volumeName,
}
volume, err := volumes.Create(client, createOpts).Extract()
if err != nil {
return volume, err
}
err = volumes.WaitForStatus(client, volume.ID, "available", 60)
if err != nil {
return volume, err
}
return volume, nil
}
// CreateVolumeFromImage will create a volume from with a random name and size of
// 1GB. An error will be returned if the volume was unable to be created.
func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) {
if testing.Short() {
t.Skip("Skipping test that requires volume creation in short mode.")
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
volumeName := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create volume: %s", volumeName)
createOpts := volumes.CreateOpts{
Size: 1,
Name: volumeName,
ImageID: choices.ImageID,
}
volume, err := volumes.Create(client, createOpts).Extract()
if err != nil {
return volume, err
}
err = volumes.WaitForStatus(client, volume.ID, "available", 60)
if err != nil {
return volume, err
}
return volume, nil
}
// DeleteVolume will delete a volume. A fatal error will occur if the volume
// failed to be deleted. This works best when used as a deferred function.
func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) {
err := volumes.Delete(client, volume.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete volume %s: %v", volume.ID, err)
}
t.Logf("Deleted volume: %s", volume.ID)
}
// CreateSnapshot will create a snapshot of the specified volume.
// Snapshot will be assigned a random name and description.
func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) {
if testing.Short() {
t.Skip("Skipping test that requires snapshot creation in short mode.")
}
snapshotName := tools.RandomString("ACPTTEST", 16)
snapshotDescription := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create snapshot: %s", snapshotName)
createOpts := snapshots.CreateOpts{
VolumeID: volume.ID,
Name: snapshotName,
Description: snapshotDescription,
}
snapshot, err := snapshots.Create(client, createOpts).Extract()
if err != nil {
return snapshot, err
}
err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60)
if err != nil {
return snapshot, err
}
return snapshot, nil
}
// DeleteSnapshot will delete a snapshot. A fatal error will occur if the
// snapshot failed to be deleted.
func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) {
err := snapshots.Delete(client, snapshot.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err)
}
// Volumes can't be deleted until their snapshots have been,
// so block up to 120 seconds for the snapshot to delete.
err = gophercloud.WaitFor(120, func() (bool, error) {
_, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("Error waiting for snapshot to delete: %v", err)
}
t.Logf("Deleted snapshot: %s", snapshot.ID)
}

View File

@ -0,0 +1,3 @@
// The v2 package contains acceptance tests for the Openstack Cinder V2 service.
package v2

View File

@ -0,0 +1,58 @@
// +build acceptance blockstorage
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots"
)
func TestSnapshotsList(t *testing.T) {
client, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := snapshots.List(client, snapshots.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve snapshots: %v", err)
}
allSnapshots, err := snapshots.ExtractSnapshots(allPages)
if err != nil {
t.Fatalf("Unable to extract snapshots: %v", err)
}
for _, snapshot := range allSnapshots {
tools.PrintResource(t, snapshot)
}
}
func TestSnapshotsCreateDelete(t *testing.T) {
client, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
volume, err := CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer DeleteVolume(t, client, volume)
snapshot, err := CreateSnapshot(t, client, volume)
if err != nil {
t.Fatalf("Unable to create snapshot: %v", err)
}
defer DeleteSnapshot(t, client, snapshot)
newSnapshot, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve snapshot: %v", err)
}
tools.PrintResource(t, newSnapshot)
}

View File

@ -0,0 +1,52 @@
// +build acceptance blockstorage
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
)
func TestVolumesList(t *testing.T) {
client, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
allPages, err := volumes.List(client, volumes.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve volumes: %v", err)
}
allVolumes, err := volumes.ExtractVolumes(allPages)
if err != nil {
t.Fatalf("Unable to extract volumes: %v", err)
}
for _, volume := range allVolumes {
tools.PrintResource(t, volume)
}
}
func TestVolumesCreateDestroy(t *testing.T) {
client, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create blockstorage client: %v", err)
}
volume, err := CreateVolume(t, client)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
defer DeleteVolume(t, client, volume)
newVolume, err := volumes.Get(client, volume.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve volume: %v", err)
}
tools.PrintResource(t, newVolume)
}

View File

@ -0,0 +1,86 @@
// +build acceptance
package openstack
import (
"os"
"testing"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
)
func TestAuthenticatedClient(t *testing.T) {
// Obtain credentials from the environment.
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to acquire credentials: %v", err)
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
t.Fatalf("Unable to authenticate: %v", err)
}
if client.TokenID == "" {
t.Errorf("No token ID assigned to the client")
}
t.Logf("Client successfully acquired a token: %v", client.TokenID)
// Find the storage service in the service catalog.
storage, err := openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
t.Errorf("Unable to locate a storage service: %v", err)
} else {
t.Logf("Located a storage service at endpoint: [%s]", storage.Endpoint)
}
}
func TestReauth(t *testing.T) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to obtain environment auth options: %v", err)
}
// Allow reauth
ao.AllowReauth = true
provider, err := openstack.NewClient(ao.IdentityEndpoint)
if err != nil {
t.Fatalf("Unable to create provider: %v", err)
}
err = openstack.Authenticate(provider, ao)
if err != nil {
t.Fatalf("Unable to authenticate: %v", err)
}
t.Logf("Creating a compute client")
_, err = openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
t.Fatalf("Unable to create compute client: %v", err)
}
t.Logf("Sleeping for 1 second")
time.Sleep(1 * time.Second)
t.Logf("Attempting to reauthenticate")
err = provider.ReauthFunc()
if err != nil {
t.Fatalf("Unable to reauthenticate: %v", err)
}
t.Logf("Creating a compute client")
_, err = openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
t.Fatalf("Unable to create compute client: %v", err)
}
}

View File

@ -0,0 +1,19 @@
// Package openstack contains common functions that can be used
// across all OpenStack components for acceptance testing.
package openstack
import (
"testing"
"github.com/gophercloud/gophercloud/openstack/common/extensions"
)
// PrintExtension prints an extension and all of its attributes.
func PrintExtension(t *testing.T, extension *extensions.Extension) {
t.Logf("Name: %s", extension.Name)
t.Logf("Namespace: %s", extension.Namespace)
t.Logf("Alias: %s", extension.Alias)
t.Logf("Description: %s", extension.Description)
t.Logf("Updated: %s", extension.Updated)
t.Logf("Links: %v", extension.Links)
}

View File

@ -0,0 +1,261 @@
// +build acceptance compute bootfromvolume
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
)
func TestBootFromImage(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
BootIndex: 0,
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationLocal,
SourceType: bootfromvolume.SourceImage,
UUID: choices.ImageID,
},
}
server, err := CreateBootableVolumeServer(t, client, blockDevices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
tools.PrintResource(t, server)
}
func TestBootFromNewVolume(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceImage,
UUID: choices.ImageID,
VolumeSize: 2,
},
}
server, err := CreateBootableVolumeServer(t, client, blockDevices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
tools.PrintResource(t, server)
}
func TestBootFromExistingVolume(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
computeClient, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
blockStorageClient, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a block storage client: %v", err)
}
volume, err := blockstorage.CreateVolumeFromImage(t, blockStorageClient)
if err != nil {
t.Fatal(err)
}
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceVolume,
UUID: volume.ID,
},
}
server, err := CreateBootableVolumeServer(t, computeClient, blockDevices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, computeClient, server)
tools.PrintResource(t, server)
}
func TestBootFromMultiEphemeralServer(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
BootIndex: 0,
DestinationType: bootfromvolume.DestinationLocal,
DeleteOnTermination: true,
SourceType: bootfromvolume.SourceImage,
UUID: choices.ImageID,
VolumeSize: 5,
},
bootfromvolume.BlockDevice{
BootIndex: -1,
DestinationType: bootfromvolume.DestinationLocal,
DeleteOnTermination: true,
GuestFormat: "ext4",
SourceType: bootfromvolume.SourceBlank,
VolumeSize: 1,
},
bootfromvolume.BlockDevice{
BootIndex: -1,
DestinationType: bootfromvolume.DestinationLocal,
DeleteOnTermination: true,
GuestFormat: "ext4",
SourceType: bootfromvolume.SourceBlank,
VolumeSize: 1,
},
}
server, err := CreateMultiEphemeralServer(t, client, blockDevices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
tools.PrintResource(t, server)
}
func TestAttachNewVolume(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
BootIndex: 0,
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationLocal,
SourceType: bootfromvolume.SourceImage,
UUID: choices.ImageID,
},
bootfromvolume.BlockDevice{
BootIndex: 1,
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceBlank,
VolumeSize: 2,
},
}
server, err := CreateBootableVolumeServer(t, client, blockDevices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
tools.PrintResource(t, server)
}
func TestAttachExistingVolume(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
computeClient, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
blockStorageClient, err := clients.NewBlockStorageV2Client()
if err != nil {
t.Fatalf("Unable to create a block storage client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
volume, err := blockstorage.CreateVolume(t, blockStorageClient)
if err != nil {
t.Fatal(err)
}
blockDevices := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
BootIndex: 0,
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationLocal,
SourceType: bootfromvolume.SourceImage,
UUID: choices.ImageID,
},
bootfromvolume.BlockDevice{
BootIndex: 1,
DeleteOnTermination: true,
DestinationType: bootfromvolume.DestinationVolume,
SourceType: bootfromvolume.SourceVolume,
UUID: volume.ID,
},
}
server, err := CreateBootableVolumeServer(t, computeClient, blockDevices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, computeClient, server)
tools.PrintResource(t, server)
}

View File

@ -0,0 +1,767 @@
// Package v2 contains common functions for creating compute-based resources
// for use in acceptance tests. See the `*_test.go` files for example usages.
package v2
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"golang.org/x/crypto/ssh"
)
// AssociateFloatingIP will associate a floating IP with an instance. An error
// will be returned if the floating IP was unable to be associated.
func AssociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) error {
associateOpts := floatingips.AssociateOpts{
FloatingIP: floatingIP.IP,
}
t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, server.ID)
err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
if err != nil {
return err
}
return nil
}
// AssociateFloatingIPWithFixedIP will associate a floating IP with an
// instance's specific fixed IP. An error will be returend if the floating IP
// was unable to be associated.
func AssociateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server, fixedIP string) error {
associateOpts := floatingips.AssociateOpts{
FloatingIP: floatingIP.IP,
FixedIP: fixedIP,
}
t.Logf("Attempting to associate floating IP %s to fixed IP %s on instance %s", floatingIP.IP, fixedIP, server.ID)
err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
if err != nil {
return err
}
return nil
}
// CreateBootableVolumeServer works like CreateServer but is configured with
// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
// An error will be returned if a server was unable to be created.
func CreateBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var server *servers.Server
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
return server, err
}
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create bootable volume server: %s", name)
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
}
if blockDevices[0].SourceType == bootfromvolume.SourceImage && blockDevices[0].DestinationType == bootfromvolume.DestinationLocal {
serverCreateOpts.ImageRef = blockDevices[0].UUID
}
server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
BlockDevice: blockDevices,
}).Extract()
if err != nil {
return server, err
}
if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
return server, err
}
newServer, err := servers.Get(client, server.ID).Extract()
return newServer, nil
}
// CreateDefaultRule will create a default security group rule with a
// random port range between 80 and 90. An error will be returned if
// a default rule was unable to be created.
func CreateDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.DefaultRule, error) {
createOpts := dsr.CreateOpts{
FromPort: tools.RandomInt(80, 89),
ToPort: tools.RandomInt(90, 99),
IPProtocol: "TCP",
CIDR: "0.0.0.0/0",
}
defaultRule, err := dsr.Create(client, createOpts).Extract()
if err != nil {
return *defaultRule, err
}
t.Logf("Created default rule: %s", defaultRule.ID)
return *defaultRule, nil
}
// CreateFloatingIP will allocate a floating IP.
// An error will be returend if one was unable to be allocated.
func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient) (*floatingips.FloatingIP, error) {
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
createOpts := floatingips.CreateOpts{
Pool: choices.FloatingIPPoolName,
}
floatingIP, err := floatingips.Create(client, createOpts).Extract()
if err != nil {
return floatingIP, err
}
t.Logf("Created floating IP: %s", floatingIP.ID)
return floatingIP, nil
}
func createKey() (string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", err
}
publicKey := privateKey.PublicKey
pub, err := ssh.NewPublicKey(&publicKey)
if err != nil {
return "", err
}
pubBytes := ssh.MarshalAuthorizedKey(pub)
pk := string(pubBytes)
return pk, nil
}
// CreateKeyPair will create a KeyPair with a random name. An error will occur
// if the keypair failed to be created. An error will be returned if the
// keypair was unable to be created.
func CreateKeyPair(t *testing.T, client *gophercloud.ServiceClient) (*keypairs.KeyPair, error) {
keyPairName := tools.RandomString("keypair_", 5)
t.Logf("Attempting to create keypair: %s", keyPairName)
createOpts := keypairs.CreateOpts{
Name: keyPairName,
}
keyPair, err := keypairs.Create(client, createOpts).Extract()
if err != nil {
return keyPair, err
}
t.Logf("Created keypair: %s", keyPairName)
return keyPair, nil
}
// CreateMultiEphemeralServer works like CreateServer but is configured with
// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
// These block devices act like block devices when booting from a volume but
// are actually local ephemeral disks.
// An error will be returned if a server was unable to be created.
func CreateMultiEphemeralServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var server *servers.Server
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
return server, err
}
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create bootable volume server: %s", name)
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
}
server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
BlockDevice: blockDevices,
}).Extract()
if err != nil {
return server, err
}
if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
return server, err
}
newServer, err := servers.Get(client, server.ID).Extract()
return newServer, nil
}
// CreateSecurityGroup will create a security group with a random name.
// An error will be returned if one was failed to be created.
func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (secgroups.SecurityGroup, error) {
createOpts := secgroups.CreateOpts{
Name: tools.RandomString("secgroup_", 5),
Description: "something",
}
securityGroup, err := secgroups.Create(client, createOpts).Extract()
if err != nil {
return *securityGroup, err
}
t.Logf("Created security group: %s", securityGroup.ID)
return *securityGroup, nil
}
// CreateSecurityGroupRule will create a security group rule with a random name
// and a random TCP port range between port 80 and 99. An error will be
// returned if the rule failed to be created.
func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) (secgroups.Rule, error) {
createOpts := secgroups.CreateRuleOpts{
ParentGroupID: securityGroupID,
FromPort: tools.RandomInt(80, 89),
ToPort: tools.RandomInt(90, 99),
IPProtocol: "TCP",
CIDR: "0.0.0.0/0",
}
rule, err := secgroups.CreateRule(client, createOpts).Extract()
if err != nil {
return *rule, err
}
t.Logf("Created security group rule: %s", rule.ID)
return *rule, nil
}
// CreateServer creates a basic instance with a randomly generated name.
// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
// The image will be the value of the OS_IMAGE_ID environment variable.
// The instance will be launched on the network specified in OS_NETWORK_NAME.
// An error will be returned if the instance was unable to be created.
func CreateServer(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var server *servers.Server
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
return server, err
}
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create server: %s", name)
pwd := tools.MakeNewPassword("")
server, err = servers.Create(client, servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
AdminPass: pwd,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
Metadata: map[string]string{
"abc": "def",
},
Personality: servers.Personality{
&servers.File{
Path: "/etc/test",
Contents: []byte("hello world"),
},
},
}).Extract()
if err != nil {
return server, err
}
if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
return server, err
}
return server, nil
}
// CreateServerWithoutImageRef creates a basic instance with a randomly generated name.
// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
// The image is intentionally missing to trigger an error.
// The instance will be launched on the network specified in OS_NETWORK_NAME.
// An error will be returned if the instance was unable to be created.
func CreateServerWithoutImageRef(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var server *servers.Server
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
return server, err
}
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create server: %s", name)
pwd := tools.MakeNewPassword("")
server, err = servers.Create(client, servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
AdminPass: pwd,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
Personality: servers.Personality{
&servers.File{
Path: "/etc/test",
Contents: []byte("hello world"),
},
},
}).Extract()
if err != nil {
return server, err
}
if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
return server, err
}
return server, nil
}
// CreateServerGroup will create a server with a random name. An error will be
// returned if the server group failed to be created.
func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) {
sg, err := servergroups.Create(client, &servergroups.CreateOpts{
Name: "test",
Policies: []string{policy},
}).Extract()
if err != nil {
return sg, err
}
return sg, nil
}
// CreateServerInServerGroup works like CreateServer but places the instance in
// a specified Server Group.
func CreateServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var server *servers.Server
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
return server, err
}
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create server: %s", name)
pwd := tools.MakeNewPassword("")
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
AdminPass: pwd,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
}
schedulerHintsOpts := schedulerhints.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
SchedulerHints: schedulerhints.SchedulerHints{
Group: serverGroup.ID,
},
}
server, err = servers.Create(client, schedulerHintsOpts).Extract()
if err != nil {
return server, err
}
return server, nil
}
// CreateServerWithPublicKey works the same as CreateServer, but additionally
// configures the server with a specified Key Pair name.
func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, keyPairName string) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var server *servers.Server
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
return server, err
}
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create server: %s", name)
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
}
server, err = servers.Create(client, keypairs.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
KeyName: keyPairName,
}).Extract()
if err != nil {
return server, err
}
if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
return server, err
}
return server, nil
}
// CreateVolumeAttachment will attach a volume to a server. An error will be
// returned if the volume failed to attach.
func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volume *volumes.Volume) (*volumeattach.VolumeAttachment, error) {
volumeAttachOptions := volumeattach.CreateOpts{
VolumeID: volume.ID,
}
t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
volumeAttachment, err := volumeattach.Create(client, server.ID, volumeAttachOptions).Extract()
if err != nil {
return volumeAttachment, err
}
if err := volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
return volumeAttachment, err
}
return volumeAttachment, nil
}
// DeleteDefaultRule deletes a default security group rule.
// A fatal error will occur if the rule failed to delete. This works best when
// using it as a deferred function.
func DeleteDefaultRule(t *testing.T, client *gophercloud.ServiceClient, defaultRule dsr.DefaultRule) {
err := dsr.Delete(client, defaultRule.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete default rule %s: %v", defaultRule.ID, err)
}
t.Logf("Deleted default rule: %s", defaultRule.ID)
}
// DeleteFloatingIP will de-allocate a floating IP. A fatal error will occur if
// the floating IP failed to de-allocate. This works best when using it as a
// deferred function.
func DeleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP) {
err := floatingips.Delete(client, floatingIP.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err)
}
t.Logf("Deleted floating IP: %s", floatingIP.ID)
}
// DeleteKeyPair will delete a specified keypair. A fatal error will occur if
// the keypair failed to be deleted. This works best when used as a deferred
// function.
func DeleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, keyPair *keypairs.KeyPair) {
err := keypairs.Delete(client, keyPair.Name).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete keypair %s: %v", keyPair.Name, err)
}
t.Logf("Deleted keypair: %s", keyPair.Name)
}
// DeleteSecurityGroup will delete a security group. A fatal error will occur
// if the group failed to be deleted. This works best as a deferred function.
func DeleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, securityGroup secgroups.SecurityGroup) {
err := secgroups.Delete(client, securityGroup.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete security group %s: %s", securityGroup.ID, err)
}
t.Logf("Deleted security group: %s", securityGroup.ID)
}
// DeleteSecurityGroupRule will delete a security group rule. A fatal error
// will occur if the rule failed to be deleted. This works best when used
// as a deferred function.
func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, rule secgroups.Rule) {
err := secgroups.DeleteRule(client, rule.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete rule: %v", err)
}
t.Logf("Deleted security group rule: %s", rule.ID)
}
// DeleteServer deletes an instance via its UUID.
// A fatal error will occur if the instance failed to be destroyed. This works
// best when using it as a deferred function.
func DeleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) {
err := servers.Delete(client, server.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete server %s: %s", server.ID, err)
}
t.Logf("Deleted server: %s", server.ID)
}
// DeleteServerGroup will delete a server group. A fatal error will occur if
// the server group failed to be deleted. This works best when used as a
// deferred function.
func DeleteServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) {
err := servergroups.Delete(client, serverGroup.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete server group %s: %v", serverGroup.ID, err)
}
t.Logf("Deleted server group %s", serverGroup.ID)
}
// DeleteVolumeAttachment will disconnect a volume from an instance. A fatal
// error will occur if the volume failed to detach. This works best when used
// as a deferred function.
func DeleteVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volumeAttachment *volumeattach.VolumeAttachment) {
err := volumeattach.Delete(client, server.ID, volumeAttachment.VolumeID).ExtractErr()
if err != nil {
t.Fatalf("Unable to detach volume: %v", err)
}
if err := volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil {
t.Fatalf("Unable to wait for volume: %v", err)
}
t.Logf("Deleted volume: %s", volumeAttachment.VolumeID)
}
// DisassociateFloatingIP will disassociate a floating IP from an instance. A
// fatal error will occur if the floating IP failed to disassociate. This works
// best when using it as a deferred function.
func DisassociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) {
disassociateOpts := floatingips.DisassociateOpts{
FloatingIP: floatingIP.IP,
}
err := floatingips.DisassociateInstance(client, server.ID, disassociateOpts).ExtractErr()
if err != nil {
t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err)
}
t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID)
}
// GetNetworkIDFromNetworks will return the network ID from a specified network
// UUID using the os-networks API extension. An error will be returned if the
// network could not be retrieved.
func GetNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
allPages, err := networks.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list networks: %v", err)
}
networkList, err := networks.ExtractNetworks(allPages)
if err != nil {
t.Fatalf("Unable to list networks: %v", err)
}
networkID := ""
for _, network := range networkList {
t.Logf("Network: %v", network)
if network.Label == networkName {
networkID = network.ID
}
}
t.Logf("Found network ID for %s: %s", networkName, networkID)
return networkID, nil
}
// GetNetworkIDFromTenantNetworks will return the network UUID for a given
// network name using the os-tenant-networks API extension. An error will be
// returned if the network could not be retrieved.
func GetNetworkIDFromTenantNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
allPages, err := tenantnetworks.List(client).AllPages()
if err != nil {
return "", err
}
allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
if err != nil {
return "", err
}
for _, network := range allTenantNetworks {
if network.Name == networkName {
return network.ID, nil
}
}
return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
}
// ImportPublicKey will create a KeyPair with a random name and a specified
// public key. An error will be returned if the keypair failed to be created.
func ImportPublicKey(t *testing.T, client *gophercloud.ServiceClient, publicKey string) (*keypairs.KeyPair, error) {
keyPairName := tools.RandomString("keypair_", 5)
t.Logf("Attempting to create keypair: %s", keyPairName)
createOpts := keypairs.CreateOpts{
Name: keyPairName,
PublicKey: publicKey,
}
keyPair, err := keypairs.Create(client, createOpts).Extract()
if err != nil {
return keyPair, err
}
t.Logf("Created keypair: %s", keyPairName)
return keyPair, nil
}
// ResizeServer performs a resize action on an instance. An error will be
// returned if the instance failed to resize.
// The new flavor that the instance will be resized to is specified in OS_FLAVOR_ID_RESIZE.
func ResizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error {
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
opts := &servers.ResizeOpts{
FlavorRef: choices.FlavorIDResize,
}
if res := servers.Resize(client, server.ID, opts); res.Err != nil {
return res.Err
}
if err := WaitForComputeStatus(client, server, "VERIFY_RESIZE"); err != nil {
return err
}
return nil
}
// WaitForComputeStatus will poll an instance's status until it either matches
// the specified status or the status becomes ERROR.
func WaitForComputeStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error {
return tools.WaitFor(func() (bool, error) {
latest, err := servers.Get(client, server.ID).Extract()
if err != nil {
return false, err
}
if latest.Status == status {
// Success!
return true, nil
}
if latest.Status == "ERROR" {
return false, fmt.Errorf("Instance in ERROR state")
}
return false, nil
})
}
//Convenience method to fill an QuotaSet-UpdateOpts-struct from a QuotaSet-struct
func FillUpdateOptsFromQuotaSet(src quotasets.QuotaSet, dest *quotasets.UpdateOpts) {
dest.FixedIps = &src.FixedIps
dest.FloatingIps = &src.FloatingIps
dest.InjectedFileContentBytes = &src.InjectedFileContentBytes
dest.InjectedFilePathBytes = &src.InjectedFilePathBytes
dest.InjectedFiles = &src.InjectedFiles
dest.KeyPairs = &src.KeyPairs
dest.Ram = &src.Ram
dest.SecurityGroupRules = &src.SecurityGroupRules
dest.SecurityGroups = &src.SecurityGroups
dest.Cores = &src.Cores
dest.Instances = &src.Instances
dest.ServerGroups = &src.ServerGroups
dest.ServerGroupMembers = &src.ServerGroupMembers
dest.MetadataItems = &src.MetadataItems
}

View File

@ -0,0 +1,67 @@
// +build acceptance compute defsecrules
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
)
func TestDefSecRulesList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := dsr.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list default rules: %v", err)
}
allDefaultRules, err := dsr.ExtractDefaultRules(allPages)
if err != nil {
t.Fatalf("Unable to extract default rules: %v", err)
}
for _, defaultRule := range allDefaultRules {
tools.PrintResource(t, defaultRule)
}
}
func TestDefSecRulesCreate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
defaultRule, err := CreateDefaultRule(t, client)
if err != nil {
t.Fatalf("Unable to create default rule: %v", err)
}
defer DeleteDefaultRule(t, client, defaultRule)
tools.PrintResource(t, defaultRule)
}
func TestDefSecRulesGet(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
defaultRule, err := CreateDefaultRule(t, client)
if err != nil {
t.Fatalf("Unable to create default rule: %v", err)
}
defer DeleteDefaultRule(t, client, defaultRule)
newDefaultRule, err := dsr.Get(client, defaultRule.ID).Extract()
if err != nil {
t.Fatalf("Unable to get default rule %s: %v", defaultRule.ID, err)
}
tools.PrintResource(t, newDefaultRule)
}

View File

@ -0,0 +1,46 @@
// +build acceptance compute extensions
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/common/extensions"
)
func TestExtensionsList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := extensions.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list extensions: %v", err)
}
allExtensions, err := extensions.ExtractExtensions(allPages)
if err != nil {
t.Fatalf("Unable to extract extensions: %v", err)
}
for _, extension := range allExtensions {
tools.PrintResource(t, extension)
}
}
func TestExtensionGet(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
extension, err := extensions.Get(client, "os-admin-actions").Extract()
if err != nil {
t.Fatalf("Unable to get extension os-admin-actions: %v", err)
}
tools.PrintResource(t, extension)
}

View File

@ -0,0 +1,74 @@
// +build acceptance compute flavors
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
)
func TestFlavorsList(t *testing.T) {
t.Logf("** Default flavors (same as Project flavors): **")
t.Logf("")
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := flavors.ListDetail(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve flavors: %v", err)
}
allFlavors, err := flavors.ExtractFlavors(allPages)
if err != nil {
t.Fatalf("Unable to extract flavor results: %v", err)
}
for _, flavor := range allFlavors {
tools.PrintResource(t, flavor)
}
flavorAccessTypes := [3]flavors.AccessType{flavors.PublicAccess, flavors.PrivateAccess, flavors.AllAccess}
for _, flavorAccessType := range flavorAccessTypes {
t.Logf("** %s flavors: **", flavorAccessType)
t.Logf("")
allPages, err := flavors.ListDetail(client, flavors.ListOpts{AccessType: flavorAccessType}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve flavors: %v", err)
}
allFlavors, err := flavors.ExtractFlavors(allPages)
if err != nil {
t.Fatalf("Unable to extract flavor results: %v", err)
}
for _, flavor := range allFlavors {
tools.PrintResource(t, flavor)
t.Logf("")
}
}
}
func TestFlavorsGet(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
flavor, err := flavors.Get(client, choices.FlavorID).Extract()
if err != nil {
t.Fatalf("Unable to get flavor information: %v", err)
}
tools.PrintResource(t, flavor)
}

View File

@ -0,0 +1,148 @@
// +build acceptance compute servers
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
func TestFloatingIPsList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := floatingips.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve floating IPs: %v", err)
}
allFloatingIPs, err := floatingips.ExtractFloatingIPs(allPages)
if err != nil {
t.Fatalf("Unable to extract floating IPs: %v", err)
}
for _, floatingIP := range allFloatingIPs {
tools.PrintResource(t, floatingIP)
}
}
func TestFloatingIPsCreate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
floatingIP, err := CreateFloatingIP(t, client)
if err != nil {
t.Fatalf("Unable to create floating IP: %v", err)
}
defer DeleteFloatingIP(t, client, floatingIP)
tools.PrintResource(t, floatingIP)
}
func TestFloatingIPsAssociate(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
floatingIP, err := CreateFloatingIP(t, client)
if err != nil {
t.Fatalf("Unable to create floating IP: %v", err)
}
defer DeleteFloatingIP(t, client, floatingIP)
tools.PrintResource(t, floatingIP)
err = AssociateFloatingIP(t, client, floatingIP, server)
if err != nil {
t.Fatalf("Unable to associate floating IP %s with server %s: %v", floatingIP.IP, server.ID, err)
}
defer DisassociateFloatingIP(t, client, floatingIP, server)
newFloatingIP, err := floatingips.Get(client, floatingIP.ID).Extract()
if err != nil {
t.Fatalf("Unable to get floating IP %s: %v", floatingIP.ID, err)
}
t.Logf("Floating IP %s is associated with Fixed IP %s", floatingIP.IP, newFloatingIP.FixedIP)
tools.PrintResource(t, newFloatingIP)
}
func TestFloatingIPsFixedIPAssociate(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
newServer, err := servers.Get(client, server.ID).Extract()
if err != nil {
t.Fatalf("Unable to get server %s: %v", server.ID, err)
}
floatingIP, err := CreateFloatingIP(t, client)
if err != nil {
t.Fatalf("Unable to create floating IP: %v", err)
}
defer DeleteFloatingIP(t, client, floatingIP)
tools.PrintResource(t, floatingIP)
var fixedIP string
for _, networkAddresses := range newServer.Addresses[choices.NetworkName].([]interface{}) {
address := networkAddresses.(map[string]interface{})
if address["OS-EXT-IPS:type"] == "fixed" {
if address["version"].(float64) == 4 {
fixedIP = address["addr"].(string)
}
}
}
err = AssociateFloatingIPWithFixedIP(t, client, floatingIP, newServer, fixedIP)
if err != nil {
t.Fatalf("Unable to associate floating IP %s with server %s: %v", floatingIP.IP, newServer.ID, err)
}
defer DisassociateFloatingIP(t, client, floatingIP, newServer)
newFloatingIP, err := floatingips.Get(client, floatingIP.ID).Extract()
if err != nil {
t.Fatalf("Unable to get floating IP %s: %v", floatingIP.ID, err)
}
t.Logf("Floating IP %s is associated with Fixed IP %s", floatingIP.IP, newFloatingIP.FixedIP)
tools.PrintResource(t, newFloatingIP)
}

View File

@ -0,0 +1,32 @@
// +build acceptance compute hypervisors
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors"
)
func TestHypervisorsList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := hypervisors.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list hypervisors: %v", err)
}
allHypervisors, err := hypervisors.ExtractHypervisors(allPages)
if err != nil {
t.Fatalf("Unable to extract hypervisors")
}
for _, h := range allHypervisors {
tools.PrintResource(t, h)
}
}

View File

@ -0,0 +1,51 @@
// +build acceptance compute images
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
)
func TestImagesList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute: client: %v", err)
}
allPages, err := images.ListDetail(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve images: %v", err)
}
allImages, err := images.ExtractImages(allPages)
if err != nil {
t.Fatalf("Unable to extract image results: %v", err)
}
for _, image := range allImages {
tools.PrintResource(t, image)
}
}
func TestImagesGet(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute: client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
image, err := images.Get(client, choices.ImageID).Extract()
if err != nil {
t.Fatalf("Unable to get image information: %v", err)
}
tools.PrintResource(t, image)
}

View File

@ -0,0 +1,107 @@
// +build acceptance compute keypairs
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
const keyName = "gophercloud_test_key_pair"
func TestKeypairsList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := keypairs.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve keypairs: %s", err)
}
allKeys, err := keypairs.ExtractKeyPairs(allPages)
if err != nil {
t.Fatalf("Unable to extract keypairs results: %s", err)
}
for _, keypair := range allKeys {
tools.PrintResource(t, keypair)
}
}
func TestKeypairsCreate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
keyPair, err := CreateKeyPair(t, client)
if err != nil {
t.Fatalf("Unable to create key pair: %v", err)
}
defer DeleteKeyPair(t, client, keyPair)
tools.PrintResource(t, keyPair)
}
func TestKeypairsImportPublicKey(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
publicKey, err := createKey()
if err != nil {
t.Fatalf("Unable to create public key: %s", err)
}
keyPair, err := ImportPublicKey(t, client, publicKey)
if err != nil {
t.Fatalf("Unable to create keypair: %s", err)
}
defer DeleteKeyPair(t, client, keyPair)
tools.PrintResource(t, keyPair)
}
func TestKeypairsServerCreateWithKey(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
publicKey, err := createKey()
if err != nil {
t.Fatalf("Unable to create public key: %s", err)
}
keyPair, err := ImportPublicKey(t, client, publicKey)
if err != nil {
t.Fatalf("Unable to create keypair: %s", err)
}
defer DeleteKeyPair(t, client, keyPair)
server, err := CreateServerWithPublicKey(t, client, keyPair.Name)
if err != nil {
t.Fatalf("Unable to create server: %s", err)
}
defer DeleteServer(t, client, server)
server, err = servers.Get(client, server.ID).Extract()
if err != nil {
t.Fatalf("Unable to retrieve server: %s", err)
}
if server.KeyName != keyPair.Name {
t.Fatalf("key name of server %s is %s, not %s", server.ID, server.KeyName, keyPair.Name)
}
}

View File

@ -0,0 +1,52 @@
// +build acceptance compute limits
package v2
import (
"strings"
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/limits"
)
func TestLimits(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
limits, err := limits.Get(client, nil).Extract()
if err != nil {
t.Fatalf("Unable to get limits: %v", err)
}
t.Logf("Limits for scoped user:")
t.Logf("%#v", limits)
}
func TestLimitsForTenant(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
// I think this is the easiest way to get the tenant ID while being
// agnostic to Identity v2 and v3.
// Technically we're just returning the limits for ourselves, but it's
// the fact that we're specifying a tenant ID that is important here.
endpointParts := strings.Split(client.Endpoint, "/")
tenantID := endpointParts[4]
getOpts := limits.GetOpts{
TenantID: tenantID,
}
limits, err := limits.Get(client, getOpts).Extract()
if err != nil {
t.Fatalf("Unable to get absolute limits: %v", err)
}
t.Logf("Limits for tenant %s:", tenantID)
t.Logf("%#v", limits)
}

View File

@ -0,0 +1,56 @@
// +build acceptance compute servers
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
)
func TestNetworksList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := networks.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list networks: %v", err)
}
allNetworks, err := networks.ExtractNetworks(allPages)
if err != nil {
t.Fatalf("Unable to list networks: %v", err)
}
for _, network := range allNetworks {
tools.PrintResource(t, network)
}
}
func TestNetworksGet(t *testing.T) {
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
if err != nil {
t.Fatal(err)
}
network, err := networks.Get(client, networkID).Extract()
if err != nil {
t.Fatalf("Unable to get network %s: %v", networkID, err)
}
tools.PrintResource(t, network)
}

View File

@ -0,0 +1,2 @@
// Package v2 package contains acceptance tests for the Openstack Compute V2 service.
package v2

View File

@ -0,0 +1,184 @@
// +build acceptance compute quotasets
package v2
import (
"fmt"
"os"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
th "github.com/gophercloud/gophercloud/testhelper"
)
func TestQuotasetGet(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
identityClient, err := clients.NewIdentityV2Client()
if err != nil {
t.Fatalf("Unable to get a new identity client: %v", err)
}
tenantID, err := getTenantID(t, identityClient)
if err != nil {
t.Fatal(err)
}
quotaSet, err := quotasets.Get(client, tenantID).Extract()
if err != nil {
t.Fatal(err)
}
tools.PrintResource(t, quotaSet)
}
func getTenantID(t *testing.T, client *gophercloud.ServiceClient) (string, error) {
allPages, err := tenants.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to get list of tenants: %v", err)
}
allTenants, err := tenants.ExtractTenants(allPages)
if err != nil {
t.Fatalf("Unable to extract tenants: %v", err)
}
for _, tenant := range allTenants {
return tenant.ID, nil
}
return "", fmt.Errorf("Unable to get tenant ID")
}
func getTenantIDByName(t *testing.T, client *gophercloud.ServiceClient, name string) (string, error) {
allPages, err := tenants.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to get list of tenants: %v", err)
}
allTenants, err := tenants.ExtractTenants(allPages)
if err != nil {
t.Fatalf("Unable to extract tenants: %v", err)
}
for _, tenant := range allTenants {
if tenant.Name == name {
return tenant.ID, nil
}
}
return "", fmt.Errorf("Unable to get tenant ID")
}
//What will be sent as desired Quotas to the Server
var UpdatQuotaOpts = quotasets.UpdateOpts{
FixedIps: gophercloud.IntToPointer(10),
FloatingIps: gophercloud.IntToPointer(10),
InjectedFileContentBytes: gophercloud.IntToPointer(10240),
InjectedFilePathBytes: gophercloud.IntToPointer(255),
InjectedFiles: gophercloud.IntToPointer(5),
KeyPairs: gophercloud.IntToPointer(10),
MetadataItems: gophercloud.IntToPointer(128),
Ram: gophercloud.IntToPointer(20000),
SecurityGroupRules: gophercloud.IntToPointer(20),
SecurityGroups: gophercloud.IntToPointer(10),
Cores: gophercloud.IntToPointer(10),
Instances: gophercloud.IntToPointer(4),
ServerGroups: gophercloud.IntToPointer(2),
ServerGroupMembers: gophercloud.IntToPointer(3),
}
//What the Server hopefully returns as the new Quotas
var UpdatedQuotas = quotasets.QuotaSet{
FixedIps: 10,
FloatingIps: 10,
InjectedFileContentBytes: 10240,
InjectedFilePathBytes: 255,
InjectedFiles: 5,
KeyPairs: 10,
MetadataItems: 128,
Ram: 20000,
SecurityGroupRules: 20,
SecurityGroups: 10,
Cores: 10,
Instances: 4,
ServerGroups: 2,
ServerGroupMembers: 3,
}
func TestQuotasetUpdateDelete(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
idclient, err := clients.NewIdentityV2Client()
if err != nil {
t.Fatalf("Could not create IdentityClient to look up tenant id!")
}
tenantid, err := getTenantIDByName(t, idclient, os.Getenv("OS_TENANT_NAME"))
if err != nil {
t.Fatalf("Id for Tenant named '%' not found. Please set OS_TENANT_NAME appropriately", os.Getenv("OS_TENANT_NAME"))
}
//save original quotas
orig, err := quotasets.Get(client, tenantid).Extract()
th.AssertNoErr(t, err)
//Test Update
res, err := quotasets.Update(client, tenantid, UpdatQuotaOpts).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, UpdatedQuotas, *res)
//Test Delete
_, err = quotasets.Delete(client, tenantid).Extract()
th.AssertNoErr(t, err)
//We dont know the default quotas, so just check if the quotas are not the same as before
newres, err := quotasets.Get(client, tenantid).Extract()
if newres == res {
t.Fatalf("Quotas after delete equal quotas before delete!")
}
restore := quotasets.UpdateOpts{}
FillUpdateOptsFromQuotaSet(*orig, &restore)
//restore original quotas
res, err = quotasets.Update(client, tenantid, restore).Extract()
th.AssertNoErr(t, err)
orig.ID = ""
th.AssertEquals(t, *orig, *res)
}
// Makes sure that the FillUpdateOptsFromQuotaSet() helper function works properly
func TestFillFromQuotaSetHelperFunction(t *testing.T) {
op := &quotasets.UpdateOpts{}
expected := `
{
"fixed_ips": 10,
"floating_ips": 10,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"key_pairs": 10,
"metadata_items": 128,
"ram": 20000,
"security_group_rules": 20,
"security_groups": 10,
"cores": 10,
"instances": 4,
"server_groups": 2,
"server_group_members": 3
}`
FillUpdateOptsFromQuotaSet(UpdatedQuotas, op)
th.AssertJSONEquals(t, expected, op)
}

View File

@ -0,0 +1,137 @@
// +build acceptance compute secgroups
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
)
func TestSecGroupsList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := secgroups.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve security groups: %v", err)
}
allSecGroups, err := secgroups.ExtractSecurityGroups(allPages)
if err != nil {
t.Fatalf("Unable to extract security groups: %v", err)
}
for _, secgroup := range allSecGroups {
tools.PrintResource(t, secgroup)
}
}
func TestSecGroupsCreate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
securityGroup, err := CreateSecurityGroup(t, client)
if err != nil {
t.Fatalf("Unable to create security group: %v", err)
}
defer DeleteSecurityGroup(t, client, securityGroup)
}
func TestSecGroupsUpdate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
securityGroup, err := CreateSecurityGroup(t, client)
if err != nil {
t.Fatalf("Unable to create security group: %v", err)
}
defer DeleteSecurityGroup(t, client, securityGroup)
updateOpts := secgroups.UpdateOpts{
Name: tools.RandomString("secgroup_", 4),
Description: tools.RandomString("dec_", 10),
}
updatedSecurityGroup, err := secgroups.Update(client, securityGroup.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update security group: %v", err)
}
t.Logf("Updated %s's name to %s", updatedSecurityGroup.ID, updatedSecurityGroup.Name)
}
func TestSecGroupsRuleCreate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
securityGroup, err := CreateSecurityGroup(t, client)
if err != nil {
t.Fatalf("Unable to create security group: %v", err)
}
defer DeleteSecurityGroup(t, client, securityGroup)
rule, err := CreateSecurityGroupRule(t, client, securityGroup.ID)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteSecurityGroupRule(t, client, rule)
newSecurityGroup, err := secgroups.Get(client, securityGroup.ID).Extract()
if err != nil {
t.Fatalf("Unable to obtain security group: %v", err)
}
tools.PrintResource(t, newSecurityGroup)
}
func TestSecGroupsAddGroupToServer(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
securityGroup, err := CreateSecurityGroup(t, client)
if err != nil {
t.Fatalf("Unable to create security group: %v", err)
}
defer DeleteSecurityGroup(t, client, securityGroup)
rule, err := CreateSecurityGroupRule(t, client, securityGroup.ID)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteSecurityGroupRule(t, client, rule)
t.Logf("Adding group %s to server %s", securityGroup.ID, server.ID)
err = secgroups.AddServer(client, server.ID, securityGroup.Name).ExtractErr()
if err != nil && err.Error() != "EOF" {
t.Fatalf("Unable to add group %s to server %s: %s", securityGroup.ID, server.ID, err)
}
t.Logf("Removing group %s from server %s", securityGroup.ID, server.ID)
err = secgroups.RemoveServer(client, server.ID, securityGroup.Name).ExtractErr()
if err != nil && err.Error() != "EOF" {
t.Fatalf("Unable to remove group %s from server %s: %s", securityGroup.ID, server.ID, err)
}
}

View File

@ -0,0 +1,93 @@
// +build acceptance compute servergroups
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
func TestServergroupsList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := servergroups.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list server groups: %v", err)
}
allServerGroups, err := servergroups.ExtractServerGroups(allPages)
if err != nil {
t.Fatalf("Unable to extract server groups: %v", err)
}
for _, serverGroup := range allServerGroups {
tools.PrintResource(t, serverGroup)
}
}
func TestServergroupsCreate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
serverGroup, err := CreateServerGroup(t, client, "anti-affinity")
if err != nil {
t.Fatalf("Unable to create server group: %v", err)
}
defer DeleteServerGroup(t, client, serverGroup)
serverGroup, err = servergroups.Get(client, serverGroup.ID).Extract()
if err != nil {
t.Fatalf("Unable to get server group: %v", err)
}
tools.PrintResource(t, serverGroup)
}
func TestServergroupsAffinityPolicy(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
serverGroup, err := CreateServerGroup(t, client, "affinity")
if err != nil {
t.Fatalf("Unable to create server group: %v", err)
}
defer DeleteServerGroup(t, client, serverGroup)
firstServer, err := CreateServerInServerGroup(t, client, serverGroup)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
if err = WaitForComputeStatus(client, firstServer, "ACTIVE"); err != nil {
t.Fatalf("Unable to wait for server: %v", err)
}
defer DeleteServer(t, client, firstServer)
firstServer, err = servers.Get(client, firstServer.ID).Extract()
secondServer, err := CreateServerInServerGroup(t, client, serverGroup)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
if err = WaitForComputeStatus(client, secondServer, "ACTIVE"); err != nil {
t.Fatalf("Unable to wait for server: %v", err)
}
defer DeleteServer(t, client, secondServer)
secondServer, err = servers.Get(client, secondServer.ID).Extract()
if firstServer.HostID != secondServer.HostID {
t.Fatalf("%s and %s were not scheduled on the same host.", firstServer.ID, secondServer.ID)
}
}

View File

@ -0,0 +1,479 @@
// +build acceptance compute servers
package v2
import (
"strings"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
th "github.com/gophercloud/gophercloud/testhelper"
)
func TestServersList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := servers.List(client, servers.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve servers: %v", err)
}
allServers, err := servers.ExtractServers(allPages)
if err != nil {
t.Fatalf("Unable to extract servers: %v", err)
}
for _, server := range allServers {
tools.PrintResource(t, server)
}
}
func TestServersCreateDestroy(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
newServer, err := servers.Get(client, server.ID).Extract()
if err != nil {
t.Errorf("Unable to retrieve server: %v", err)
}
tools.PrintResource(t, newServer)
allAddressPages, err := servers.ListAddresses(client, server.ID).AllPages()
if err != nil {
t.Errorf("Unable to list server addresses: %v", err)
}
allAddresses, err := servers.ExtractAddresses(allAddressPages)
if err != nil {
t.Errorf("Unable to extract server addresses: %v", err)
}
for network, address := range allAddresses {
t.Logf("Addresses on %s: %+v", network, address)
}
allInterfacePages, err := attachinterfaces.List(client, server.ID).AllPages()
if err != nil {
t.Errorf("Unable to list server Interfaces: %v", err)
}
allInterfaces, err := attachinterfaces.ExtractInterfaces(allInterfacePages)
if err != nil {
t.Errorf("Unable to extract server Interfaces: %v", err)
}
for _, Interface := range allInterfaces {
t.Logf("Interfaces: %+v", Interface)
}
allNetworkAddressPages, err := servers.ListAddressesByNetwork(client, server.ID, choices.NetworkName).AllPages()
if err != nil {
t.Errorf("Unable to list server addresses: %v", err)
}
allNetworkAddresses, err := servers.ExtractNetworkAddresses(allNetworkAddressPages)
if err != nil {
t.Errorf("Unable to extract server addresses: %v", err)
}
t.Logf("Addresses on %s:", choices.NetworkName)
for _, address := range allNetworkAddresses {
t.Logf("%+v", address)
}
}
func TestServersCreateDestroyWithExtensions(t *testing.T) {
var extendedServer struct {
servers.Server
availabilityzones.ServerExt
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
err = servers.Get(client, server.ID).ExtractInto(&extendedServer)
if err != nil {
t.Errorf("Unable to retrieve server: %v", err)
}
tools.PrintResource(t, extendedServer)
}
func TestServersWithoutImageRef(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServerWithoutImageRef(t, client)
if err != nil {
if err400, ok := err.(*gophercloud.ErrUnexpectedResponseCode); ok {
if !strings.Contains("Missing imageRef attribute", string(err400.Body)) {
defer DeleteServer(t, client, server)
}
}
}
}
func TestServersUpdate(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
alternateName := tools.RandomString("ACPTTEST", 16)
for alternateName == server.Name {
alternateName = tools.RandomString("ACPTTEST", 16)
}
t.Logf("Attempting to rename the server to %s.", alternateName)
updateOpts := servers.UpdateOpts{
Name: alternateName,
}
updated, err := servers.Update(client, server.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to rename server: %v", err)
}
if updated.ID != server.ID {
t.Errorf("Updated server ID [%s] didn't match original server ID [%s]!", updated.ID, server.ID)
}
err = tools.WaitFor(func() (bool, error) {
latest, err := servers.Get(client, updated.ID).Extract()
if err != nil {
return false, err
}
return latest.Name == alternateName, nil
})
}
func TestServersMetadata(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
metadata, err := servers.UpdateMetadata(client, server.ID, servers.MetadataOpts{
"foo": "bar",
"this": "that",
}).Extract()
if err != nil {
t.Fatalf("Unable to update metadata: %v", err)
}
t.Logf("UpdateMetadata result: %+v\n", metadata)
err = servers.DeleteMetadatum(client, server.ID, "foo").ExtractErr()
if err != nil {
t.Fatalf("Unable to delete metadatum: %v", err)
}
metadata, err = servers.CreateMetadatum(client, server.ID, servers.MetadatumOpts{
"foo": "baz",
}).Extract()
if err != nil {
t.Fatalf("Unable to create metadatum: %v", err)
}
t.Logf("CreateMetadatum result: %+v\n", metadata)
metadata, err = servers.Metadatum(client, server.ID, "foo").Extract()
if err != nil {
t.Fatalf("Unable to get metadatum: %v", err)
}
t.Logf("Metadatum result: %+v\n", metadata)
th.AssertEquals(t, "baz", metadata["foo"])
metadata, err = servers.Metadata(client, server.ID).Extract()
if err != nil {
t.Fatalf("Unable to get metadata: %v", err)
}
t.Logf("Metadata result: %+v\n", metadata)
metadata, err = servers.ResetMetadata(client, server.ID, servers.MetadataOpts{}).Extract()
if err != nil {
t.Fatalf("Unable to reset metadata: %v", err)
}
t.Logf("ResetMetadata result: %+v\n", metadata)
th.AssertDeepEquals(t, map[string]string{}, metadata)
}
func TestServersActionChangeAdminPassword(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
randomPassword := tools.MakeNewPassword(server.AdminPass)
res := servers.ChangeAdminPassword(client, server.ID, randomPassword)
if res.Err != nil {
t.Fatal(res.Err)
}
if err = WaitForComputeStatus(client, server, "PASSWORD"); err != nil {
t.Fatal(err)
}
if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestServersActionReboot(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
t.Logf("Attempting reboot of server %s", server.ID)
res := servers.Reboot(client, server.ID, rebootOpts)
if res.Err != nil {
t.Fatalf("Unable to reboot server: %v", res.Err)
}
if err = WaitForComputeStatus(client, server, "REBOOT"); err != nil {
t.Fatal(err)
}
if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestServersActionRebuild(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
t.Logf("Attempting to rebuild server %s", server.ID)
rebuildOpts := servers.RebuildOpts{
Name: tools.RandomString("ACPTTEST", 16),
AdminPass: tools.MakeNewPassword(server.AdminPass),
ImageID: choices.ImageID,
}
rebuilt, err := servers.Rebuild(client, server.ID, rebuildOpts).Extract()
if err != nil {
t.Fatal(err)
}
if rebuilt.ID != server.ID {
t.Errorf("Expected rebuilt server ID of [%s]; got [%s]", server.ID, rebuilt.ID)
}
if err = WaitForComputeStatus(client, rebuilt, "REBUILD"); err != nil {
t.Fatal(err)
}
if err = WaitForComputeStatus(client, rebuilt, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestServersActionResizeConfirm(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
t.Logf("Attempting to resize server %s", server.ID)
ResizeServer(t, client, server)
t.Logf("Attempting to confirm resize for server %s", server.ID)
if res := servers.ConfirmResize(client, server.ID); res.Err != nil {
t.Fatal(res.Err)
}
if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestServersActionResizeRevert(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
t.Logf("Attempting to resize server %s", server.ID)
ResizeServer(t, client, server)
t.Logf("Attempting to revert resize for server %s", server.ID)
if res := servers.RevertResize(client, server.ID); res.Err != nil {
t.Fatal(res.Err)
}
if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestServersActionPause(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
t.Logf("Attempting to pause server %s", server.ID)
err = pauseunpause.Pause(client, server.ID).ExtractErr()
if err != nil {
t.Fatal(err)
}
err = WaitForComputeStatus(client, server, "PAUSED")
if err != nil {
t.Fatal(err)
}
err = pauseunpause.Unpause(client, server.ID).ExtractErr()
if err != nil {
t.Fatal(err)
}
err = WaitForComputeStatus(client, server, "ACTIVE")
if err != nil {
t.Fatal(err)
}
}
func TestServersActionSuspend(t *testing.T) {
t.Parallel()
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteServer(t, client, server)
t.Logf("Attempting to suspend server %s", server.ID)
err = suspendresume.Suspend(client, server.ID).ExtractErr()
if err != nil {
t.Fatal(err)
}
err = WaitForComputeStatus(client, server, "SUSPENDED")
if err != nil {
t.Fatal(err)
}
err = suspendresume.Resume(client, server.ID).ExtractErr()
if err != nil {
t.Fatal(err)
}
err = WaitForComputeStatus(client, server, "ACTIVE")
if err != nil {
t.Fatal(err)
}
}

View File

@ -0,0 +1,56 @@
// +build acceptance compute servers
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
)
func TestTenantNetworksList(t *testing.T) {
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
allPages, err := tenantnetworks.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list networks: %v", err)
}
allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
if err != nil {
t.Fatalf("Unable to list networks: %v", err)
}
for _, network := range allTenantNetworks {
tools.PrintResource(t, network)
}
}
func TestTenantNetworksGet(t *testing.T) {
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
if err != nil {
t.Fatal(err)
}
network, err := tenantnetworks.Get(client, networkID).Extract()
if err != nil {
t.Fatalf("Unable to get network %s: %v", networkID, err)
}
tools.PrintResource(t, network)
}

View File

@ -0,0 +1,78 @@
// +build acceptance compute volumeattach
package v2
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
)
func TestVolumeAttachAttachment(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
client, err := clients.NewComputeV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
blockClient, err := clients.NewBlockStorageV1Client()
if err != nil {
t.Fatalf("Unable to create a blockstorage client: %v", err)
}
server, err := CreateServer(t, client)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer DeleteServer(t, client, server)
volume, err := createVolume(t, blockClient)
if err != nil {
t.Fatalf("Unable to create volume: %v", err)
}
if err = volumes.WaitForStatus(blockClient, volume.ID, "available", 60); err != nil {
t.Fatalf("Unable to wait for volume: %v", err)
}
defer deleteVolume(t, blockClient, volume)
volumeAttachment, err := CreateVolumeAttachment(t, client, blockClient, server, volume)
if err != nil {
t.Fatalf("Unable to attach volume: %v", err)
}
defer DeleteVolumeAttachment(t, client, blockClient, server, volumeAttachment)
tools.PrintResource(t, volumeAttachment)
}
func createVolume(t *testing.T, blockClient *gophercloud.ServiceClient) (*volumes.Volume, error) {
volumeName := tools.RandomString("ACPTTEST", 16)
createOpts := volumes.CreateOpts{
Size: 1,
Name: volumeName,
}
volume, err := volumes.Create(blockClient, createOpts).Extract()
if err != nil {
return volume, err
}
t.Logf("Created volume: %s", volume.ID)
return volume, nil
}
func deleteVolume(t *testing.T, blockClient *gophercloud.ServiceClient, volume *volumes.Volume) {
err := volumes.Delete(blockClient, volume.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete volume: %v", err)
}
t.Logf("Deleted volume: %s", volume.ID)
}

View File

@ -0,0 +1,70 @@
// +build acceptance db
package v1
import (
"os"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
th "github.com/gophercloud/gophercloud/testhelper"
)
func newClient(t *testing.T) *gophercloud.ServiceClient {
ao, err := openstack.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
client, err := openstack.AuthenticatedClient(ao)
th.AssertNoErr(t, err)
c, err := openstack.NewDBV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
th.AssertNoErr(t, err)
return c
}
type context struct {
test *testing.T
client *gophercloud.ServiceClient
instanceID string
DBIDs []string
users []string
}
func newContext(t *testing.T) context {
return context{
test: t,
client: newClient(t),
}
}
func (c context) Logf(msg string, args ...interface{}) {
if len(args) > 0 {
c.test.Logf(msg, args...)
} else {
c.test.Log(msg)
}
}
func (c context) AssertNoErr(err error) {
th.AssertNoErr(c.test, err)
}
func (c context) WaitUntilActive(id string) {
err := gophercloud.WaitFor(60, func() (bool, error) {
inst, err := instances.Get(c.client, id).Extract()
if err != nil {
return false, err
}
if inst.Status == "ACTIVE" {
return true, nil
}
return false, nil
})
c.AssertNoErr(err)
}

View File

@ -0,0 +1,45 @@
// +build acceptance db
package v1
import (
db "github.com/gophercloud/gophercloud/openstack/db/v1/databases"
"github.com/gophercloud/gophercloud/pagination"
)
func (c context) createDBs() {
opts := db.BatchCreateOpts{
db.CreateOpts{Name: "db1"},
db.CreateOpts{Name: "db2"},
db.CreateOpts{Name: "db3"},
}
err := db.Create(c.client, c.instanceID, opts).ExtractErr()
c.AssertNoErr(err)
c.Logf("Created three databases on instance %s: db1, db2, db3", c.instanceID)
}
func (c context) listDBs() {
c.Logf("Listing databases on instance %s", c.instanceID)
err := db.List(c.client, c.instanceID).EachPage(func(page pagination.Page) (bool, error) {
dbList, err := db.ExtractDBs(page)
c.AssertNoErr(err)
for _, db := range dbList {
c.Logf("DB: %#v", db)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) deleteDBs() {
for _, id := range []string{"db1", "db2", "db3"} {
err := db.Delete(c.client, c.instanceID, id).ExtractErr()
c.AssertNoErr(err)
c.Logf("Deleted DB %s", id)
}
}

View File

@ -0,0 +1,31 @@
// +build acceptance db
package v1
import (
"github.com/gophercloud/gophercloud/openstack/db/v1/flavors"
"github.com/gophercloud/gophercloud/pagination"
)
func (c context) listFlavors() {
c.Logf("Listing flavors")
err := flavors.List(c.client).EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := flavors.ExtractFlavors(page)
c.AssertNoErr(err)
for _, f := range flavorList {
c.Logf("Flavor: ID [%s] Name [%s] RAM [%d]", f.ID, f.Name, f.RAM)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) getFlavor() {
flavor, err := flavors.Get(c.client, "1").Extract()
c.Logf("Getting flavor %s", flavor.ID)
c.AssertNoErr(err)
}

View File

@ -0,0 +1,138 @@
// +build acceptance db
package v1
import (
"os"
"testing"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/db/v1/instances"
"github.com/gophercloud/gophercloud/pagination"
th "github.com/gophercloud/gophercloud/testhelper"
)
const envDSType = "DATASTORE_TYPE_ID"
func TestRunner(t *testing.T) {
c := newContext(t)
// FLAVOR tests
c.listFlavors()
c.getFlavor()
// INSTANCE tests
c.createInstance()
c.listInstances()
c.getInstance()
c.isRootEnabled()
c.enableRootUser()
c.isRootEnabled()
c.restartInstance()
//c.resizeInstance()
//c.resizeVol()
// DATABASE tests
c.createDBs()
c.listDBs()
// USER tests
c.createUsers()
c.listUsers()
// TEARDOWN
c.deleteUsers()
c.deleteDBs()
c.deleteInstance()
}
func (c context) createInstance() {
if os.Getenv(envDSType) == "" {
c.test.Fatalf("%s must be set as an environment var", envDSType)
}
opts := instances.CreateOpts{
FlavorRef: "2",
Size: 5,
Name: tools.RandomString("gopher_db", 5),
Datastore: &instances.DatastoreOpts{Type: os.Getenv(envDSType)},
}
instance, err := instances.Create(c.client, opts).Extract()
th.AssertNoErr(c.test, err)
c.Logf("Restarting %s. Waiting...", instance.ID)
c.WaitUntilActive(instance.ID)
c.Logf("Created Instance %s", instance.ID)
c.instanceID = instance.ID
}
func (c context) listInstances() {
c.Logf("Listing instances")
err := instances.List(c.client).EachPage(func(page pagination.Page) (bool, error) {
instanceList, err := instances.ExtractInstances(page)
c.AssertNoErr(err)
for _, i := range instanceList {
c.Logf("Instance: ID [%s] Name [%s] Status [%s] VolSize [%d] Datastore Type [%s]",
i.ID, i.Name, i.Status, i.Volume.Size, i.Datastore.Type)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) getInstance() {
instance, err := instances.Get(c.client, c.instanceID).Extract()
c.AssertNoErr(err)
c.Logf("Getting instance: %s", instance.ID)
}
func (c context) deleteInstance() {
err := instances.Delete(c.client, c.instanceID).ExtractErr()
c.AssertNoErr(err)
c.Logf("Deleted instance %s", c.instanceID)
}
func (c context) enableRootUser() {
_, err := instances.EnableRootUser(c.client, c.instanceID).Extract()
c.AssertNoErr(err)
c.Logf("Enabled root user on %s", c.instanceID)
}
func (c context) isRootEnabled() {
enabled, err := instances.IsRootEnabled(c.client, c.instanceID)
c.AssertNoErr(err)
c.Logf("Is root enabled? %d", enabled)
}
func (c context) restartInstance() {
id := c.instanceID
err := instances.Restart(c.client, id).ExtractErr()
c.AssertNoErr(err)
c.Logf("Restarting %s. Waiting...", id)
c.WaitUntilActive(id)
c.Logf("Restarted %s", id)
}
func (c context) resizeInstance() {
id := c.instanceID
err := instances.Resize(c.client, id, "3").ExtractErr()
c.AssertNoErr(err)
c.Logf("Resizing %s. Waiting...", id)
c.WaitUntilActive(id)
c.Logf("Resized %s with flavorRef %s", id, "2")
}
func (c context) resizeVol() {
id := c.instanceID
err := instances.ResizeVolume(c.client, id, 4).ExtractErr()
c.AssertNoErr(err)
c.Logf("Resizing volume of %s. Waiting...", id)
c.WaitUntilActive(id)
c.Logf("Resized the volume of %s to %d GB", id, 2)
}

View File

@ -0,0 +1 @@
package v1

View File

@ -0,0 +1,70 @@
// +build acceptance db
package v1
import (
"github.com/gophercloud/gophercloud/acceptance/tools"
db "github.com/gophercloud/gophercloud/openstack/db/v1/databases"
u "github.com/gophercloud/gophercloud/openstack/db/v1/users"
"github.com/gophercloud/gophercloud/pagination"
)
func (c context) createUsers() {
users := []string{
tools.RandomString("user_", 5),
tools.RandomString("user_", 5),
tools.RandomString("user_", 5),
}
db1 := db.CreateOpts{Name: "db1"}
db2 := db.CreateOpts{Name: "db2"}
db3 := db.CreateOpts{Name: "db3"}
opts := u.BatchCreateOpts{
u.CreateOpts{
Name: users[0],
Password: tools.RandomString("", 5),
Databases: db.BatchCreateOpts{db1, db2, db3},
},
u.CreateOpts{
Name: users[1],
Password: tools.RandomString("", 5),
Databases: db.BatchCreateOpts{db1, db2},
},
u.CreateOpts{
Name: users[2],
Password: tools.RandomString("", 5),
Databases: db.BatchCreateOpts{db3},
},
}
err := u.Create(c.client, c.instanceID, opts).ExtractErr()
c.AssertNoErr(err)
c.Logf("Created three users on instance %s: %s, %s, %s", c.instanceID, users[0], users[1], users[2])
c.users = users
}
func (c context) listUsers() {
c.Logf("Listing databases on instance %s", c.instanceID)
err := db.List(c.client, c.instanceID).EachPage(func(page pagination.Page) (bool, error) {
dbList, err := db.ExtractDBs(page)
c.AssertNoErr(err)
for _, db := range dbList {
c.Logf("DB: %#v", db)
}
return true, nil
})
c.AssertNoErr(err)
}
func (c context) deleteUsers() {
for _, id := range c.DBIDs {
err := db.Delete(c.client, c.instanceID, id).ExtractErr()
c.AssertNoErr(err)
c.Logf("Deleted DB %s", id)
}
}

View File

@ -0,0 +1,164 @@
package v2
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
)
// CreateRecordSet will create a RecordSet with a random name. An error will
// be returned if the zone was unable to be created.
func CreateRecordSet(t *testing.T, client *gophercloud.ServiceClient, zone *zones.Zone) (*recordsets.RecordSet, error) {
t.Logf("Attempting to create recordset: %s", zone.Name)
createOpts := recordsets.CreateOpts{
Name: zone.Name,
Type: "A",
TTL: 3600,
Description: "Test recordset",
Records: []string{"10.1.0.2"},
}
rs, err := recordsets.Create(client, zone.ID, createOpts).Extract()
if err != nil {
return rs, err
}
if err := WaitForRecordSetStatus(client, rs, "ACTIVE"); err != nil {
return rs, err
}
newRS, err := recordsets.Get(client, rs.ZoneID, rs.ID).Extract()
if err != nil {
return newRS, err
}
t.Logf("Created record set: %s", newRS.Name)
return rs, nil
}
// CreateZone will create a Zone with a random name. An error will
// be returned if the zone was unable to be created.
func CreateZone(t *testing.T, client *gophercloud.ServiceClient) (*zones.Zone, error) {
zoneName := tools.RandomString("ACPTTEST", 8) + ".com."
t.Logf("Attempting to create zone: %s", zoneName)
createOpts := zones.CreateOpts{
Name: zoneName,
Email: "root@example.com",
Type: "PRIMARY",
TTL: 7200,
Description: "Test zone",
}
zone, err := zones.Create(client, createOpts).Extract()
if err != nil {
return zone, err
}
if err := WaitForZoneStatus(client, zone, "ACTIVE"); err != nil {
return zone, err
}
newZone, err := zones.Get(client, zone.ID).Extract()
if err != nil {
return zone, err
}
t.Logf("Created Zone: %s", zoneName)
return newZone, nil
}
// CreateSecondaryZone will create a Zone with a random name. An error will
// be returned if the zone was unable to be created.
//
// This is only for example purposes as it will try to do a zone transfer.
func CreateSecondaryZone(t *testing.T, client *gophercloud.ServiceClient) (*zones.Zone, error) {
zoneName := tools.RandomString("ACPTTEST", 8) + ".com."
t.Logf("Attempting to create zone: %s", zoneName)
createOpts := zones.CreateOpts{
Name: zoneName,
Type: "SECONDARY",
Masters: []string{"10.0.0.1"},
}
zone, err := zones.Create(client, createOpts).Extract()
if err != nil {
return zone, err
}
if err := WaitForZoneStatus(client, zone, "ACTIVE"); err != nil {
return zone, err
}
newZone, err := zones.Get(client, zone.ID).Extract()
if err != nil {
return zone, err
}
t.Logf("Created Zone: %s", zoneName)
return newZone, nil
}
// DeleteRecordSet will delete a specified record set. A fatal error will occur if
// the record set failed to be deleted. This works best when used as a deferred
// function.
func DeleteRecordSet(t *testing.T, client *gophercloud.ServiceClient, rs *recordsets.RecordSet) {
err := recordsets.Delete(client, rs.ZoneID, rs.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete record set %s: %v", rs.ID, err)
}
t.Logf("Deleted record set: %s", rs.ID)
}
// DeleteZone will delete a specified zone. A fatal error will occur if
// the zone failed to be deleted. This works best when used as a deferred
// function.
func DeleteZone(t *testing.T, client *gophercloud.ServiceClient, zone *zones.Zone) {
_, err := zones.Delete(client, zone.ID).Extract()
if err != nil {
t.Fatalf("Unable to delete zone %s: %v", zone.ID, err)
}
t.Logf("Deleted zone: %s", zone.ID)
}
// WaitForRecordSetStatus will poll a record set's status until it either matches
// the specified status or the status becomes ERROR.
func WaitForRecordSetStatus(client *gophercloud.ServiceClient, rs *recordsets.RecordSet, status string) error {
return gophercloud.WaitFor(60, func() (bool, error) {
current, err := recordsets.Get(client, rs.ZoneID, rs.ID).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}
// WaitForZoneStatus will poll a zone's status until it either matches
// the specified status or the status becomes ERROR.
func WaitForZoneStatus(client *gophercloud.ServiceClient, zone *zones.Zone, status string) error {
return gophercloud.WaitFor(60, func() (bool, error) {
current, err := zones.Get(client, zone.ID).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,105 @@
// +build acceptance dns recordsets
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
)
func TestRecordSetsListByZone(t *testing.T) {
client, err := clients.NewDNSV2Client()
if err != nil {
t.Fatalf("Unable to create a DNS client: %v", err)
}
zone, err := CreateZone(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteZone(t, client, zone)
var allRecordSets []recordsets.RecordSet
allPages, err := recordsets.ListByZone(client, zone.ID, nil).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve recordsets: %v", err)
}
allRecordSets, err = recordsets.ExtractRecordSets(allPages)
if err != nil {
t.Fatalf("Unable to extract recordsets: %v", err)
}
for _, recordset := range allRecordSets {
tools.PrintResource(t, &recordset)
}
}
func TestRecordSetsListByZoneLimited(t *testing.T) {
client, err := clients.NewDNSV2Client()
if err != nil {
t.Fatalf("Unable to create a DNS client: %v", err)
}
zone, err := CreateZone(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteZone(t, client, zone)
var allRecordSets []recordsets.RecordSet
listOpts := recordsets.ListOpts{
Limit: 1,
}
allPages, err := recordsets.ListByZone(client, zone.ID, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve recordsets: %v", err)
}
allRecordSets, err = recordsets.ExtractRecordSets(allPages)
if err != nil {
t.Fatalf("Unable to extract recordsets: %v", err)
}
for _, recordset := range allRecordSets {
tools.PrintResource(t, &recordset)
}
}
func TestRecordSetCRUD(t *testing.T) {
client, err := clients.NewDNSV2Client()
if err != nil {
t.Fatalf("Unable to create a DNS client: %v", err)
}
zone, err := CreateZone(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteZone(t, client, zone)
tools.PrintResource(t, &zone)
rs, err := CreateRecordSet(t, client, zone)
if err != nil {
t.Fatal(err)
}
defer DeleteRecordSet(t, client, rs)
tools.PrintResource(t, &rs)
updateOpts := recordsets.UpdateOpts{
Description: "New description",
TTL: 0,
}
newRS, err := recordsets.Update(client, rs.ZoneID, rs.ID, updateOpts).Extract()
if err != nil {
t.Fatal(err)
}
tools.PrintResource(t, &newRS)
}

View File

@ -0,0 +1,60 @@
// +build acceptance dns zones
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
)
func TestZonesList(t *testing.T) {
client, err := clients.NewDNSV2Client()
if err != nil {
t.Fatalf("Unable to create a DNS client: %v", err)
}
var allZones []zones.Zone
allPages, err := zones.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve zones: %v", err)
}
allZones, err = zones.ExtractZones(allPages)
if err != nil {
t.Fatalf("Unable to extract zones: %v", err)
}
for _, zone := range allZones {
tools.PrintResource(t, &zone)
}
}
func TestZonesCRUD(t *testing.T) {
client, err := clients.NewDNSV2Client()
if err != nil {
t.Fatalf("Unable to create a DNS client: %v", err)
}
zone, err := CreateZone(t, client)
if err != nil {
t.Fatal(err)
}
defer DeleteZone(t, client, zone)
tools.PrintResource(t, &zone)
updateOpts := zones.UpdateOpts{
Description: "New description",
TTL: 0,
}
newZone, err := zones.Update(client, zone.ID, updateOpts).Extract()
if err != nil {
t.Fatal(err)
}
tools.PrintResource(t, &newZone)
}

View File

@ -0,0 +1,46 @@
// +build acceptance identity
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v2/extensions"
)
func TestExtensionsList(t *testing.T) {
client, err := clients.NewIdentityV2Client()
if err != nil {
t.Fatalf("Unable to create an identity client: %v", err)
}
allPages, err := extensions.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list extensions: %v", err)
}
allExtensions, err := extensions.ExtractExtensions(allPages)
if err != nil {
t.Fatalf("Unable to extract extensions: %v", err)
}
for _, extension := range allExtensions {
tools.PrintResource(t, extension)
}
}
func TestExtensionsGet(t *testing.T) {
client, err := clients.NewIdentityV2Client()
if err != nil {
t.Fatalf("Unable to create an identity client: %v", err)
}
extension, err := extensions.Get(client, "OS-KSCRUD").Extract()
if err != nil {
t.Fatalf("Unable to get extension OS-KSCRUD: %v", err)
}
tools.PrintResource(t, extension)
}

View File

@ -0,0 +1,186 @@
// Package v2 contains common functions for creating identity-based resources
// for use in acceptance tests. See the `*_test.go` files for example usages.
package v2
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v2/extensions/admin/roles"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
)
// AddUserRole will grant a role to a user in a tenant. An error will be
// returned if the grant was unsuccessful.
func AddUserRole(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants.Tenant, user *users.User, role *roles.Role) error {
t.Logf("Attempting to grant user %s role %s in tenant %s", user.ID, role.ID, tenant.ID)
err := roles.AddUser(client, tenant.ID, user.ID, role.ID).ExtractErr()
if err != nil {
return err
}
t.Logf("Granted user %s role %s in tenant %s", user.ID, role.ID, tenant.ID)
return nil
}
// CreateTenant will create a project with a random name.
// It takes an optional createOpts parameter since creating a project
// has so many options. An error will be returned if the project was
// unable to be created.
func CreateTenant(t *testing.T, client *gophercloud.ServiceClient, c *tenants.CreateOpts) (*tenants.Tenant, error) {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create tenant: %s", name)
var createOpts tenants.CreateOpts
if c != nil {
createOpts = *c
} else {
createOpts = tenants.CreateOpts{}
}
createOpts.Name = name
tenant, err := tenants.Create(client, createOpts).Extract()
if err != nil {
t.Logf("Foo")
return tenant, err
}
t.Logf("Successfully created project %s with ID %s", name, tenant.ID)
return tenant, nil
}
// CreateUser will create a user with a random name and adds them to the given
// tenant. An error will be returned if the user was unable to be created.
func CreateUser(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants.Tenant) (*users.User, error) {
userName := tools.RandomString("user_", 5)
userEmail := userName + "@foo.com"
t.Logf("Creating user: %s", userName)
createOpts := users.CreateOpts{
Name: userName,
Enabled: gophercloud.Disabled,
TenantID: tenant.ID,
Email: userEmail,
}
user, err := users.Create(client, createOpts).Extract()
if err != nil {
return user, err
}
return user, nil
}
// DeleteTenant will delete a tenant by ID. A fatal error will occur if
// the tenant ID failed to be deleted. This works best when using it as
// a deferred function.
func DeleteTenant(t *testing.T, client *gophercloud.ServiceClient, tenantID string) {
err := tenants.Delete(client, tenantID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete tenant %s: %v", tenantID, err)
}
t.Logf("Deleted tenant: %s", tenantID)
}
// DeleteUser will delete a user. A fatal error will occur if the delete was
// unsuccessful. This works best when used as a deferred function.
func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, user *users.User) {
t.Logf("Attempting to delete user: %s", user.Name)
result := users.Delete(client, user.ID)
if result.Err != nil {
t.Fatalf("Unable to delete user")
}
t.Logf("Deleted user: %s", user.Name)
}
// DeleteUserRole will revoke a role of a user in a tenant. A fatal error will
// occur if the revoke was unsuccessful. This works best when used as a
// deferred function.
func DeleteUserRole(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants.Tenant, user *users.User, role *roles.Role) {
t.Logf("Attempting to remove role %s from user %s in tenant %s", role.ID, user.ID, tenant.ID)
err := roles.DeleteUser(client, tenant.ID, user.ID, role.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to remove role")
}
t.Logf("Removed role %s from user %s in tenant %s", role.ID, user.ID, tenant.ID)
}
// FindRole finds all roles that the current authenticated client has access
// to and returns the first one found. An error will be returned if the lookup
// was unsuccessful.
func FindRole(t *testing.T, client *gophercloud.ServiceClient) (*roles.Role, error) {
var role *roles.Role
allPages, err := roles.List(client).AllPages()
if err != nil {
return role, err
}
allRoles, err := roles.ExtractRoles(allPages)
if err != nil {
return role, err
}
for _, r := range allRoles {
role = &r
break
}
return role, nil
}
// FindTenant finds all tenants that the current authenticated client has access
// to and returns the first one found. An error will be returned if the lookup
// was unsuccessful.
func FindTenant(t *testing.T, client *gophercloud.ServiceClient) (*tenants.Tenant, error) {
var tenant *tenants.Tenant
allPages, err := tenants.List(client, nil).AllPages()
if err != nil {
return tenant, err
}
allTenants, err := tenants.ExtractTenants(allPages)
if err != nil {
return tenant, err
}
for _, t := range allTenants {
tenant = &t
break
}
return tenant, nil
}
// UpdateUser will update an existing user with a new randomly generated name.
// An error will be returned if the update was unsuccessful.
func UpdateUser(t *testing.T, client *gophercloud.ServiceClient, user *users.User) (*users.User, error) {
userName := tools.RandomString("user_", 5)
userEmail := userName + "@foo.com"
t.Logf("Attempting to update user name from %s to %s", user.Name, userName)
updateOpts := users.UpdateOpts{
Name: userName,
Email: userEmail,
}
newUser, err := users.Update(client, user.ID, updateOpts).Extract()
if err != nil {
return newUser, err
}
return newUser, nil
}

View File

@ -0,0 +1 @@
package v2

View File

@ -0,0 +1,77 @@
// +build acceptance identity roles
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v2/extensions/admin/roles"
"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
)
func TestRolesAddToUser(t *testing.T) {
client, err := clients.NewIdentityV2AdminClient()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
tenant, err := FindTenant(t, client)
if err != nil {
t.Fatalf("Unable to get a tenant: %v", err)
}
role, err := FindRole(t, client)
if err != nil {
t.Fatalf("Unable to get a role: %v", err)
}
user, err := CreateUser(t, client, tenant)
if err != nil {
t.Fatalf("Unable to create a user: %v", err)
}
defer DeleteUser(t, client, user)
err = AddUserRole(t, client, tenant, user, role)
if err != nil {
t.Fatalf("Unable to add role to user: %v", err)
}
defer DeleteUserRole(t, client, tenant, user, role)
allPages, err := users.ListRoles(client, tenant.ID, user.ID).AllPages()
if err != nil {
t.Fatalf("Unable to obtain roles for user: %v", err)
}
allRoles, err := users.ExtractRoles(allPages)
if err != nil {
t.Fatalf("Unable to extract roles: %v", err)
}
t.Logf("Roles of user %s:", user.Name)
for _, role := range allRoles {
tools.PrintResource(t, role)
}
}
func TestRolesList(t *testing.T) {
client, err := clients.NewIdentityV2AdminClient()
if err != nil {
t.Fatalf("Unable to create an identity client: %v", err)
}
allPages, err := roles.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list all roles: %v", err)
}
allRoles, err := roles.ExtractRoles(allPages)
if err != nil {
t.Fatalf("Unable to extract roles: %v", err)
}
for _, r := range allRoles {
tools.PrintResource(t, r)
}
}

View File

@ -0,0 +1,63 @@
// +build acceptance identity
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
)
func TestTenantsList(t *testing.T) {
client, err := clients.NewIdentityV2Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
allPages, err := tenants.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list tenants: %v", err)
}
allTenants, err := tenants.ExtractTenants(allPages)
if err != nil {
t.Fatalf("Unable to extract tenants: %v", err)
}
for _, tenant := range allTenants {
tools.PrintResource(t, tenant)
}
}
func TestTenantsCRUD(t *testing.T) {
client, err := clients.NewIdentityV2AdminClient()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
tenant, err := CreateTenant(t, client, nil)
if err != nil {
t.Fatalf("Unable to create tenant: %v", err)
}
defer DeleteTenant(t, client, tenant.ID)
tenant, err = tenants.Get(client, tenant.ID).Extract()
if err != nil {
t.Fatalf("Unable to get tenant: %v", err)
}
tools.PrintResource(t, tenant)
updateOpts := tenants.UpdateOpts{
Description: "some tenant",
}
newTenant, err := tenants.Update(client, tenant.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update tenant: %v", err)
}
tools.PrintResource(t, newTenant)
}

View File

@ -0,0 +1,69 @@
// +build acceptance identity
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
)
func TestTokenAuthenticate(t *testing.T) {
client, err := clients.NewIdentityV2UnauthenticatedClient()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
authOptions, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to obtain authentication options: %v", err)
}
result := tokens.Create(client, authOptions)
token, err := result.ExtractToken()
if err != nil {
t.Fatalf("Unable to extract token: %v", err)
}
tools.PrintResource(t, token)
catalog, err := result.ExtractServiceCatalog()
if err != nil {
t.Fatalf("Unable to extract service catalog: %v", err)
}
for _, entry := range catalog.Entries {
tools.PrintResource(t, entry)
}
}
func TestTokenValidate(t *testing.T) {
client, err := clients.NewIdentityV2Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
authOptions, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to obtain authentication options: %v", err)
}
result := tokens.Create(client, authOptions)
token, err := result.ExtractToken()
if err != nil {
t.Fatalf("Unable to extract token: %v", err)
}
tools.PrintResource(t, token)
getResult := tokens.Get(client, token.ID)
user, err := getResult.ExtractUser()
if err != nil {
t.Fatalf("Unable to extract user: %v", err)
}
tools.PrintResource(t, user)
}

View File

@ -0,0 +1,59 @@
// +build acceptance identity
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
)
func TestUsersList(t *testing.T) {
client, err := clients.NewIdentityV2AdminClient()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allPages, err := users.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
for _, user := range allUsers {
tools.PrintResource(t, user)
}
}
func TestUsersCreateUpdateDelete(t *testing.T) {
client, err := clients.NewIdentityV2AdminClient()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
tenant, err := FindTenant(t, client)
if err != nil {
t.Fatalf("Unable to get a tenant: %v", err)
}
user, err := CreateUser(t, client, tenant)
if err != nil {
t.Fatalf("Unable to create a user: %v", err)
}
defer DeleteUser(t, client, user)
tools.PrintResource(t, user)
newUser, err := UpdateUser(t, client, user)
if err != nil {
t.Fatalf("Unable to update user: %v", err)
}
tools.PrintResource(t, newUser)
}

View File

@ -0,0 +1,86 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints"
"github.com/gophercloud/gophercloud/openstack/identity/v3/services"
)
func TestEndpointsList(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
allPages, err := endpoints.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list endpoints: %v", err)
}
allEndpoints, err := endpoints.ExtractEndpoints(allPages)
if err != nil {
t.Fatalf("Unable to extract endpoints: %v", err)
}
for _, endpoint := range allEndpoints {
tools.PrintResource(t, endpoint)
}
}
func TestEndpointsNavigateCatalog(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
// Discover the service we're interested in.
serviceListOpts := services.ListOpts{
ServiceType: "compute",
}
allPages, err := services.List(client, serviceListOpts).AllPages()
if err != nil {
t.Fatalf("Unable to lookup compute service: %v", err)
}
allServices, err := services.ExtractServices(allPages)
if err != nil {
t.Fatalf("Unable to extract service: %v")
}
if len(allServices) != 1 {
t.Fatalf("Expected one service, got %d", len(allServices))
}
computeService := allServices[0]
tools.PrintResource(t, computeService)
// Enumerate the endpoints available for this service.
endpointListOpts := endpoints.ListOpts{
Availability: gophercloud.AvailabilityPublic,
ServiceID: computeService.ID,
}
allPages, err = endpoints.List(client, endpointListOpts).AllPages()
if err != nil {
t.Fatalf("Unable to lookup compute endpoint: %v", err)
}
allEndpoints, err := endpoints.ExtractEndpoints(allPages)
if err != nil {
t.Fatalf("Unable to extract endpoint: %v")
}
if len(allEndpoints) != 1 {
t.Fatalf("Expected one endpoint, got %d", len(allEndpoints))
}
tools.PrintResource(t, allEndpoints[0])
}

View File

@ -0,0 +1,88 @@
package v3
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
)
// CreateProject will create a project with a random name.
// It takes an optional createOpts parameter since creating a project
// has so many options. An error will be returned if the project was
// unable to be created.
func CreateProject(t *testing.T, client *gophercloud.ServiceClient, c *projects.CreateOpts) (*projects.Project, error) {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create project: %s", name)
var createOpts projects.CreateOpts
if c != nil {
createOpts = *c
} else {
createOpts = projects.CreateOpts{}
}
createOpts.Name = name
project, err := projects.Create(client, createOpts).Extract()
if err != nil {
return project, err
}
t.Logf("Successfully created project %s with ID %s", name, project.ID)
return project, nil
}
// CreateUser will create a project with a random name.
// It takes an optional createOpts parameter since creating a user
// has so many options. An error will be returned if the user was
// unable to be created.
func CreateUser(t *testing.T, client *gophercloud.ServiceClient, c *users.CreateOpts) (*users.User, error) {
name := tools.RandomString("ACPTTEST", 8)
t.Logf("Attempting to create user: %s", name)
var createOpts users.CreateOpts
if c != nil {
createOpts = *c
} else {
createOpts = users.CreateOpts{}
}
createOpts.Name = name
user, err := users.Create(client, createOpts).Extract()
if err != nil {
return user, err
}
t.Logf("Successfully created user %s with ID %s", name, user.ID)
return user, nil
}
// DeleteProject will delete a project by ID. A fatal error will occur if
// the project ID failed to be deleted. This works best when using it as
// a deferred function.
func DeleteProject(t *testing.T, client *gophercloud.ServiceClient, projectID string) {
err := projects.Delete(client, projectID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete project %s: %v", projectID, err)
}
t.Logf("Deleted project: %s", projectID)
}
// DeleteUser will delete a user by ID. A fatal error will occur if
// the user failed to be deleted. This works best when using it as
// a deferred function.
func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
err := users.Delete(client, userID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete user %s: %v", userID, err)
}
t.Logf("Deleted user: %s", userID)
}

View File

@ -0,0 +1 @@
package v3

View File

@ -0,0 +1,158 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
)
func TestProjectsList(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
var iTrue bool = true
listOpts := projects.ListOpts{
Enabled: &iTrue,
}
allPages, err := projects.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list projects: %v", err)
}
allProjects, err := projects.ExtractProjects(allPages)
if err != nil {
t.Fatalf("Unable to extract projects: %v", err)
}
for _, project := range allProjects {
tools.PrintResource(t, project)
}
}
func TestProjectsGet(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allPages, err := projects.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list projects: %v", err)
}
allProjects, err := projects.ExtractProjects(allPages)
if err != nil {
t.Fatalf("Unable to extract projects: %v", err)
}
project := allProjects[0]
p, err := projects.Get(client, project.ID).Extract()
if err != nil {
t.Fatalf("Unable to get project: %v", err)
}
tools.PrintResource(t, p)
}
func TestProjectsCRUD(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
project, err := CreateProject(t, client, nil)
if err != nil {
t.Fatalf("Unable to create project: %v", err)
}
defer DeleteProject(t, client, project.ID)
tools.PrintResource(t, project)
var iFalse bool = false
updateOpts := projects.UpdateOpts{
Enabled: &iFalse,
}
updatedProject, err := projects.Update(client, project.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update project: %v", err)
}
tools.PrintResource(t, updatedProject)
}
func TestProjectsDomain(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
var iTrue = true
createOpts := projects.CreateOpts{
IsDomain: &iTrue,
}
projectDomain, err := CreateProject(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create project: %v", err)
}
defer DeleteProject(t, client, projectDomain.ID)
tools.PrintResource(t, projectDomain)
createOpts = projects.CreateOpts{
DomainID: projectDomain.ID,
}
project, err := CreateProject(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create project: %v", err)
}
defer DeleteProject(t, client, project.ID)
tools.PrintResource(t, project)
var iFalse = false
updateOpts := projects.UpdateOpts{
Enabled: &iFalse,
}
_, err = projects.Update(client, projectDomain.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to disable domain: %v")
}
}
func TestProjectsNested(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
projectMain, err := CreateProject(t, client, nil)
if err != nil {
t.Fatalf("Unable to create project: %v", err)
}
defer DeleteProject(t, client, projectMain.ID)
tools.PrintResource(t, projectMain)
createOpts := projects.CreateOpts{
ParentID: projectMain.ID,
}
project, err := CreateProject(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create project: %v", err)
}
defer DeleteProject(t, client, project.ID)
tools.PrintResource(t, project)
}

View File

@ -0,0 +1,33 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/services"
)
func TestServicesList(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
allPages, err := services.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list services: %v", err)
}
allServices, err := services.ExtractServices(allPages)
if err != nil {
t.Fatalf("Unable to extract services: %v", err)
}
for _, service := range allServices {
tools.PrintResource(t, service)
}
}

View File

@ -0,0 +1,60 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
)
func TestGetToken(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v")
}
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to obtain environment auth options: %v", err)
}
authOptions := tokens.AuthOptions{
Username: ao.Username,
Password: ao.Password,
DomainName: "default",
}
token, err := tokens.Create(client, &authOptions).Extract()
if err != nil {
t.Fatalf("Unable to get token: %v", err)
}
tools.PrintResource(t, token)
catalog, err := tokens.Get(client, token.ID).ExtractServiceCatalog()
if err != nil {
t.Fatalf("Unable to get catalog from token: %v", err)
}
tools.PrintResource(t, catalog)
user, err := tokens.Get(client, token.ID).ExtractUser()
if err != nil {
t.Fatalf("Unable to get user from token: %v", err)
}
tools.PrintResource(t, user)
roles, err := tokens.Get(client, token.ID).ExtractRoles()
if err != nil {
t.Fatalf("Unable to get roles from token: %v", err)
}
tools.PrintResource(t, roles)
project, err := tokens.Get(client, token.ID).ExtractProject()
if err != nil {
t.Fatalf("Unable to get project from token: %v", err)
}
tools.PrintResource(t, project)
}

View File

@ -0,0 +1,156 @@
// +build acceptance
package v3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/identity/v3/groups"
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
)
func TestUsersList(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
var iTrue bool = true
listOpts := users.ListOpts{
Enabled: &iTrue,
}
allPages, err := users.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
for _, user := range allUsers {
tools.PrintResource(t, user)
tools.PrintResource(t, user.Extra)
}
}
func TestUsersGet(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allPages, err := users.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
user := allUsers[0]
p, err := users.Get(client, user.ID).Extract()
if err != nil {
t.Fatalf("Unable to get user: %v", err)
}
tools.PrintResource(t, p)
}
func TestUserCRUD(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
project, err := CreateProject(t, client, nil)
if err != nil {
t.Fatalf("Unable to create project: %v", err)
}
defer DeleteProject(t, client, project.ID)
tools.PrintResource(t, project)
createOpts := users.CreateOpts{
DefaultProjectID: project.ID,
Password: "foobar",
DomainID: "default",
Options: map[users.Option]interface{}{
users.IgnorePasswordExpiry: true,
users.MultiFactorAuthRules: []interface{}{
[]string{"password", "totp"},
[]string{"password", "custom-auth-method"},
},
},
Extra: map[string]interface{}{
"email": "jsmith@example.com",
},
}
user, err := CreateUser(t, client, &createOpts)
if err != nil {
t.Fatalf("Unable to create user: %v", err)
}
defer DeleteUser(t, client, user.ID)
tools.PrintResource(t, user)
tools.PrintResource(t, user.Extra)
iFalse := false
updateOpts := users.UpdateOpts{
Enabled: &iFalse,
Options: map[users.Option]interface{}{
users.MultiFactorAuthRules: nil,
},
Extra: map[string]interface{}{
"disabled_reason": "DDOS",
},
}
newUser, err := users.Update(client, user.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update user: %v", err)
}
tools.PrintResource(t, newUser)
tools.PrintResource(t, newUser.Extra)
}
func TestUsersListGroups(t *testing.T) {
client, err := clients.NewIdentityV3Client()
if err != nil {
t.Fatalf("Unable to obtain an identity client: %v", err)
}
allUserPages, err := users.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list users: %v", err)
}
allUsers, err := users.ExtractUsers(allUserPages)
if err != nil {
t.Fatalf("Unable to extract users: %v", err)
}
user := allUsers[0]
allGroupPages, err := users.ListGroups(client, user.ID).AllPages()
if err != nil {
t.Fatalf("Unable to list groups: %v", err)
}
allGroups, err := groups.ExtractGroups(allGroupPages)
if err != nil {
t.Fatalf("Unable to extract groups: %v", err)
}
for _, group := range allGroups {
tools.PrintResource(t, group)
tools.PrintResource(t, group.Extra)
}
}

View File

@ -0,0 +1,80 @@
// +build acceptance imageservice images
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/gophercloud/pagination"
)
func TestImagesListEachPage(t *testing.T) {
client, err := clients.NewImageServiceV2Client()
if err != nil {
t.Fatalf("Unable to create an image service client: %v", err)
}
listOpts := images.ListOpts{
Limit: 1,
}
pager := images.List(client, listOpts)
err = pager.EachPage(func(page pagination.Page) (bool, error) {
images, err := images.ExtractImages(page)
if err != nil {
t.Fatalf("Unable to extract images: %v", err)
}
for _, image := range images {
tools.PrintResource(t, image)
tools.PrintResource(t, image.Properties)
}
return true, nil
})
}
func TestImagesListAllPages(t *testing.T) {
client, err := clients.NewImageServiceV2Client()
if err != nil {
t.Fatalf("Unable to create an image service client: %v", err)
}
listOpts := images.ListOpts{
Limit: 1,
}
allPages, err := images.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to retrieve all images: %v", err)
}
allImages, err := images.ExtractImages(allPages)
if err != nil {
t.Fatalf("Unable to extract images: %v", err)
}
for _, image := range allImages {
tools.PrintResource(t, image)
tools.PrintResource(t, image.Properties)
}
}
func TestImagesCreateDestroyEmptyImage(t *testing.T) {
client, err := clients.NewImageServiceV2Client()
if err != nil {
t.Fatalf("Unable to create an image service client: %v", err)
}
image, err := CreateEmptyImage(t, client)
if err != nil {
t.Fatalf("Unable to create empty image: %v", err)
}
defer DeleteImage(t, client, image)
tools.PrintResource(t, image)
}

View File

@ -0,0 +1,55 @@
// Package v2 contains common functions for creating imageservice resources
// for use in acceptance tests. See the `*_test.go` files for example usages.
package v2
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
)
// CreateEmptyImage will create an image, but with no actual image data.
// An error will be returned if an image was unable to be created.
func CreateEmptyImage(t *testing.T, client *gophercloud.ServiceClient) (*images.Image, error) {
var image *images.Image
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create image: %s", name)
protected := false
visibility := images.ImageVisibilityPrivate
createOpts := &images.CreateOpts{
Name: name,
ContainerFormat: "bare",
DiskFormat: "qcow2",
MinDisk: 0,
MinRAM: 0,
Protected: &protected,
Visibility: &visibility,
Properties: map[string]string{
"architecture": "x86_64",
},
}
image, err := images.Create(client, createOpts).Extract()
if err != nil {
return image, err
}
t.Logf("Created image %s: %#v", name, image)
return image, nil
}
// DeleteImage deletes an image.
// A fatal error will occur if the image failed to delete. This works best when
// used as a deferred function.
func DeleteImage(t *testing.T, client *gophercloud.ServiceClient, image *images.Image) {
err := images.Delete(client, image.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete image %s: %v", image.ID, err)
}
t.Logf("Deleted image: %s", image.ID)
}

View File

@ -0,0 +1,53 @@
// +build acceptance networking
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/apiversions"
)
func TestAPIVersionsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := apiversions.ListVersions(client).AllPages()
if err != nil {
t.Fatalf("Unable to list api versions: %v", err)
}
allAPIVersions, err := apiversions.ExtractAPIVersions(allPages)
if err != nil {
t.Fatalf("Unable to extract api versions: %v", err)
}
for _, apiVersion := range allAPIVersions {
tools.PrintResource(t, apiVersion)
}
}
func TestAPIResourcesList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := apiversions.ListVersionResources(client, "v2.0").AllPages()
if err != nil {
t.Fatalf("Unable to list api version reosources: %v", err)
}
allVersionResources, err := apiversions.ExtractVersionResources(allPages)
if err != nil {
t.Fatalf("Unable to extract version resources: %v", err)
}
for _, versionResource := range allVersionResources {
tools.PrintResource(t, versionResource)
}
}

View File

@ -0,0 +1,46 @@
// +build acceptance networking extensions
package v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/common/extensions"
)
func TestExtensionsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := extensions.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list extensions: %v", err)
}
allExtensions, err := extensions.ExtractExtensions(allPages)
if err != nil {
t.Fatalf("Unable to extract extensions: %v", err)
}
for _, extension := range allExtensions {
tools.PrintResource(t, extension)
}
}
func TestExtensionGet(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
extension, err := extensions.Get(client, "router").Extract()
if err != nil {
t.Fatalf("Unable to get extension port-security: %v", err)
}
tools.PrintResource(t, extension)
}

View File

@ -0,0 +1,138 @@
package extensions
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
)
// CreateExternalNetwork will create an external network. An error will be
// returned if the creation failed.
func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*networks.Network, error) {
networkName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create external network: %s", networkName)
adminStateUp := true
isExternal := true
createOpts := external.CreateOpts{
External: &isExternal,
}
createOpts.Name = networkName
createOpts.AdminStateUp = &adminStateUp
network, err := networks.Create(client, createOpts).Extract()
if err != nil {
return network, err
}
t.Logf("Created external network: %s", networkName)
return network, nil
}
// CreatePortWithSecurityGroup will create a port with a security group
// attached. An error will be returned if the port could not be created.
func CreatePortWithSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, networkID, subnetID, secGroupID string) (*ports.Port, error) {
portName := tools.RandomString("TESTACC-", 8)
iFalse := false
t.Logf("Attempting to create port: %s", portName)
createOpts := ports.CreateOpts{
NetworkID: networkID,
Name: portName,
AdminStateUp: &iFalse,
FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}},
SecurityGroups: []string{secGroupID},
}
port, err := ports.Create(client, createOpts).Extract()
if err != nil {
return port, err
}
t.Logf("Successfully created port: %s", portName)
return port, nil
}
// CreateSecurityGroup will create a security group with a random name.
// An error will be returned if one was failed to be created.
func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*groups.SecGroup, error) {
secGroupName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create security group: %s", secGroupName)
createOpts := groups.CreateOpts{
Name: secGroupName,
}
secGroup, err := groups.Create(client, createOpts).Extract()
if err != nil {
return secGroup, err
}
t.Logf("Created security group: %s", secGroup.ID)
return secGroup, nil
}
// CreateSecurityGroupRule will create a security group rule with a random name
// and random port between 80 and 99.
// An error will be returned if one was failed to be created.
func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, secGroupID string) (*rules.SecGroupRule, error) {
t.Logf("Attempting to create security group rule in group: %s", secGroupID)
fromPort := tools.RandomInt(80, 89)
toPort := tools.RandomInt(90, 99)
createOpts := rules.CreateOpts{
Direction: "ingress",
EtherType: "IPv4",
SecGroupID: secGroupID,
PortRangeMin: fromPort,
PortRangeMax: toPort,
Protocol: rules.ProtocolTCP,
}
rule, err := rules.Create(client, createOpts).Extract()
if err != nil {
return rule, err
}
t.Logf("Created security group rule: %s", rule.ID)
return rule, nil
}
// DeleteSecurityGroup will delete a security group of a specified ID.
// A fatal error will occur if the deletion failed. This works best as a
// deferred function
func DeleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, secGroupID string) {
t.Logf("Attempting to delete security group: %s", secGroupID)
err := groups.Delete(client, secGroupID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete security group: %v", err)
}
}
// DeleteSecurityGroupRule will delete a security group rule of a specified ID.
// A fatal error will occur if the deletion failed. This works best as a
// deferred function
func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, ruleID string) {
t.Logf("Attempting to delete security group rule: %s", ruleID)
err := rules.Delete(client, ruleID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete security group rule: %v", err)
}
}

View File

@ -0,0 +1,212 @@
// +build acceptance networking fwaas
package fwaas
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
layer3 "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion"
)
func TestFirewallList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := firewalls.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list firewalls: %v", err)
}
allFirewalls, err := firewalls.ExtractFirewalls(allPages)
if err != nil {
t.Fatalf("Unable to extract firewalls: %v", err)
}
for _, firewall := range allFirewalls {
tools.PrintResource(t, firewall)
}
}
func TestFirewallCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
router, err := layer3.CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer layer3.DeleteRouter(t, client, router.ID)
rule, err := CreateRule(t, client)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteRule(t, client, rule.ID)
tools.PrintResource(t, rule)
policy, err := CreatePolicy(t, client, rule.ID)
if err != nil {
t.Fatalf("Unable to create policy: %v", err)
}
defer DeletePolicy(t, client, policy.ID)
tools.PrintResource(t, policy)
firewall, err := CreateFirewall(t, client, policy.ID)
if err != nil {
t.Fatalf("Unable to create firewall: %v", err)
}
defer DeleteFirewall(t, client, firewall.ID)
tools.PrintResource(t, firewall)
updateOpts := firewalls.UpdateOpts{
PolicyID: policy.ID,
Description: "Some firewall description",
}
_, err = firewalls.Update(client, firewall.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update firewall: %v", err)
}
newFirewall, err := firewalls.Get(client, firewall.ID).Extract()
if err != nil {
t.Fatalf("Unable to get firewall: %v", err)
}
tools.PrintResource(t, newFirewall)
}
func TestFirewallCRUDRouter(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
router, err := layer3.CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer layer3.DeleteRouter(t, client, router.ID)
rule, err := CreateRule(t, client)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteRule(t, client, rule.ID)
tools.PrintResource(t, rule)
policy, err := CreatePolicy(t, client, rule.ID)
if err != nil {
t.Fatalf("Unable to create policy: %v", err)
}
defer DeletePolicy(t, client, policy.ID)
tools.PrintResource(t, policy)
firewall, err := CreateFirewallOnRouter(t, client, policy.ID, router.ID)
if err != nil {
t.Fatalf("Unable to create firewall: %v", err)
}
defer DeleteFirewall(t, client, firewall.ID)
tools.PrintResource(t, firewall)
router2, err := layer3.CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer layer3.DeleteRouter(t, client, router2.ID)
firewallUpdateOpts := firewalls.UpdateOpts{
PolicyID: policy.ID,
Description: "Some firewall description",
}
updateOpts := routerinsertion.UpdateOptsExt{
firewallUpdateOpts,
[]string{router2.ID},
}
_, err = firewalls.Update(client, firewall.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update firewall: %v", err)
}
newFirewall, err := firewalls.Get(client, firewall.ID).Extract()
if err != nil {
t.Fatalf("Unable to get firewall: %v", err)
}
tools.PrintResource(t, newFirewall)
}
func TestFirewallCRUDRemoveRouter(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
router, err := layer3.CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer layer3.DeleteRouter(t, client, router.ID)
rule, err := CreateRule(t, client)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteRule(t, client, rule.ID)
tools.PrintResource(t, rule)
policy, err := CreatePolicy(t, client, rule.ID)
if err != nil {
t.Fatalf("Unable to create policy: %v", err)
}
defer DeletePolicy(t, client, policy.ID)
tools.PrintResource(t, policy)
firewall, err := CreateFirewallOnRouter(t, client, policy.ID, router.ID)
if err != nil {
t.Fatalf("Unable to create firewall: %v", err)
}
defer DeleteFirewall(t, client, firewall.ID)
tools.PrintResource(t, firewall)
firewallUpdateOpts := firewalls.UpdateOpts{
PolicyID: policy.ID,
Description: "Some firewall description",
}
updateOpts := routerinsertion.UpdateOptsExt{
firewallUpdateOpts,
[]string{},
}
_, err = firewalls.Update(client, firewall.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update firewall: %v", err)
}
newFirewall, err := firewalls.Get(client, firewall.ID).Extract()
if err != nil {
t.Fatalf("Unable to get firewall: %v", err)
}
tools.PrintResource(t, newFirewall)
}

View File

@ -0,0 +1,203 @@
package fwaas
import (
"fmt"
"strconv"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
)
// CreateFirewall will create a Firewaill with a random name and a specified
// policy ID. An error will be returned if the firewall could not be created.
func CreateFirewall(t *testing.T, client *gophercloud.ServiceClient, policyID string) (*firewalls.Firewall, error) {
firewallName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create firewall %s", firewallName)
iTrue := true
createOpts := firewalls.CreateOpts{
Name: firewallName,
PolicyID: policyID,
AdminStateUp: &iTrue,
}
firewall, err := firewalls.Create(client, createOpts).Extract()
if err != nil {
return firewall, err
}
t.Logf("Waiting for firewall to become active.")
if err := WaitForFirewallState(client, firewall.ID, "ACTIVE", 60); err != nil {
return firewall, err
}
t.Logf("Successfully created firewall %s", firewallName)
return firewall, nil
}
// CreateFirewallOnRouter will create a Firewall with a random name and a
// specified policy ID attached to a specified Router. An error will be
// returned if the firewall could not be created.
func CreateFirewallOnRouter(t *testing.T, client *gophercloud.ServiceClient, policyID string, routerID string) (*firewalls.Firewall, error) {
firewallName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create firewall %s", firewallName)
firewallCreateOpts := firewalls.CreateOpts{
Name: firewallName,
PolicyID: policyID,
}
createOpts := routerinsertion.CreateOptsExt{
CreateOptsBuilder: firewallCreateOpts,
RouterIDs: []string{routerID},
}
firewall, err := firewalls.Create(client, createOpts).Extract()
if err != nil {
return firewall, err
}
t.Logf("Waiting for firewall to become active.")
if err := WaitForFirewallState(client, firewall.ID, "ACTIVE", 60); err != nil {
return firewall, err
}
t.Logf("Successfully created firewall %s", firewallName)
return firewall, nil
}
// CreatePolicy will create a Firewall Policy with a random name and given
// rule. An error will be returned if the rule could not be created.
func CreatePolicy(t *testing.T, client *gophercloud.ServiceClient, ruleID string) (*policies.Policy, error) {
policyName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create policy %s", policyName)
createOpts := policies.CreateOpts{
Name: policyName,
Rules: []string{
ruleID,
},
}
policy, err := policies.Create(client, createOpts).Extract()
if err != nil {
return policy, err
}
t.Logf("Successfully created policy %s", policyName)
return policy, nil
}
// CreateRule will create a Firewall Rule with a random source address and
//source port, destination address and port. An error will be returned if
// the rule could not be created.
func CreateRule(t *testing.T, client *gophercloud.ServiceClient) (*rules.Rule, error) {
ruleName := tools.RandomString("TESTACC-", 8)
sourceAddress := fmt.Sprintf("192.168.1.%d", tools.RandomInt(1, 100))
sourcePort := strconv.Itoa(tools.RandomInt(1, 100))
destinationAddress := fmt.Sprintf("192.168.2.%d", tools.RandomInt(1, 100))
destinationPort := strconv.Itoa(tools.RandomInt(1, 100))
t.Logf("Attempting to create rule %s with source %s:%s and destination %s:%s",
ruleName, sourceAddress, sourcePort, destinationAddress, destinationPort)
createOpts := rules.CreateOpts{
Name: ruleName,
Protocol: rules.ProtocolTCP,
Action: "allow",
SourceIPAddress: sourceAddress,
SourcePort: sourcePort,
DestinationIPAddress: destinationAddress,
DestinationPort: destinationPort,
}
rule, err := rules.Create(client, createOpts).Extract()
if err != nil {
return rule, err
}
t.Logf("Rule %s successfully created", ruleName)
return rule, nil
}
// DeleteFirewall will delete a firewall with a specified ID. A fatal error
// will occur if the delete was not successful. This works best when used as
// a deferred function.
func DeleteFirewall(t *testing.T, client *gophercloud.ServiceClient, firewallID string) {
t.Logf("Attempting to delete firewall: %s", firewallID)
err := firewalls.Delete(client, firewallID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete firewall %s: %v", firewallID, err)
}
t.Logf("Waiting for firewall to delete.")
if err := WaitForFirewallState(client, firewallID, "DELETED", 60); err != nil {
t.Logf("Unable to delete firewall: %s", firewallID)
}
t.Logf("Firewall deleted: %s", firewallID)
}
// DeletePolicy will delete a policy with a specified ID. A fatal error will
// occur if the delete was not successful. This works best when used as a
// deferred function.
func DeletePolicy(t *testing.T, client *gophercloud.ServiceClient, policyID string) {
t.Logf("Attempting to delete policy: %s", policyID)
err := policies.Delete(client, policyID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete policy %s: %v", policyID, err)
}
t.Logf("Deleted policy: %s", policyID)
}
// DeleteRule will delete a rule with a specified ID. A fatal error will occur
// if the delete was not successful. This works best when used as a deferred
// function.
func DeleteRule(t *testing.T, client *gophercloud.ServiceClient, ruleID string) {
t.Logf("Attempting to delete rule: %s", ruleID)
err := rules.Delete(client, ruleID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete rule %s: %v", ruleID, err)
}
t.Logf("Deleted rule: %s", ruleID)
}
// WaitForFirewallState will wait until a firewall reaches a given state.
func WaitForFirewallState(client *gophercloud.ServiceClient, firewallID, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := firewalls.Get(client, firewallID).Extract()
if err != nil {
if httpStatus, ok := err.(gophercloud.ErrDefault404); ok {
if httpStatus.Actual == 404 {
if status == "DELETED" {
return true, nil
}
}
}
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1 @@
package fwaas

View File

@ -0,0 +1,71 @@
// +build acceptance networking fwaas
package fwaas
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
)
func TestPolicyList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := policies.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list policies: %v", err)
}
allPolicies, err := policies.ExtractPolicies(allPages)
if err != nil {
t.Fatalf("Unable to extract policies: %v", err)
}
for _, policy := range allPolicies {
tools.PrintResource(t, policy)
}
}
func TestPolicyCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
rule, err := CreateRule(t, client)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteRule(t, client, rule.ID)
tools.PrintResource(t, rule)
policy, err := CreatePolicy(t, client, rule.ID)
if err != nil {
t.Fatalf("Unable to create policy: %v", err)
}
defer DeletePolicy(t, client, policy.ID)
tools.PrintResource(t, policy)
updateOpts := policies.UpdateOpts{
Description: "Some policy description",
}
_, err = policies.Update(client, policy.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update policy: %v", err)
}
newPolicy, err := policies.Get(client, policy.ID).Extract()
if err != nil {
t.Fatalf("Unable to get policy: %v", err)
}
tools.PrintResource(t, newPolicy)
}

View File

@ -0,0 +1,64 @@
// +build acceptance networking fwaas
package fwaas
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
)
func TestRuleList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := rules.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list rules: %v", err)
}
allRules, err := rules.ExtractRules(allPages)
if err != nil {
t.Fatalf("Unable to extract rules: %v", err)
}
for _, rule := range allRules {
tools.PrintResource(t, rule)
}
}
func TestRuleCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
rule, err := CreateRule(t, client)
if err != nil {
t.Fatalf("Unable to create rule: %v", err)
}
defer DeleteRule(t, client, rule.ID)
tools.PrintResource(t, rule)
ruleDescription := "Some rule description"
updateOpts := rules.UpdateOpts{
Description: &ruleDescription,
}
_, err = rules.Update(client, rule.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update rule: %v", err)
}
newRule, err := rules.Get(client, rule.ID).Extract()
if err != nil {
t.Fatalf("Unable to get rule: %v", err)
}
tools.PrintResource(t, newRule)
}

View File

@ -0,0 +1,100 @@
// +build acceptance networking layer3 floatingips
package layer3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)
func TestLayer3FloatingIPsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
listOpts := floatingips.ListOpts{
Status: "DOWN",
}
allPages, err := floatingips.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list floating IPs: %v", err)
}
allFIPs, err := floatingips.ExtractFloatingIPs(allPages)
if err != nil {
t.Fatalf("Unable to extract floating IPs: %v", err)
}
for _, fip := range allFIPs {
tools.PrintResource(t, fip)
}
}
func TestLayer3FloatingIPsCreateDelete(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatalf("Unable to get choices: %v", err)
}
netid, err := networks.IDFromName(client, choices.NetworkName)
if err != nil {
t.Fatalf("Unable to find network id: %v", err)
}
subnet, err := networking.CreateSubnet(t, client, netid)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
router, err := CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer DeleteRouter(t, client, router.ID)
port, err := networking.CreatePort(t, client, netid, subnet.ID)
if err != nil {
t.Fatalf("Unable to create port: %v", err)
}
_, err = CreateRouterInterface(t, client, port.ID, router.ID)
if err != nil {
t.Fatalf("Unable to create router interface: %v", err)
}
defer DeleteRouterInterface(t, client, port.ID, router.ID)
fip, err := CreateFloatingIP(t, client, choices.ExternalNetworkID, port.ID)
if err != nil {
t.Fatalf("Unable to create floating IP: %v", err)
}
defer DeleteFloatingIP(t, client, fip.ID)
newFip, err := floatingips.Get(client, fip.ID).Extract()
if err != nil {
t.Fatalf("Unable to get floating ip: %v", err)
}
tools.PrintResource(t, newFip)
// Disassociate the floating IP
updateOpts := floatingips.UpdateOpts{
PortID: nil,
}
newFip, err = floatingips.Update(client, fip.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to disassociate floating IP: %v", err)
}
}

View File

@ -0,0 +1,248 @@
package layer3
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
)
// CreateFloatingIP creates a floating IP on a given network and port. An error
// will be returned if the creation failed.
func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, networkID, portID string) (*floatingips.FloatingIP, error) {
t.Logf("Attempting to create floating IP on port: %s", portID)
createOpts := &floatingips.CreateOpts{
FloatingNetworkID: networkID,
PortID: portID,
}
floatingIP, err := floatingips.Create(client, createOpts).Extract()
if err != nil {
return floatingIP, err
}
t.Logf("Created floating IP.")
return floatingIP, err
}
// CreateExternalRouter creates a router on the external network. This requires
// the OS_EXTGW_ID environment variable to be set. An error is returned if the
// creation failed.
func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*routers.Router, error) {
var router *routers.Router
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
return router, err
}
routerName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create external router: %s", routerName)
adminStateUp := true
gatewayInfo := routers.GatewayInfo{
NetworkID: choices.ExternalNetworkID,
}
createOpts := routers.CreateOpts{
Name: routerName,
AdminStateUp: &adminStateUp,
GatewayInfo: &gatewayInfo,
}
router, err = routers.Create(client, createOpts).Extract()
if err != nil {
return router, err
}
if err := WaitForRouterToCreate(client, router.ID, 60); err != nil {
return router, err
}
t.Logf("Created router: %s", routerName)
return router, nil
}
// CreateRouter creates a router on a specified Network ID. An error will be
// returned if the creation failed.
func CreateRouter(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*routers.Router, error) {
routerName := tools.RandomString("TESTACC-", 8)
t.Logf("Attempting to create router: %s", routerName)
adminStateUp := true
gatewayInfo := routers.GatewayInfo{
NetworkID: networkID,
}
createOpts := routers.CreateOpts{
Name: routerName,
AdminStateUp: &adminStateUp,
GatewayInfo: &gatewayInfo,
}
router, err := routers.Create(client, createOpts).Extract()
if err != nil {
return router, err
}
if err := WaitForRouterToCreate(client, router.ID, 60); err != nil {
return router, err
}
t.Logf("Created router: %s", routerName)
return router, nil
}
// CreateRouterInterface will attach a subnet to a router. An error will be
// returned if the operation fails.
func CreateRouterInterface(t *testing.T, client *gophercloud.ServiceClient, portID, routerID string) (*routers.InterfaceInfo, error) {
t.Logf("Attempting to add port %s to router %s", portID, routerID)
aiOpts := routers.AddInterfaceOpts{
PortID: portID,
}
iface, err := routers.AddInterface(client, routerID, aiOpts).Extract()
if err != nil {
return iface, err
}
if err := WaitForRouterInterfaceToAttach(client, portID, 60); err != nil {
return iface, err
}
t.Logf("Successfully added port %s to router %s", portID, routerID)
return iface, nil
}
// DeleteRouter deletes a router of a specified ID. A fatal error will occur
// if the deletion failed. This works best when used as a deferred function.
func DeleteRouter(t *testing.T, client *gophercloud.ServiceClient, routerID string) {
t.Logf("Attempting to delete router: %s", routerID)
err := routers.Delete(client, routerID).ExtractErr()
if err != nil {
t.Fatalf("Error deleting router: %v", err)
}
if err := WaitForRouterToDelete(client, routerID, 60); err != nil {
t.Fatalf("Error waiting for router to delete: %v", err)
}
t.Logf("Deleted router: %s", routerID)
}
// DeleteRouterInterface will detach a subnet to a router. A fatal error will
// occur if the deletion failed. This works best when used as a deferred
// function.
func DeleteRouterInterface(t *testing.T, client *gophercloud.ServiceClient, portID, routerID string) {
t.Logf("Attempting to detach port %s from router %s", portID, routerID)
riOpts := routers.RemoveInterfaceOpts{
PortID: portID,
}
_, err := routers.RemoveInterface(client, routerID, riOpts).Extract()
if err != nil {
t.Fatalf("Failed to detach port %s from router %s", portID, routerID)
}
if err := WaitForRouterInterfaceToDetach(client, portID, 60); err != nil {
t.Fatalf("Failed to wait for port %s to detach from router %s", portID, routerID)
}
t.Logf("Successfully detached port %s from router %s", portID, routerID)
}
// DeleteFloatingIP deletes a floatingIP of a specified ID. A fatal error will
// occur if the deletion failed. This works best when used as a deferred
// function.
func DeleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIPID string) {
t.Logf("Attempting to delete floating IP: %s", floatingIPID)
err := floatingips.Delete(client, floatingIPID).ExtractErr()
if err != nil {
t.Fatalf("Failed to delete floating IP: %v", err)
}
t.Logf("Deleted floating IP: %s", floatingIPID)
}
func WaitForRouterToCreate(client *gophercloud.ServiceClient, routerID string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
r, err := routers.Get(client, routerID).Extract()
if err != nil {
return false, err
}
if r.Status == "ACTIVE" {
return true, nil
}
return false, nil
})
}
func WaitForRouterToDelete(client *gophercloud.ServiceClient, routerID string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
_, err := routers.Get(client, routerID).Extract()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return true, nil
}
return false, err
}
return false, nil
})
}
func WaitForRouterInterfaceToAttach(client *gophercloud.ServiceClient, routerInterfaceID string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
r, err := ports.Get(client, routerInterfaceID).Extract()
if err != nil {
return false, err
}
if r.Status == "ACTIVE" {
return true, nil
}
return false, nil
})
}
func WaitForRouterInterfaceToDetach(client *gophercloud.ServiceClient, routerInterfaceID string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
r, err := ports.Get(client, routerInterfaceID).Extract()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return true, nil
}
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
return false, nil
}
}
return false, err
}
if r.Status == "ACTIVE" {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,119 @@
// +build acceptance networking layer3 router
package layer3
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)
func TestLayer3RouterList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
listOpts := routers.ListOpts{}
allPages, err := routers.List(client, listOpts).AllPages()
if err != nil {
t.Fatalf("Unable to list routers: %v", err)
}
allRouters, err := routers.ExtractRouters(allPages)
if err != nil {
t.Fatalf("Unable to extract routers: %v", err)
}
for _, router := range allRouters {
tools.PrintResource(t, router)
}
}
func TestLayer3RouterCreateDelete(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
router, err := CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer DeleteRouter(t, client, router.ID)
tools.PrintResource(t, router)
newName := tools.RandomString("TESTACC-", 8)
updateOpts := routers.UpdateOpts{
Name: newName,
}
_, err = routers.Update(client, router.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update router: %v", err)
}
newRouter, err := routers.Get(client, router.ID).Extract()
if err != nil {
t.Fatalf("Unable to get router: %v", err)
}
tools.PrintResource(t, newRouter)
}
func TestLayer3RouterInterface(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := clients.AcceptanceTestChoicesFromEnv()
if err != nil {
t.Fatalf("Unable to get choices: %v", err)
}
netid, err := networks.IDFromName(client, choices.NetworkName)
if err != nil {
t.Fatalf("Unable to find network id: %v", err)
}
subnet, err := networking.CreateSubnet(t, client, netid)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
tools.PrintResource(t, subnet)
router, err := CreateExternalRouter(t, client)
if err != nil {
t.Fatalf("Unable to create router: %v", err)
}
defer DeleteRouter(t, client, router.ID)
aiOpts := routers.AddInterfaceOpts{
SubnetID: subnet.ID,
}
iface, err := routers.AddInterface(client, router.ID, aiOpts).Extract()
if err != nil {
t.Fatalf("Failed to add interface to router: %v", err)
}
tools.PrintResource(t, router)
tools.PrintResource(t, iface)
riOpts := routers.RemoveInterfaceOpts{
SubnetID: subnet.ID,
}
_, err = routers.RemoveInterface(client, router.ID, riOpts).Extract()
if err != nil {
t.Fatalf("Failed to remove interface from router: %v", err)
}
}

View File

@ -0,0 +1,160 @@
package lbaas
import (
"fmt"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
)
// CreateMember will create a load balancer member in a specified pool on a
// random port. An error will be returned if the member could not be created.
func CreateMember(t *testing.T, client *gophercloud.ServiceClient, poolID string) (*members.Member, error) {
protocolPort := tools.RandomInt(100, 1000)
address := tools.RandomInt(2, 200)
t.Logf("Attempting to create member in port %d", protocolPort)
createOpts := members.CreateOpts{
PoolID: poolID,
ProtocolPort: protocolPort,
Address: fmt.Sprintf("192.168.1.%d", address),
}
member, err := members.Create(client, createOpts).Extract()
if err != nil {
return member, err
}
t.Logf("Successfully created member %s", member.ID)
return member, nil
}
// CreateMonitor will create a monitor with a random name for a specific pool.
// An error will be returned if the monitor could not be created.
func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient) (*monitors.Monitor, error) {
t.Logf("Attempting to create monitor.")
createOpts := monitors.CreateOpts{
Type: monitors.TypePING,
Delay: 90,
Timeout: 60,
MaxRetries: 10,
AdminStateUp: gophercloud.Enabled,
}
monitor, err := monitors.Create(client, createOpts).Extract()
if err != nil {
return monitor, err
}
t.Logf("Successfully created monitor %s", monitor.ID)
return monitor, nil
}
// CreatePool will create a pool with a random name. An error will be returned
// if the pool could not be deleted.
func CreatePool(t *testing.T, client *gophercloud.ServiceClient, subnetID string) (*pools.Pool, error) {
poolName := tools.RandomString("TESTACCT-", 8)
t.Logf("Attempting to create pool %s", poolName)
createOpts := pools.CreateOpts{
Name: poolName,
SubnetID: subnetID,
Protocol: pools.ProtocolTCP,
LBMethod: pools.LBMethodRoundRobin,
}
pool, err := pools.Create(client, createOpts).Extract()
if err != nil {
return pool, err
}
t.Logf("Successfully created pool %s", poolName)
return pool, nil
}
// CreateVIP will create a vip with a random name and a random port in a
// specified subnet and pool. An error will be returned if the vip could
// not be created.
func CreateVIP(t *testing.T, client *gophercloud.ServiceClient, subnetID, poolID string) (*vips.VirtualIP, error) {
vipName := tools.RandomString("TESTACCT-", 8)
vipPort := tools.RandomInt(100, 10000)
t.Logf("Attempting to create VIP %s", vipName)
createOpts := vips.CreateOpts{
Name: vipName,
SubnetID: subnetID,
PoolID: poolID,
Protocol: "TCP",
ProtocolPort: vipPort,
}
vip, err := vips.Create(client, createOpts).Extract()
if err != nil {
return vip, err
}
t.Logf("Successfully created vip %s", vipName)
return vip, nil
}
// DeleteMember will delete a specified member. A fatal error will occur if
// the member could not be deleted. This works best when used as a deferred
// function.
func DeleteMember(t *testing.T, client *gophercloud.ServiceClient, memberID string) {
t.Logf("Attempting to delete member %s", memberID)
if err := members.Delete(client, memberID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete member: %v", err)
}
t.Logf("Successfully deleted member %s", memberID)
}
// DeleteMonitor will delete a specified monitor. A fatal error will occur if
// the monitor could not be deleted. This works best when used as a deferred
// function.
func DeleteMonitor(t *testing.T, client *gophercloud.ServiceClient, monitorID string) {
t.Logf("Attempting to delete monitor %s", monitorID)
if err := monitors.Delete(client, monitorID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete monitor: %v", err)
}
t.Logf("Successfully deleted monitor %s", monitorID)
}
// DeletePool will delete a specified pool. A fatal error will occur if the
// pool could not be deleted. This works best when used as a deferred function.
func DeletePool(t *testing.T, client *gophercloud.ServiceClient, poolID string) {
t.Logf("Attempting to delete pool %s", poolID)
if err := pools.Delete(client, poolID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete pool: %v", err)
}
t.Logf("Successfully deleted pool %s", poolID)
}
// DeleteVIP will delete a specified vip. A fatal error will occur if the vip
// could not be deleted. This works best when used as a deferred function.
func DeleteVIP(t *testing.T, client *gophercloud.ServiceClient, vipID string) {
t.Logf("Attempting to delete vip %s", vipID)
if err := vips.Delete(client, vipID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete vip: %v", err)
}
t.Logf("Successfully deleted vip %s", vipID)
}

View File

@ -0,0 +1,83 @@
// +build acceptance networking lbaas member
package lbaas
import (
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
)
func TestMembersList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := members.List(client, members.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to list members: %v", err)
}
allMembers, err := members.ExtractMembers(allPages)
if err != nil {
t.Fatalf("Unable to extract members: %v", err)
}
for _, member := range allMembers {
tools.PrintResource(t, member)
}
}
func TestMembersCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
network, err := networking.CreateNetwork(t, client)
if err != nil {
t.Fatalf("Unable to create network: %v", err)
}
defer networking.DeleteNetwork(t, client, network.ID)
subnet, err := networking.CreateSubnet(t, client, network.ID)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
pool, err := CreatePool(t, client, subnet.ID)
if err != nil {
t.Fatalf("Unable to create pool: %v", err)
}
defer DeletePool(t, client, pool.ID)
member, err := CreateMember(t, client, pool.ID)
if err != nil {
t.Fatalf("Unable to create member: %v", err)
}
defer DeleteMember(t, client, member.ID)
tools.PrintResource(t, member)
updateOpts := members.UpdateOpts{
AdminStateUp: gophercloud.Enabled,
}
_, err = members.Update(client, member.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update member: %v")
}
newMember, err := members.Get(client, member.ID).Extract()
if err != nil {
t.Fatalf("Unable to get member: %v")
}
tools.PrintResource(t, newMember)
}

View File

@ -0,0 +1,63 @@
// +build acceptance networking lbaas monitors
package lbaas
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
)
func TestMonitorsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := monitors.List(client, monitors.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to list monitors: %v", err)
}
allMonitors, err := monitors.ExtractMonitors(allPages)
if err != nil {
t.Fatalf("Unable to extract monitors: %v", err)
}
for _, monitor := range allMonitors {
tools.PrintResource(t, monitor)
}
}
func TestMonitorsCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
monitor, err := CreateMonitor(t, client)
if err != nil {
t.Fatalf("Unable to create monitor: %v", err)
}
defer DeleteMonitor(t, client, monitor.ID)
tools.PrintResource(t, monitor)
updateOpts := monitors.UpdateOpts{
Delay: 999,
}
_, err = monitors.Update(client, monitor.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update monitor: %v")
}
newMonitor, err := monitors.Get(client, monitor.ID).Extract()
if err != nil {
t.Fatalf("Unable to get monitor: %v")
}
tools.PrintResource(t, newMonitor)
}

View File

@ -0,0 +1 @@
package lbaas

View File

@ -0,0 +1,118 @@
// +build acceptance networking lbaas pool
package lbaas
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
)
func TestPoolsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := pools.List(client, pools.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to list pools: %v", err)
}
allPools, err := pools.ExtractPools(allPages)
if err != nil {
t.Fatalf("Unable to extract pools: %v", err)
}
for _, pool := range allPools {
tools.PrintResource(t, pool)
}
}
func TestPoolsCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
network, err := networking.CreateNetwork(t, client)
if err != nil {
t.Fatalf("Unable to create network: %v", err)
}
defer networking.DeleteNetwork(t, client, network.ID)
subnet, err := networking.CreateSubnet(t, client, network.ID)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
pool, err := CreatePool(t, client, subnet.ID)
if err != nil {
t.Fatalf("Unable to create pool: %v", err)
}
defer DeletePool(t, client, pool.ID)
tools.PrintResource(t, pool)
updateOpts := pools.UpdateOpts{
LBMethod: pools.LBMethodLeastConnections,
}
_, err = pools.Update(client, pool.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update pool: %v")
}
newPool, err := pools.Get(client, pool.ID).Extract()
if err != nil {
t.Fatalf("Unable to get pool: %v")
}
tools.PrintResource(t, newPool)
}
func TestPoolsMonitors(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
network, err := networking.CreateNetwork(t, client)
if err != nil {
t.Fatalf("Unable to create network: %v", err)
}
defer networking.DeleteNetwork(t, client, network.ID)
subnet, err := networking.CreateSubnet(t, client, network.ID)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
pool, err := CreatePool(t, client, subnet.ID)
if err != nil {
t.Fatalf("Unable to create pool: %v", err)
}
defer DeletePool(t, client, pool.ID)
monitor, err := CreateMonitor(t, client)
if err != nil {
t.Fatalf("Unable to create monitor: %v", err)
}
defer DeleteMonitor(t, client, monitor.ID)
t.Logf("Associating monitor %s with pool %s", monitor.ID, pool.ID)
if res := pools.AssociateMonitor(client, pool.ID, monitor.ID); res.Err != nil {
t.Fatalf("Unable to associate monitor to pool")
}
t.Logf("Disassociating monitor %s with pool %s", monitor.ID, pool.ID)
if res := pools.DisassociateMonitor(client, pool.ID, monitor.ID); res.Err != nil {
t.Fatalf("Unable to disassociate monitor from pool")
}
}

View File

@ -0,0 +1,83 @@
// +build acceptance networking lbaas vip
package lbaas
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
)
func TestVIPsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := vips.List(client, vips.ListOpts{}).AllPages()
if err != nil {
t.Fatalf("Unable to list vips: %v", err)
}
allVIPs, err := vips.ExtractVIPs(allPages)
if err != nil {
t.Fatalf("Unable to extract vips: %v", err)
}
for _, vip := range allVIPs {
tools.PrintResource(t, vip)
}
}
func TestVIPsCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
network, err := networking.CreateNetwork(t, client)
if err != nil {
t.Fatalf("Unable to create network: %v", err)
}
defer networking.DeleteNetwork(t, client, network.ID)
subnet, err := networking.CreateSubnet(t, client, network.ID)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
pool, err := CreatePool(t, client, subnet.ID)
if err != nil {
t.Fatalf("Unable to create pool: %v", err)
}
defer DeletePool(t, client, pool.ID)
vip, err := CreateVIP(t, client, subnet.ID, pool.ID)
if err != nil {
t.Fatalf("Unable to create vip: %v", err)
}
defer DeleteVIP(t, client, vip.ID)
tools.PrintResource(t, vip)
connLimit := 100
updateOpts := vips.UpdateOpts{
ConnLimit: &connLimit,
}
_, err = vips.Update(client, vip.ID, updateOpts).Extract()
if err != nil {
t.Fatalf("Unable to update vip: %v")
}
newVIP, err := vips.Get(client, vip.ID).Extract()
if err != nil {
t.Fatalf("Unable to get vip: %v")
}
tools.PrintResource(t, newVIP)
}

View File

@ -0,0 +1,282 @@
package lbaas_v2
import (
"fmt"
"strings"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)
const loadbalancerActiveTimeoutSeconds = 300
const loadbalancerDeleteTimeoutSeconds = 300
// CreateListener will create a listener for a given load balancer on a random
// port with a random name. An error will be returned if the listener could not
// be created.
func CreateListener(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer) (*listeners.Listener, error) {
listenerName := tools.RandomString("TESTACCT-", 8)
listenerPort := tools.RandomInt(1, 100)
t.Logf("Attempting to create listener %s on port %d", listenerName, listenerPort)
createOpts := listeners.CreateOpts{
Name: listenerName,
LoadbalancerID: lb.ID,
Protocol: "TCP",
ProtocolPort: listenerPort,
}
listener, err := listeners.Create(client, createOpts).Extract()
if err != nil {
return listener, err
}
t.Logf("Successfully created listener %s", listenerName)
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
return listener, fmt.Errorf("Timed out waiting for loadbalancer to become active")
}
return listener, nil
}
// CreateLoadBalancer will create a load balancer with a random name on a given
// subnet. An error will be returned if the loadbalancer could not be created.
func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetID string) (*loadbalancers.LoadBalancer, error) {
lbName := tools.RandomString("TESTACCT-", 8)
t.Logf("Attempting to create loadbalancer %s on subnet %s", lbName, subnetID)
createOpts := loadbalancers.CreateOpts{
Name: lbName,
VipSubnetID: subnetID,
AdminStateUp: gophercloud.Enabled,
}
lb, err := loadbalancers.Create(client, createOpts).Extract()
if err != nil {
return lb, err
}
t.Logf("Successfully created loadbalancer %s on subnet %s", lbName, subnetID)
t.Logf("Waiting for loadbalancer %s to become active", lbName)
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
return lb, err
}
t.Logf("LoadBalancer %s is active", lbName)
return lb, nil
}
// CreateMember will create a member with a random name, port, address, and
// weight. An error will be returned if the member could not be created.
func CreateMember(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer, pool *pools.Pool, subnetID, subnetCIDR string) (*pools.Member, error) {
memberName := tools.RandomString("TESTACCT-", 8)
memberPort := tools.RandomInt(100, 1000)
memberWeight := tools.RandomInt(1, 10)
cidrParts := strings.Split(subnetCIDR, "/")
subnetParts := strings.Split(cidrParts[0], ".")
memberAddress := fmt.Sprintf("%s.%s.%s.%d", subnetParts[0], subnetParts[1], subnetParts[2], tools.RandomInt(10, 100))
t.Logf("Attempting to create member %s", memberName)
createOpts := pools.CreateMemberOpts{
Name: memberName,
ProtocolPort: memberPort,
Weight: memberWeight,
Address: memberAddress,
SubnetID: subnetID,
}
t.Logf("Member create opts: %#v", createOpts)
member, err := pools.CreateMember(client, pool.ID, createOpts).Extract()
if err != nil {
return member, err
}
t.Logf("Successfully created member %s", memberName)
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
return member, fmt.Errorf("Timed out waiting for loadbalancer to become active")
}
return member, nil
}
// CreateMonitor will create a monitor with a random name for a specific pool.
// An error will be returned if the monitor could not be created.
func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer, pool *pools.Pool) (*monitors.Monitor, error) {
monitorName := tools.RandomString("TESTACCT-", 8)
t.Logf("Attempting to create monitor %s", monitorName)
createOpts := monitors.CreateOpts{
PoolID: pool.ID,
Name: monitorName,
Delay: 10,
Timeout: 5,
MaxRetries: 5,
Type: "PING",
}
monitor, err := monitors.Create(client, createOpts).Extract()
if err != nil {
return monitor, err
}
t.Logf("Successfully created monitor: %s", monitorName)
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
return monitor, fmt.Errorf("Timed out waiting for loadbalancer to become active")
}
return monitor, nil
}
// CreatePool will create a pool with a random name with a specified listener
// and loadbalancer. An error will be returned if the pool could not be
// created.
func CreatePool(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalancers.LoadBalancer) (*pools.Pool, error) {
poolName := tools.RandomString("TESTACCT-", 8)
t.Logf("Attempting to create pool %s", poolName)
createOpts := pools.CreateOpts{
Name: poolName,
Protocol: pools.ProtocolTCP,
LoadbalancerID: lb.ID,
LBMethod: pools.LBMethodLeastConnections,
}
pool, err := pools.Create(client, createOpts).Extract()
if err != nil {
return pool, err
}
t.Logf("Successfully created pool %s", poolName)
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
return pool, fmt.Errorf("Timed out waiting for loadbalancer to become active")
}
return pool, nil
}
// DeleteListener will delete a specified listener. A fatal error will occur if
// the listener could not be deleted. This works best when used as a deferred
// function.
func DeleteListener(t *testing.T, client *gophercloud.ServiceClient, lbID, listenerID string) {
t.Logf("Attempting to delete listener %s", listenerID)
if err := listeners.Delete(client, listenerID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete listener: %v", err)
}
if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
t.Logf("Successfully deleted listener %s", listenerID)
}
// DeleteMember will delete a specified member. A fatal error will occur if the
// member could not be deleted. This works best when used as a deferred
// function.
func DeleteMember(t *testing.T, client *gophercloud.ServiceClient, lbID, poolID, memberID string) {
t.Logf("Attempting to delete member %s", memberID)
if err := pools.DeleteMember(client, poolID, memberID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete member: %s", memberID)
}
if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
t.Logf("Successfully deleted member %s", memberID)
}
// DeleteLoadBalancer will delete a specified loadbalancer. A fatal error will
// occur if the loadbalancer could not be deleted. This works best when used
// as a deferred function.
func DeleteLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, lbID string) {
t.Logf("Attempting to delete loadbalancer %s", lbID)
if err := loadbalancers.Delete(client, lbID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete loadbalancer: %v", err)
}
t.Logf("Waiting for loadbalancer %s to delete", lbID)
if err := WaitForLoadBalancerState(client, lbID, "DELETED", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Loadbalancer did not delete in time.")
}
t.Logf("Successfully deleted loadbalancer %s", lbID)
}
// DeleteMonitor will delete a specified monitor. A fatal error will occur if
// the monitor could not be deleted. This works best when used as a deferred
// function.
func DeleteMonitor(t *testing.T, client *gophercloud.ServiceClient, lbID, monitorID string) {
t.Logf("Attempting to delete monitor %s", monitorID)
if err := monitors.Delete(client, monitorID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete monitor: %v", err)
}
if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
t.Logf("Successfully deleted monitor %s", monitorID)
}
// DeletePool will delete a specified pool. A fatal error will occur if the
// pool could not be deleted. This works best when used as a deferred function.
func DeletePool(t *testing.T, client *gophercloud.ServiceClient, lbID, poolID string) {
t.Logf("Attempting to delete pool %s", poolID)
if err := pools.Delete(client, poolID).ExtractErr(); err != nil {
t.Fatalf("Unable to delete pool: %v", err)
}
if err := WaitForLoadBalancerState(client, lbID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
t.Logf("Successfully deleted pool %s", poolID)
}
// WaitForLoadBalancerState will wait until a loadbalancer reaches a given state.
func WaitForLoadBalancerState(client *gophercloud.ServiceClient, lbID, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := loadbalancers.Get(client, lbID).Extract()
if err != nil {
if httpStatus, ok := err.(gophercloud.ErrDefault404); ok {
if httpStatus.Actual == 404 {
if status == "DELETED" {
return true, nil
}
}
}
return false, err
}
if current.ProvisioningStatus == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,32 @@
// +build acceptance networking lbaas_v2 listeners
package lbaas_v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
)
func TestListenersList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := listeners.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list listeners: %v", err)
}
allListeners, err := listeners.ExtractListeners(allPages)
if err != nil {
t.Fatalf("Unable to extract listeners: %v", err)
}
for _, listener := range allListeners {
tools.PrintResource(t, listener)
}
}

View File

@ -0,0 +1,178 @@
// +build acceptance networking lbaas_v2 loadbalancers
package lbaas_v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)
func TestLoadbalancersList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := loadbalancers.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list loadbalancers: %v", err)
}
allLoadbalancers, err := loadbalancers.ExtractLoadBalancers(allPages)
if err != nil {
t.Fatalf("Unable to extract loadbalancers: %v", err)
}
for _, lb := range allLoadbalancers {
tools.PrintResource(t, lb)
}
}
func TestLoadbalancersCRUD(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
network, err := networking.CreateNetwork(t, client)
if err != nil {
t.Fatalf("Unable to create network: %v", err)
}
defer networking.DeleteNetwork(t, client, network.ID)
subnet, err := networking.CreateSubnet(t, client, network.ID)
if err != nil {
t.Fatalf("Unable to create subnet: %v", err)
}
defer networking.DeleteSubnet(t, client, subnet.ID)
lb, err := CreateLoadBalancer(t, client, subnet.ID)
if err != nil {
t.Fatalf("Unable to create loadbalancer: %v", err)
}
defer DeleteLoadBalancer(t, client, lb.ID)
newLB, err := loadbalancers.Get(client, lb.ID).Extract()
if err != nil {
t.Fatalf("Unable to get loadbalancer: %v", err)
}
tools.PrintResource(t, newLB)
// Because of the time it takes to create a loadbalancer,
// this test will include some other resources.
// Listener
listener, err := CreateListener(t, client, lb)
if err != nil {
t.Fatalf("Unable to create listener: %v", err)
}
defer DeleteListener(t, client, lb.ID, listener.ID)
updateListenerOpts := listeners.UpdateOpts{
Description: "Some listener description",
}
_, err = listeners.Update(client, listener.ID, updateListenerOpts).Extract()
if err != nil {
t.Fatalf("Unable to update listener")
}
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
newListener, err := listeners.Get(client, listener.ID).Extract()
if err != nil {
t.Fatalf("Unable to get listener")
}
tools.PrintResource(t, newListener)
// Pool
pool, err := CreatePool(t, client, lb)
if err != nil {
t.Fatalf("Unable to create pool: %v", err)
}
defer DeletePool(t, client, lb.ID, pool.ID)
updatePoolOpts := pools.UpdateOpts{
Description: "Some pool description",
}
_, err = pools.Update(client, pool.ID, updatePoolOpts).Extract()
if err != nil {
t.Fatalf("Unable to update pool")
}
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
newPool, err := pools.Get(client, pool.ID).Extract()
if err != nil {
t.Fatalf("Unable to get pool")
}
tools.PrintResource(t, newPool)
// Member
member, err := CreateMember(t, client, lb, newPool, subnet.ID, subnet.CIDR)
if err != nil {
t.Fatalf("Unable to create member: %v", err)
}
defer DeleteMember(t, client, lb.ID, pool.ID, member.ID)
newWeight := tools.RandomInt(11, 100)
updateMemberOpts := pools.UpdateMemberOpts{
Weight: newWeight,
}
_, err = pools.UpdateMember(client, pool.ID, member.ID, updateMemberOpts).Extract()
if err != nil {
t.Fatalf("Unable to update pool")
}
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
newMember, err := pools.GetMember(client, pool.ID, member.ID).Extract()
if err != nil {
t.Fatalf("Unable to get member")
}
tools.PrintResource(t, newMember)
// Monitor
monitor, err := CreateMonitor(t, client, lb, newPool)
if err != nil {
t.Fatalf("Unable to create monitor: %v", err)
}
defer DeleteMonitor(t, client, lb.ID, monitor.ID)
newDelay := tools.RandomInt(20, 30)
updateMonitorOpts := monitors.UpdateOpts{
Delay: newDelay,
}
_, err = monitors.Update(client, monitor.ID, updateMonitorOpts).Extract()
if err != nil {
t.Fatalf("Unable to update monitor")
}
if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE", loadbalancerActiveTimeoutSeconds); err != nil {
t.Fatalf("Timed out waiting for loadbalancer to become active")
}
newMonitor, err := monitors.Get(client, monitor.ID).Extract()
if err != nil {
t.Fatalf("Unable to get monitor")
}
tools.PrintResource(t, newMonitor)
}

View File

@ -0,0 +1,32 @@
// +build acceptance networking lbaas_v2 monitors
package lbaas_v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
)
func TestMonitorsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := monitors.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list monitors: %v", err)
}
allMonitors, err := monitors.ExtractMonitors(allPages)
if err != nil {
t.Fatalf("Unable to extract monitors: %v", err)
}
for _, monitor := range allMonitors {
tools.PrintResource(t, monitor)
}
}

View File

@ -0,0 +1 @@
package lbaas_v2

View File

@ -0,0 +1,32 @@
// +build acceptance networking lbaas_v2 pools
package lbaas_v2
import (
"testing"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)
func TestPoolsList(t *testing.T) {
client, err := clients.NewNetworkV2Client()
if err != nil {
t.Fatalf("Unable to create a network client: %v", err)
}
allPages, err := pools.List(client, nil).AllPages()
if err != nil {
t.Fatalf("Unable to list pools: %v", err)
}
allPools, err := pools.ExtractPools(allPages)
if err != nil {
t.Fatalf("Unable to extract pools: %v", err)
}
for _, pool := range allPools {
tools.PrintResource(t, pool)
}
}

View File

@ -0,0 +1 @@
package extensions

Some files were not shown because too many files have changed in this diff Show More