/
cloudinfo.go
147 lines (114 loc) · 3.56 KB
/
cloudinfo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package config
import (
"fmt"
"sort"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/availabilityzones"
"github.com/gophercloud/utils/openstack/clientconfig"
azutils "github.com/gophercloud/utils/openstack/compute/v2/availabilityzones"
"github.com/openshift/openstack-cinder-csi-driver-operator/pkg/version"
)
// CloudInfo caches data fetched from the user's openstack cloud
type CloudInfo struct {
ComputeZones []string
VolumeZones []string
clients *clients
}
type clients struct {
computeClient *gophercloud.ServiceClient
volumeClient *gophercloud.ServiceClient
}
var ci *CloudInfo
func enableTopologyFeature() (bool, error) {
var err error
if ci == nil {
ci, err = getCloudInfo()
if err != nil {
return false, fmt.Errorf("couldn't collect info about cloud availability zones: %w", err)
}
}
// for us to enable the topology feature, we need to have an equal number
// of availability zones for the compute and volume services...
if len(ci.ComputeZones) != len(ci.VolumeZones) {
return false, nil
}
// and these AZs have to have identical names
for i := range ci.ComputeZones {
if ci.ComputeZones[i] != ci.VolumeZones[i] {
return false, nil
}
}
return true, nil
}
// getCloudInfo fetches and caches metadata from openstack
func getCloudInfo() (*CloudInfo, error) {
var ci *CloudInfo
var err error
ci = &CloudInfo{
clients: &clients{},
}
opts := new(clientconfig.ClientOpts)
opts.Cloud = "openstack"
// we represent version using commits since we don't tag releases
ua := gophercloud.UserAgent{}
ua.Prepend(fmt.Sprintf("openstack-cinder-csi-driver-operator/%s", version.Get().GitCommit))
ci.clients.computeClient, err = clientconfig.NewServiceClient("compute", opts)
if err != nil {
return nil, fmt.Errorf("failed to create a compute client: %w", err)
}
ci.clients.computeClient.UserAgent = ua
ci.clients.volumeClient, err = clientconfig.NewServiceClient("volume", opts)
if err != nil {
return nil, fmt.Errorf("failed to create a volume client: %w", err)
}
ci.clients.volumeClient.UserAgent = ua
err = ci.collectInfo()
if err != nil {
return nil, fmt.Errorf("failed to generate OpenStack cloud info: %w", err)
}
return ci, nil
}
func (ci *CloudInfo) collectInfo() error {
var err error
ci.ComputeZones, err = ci.getComputeZones()
if err != nil {
return err
}
ci.VolumeZones, err = ci.getVolumeZones()
if err != nil {
return err
}
return nil
}
func (ci *CloudInfo) getComputeZones() ([]string, error) {
zones, err := azutils.ListAvailableAvailabilityZones(ci.clients.computeClient)
if err != nil {
return nil, fmt.Errorf("failed to list compute availability zones: %w", err)
}
if len(zones) == 0 {
return nil, fmt.Errorf("could not find an available compute availability zone")
}
sort.Strings(zones)
return zones, nil
}
func (ci *CloudInfo) getVolumeZones() ([]string, error) {
allPages, err := availabilityzones.List(ci.clients.volumeClient).AllPages()
if err != nil {
return nil, fmt.Errorf("failed to list volume availability zones: %w", err)
}
availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages)
if err != nil {
return nil, fmt.Errorf("failed to parse response with volume availability zone list: %w", err)
}
if len(availabilityZoneInfo) == 0 {
return nil, fmt.Errorf("could not find an available volume availability zone")
}
var zones []string
for _, zone := range availabilityZoneInfo {
if zone.ZoneState.Available {
zones = append(zones, zone.ZoneName)
}
}
sort.Strings(zones)
return zones, nil
}