7fed3d210b
Change-Id: I4748ce465b2886098cfab37277a18cc0b571c22b Implements: blueprint enhance-unit-testing Signed-off-by: mozhuli <21621232@zju.edu.cn>
948 lines
26 KiB
Go
948 lines
26 KiB
Go
/*
|
|
Copyright (c) 2017 OpenStack Foundation.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package openstack
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
crv1 "git.openstack.org/openstack/stackube/pkg/apis/v1"
|
|
crdClient "git.openstack.org/openstack/stackube/pkg/kubecrd"
|
|
drivertypes "git.openstack.org/openstack/stackube/pkg/openstack/types"
|
|
"git.openstack.org/openstack/stackube/pkg/util"
|
|
|
|
"github.com/docker/distribution/uuid"
|
|
"github.com/golang/glog"
|
|
"github.com/gophercloud/gophercloud"
|
|
"github.com/gophercloud/gophercloud/openstack"
|
|
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
|
|
"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
|
|
"github.com/gophercloud/gophercloud/pagination"
|
|
|
|
gcfg "gopkg.in/gcfg.v1"
|
|
)
|
|
|
|
const (
|
|
StatusCodeAlreadyExists int = 409
|
|
|
|
podNamePrefix = "kube"
|
|
securitygroupName = "kube-securitygroup-default"
|
|
HostnameMaxLen = 63
|
|
|
|
// Service affinities
|
|
ServiceAffinityNone = "None"
|
|
ServiceAffinityClientIP = "ClientIP"
|
|
)
|
|
|
|
var (
|
|
adminStateUp = true
|
|
|
|
ErrNotFound = errors.New("NotFound")
|
|
ErrMultipleResults = errors.New("MultipleResults")
|
|
)
|
|
|
|
// Interface should be implemented by a openstack client.
|
|
type Interface interface {
|
|
// CreateTenant creates tenant by tenantname.
|
|
CreateTenant(tenantName string) (string, error)
|
|
// DeleteTenant deletes tenant by tenantName.
|
|
DeleteTenant(tenantName string) error
|
|
// GetTenantIDFromName gets tenantID by tenantName.
|
|
GetTenantIDFromName(tenantName string) (string, error)
|
|
// CheckTenantByID checks tenant exist or not by tenantID.
|
|
CheckTenantByID(tenantID string) (bool, error)
|
|
// CreateUser creates user with username, password in the tenant.
|
|
CreateUser(username, password, tenantID string) error
|
|
// DeleteAllUsersOnTenant deletes all users on the tenant.
|
|
DeleteAllUsersOnTenant(tenantName string) error
|
|
// CreateNetwork creates network.
|
|
CreateNetwork(network *drivertypes.Network) error
|
|
// GetNetworkByID gets network by networkID.
|
|
GetNetworkByID(networkID string) (*drivertypes.Network, error)
|
|
// GetNetworkByName gets network by networkName.
|
|
GetNetworkByName(networkName string) (*drivertypes.Network, error)
|
|
// DeleteNetwork deletes network by networkName.
|
|
DeleteNetwork(networkName string) error
|
|
// GetProviderSubnet gets provider subnet by id
|
|
GetProviderSubnet(osSubnetID string) (*drivertypes.Subnet, error)
|
|
// CreatePort creates port by neworkID, tenantID and portName.
|
|
CreatePort(networkID, tenantID, portName string) (*portsbinding.Port, error)
|
|
// GetPort gets port by portName.
|
|
GetPort(name string) (*ports.Port, error)
|
|
// ListPorts lists ports by networkID and deviceOwner.
|
|
ListPorts(networkID, deviceOwner string) ([]ports.Port, error)
|
|
// DeletePortByName deletes port by portName.
|
|
DeletePortByName(portName string) error
|
|
// DeletePortByID deletes port by portID.
|
|
DeletePortByID(portID string) error
|
|
// UpdatePortsBinding updates port binding.
|
|
UpdatePortsBinding(portID, deviceOwner string) error
|
|
// LoadBalancerExist returns whether a load balancer has already been exist.
|
|
LoadBalancerExist(name string) (bool, error)
|
|
// EnsureLoadBalancer ensures a load balancer is created.
|
|
EnsureLoadBalancer(lb *LoadBalancer) (*LoadBalancerStatus, error)
|
|
// EnsureLoadBalancerDeleted ensures a load balancer is deleted.
|
|
EnsureLoadBalancerDeleted(name string) error
|
|
// GetCRDClient returns the CRDClient.
|
|
GetCRDClient() crdClient.Interface
|
|
// GetPluginName returns the plugin name.
|
|
GetPluginName() string
|
|
// GetIntegrationBridge returns the integration bridge name.
|
|
GetIntegrationBridge() string
|
|
}
|
|
|
|
// Client implements the openstack client Interface.
|
|
type Client struct {
|
|
Identity *gophercloud.ServiceClient
|
|
Provider *gophercloud.ProviderClient
|
|
Network *gophercloud.ServiceClient
|
|
Region string
|
|
ExtNetID string
|
|
PluginName string
|
|
IntegrationBridge string
|
|
CRDClient crdClient.Interface
|
|
}
|
|
|
|
type PluginOpts struct {
|
|
PluginName string `gcfg:"plugin-name"`
|
|
IntegrationBridge string `gcfg:"integration-bridge"`
|
|
}
|
|
|
|
// Config used to configure the openstack client.
|
|
type Config struct {
|
|
Global struct {
|
|
AuthUrl string `gcfg:"auth-url"`
|
|
Username string `gcfg:"username"`
|
|
Password string `gcfg:"password"`
|
|
TenantName string `gcfg:"tenant-name"`
|
|
Region string `gcfg:"region"`
|
|
ExtNetID string `gcfg:"ext-net-id"`
|
|
}
|
|
Plugin PluginOpts
|
|
}
|
|
|
|
func toAuthOptions(cfg Config) gophercloud.AuthOptions {
|
|
return gophercloud.AuthOptions{
|
|
IdentityEndpoint: cfg.Global.AuthUrl,
|
|
Username: cfg.Global.Username,
|
|
Password: cfg.Global.Password,
|
|
TenantName: cfg.Global.TenantName,
|
|
AllowReauth: true,
|
|
}
|
|
}
|
|
|
|
// NewClient returns a new openstack client.
|
|
func NewClient(config string, kubeConfig string) (Interface, error) {
|
|
var opts gophercloud.AuthOptions
|
|
cfg, err := readConfig(config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed read cloudconfig: %v", err)
|
|
}
|
|
glog.V(1).Infof("Initializing openstack client with config %v", cfg)
|
|
|
|
if cfg.Global.ExtNetID == "" {
|
|
return nil, fmt.Errorf("external network ID not set")
|
|
}
|
|
|
|
opts = toAuthOptions(cfg)
|
|
provider, err := openstack.AuthenticatedClient(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity, err := openstack.NewIdentityV2(provider, gophercloud.EndpointOpts{
|
|
Availability: gophercloud.AvailabilityAdmin,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
network, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
|
|
Region: cfg.Global.Region,
|
|
})
|
|
if err != nil {
|
|
glog.Warning("Failed to find neutron endpoint: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
// Create CRD client
|
|
k8sConfig, err := util.NewClusterConfig(kubeConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build kubeconfig: %v", err)
|
|
}
|
|
kubeCRDClient, err := crdClient.NewCRDClient(k8sConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create client for CRD: %v", err)
|
|
}
|
|
|
|
client := &Client{
|
|
Identity: identity,
|
|
Provider: provider,
|
|
Network: network,
|
|
Region: cfg.Global.Region,
|
|
ExtNetID: cfg.Global.ExtNetID,
|
|
PluginName: cfg.Plugin.PluginName,
|
|
IntegrationBridge: cfg.Plugin.IntegrationBridge,
|
|
CRDClient: kubeCRDClient,
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func readConfig(config string) (Config, error) {
|
|
conf, err := os.Open(config)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
var cfg Config
|
|
err = gcfg.ReadInto(&cfg, conf)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// GetCRDClient returns the CRDClient.
|
|
func (os *Client) GetCRDClient() crdClient.Interface {
|
|
return os.CRDClient
|
|
}
|
|
|
|
// GetPluginName returns the plugin name.
|
|
func (os *Client) GetPluginName() string {
|
|
return os.PluginName
|
|
}
|
|
|
|
// GetIntegrationBridge returns the integration bridge name.
|
|
func (os *Client) GetIntegrationBridge() string {
|
|
return os.IntegrationBridge
|
|
}
|
|
|
|
// GetTenantIDFromName gets tenantID by tenantName.
|
|
func (os *Client) GetTenantIDFromName(tenantName string) (string, error) {
|
|
if util.IsSystemNamespace(tenantName) {
|
|
tenantName = util.SystemTenant
|
|
}
|
|
|
|
// If tenantID is specified, return it directly
|
|
var (
|
|
tenant *crv1.Tenant
|
|
err error
|
|
)
|
|
if tenant, err = os.CRDClient.GetTenant(tenantName); err != nil {
|
|
return "", err
|
|
}
|
|
if tenant.Spec.TenantID != "" {
|
|
return tenant.Spec.TenantID, nil
|
|
}
|
|
|
|
// Otherwise, fetch tenantID from OpenStack
|
|
var tenantID string
|
|
err = tenants.List(os.Identity, nil).EachPage(func(page pagination.Page) (bool, error) {
|
|
tenantList, err1 := tenants.ExtractTenants(page)
|
|
if err1 != nil {
|
|
return false, err1
|
|
}
|
|
for _, t := range tenantList {
|
|
if t.Name == tenantName {
|
|
tenantID = t.ID
|
|
break
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
glog.V(3).Infof("Got tenantID: %v for tenantName: %v", tenantID, tenantName)
|
|
|
|
return tenantID, nil
|
|
}
|
|
|
|
// CreateTenant creates tenant by tenantname.
|
|
func (os *Client) CreateTenant(tenantName string) (string, error) {
|
|
createOpts := tenants.CreateOpts{
|
|
Name: tenantName,
|
|
Description: "stackube",
|
|
Enabled: gophercloud.Enabled,
|
|
}
|
|
|
|
_, err := tenants.Create(os.Identity, createOpts).Extract()
|
|
if err != nil && !IsAlreadyExists(err) {
|
|
glog.Errorf("Failed to create tenant %s: %v", tenantName, err)
|
|
return "", err
|
|
}
|
|
glog.V(4).Infof("Tenant %s created", tenantName)
|
|
tenantID, err := os.GetTenantIDFromName(tenantName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return tenantID, nil
|
|
}
|
|
|
|
// DeleteTenant deletes tenant by tenantName.
|
|
func (os *Client) DeleteTenant(tenantName string) error {
|
|
return tenants.List(os.Identity, nil).EachPage(func(page pagination.Page) (bool, error) {
|
|
tenantList, err := tenants.ExtractTenants(page)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, t := range tenantList {
|
|
if t.Name == tenantName {
|
|
err := tenants.Delete(os.Identity, t.ID).ExtractErr()
|
|
if err != nil {
|
|
glog.Errorf("Delete openstack tenant %s error: %v", tenantName, err)
|
|
return false, err
|
|
}
|
|
glog.V(4).Infof("Tenant %s deleted", tenantName)
|
|
break
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
}
|
|
|
|
// CreateUser creates user with username, password in the tenant.
|
|
func (os *Client) CreateUser(username, password, tenantID string) error {
|
|
opts := users.CreateOpts{
|
|
Name: username,
|
|
TenantID: tenantID,
|
|
Enabled: gophercloud.Enabled,
|
|
Password: password,
|
|
}
|
|
_, err := users.Create(os.Identity, opts).Extract()
|
|
if err != nil && !IsAlreadyExists(err) {
|
|
glog.Errorf("Failed to create user %s: %v", username, err)
|
|
return err
|
|
}
|
|
glog.V(4).Infof("User %s created", username)
|
|
return nil
|
|
}
|
|
|
|
// DeleteAllUsersOnTenant deletes all users on the tenant.
|
|
func (os *Client) DeleteAllUsersOnTenant(tenantName string) error {
|
|
tenantID, err := os.GetTenantIDFromName(tenantName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return users.ListUsers(os.Identity, tenantID).EachPage(func(page pagination.Page) (bool, error) {
|
|
usersList, err := users.ExtractUsers(page)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, u := range usersList {
|
|
res := users.Delete(os.Identity, u.ID)
|
|
if res.Err != nil {
|
|
glog.Errorf("Delete openstack user %s error: %v", u.Name, err)
|
|
return false, err
|
|
}
|
|
glog.V(4).Infof("User %s deleted", u.Name)
|
|
}
|
|
return true, nil
|
|
})
|
|
}
|
|
|
|
// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
|
|
func IsAlreadyExists(err error) bool {
|
|
return reasonForError(err) == StatusCodeAlreadyExists
|
|
}
|
|
|
|
func reasonForError(err error) int {
|
|
switch t := err.(type) {
|
|
case gophercloud.ErrUnexpectedResponseCode:
|
|
return t.Actual
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// GetOpenStackNetworkByTenantID gets tenant's network by tenantID(tenant and network are one to one mapping in stackube)
|
|
func (os *Client) GetOpenStackNetworkByTenantID(tenantID string) (*networks.Network, error) {
|
|
opts := networks.ListOpts{TenantID: tenantID}
|
|
return os.getOpenStackNetwork(&opts)
|
|
}
|
|
|
|
// Get openstack network by id
|
|
func (os *Client) getOpenStackNetworkByID(id string) (*networks.Network, error) {
|
|
opts := networks.ListOpts{ID: id}
|
|
return os.getOpenStackNetwork(&opts)
|
|
}
|
|
|
|
// Get openstack network by name
|
|
func (os *Client) getOpenStackNetworkByName(name string) (*networks.Network, error) {
|
|
opts := networks.ListOpts{Name: name}
|
|
return os.getOpenStackNetwork(&opts)
|
|
}
|
|
|
|
// Get openstack network
|
|
func (os *Client) getOpenStackNetwork(opts *networks.ListOpts) (*networks.Network, error) {
|
|
var osNetwork *networks.Network
|
|
pager := networks.List(os.Network, *opts)
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
networkList, e := networks.ExtractNetworks(page)
|
|
if len(networkList) > 1 {
|
|
return false, ErrMultipleResults
|
|
}
|
|
|
|
if len(networkList) == 1 {
|
|
osNetwork = &networkList[0]
|
|
}
|
|
|
|
return true, e
|
|
})
|
|
|
|
if err == nil && osNetwork == nil {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return osNetwork, err
|
|
}
|
|
|
|
// GetProviderSubnet gets provider subnet by subnetID
|
|
func (os *Client) GetProviderSubnet(osSubnetID string) (*drivertypes.Subnet, error) {
|
|
s, err := subnets.Get(os.Network, osSubnetID).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Get openstack subnet failed: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
var routes []*drivertypes.Route
|
|
for _, r := range s.HostRoutes {
|
|
route := drivertypes.Route{
|
|
Nexthop: r.NextHop,
|
|
DestinationCIDR: r.DestinationCIDR,
|
|
}
|
|
routes = append(routes, &route)
|
|
}
|
|
|
|
providerSubnet := drivertypes.Subnet{
|
|
Uid: s.ID,
|
|
Cidr: s.CIDR,
|
|
Gateway: s.GatewayIP,
|
|
Name: s.Name,
|
|
Dnsservers: s.DNSNameservers,
|
|
Routes: routes,
|
|
}
|
|
|
|
return &providerSubnet, nil
|
|
}
|
|
|
|
// GetNetworkByID gets network by networkID
|
|
func (os *Client) GetNetworkByID(networkID string) (*drivertypes.Network, error) {
|
|
osNetwork, err := os.getOpenStackNetworkByID(networkID)
|
|
if err != nil {
|
|
glog.Errorf("failed to fetch openstack network by iD: %v, failure: %v", networkID, err)
|
|
return nil, err
|
|
}
|
|
|
|
return os.OSNetworktoProviderNetwork(osNetwork)
|
|
}
|
|
|
|
// GetNetworkByName gets network by networkName
|
|
func (os *Client) GetNetworkByName(networkName string) (*drivertypes.Network, error) {
|
|
osNetwork, err := os.getOpenStackNetworkByName(networkName)
|
|
if err != nil {
|
|
glog.Warningf("try to fetch openstack network by name: %v but failed: %v", networkName, err)
|
|
return nil, err
|
|
}
|
|
|
|
return os.OSNetworktoProviderNetwork(osNetwork)
|
|
}
|
|
|
|
// OSNetworktoProviderNetwork transfers networks.Network to drivertypes.Network.
|
|
func (os *Client) OSNetworktoProviderNetwork(osNetwork *networks.Network) (*drivertypes.Network, error) {
|
|
var providerNetwork drivertypes.Network
|
|
var providerSubnets []*drivertypes.Subnet
|
|
providerNetwork.Name = osNetwork.Name
|
|
providerNetwork.Uid = osNetwork.ID
|
|
providerNetwork.Status = os.ToProviderStatus(osNetwork.Status)
|
|
providerNetwork.TenantID = osNetwork.TenantID
|
|
|
|
for _, subnetID := range osNetwork.Subnets {
|
|
s, err := os.GetProviderSubnet(subnetID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
providerSubnets = append(providerSubnets, s)
|
|
}
|
|
|
|
providerNetwork.Subnets = providerSubnets
|
|
|
|
return &providerNetwork, nil
|
|
}
|
|
|
|
// ToProviderStatus transfers networks.Network's status to drivertypes.Network's status.
|
|
func (os *Client) ToProviderStatus(status string) string {
|
|
switch status {
|
|
case "ACTIVE":
|
|
return "Active"
|
|
case "BUILD":
|
|
return "Pending"
|
|
case "DOWN", "ERROR":
|
|
return "Failed"
|
|
default:
|
|
return "Failed"
|
|
}
|
|
}
|
|
|
|
// CreateNetwork creates network.
|
|
func (os *Client) CreateNetwork(network *drivertypes.Network) error {
|
|
if len(network.Subnets) == 0 {
|
|
return errors.New("Subnets is null")
|
|
}
|
|
|
|
// create network
|
|
opts := networks.CreateOpts{
|
|
Name: network.Name,
|
|
AdminStateUp: &adminStateUp,
|
|
TenantID: network.TenantID,
|
|
}
|
|
osNet, err := networks.Create(os.Network, opts).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Create openstack network %s failed: %v", network.Name, err)
|
|
return err
|
|
}
|
|
|
|
// create router
|
|
routerOpts := routers.CreateOpts{
|
|
// use network name as router name for convenience
|
|
Name: network.Name,
|
|
TenantID: network.TenantID,
|
|
GatewayInfo: &routers.GatewayInfo{NetworkID: os.ExtNetID},
|
|
}
|
|
osRouter, err := routers.Create(os.Network, routerOpts).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Create openstack router %s failed: %v", network.Name, err)
|
|
delErr := os.DeleteNetwork(network.Name)
|
|
if delErr != nil {
|
|
glog.Errorf("Delete openstack network %s failed: %v", network.Name, delErr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// create subnets and connect them to router
|
|
networkID := osNet.ID
|
|
network.Status = os.ToProviderStatus(osNet.Status)
|
|
network.Uid = osNet.ID
|
|
for _, sub := range network.Subnets {
|
|
// create subnet
|
|
subnetOpts := subnets.CreateOpts{
|
|
NetworkID: networkID,
|
|
CIDR: sub.Cidr,
|
|
Name: sub.Name,
|
|
IPVersion: gophercloud.IPv4,
|
|
TenantID: network.TenantID,
|
|
GatewayIP: &sub.Gateway,
|
|
DNSNameservers: sub.Dnsservers,
|
|
}
|
|
s, err := subnets.Create(os.Network, subnetOpts).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Create openstack subnet %s failed: %v", sub.Name, err)
|
|
delErr := os.DeleteNetwork(network.Name)
|
|
if delErr != nil {
|
|
glog.Errorf("Delete openstack network %s failed: %v", network.Name, delErr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// add subnet to router
|
|
opts := routers.AddInterfaceOpts{
|
|
SubnetID: s.ID,
|
|
}
|
|
_, err = routers.AddInterface(os.Network, osRouter.ID, opts).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Create openstack subnet %s failed: %v", sub.Name, err)
|
|
delErr := os.DeleteNetwork(network.Name)
|
|
if delErr != nil {
|
|
glog.Errorf("Delete openstack network %s failed: %v", network.Name, delErr)
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateNetwork updates network.
|
|
func (os *Client) UpdateNetwork(network *drivertypes.Network) error {
|
|
// TODO: update network subnets
|
|
return nil
|
|
}
|
|
|
|
func (os *Client) getRouterByName(name string) (*routers.Router, error) {
|
|
var result *routers.Router
|
|
|
|
opts := routers.ListOpts{Name: name}
|
|
pager := routers.List(os.Network, opts)
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
routerList, e := routers.ExtractRouters(page)
|
|
if len(routerList) > 1 {
|
|
return false, ErrMultipleResults
|
|
} else if len(routerList) == 1 {
|
|
result = &routerList[0]
|
|
}
|
|
|
|
return true, e
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// DeleteNetwork deletes network by networkName.
|
|
func (os *Client) DeleteNetwork(networkName string) error {
|
|
osNetwork, err := os.getOpenStackNetworkByName(networkName)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack network failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
if osNetwork != nil {
|
|
// Delete ports
|
|
opts := ports.ListOpts{NetworkID: osNetwork.ID}
|
|
pager := ports.List(os.Network, opts)
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
portList, err := ports.ExtractPorts(page)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack ports error: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
for _, port := range portList {
|
|
if port.DeviceOwner == "network:router_interface" {
|
|
continue
|
|
}
|
|
|
|
err = ports.Delete(os.Network, port.ID).ExtractErr()
|
|
if err != nil {
|
|
glog.Warningf("Delete port %v failed: %v", port.ID, err)
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
glog.Errorf("Delete ports error: %v", err)
|
|
}
|
|
|
|
router, err := os.getRouterByName(networkName)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack router %s error: %v", networkName, err)
|
|
return err
|
|
}
|
|
|
|
// delete all subnets
|
|
for _, subnet := range osNetwork.Subnets {
|
|
if router != nil {
|
|
opts := routers.RemoveInterfaceOpts{SubnetID: subnet}
|
|
_, err := routers.RemoveInterface(os.Network, router.ID, opts).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Get openstack router %s error: %v", networkName, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = subnets.Delete(os.Network, subnet).ExtractErr()
|
|
if err != nil {
|
|
glog.Errorf("Delete openstack subnet %s error: %v", subnet, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// delete router
|
|
if router != nil {
|
|
err = routers.Delete(os.Network, router.ID).ExtractErr()
|
|
if err != nil {
|
|
glog.Errorf("Delete openstack router %s error: %v", router.ID, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// delete network
|
|
err = networks.Delete(os.Network, osNetwork.ID).ExtractErr()
|
|
if err != nil {
|
|
glog.Errorf("Delete openstack network %s error: %v", osNetwork.ID, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckTenantByID checks tenant exist or not by tenantID.
|
|
func (os *Client) CheckTenantByID(tenantID string) (bool, error) {
|
|
opts := tenants.ListOpts{}
|
|
pager := tenants.List(os.Identity, &opts)
|
|
|
|
var found bool
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
|
|
tenantList, err := tenants.ExtractTenants(page)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if len(tenantList) == 0 {
|
|
return false, ErrNotFound
|
|
}
|
|
|
|
for _, t := range tenantList {
|
|
if t.ID == tenantID || t.Name == tenantID {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
|
|
return found, err
|
|
}
|
|
|
|
// GetPort gets port by portName.
|
|
func (os *Client) GetPort(name string) (*ports.Port, error) {
|
|
opts := ports.ListOpts{Name: name}
|
|
pager := ports.List(os.Network, opts)
|
|
|
|
var port *ports.Port
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
portList, err := ports.ExtractPorts(page)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack ports error: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
if len(portList) > 1 {
|
|
return false, ErrMultipleResults
|
|
}
|
|
|
|
if len(portList) == 0 {
|
|
return false, ErrNotFound
|
|
}
|
|
|
|
port = &portList[0]
|
|
|
|
return true, err
|
|
})
|
|
|
|
return port, err
|
|
}
|
|
|
|
func getHostName() string {
|
|
host, err := os.Hostname()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return host
|
|
}
|
|
|
|
func (os *Client) ensureSecurityGroup(tenantID string) (string, error) {
|
|
var securitygroup *groups.SecGroup
|
|
|
|
opts := groups.ListOpts{
|
|
TenantID: tenantID,
|
|
Name: securitygroupName,
|
|
}
|
|
pager := groups.List(os.Network, opts)
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
sg, err := groups.ExtractGroups(page)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack securitygroups error: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
if len(sg) > 0 {
|
|
securitygroup = &sg[0]
|
|
}
|
|
|
|
return true, err
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// If securitygroup doesn't exist, create a new one
|
|
if securitygroup == nil {
|
|
securitygroup, err = groups.Create(os.Network, groups.CreateOpts{
|
|
Name: securitygroupName,
|
|
TenantID: tenantID,
|
|
}).Extract()
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
var secGroupsRules int
|
|
listopts := rules.ListOpts{
|
|
TenantID: tenantID,
|
|
Direction: string(rules.DirIngress),
|
|
SecGroupID: securitygroup.ID,
|
|
}
|
|
rulesPager := rules.List(os.Network, listopts)
|
|
err = rulesPager.EachPage(func(page pagination.Page) (bool, error) {
|
|
r, err := rules.ExtractRules(page)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack securitygroup rules error: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
secGroupsRules = len(r)
|
|
|
|
return true, err
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// create new rules
|
|
if secGroupsRules == 0 {
|
|
// create egress rule
|
|
_, err = rules.Create(os.Network, rules.CreateOpts{
|
|
TenantID: tenantID,
|
|
SecGroupID: securitygroup.ID,
|
|
Direction: rules.DirEgress,
|
|
EtherType: rules.EtherType4,
|
|
}).Extract()
|
|
|
|
// create ingress rule
|
|
_, err := rules.Create(os.Network, rules.CreateOpts{
|
|
TenantID: tenantID,
|
|
SecGroupID: securitygroup.ID,
|
|
Direction: rules.DirIngress,
|
|
EtherType: rules.EtherType4,
|
|
}).Extract()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return securitygroup.ID, nil
|
|
}
|
|
|
|
// CreatePort creates port by neworkID, tenantID and portName.
|
|
func (os *Client) CreatePort(networkID, tenantID, portName string) (*portsbinding.Port, error) {
|
|
securitygroup, err := os.ensureSecurityGroup(tenantID)
|
|
if err != nil {
|
|
glog.Errorf("EnsureSecurityGroup failed: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
opts := portsbinding.CreateOpts{
|
|
HostID: getHostName(),
|
|
CreateOptsBuilder: ports.CreateOpts{
|
|
NetworkID: networkID,
|
|
Name: portName,
|
|
AdminStateUp: &adminStateUp,
|
|
TenantID: tenantID,
|
|
DeviceID: uuid.Generate().String(),
|
|
DeviceOwner: fmt.Sprintf("compute:%s", getHostName()),
|
|
SecurityGroups: []string{securitygroup},
|
|
},
|
|
}
|
|
|
|
port, err := portsbinding.Create(os.Network, opts).Extract()
|
|
if err != nil {
|
|
glog.Errorf("Create port %s failed: %v", portName, err)
|
|
return nil, err
|
|
}
|
|
return port, nil
|
|
}
|
|
|
|
// ListPorts lists ports by networkID and deviceOwner.
|
|
func (os *Client) ListPorts(networkID, deviceOwner string) ([]ports.Port, error) {
|
|
var results []ports.Port
|
|
opts := ports.ListOpts{
|
|
NetworkID: networkID,
|
|
DeviceOwner: deviceOwner,
|
|
}
|
|
pager := ports.List(os.Network, opts)
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
portList, err := ports.ExtractPorts(page)
|
|
if err != nil {
|
|
glog.Errorf("Get openstack ports error: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
for _, port := range portList {
|
|
results = append(results, port)
|
|
}
|
|
|
|
return true, err
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// DeletePortByName deletes port by portName
|
|
func (os *Client) DeletePortByName(portName string) error {
|
|
port, err := os.GetPort(portName)
|
|
if err == util.ErrNotFound {
|
|
glog.V(4).Infof("Port %s already deleted", portName)
|
|
return nil
|
|
} else if err != nil {
|
|
glog.Errorf("Get openstack port %s failed: %v", portName, err)
|
|
return err
|
|
}
|
|
|
|
if port != nil {
|
|
err := ports.Delete(os.Network, port.ID).ExtractErr()
|
|
if err != nil {
|
|
glog.Errorf("Delete openstack port %s failed: %v", portName, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeletePortByID deletes port by portID.
|
|
func (os *Client) DeletePortByID(portID string) error {
|
|
err := ports.Delete(os.Network, portID).ExtractErr()
|
|
if err != nil {
|
|
glog.Errorf("Delete openstack port portID %s failed: %v", portID, err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdatePortsBinding updates port binding.
|
|
func (os *Client) UpdatePortsBinding(portID, deviceOwner string) error {
|
|
// Update hostname in order to make sure it is correct
|
|
updateOpts := portsbinding.UpdateOpts{
|
|
HostID: getHostName(),
|
|
UpdateOptsBuilder: ports.UpdateOpts{
|
|
DeviceOwner: deviceOwner,
|
|
},
|
|
}
|
|
_, err := portsbinding.Update(os.Network, portID, updateOpts).Extract()
|
|
return err
|
|
}
|