Update openstack provider to gophercloud v1.0 API
This commit is contained in:
parent
c85c0a4f06
commit
f184eebc9b
@ -21,11 +21,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"code.google.com/p/gcfg"
|
"code.google.com/p/gcfg"
|
||||||
"github.com/rackspace/gophercloud"
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
"github.com/rackspace/gophercloud/pagination"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
@ -35,21 +38,23 @@ import (
|
|||||||
var ErrServerNotFound = errors.New("Server not found")
|
var ErrServerNotFound = errors.New("Server not found")
|
||||||
var ErrMultipleServersFound = errors.New("Multiple servers matched query")
|
var ErrMultipleServersFound = errors.New("Multiple servers matched query")
|
||||||
var ErrFlavorNotFound = errors.New("Flavor not found")
|
var ErrFlavorNotFound = errors.New("Flavor not found")
|
||||||
|
var ErrNoAddressFound = errors.New("No address found for host")
|
||||||
|
var ErrInvalidAddress = errors.New("Invalid address")
|
||||||
|
var ErrAttrNotFound = errors.New("Expected attribute not found")
|
||||||
|
|
||||||
// OpenStack is an implementation of cloud provider Interface for OpenStack.
|
// OpenStack is an implementation of cloud provider Interface for OpenStack.
|
||||||
type OpenStack struct {
|
type OpenStack struct {
|
||||||
provider string
|
provider *gophercloud.ProviderClient
|
||||||
authOpt gophercloud.AuthOptions
|
|
||||||
region string
|
region string
|
||||||
access *gophercloud.Access
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Global struct {
|
Global struct {
|
||||||
AuthUrl string
|
AuthUrl string
|
||||||
Username, Password string
|
Username, UserId string
|
||||||
ApiKey string
|
Password, ApiKey string
|
||||||
TenantId, TenantName string
|
TenantId, TenantName string
|
||||||
|
DomainId, DomainName string
|
||||||
Region string
|
Region string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,11 +71,13 @@ func init() {
|
|||||||
|
|
||||||
func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
|
func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
|
||||||
return gophercloud.AuthOptions{
|
return gophercloud.AuthOptions{
|
||||||
Username: cfg.Global.Username,
|
IdentityEndpoint: cfg.Global.AuthUrl,
|
||||||
Password: cfg.Global.Password,
|
Username: cfg.Global.Username,
|
||||||
ApiKey: cfg.Global.ApiKey,
|
UserID: cfg.Global.UserId,
|
||||||
TenantId: cfg.Global.TenantId,
|
Password: cfg.Global.Password,
|
||||||
TenantName: cfg.Global.TenantName,
|
APIKey: cfg.Global.ApiKey,
|
||||||
|
TenantID: cfg.Global.TenantId,
|
||||||
|
TenantName: cfg.Global.TenantName,
|
||||||
|
|
||||||
// Persistent service, so we need to be able to renew tokens
|
// Persistent service, so we need to be able to renew tokens
|
||||||
AllowReauth: true,
|
AllowReauth: true,
|
||||||
@ -89,118 +96,184 @@ func readConfig(config io.Reader) (Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newOpenStack(cfg Config) (*OpenStack, error) {
|
func newOpenStack(cfg Config) (*OpenStack, error) {
|
||||||
os := OpenStack{
|
provider, err := openstack.AuthenticatedClient(cfg.toAuthOptions())
|
||||||
provider: cfg.Global.AuthUrl,
|
if err != nil {
|
||||||
authOpt: cfg.toAuthOptions(),
|
return nil, err
|
||||||
region: cfg.Global.Region,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
access, err := gophercloud.Authenticate(os.provider, os.authOpt)
|
os := OpenStack{
|
||||||
os.access = access
|
provider: provider,
|
||||||
|
region: cfg.Global.Region,
|
||||||
return &os, err
|
}
|
||||||
|
return &os, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Instances struct {
|
type Instances struct {
|
||||||
servers gophercloud.CloudServersProvider
|
compute *gophercloud.ServiceClient
|
||||||
flavor_to_resource map[string]*api.NodeResources // keyed by flavor id
|
flavor_to_resource map[string]*api.NodeResources // keyed by flavor id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instances returns an implementation of Instances for OpenStack.
|
// Instances returns an implementation of Instances for OpenStack.
|
||||||
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
|
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
|
||||||
servers, err := gophercloud.ServersApi(os.access, gophercloud.ApiCriteria{
|
compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
|
||||||
Type: "compute",
|
Region: os.region,
|
||||||
UrlChoice: gophercloud.PublicURL,
|
|
||||||
Region: os.region,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
flavors, err := servers.ListFlavors()
|
pager := flavors.ListDetail(compute, nil)
|
||||||
if err != nil {
|
|
||||||
return nil, false
|
flavor_to_resource := make(map[string]*api.NodeResources)
|
||||||
}
|
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||||
flavor_to_resource := make(map[string]*api.NodeResources, len(flavors))
|
flavorList, err := flavors.ExtractFlavors(page)
|
||||||
for _, flavor := range flavors {
|
if err != nil {
|
||||||
rsrc := api.NodeResources{
|
return false, err
|
||||||
Capacity: api.ResourceList{
|
|
||||||
"cpu": util.NewIntOrStringFromInt(flavor.VCpus),
|
|
||||||
"memory": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Ram)),
|
|
||||||
"openstack.org/disk": util.NewIntOrStringFromString(fmt.Sprintf("%dGB", flavor.Disk)),
|
|
||||||
"openstack.org/rxTxFactor": util.NewIntOrStringFromInt(int(flavor.RxTxFactor * 1000)),
|
|
||||||
"openstack.org/swap": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Swap)),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
flavor_to_resource[flavor.Id] = &rsrc
|
for _, flavor := range flavorList {
|
||||||
|
rsrc := api.NodeResources{
|
||||||
|
Capacity: api.ResourceList{
|
||||||
|
"cpu": util.NewIntOrStringFromInt(flavor.VCPUs),
|
||||||
|
"memory": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.RAM)),
|
||||||
|
"openstack.org/disk": util.NewIntOrStringFromString(fmt.Sprintf("%dGB", flavor.Disk)),
|
||||||
|
"openstack.org/rxTxFactor": util.NewIntOrStringFromInt(int(flavor.RxTxFactor * 1000)),
|
||||||
|
"openstack.org/swap": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Swap)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flavor_to_resource[flavor.ID] = &rsrc
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Instances{servers, flavor_to_resource}, true
|
return &Instances{compute, flavor_to_resource}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instances) List(name_filter string) ([]string, error) {
|
func (i *Instances) List(name_filter string) ([]string, error) {
|
||||||
filter := url.Values{}
|
opts := servers.ListOpts{
|
||||||
filter.Set("name", name_filter)
|
Name: name_filter,
|
||||||
filter.Set("status", "ACTIVE")
|
Status: "ACTIVE",
|
||||||
|
}
|
||||||
|
pager := servers.List(i.compute, opts)
|
||||||
|
|
||||||
servers, err := i.servers.ListServersByFilter(filter)
|
ret := make([]string, 0)
|
||||||
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||||
|
sList, err := servers.ExtractServers(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, server := range sList {
|
||||||
|
ret = append(ret, server.Name)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := make([]string, len(servers))
|
|
||||||
for idx, srv := range servers {
|
|
||||||
ret[idx] = srv.Name
|
|
||||||
}
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerByName(api gophercloud.CloudServersProvider, name string) (*gophercloud.Server, error) {
|
func getServerByName(client *gophercloud.ServiceClient, name string) (*servers.Server, error) {
|
||||||
filter := url.Values{}
|
opts := servers.ListOpts{
|
||||||
filter.Set("name", fmt.Sprintf("^%s$", regexp.QuoteMeta(name)))
|
Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(name)),
|
||||||
filter.Set("status", "ACTIVE")
|
Status: "ACTIVE",
|
||||||
|
}
|
||||||
|
pager := servers.List(client, opts)
|
||||||
|
|
||||||
servers, err := api.ListServersByFilter(filter)
|
serverList := make([]servers.Server, 0, 1)
|
||||||
|
|
||||||
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||||
|
s, err := servers.ExtractServers(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
serverList = append(serverList, s...)
|
||||||
|
if len(serverList) > 1 {
|
||||||
|
return false, ErrMultipleServersFound
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(servers) == 0 {
|
if len(serverList) == 0 {
|
||||||
return nil, ErrServerNotFound
|
return nil, ErrServerNotFound
|
||||||
} else if len(servers) > 1 {
|
} else if len(serverList) > 1 {
|
||||||
return nil, ErrMultipleServersFound
|
return nil, ErrMultipleServersFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return &servers[0], nil
|
return &serverList[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstAddr(netblob interface{}) string {
|
||||||
|
// Run-time types for the win :(
|
||||||
|
list, ok := netblob.([]interface{})
|
||||||
|
if !ok || len(list) < 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
props, ok := list[0].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
tmp, ok := props["addr"]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
addr, ok := tmp.(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instances) IPAddress(name string) (net.IP, error) {
|
func (i *Instances) IPAddress(name string) (net.IP, error) {
|
||||||
srv, err := getServerByName(i.servers, name)
|
srv, err := getServerByName(i.compute, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var s string
|
var s string
|
||||||
if len(srv.Addresses.Private) > 0 {
|
if s == "" {
|
||||||
s = srv.Addresses.Private[0].Addr
|
s = firstAddr(srv.Addresses["private"])
|
||||||
} else if len(srv.Addresses.Public) > 0 {
|
}
|
||||||
s = srv.Addresses.Public[0].Addr
|
if s == "" {
|
||||||
} else if srv.AccessIPv4 != "" {
|
s = firstAddr(srv.Addresses["public"])
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
s = srv.AccessIPv4
|
s = srv.AccessIPv4
|
||||||
} else {
|
}
|
||||||
|
if s == "" {
|
||||||
s = srv.AccessIPv6
|
s = srv.AccessIPv6
|
||||||
}
|
}
|
||||||
return net.ParseIP(s), nil
|
if s == "" {
|
||||||
|
return nil, ErrNoAddressFound
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(s)
|
||||||
|
if ip == nil {
|
||||||
|
return nil, ErrInvalidAddress
|
||||||
|
}
|
||||||
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instances) GetNodeResources(name string) (*api.NodeResources, error) {
|
func (i *Instances) GetNodeResources(name string) (*api.NodeResources, error) {
|
||||||
srv, err := getServerByName(i.servers, name)
|
srv, err := getServerByName(i.compute, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rsrc, ok := i.flavor_to_resource[srv.Flavor.Id]
|
s, ok := srv.Flavor["id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrAttrNotFound
|
||||||
|
}
|
||||||
|
flavId, ok := s.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrAttrNotFound
|
||||||
|
}
|
||||||
|
rsrc, ok := i.flavor_to_resource[flavId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrFlavorNotFound
|
return nil, ErrFlavorNotFound
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,6 @@ func TestToAuthOptions(t *testing.T) {
|
|||||||
// standard OS_* OpenStack client environment variables.
|
// standard OS_* OpenStack client environment variables.
|
||||||
func configFromEnv() (cfg Config, ok bool) {
|
func configFromEnv() (cfg Config, ok bool) {
|
||||||
cfg.Global.AuthUrl = os.Getenv("OS_AUTH_URL")
|
cfg.Global.AuthUrl = os.Getenv("OS_AUTH_URL")
|
||||||
// gophercloud wants "provider" to point specifically at tokens URL
|
|
||||||
if !strings.HasSuffix(cfg.Global.AuthUrl, "/tokens") {
|
|
||||||
cfg.Global.AuthUrl += "/tokens"
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.Global.TenantId = os.Getenv("OS_TENANT_ID")
|
cfg.Global.TenantId = os.Getenv("OS_TENANT_ID")
|
||||||
// Rax/nova _insists_ that we don't specify both tenant ID and name
|
// Rax/nova _insists_ that we don't specify both tenant ID and name
|
||||||
@ -75,11 +71,14 @@ func configFromEnv() (cfg Config, ok bool) {
|
|||||||
cfg.Global.Password = os.Getenv("OS_PASSWORD")
|
cfg.Global.Password = os.Getenv("OS_PASSWORD")
|
||||||
cfg.Global.ApiKey = os.Getenv("OS_API_KEY")
|
cfg.Global.ApiKey = os.Getenv("OS_API_KEY")
|
||||||
cfg.Global.Region = os.Getenv("OS_REGION_NAME")
|
cfg.Global.Region = os.Getenv("OS_REGION_NAME")
|
||||||
|
cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID")
|
||||||
|
cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME")
|
||||||
|
|
||||||
ok = (cfg.Global.AuthUrl != "" &&
|
ok = (cfg.Global.AuthUrl != "" &&
|
||||||
cfg.Global.Username != "" &&
|
cfg.Global.Username != "" &&
|
||||||
(cfg.Global.Password != "" || cfg.Global.ApiKey != "") &&
|
(cfg.Global.Password != "" || cfg.Global.ApiKey != "") &&
|
||||||
(cfg.Global.TenantId != "" || cfg.Global.TenantName != ""))
|
(cfg.Global.TenantId != "" || cfg.Global.TenantName != "" ||
|
||||||
|
cfg.Global.DomainId != "" || cfg.Global.DomainName != ""))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user