mirror of https://github.com/k3s-io/k3s
openstack: cache InstanceID and use it for volume management.
Hostnames are not related to server names or instance IDs in any way, don't use them when attaching/detaching volumes.pull/6/head
parent
e49f0de95b
commit
7fc8bcae60
|
@ -17,12 +17,13 @@ limitations under the License.
|
||||||
package openstack
|
package openstack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
ossys "os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -48,6 +49,12 @@ import (
|
||||||
|
|
||||||
const ProviderName = "openstack"
|
const ProviderName = "openstack"
|
||||||
|
|
||||||
|
// metadataUrl is URL to OpenStack metadata server. It's hadrcoded IPv4
|
||||||
|
// link-local address as documented in "OpenStack Cloud Administrator Guide",
|
||||||
|
// chapter Compute - Networking with nova-network.
|
||||||
|
// http://docs.openstack.org/admin-guide-cloud/compute-networking-nova.html#metadata-service
|
||||||
|
const metadataUrl = "http://169.254.169.254/openstack/2012-08-10/meta_data.json"
|
||||||
|
|
||||||
var ErrNotFound = errors.New("Failed to find object")
|
var ErrNotFound = errors.New("Failed to find object")
|
||||||
var ErrMultipleResults = errors.New("Multiple results where only one expected")
|
var ErrMultipleResults = errors.New("Multiple results where only one expected")
|
||||||
var ErrNoAddressFound = errors.New("No address found for host")
|
var ErrNoAddressFound = errors.New("No address found for host")
|
||||||
|
@ -86,6 +93,8 @@ type OpenStack struct {
|
||||||
provider *gophercloud.ProviderClient
|
provider *gophercloud.ProviderClient
|
||||||
region string
|
region string
|
||||||
lbOpts LoadBalancerOpts
|
lbOpts LoadBalancerOpts
|
||||||
|
// InstanceID of the server where this OpenStack object is instantiated.
|
||||||
|
localInstanceID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -140,17 +149,89 @@ func readConfig(config io.Reader) (Config, error) {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseMetadataUUID reads JSON from OpenStack metadata server and parses
|
||||||
|
// instance ID out of it.
|
||||||
|
func parseMetadataUUID(jsonData []byte) (string, error) {
|
||||||
|
// We should receive an object with { 'uuid': '<uuid>' } and couple of other
|
||||||
|
// properties (which we ignore).
|
||||||
|
|
||||||
|
obj := struct{ UUID string }{}
|
||||||
|
err := json.Unmarshal(jsonData, &obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := obj.UUID
|
||||||
|
if uuid == "" {
|
||||||
|
err = fmt.Errorf("cannot parse OpenStack metadata, got empty uuid")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInstanceID() (string, error) {
|
||||||
|
// Try to find instance ID on the local filesystem (created by cloud-init)
|
||||||
|
const instanceIDFile = "/var/lib/cloud/data/instance-id"
|
||||||
|
idBytes, err := ioutil.ReadFile(instanceIDFile)
|
||||||
|
if err == nil {
|
||||||
|
instanceID := string(idBytes)
|
||||||
|
instanceID = strings.TrimSpace(instanceID)
|
||||||
|
glog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
|
||||||
|
if instanceID != "" {
|
||||||
|
return instanceID, nil
|
||||||
|
}
|
||||||
|
// Fall through with empty instanceID and try metadata server.
|
||||||
|
}
|
||||||
|
glog.V(5).Infof("Cannot read %s: '%v', trying metadata server", instanceIDFile, err)
|
||||||
|
|
||||||
|
// Try to get JSON from metdata server.
|
||||||
|
resp, err := http.Get(metadataUrl)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(3).Infof("Cannot read %s: %v", metadataUrl, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("got unexpected status code when reading metadata from %s: %s", metadataUrl, resp.Status)
|
||||||
|
glog.V(3).Infof("%v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(3).Infof("Cannot get HTTP response body from %s: %v", metadataUrl, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
instanceID, err := parseMetadataUUID(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(3).Infof("Cannot parse instance ID from metadata from %s: %v", metadataUrl, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(3).Infof("Got instance id from %s: %s", metadataUrl, instanceID)
|
||||||
|
return instanceID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newOpenStack(cfg Config) (*OpenStack, error) {
|
func newOpenStack(cfg Config) (*OpenStack, error) {
|
||||||
provider, err := openstack.AuthenticatedClient(cfg.toAuthOptions())
|
provider, err := openstack.AuthenticatedClient(cfg.toAuthOptions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
os := OpenStack{
|
id, err := readInstanceID()
|
||||||
provider: provider,
|
if err != nil {
|
||||||
region: cfg.Global.Region,
|
return nil, err
|
||||||
lbOpts: cfg.LoadBalancer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os := OpenStack{
|
||||||
|
provider: provider,
|
||||||
|
region: cfg.Global.Region,
|
||||||
|
lbOpts: cfg.LoadBalancer,
|
||||||
|
localInstanceID: id,
|
||||||
|
}
|
||||||
|
|
||||||
return &os, nil
|
return &os, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -786,15 +867,10 @@ func (os *OpenStack) AttachDisk(diskName string) (string, error) {
|
||||||
glog.Errorf("Unable to initialize nova client for region: %s", os.region)
|
glog.Errorf("Unable to initialize nova client for region: %s", os.region)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
compute_id, err := os.getComputeIDbyHostname(cClient)
|
|
||||||
if err != nil || len(compute_id) == 0 {
|
|
||||||
glog.Errorf("Unable to get minion's id by minion's hostname")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
|
if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
|
||||||
if compute_id == disk.Attachments[0]["server_id"] {
|
if os.localInstanceID == disk.Attachments[0]["server_id"] {
|
||||||
glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, compute_id)
|
glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, os.localInstanceID)
|
||||||
return disk.ID, nil
|
return disk.ID, nil
|
||||||
} else {
|
} else {
|
||||||
errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
|
errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
|
||||||
|
@ -803,14 +879,14 @@ func (os *OpenStack) AttachDisk(diskName string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add read only flag here if possible spothanis
|
// add read only flag here if possible spothanis
|
||||||
_, err = volumeattach.Create(cClient, compute_id, &volumeattach.CreateOpts{
|
_, err = volumeattach.Create(cClient, os.localInstanceID, &volumeattach.CreateOpts{
|
||||||
VolumeID: disk.ID,
|
VolumeID: disk.ID,
|
||||||
}).Extract()
|
}).Extract()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to attach %s volume to %s compute", diskName, compute_id)
|
glog.Errorf("Failed to attach %s volume to %s compute", diskName, os.localInstanceID)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, compute_id)
|
glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, os.localInstanceID)
|
||||||
return disk.ID, nil
|
return disk.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -827,22 +903,17 @@ func (os *OpenStack) DetachDisk(partialDiskId string) error {
|
||||||
glog.Errorf("Unable to initialize nova client for region: %s", os.region)
|
glog.Errorf("Unable to initialize nova client for region: %s", os.region)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
compute_id, err := os.getComputeIDbyHostname(cClient)
|
if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && os.localInstanceID == disk.Attachments[0]["server_id"] {
|
||||||
if err != nil || len(compute_id) == 0 {
|
|
||||||
glog.Errorf("Unable to get compute id while detaching disk")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && compute_id == disk.Attachments[0]["server_id"] {
|
|
||||||
// This is a blocking call and effects kubelet's performance directly.
|
// This is a blocking call and effects kubelet's performance directly.
|
||||||
// We should consider kicking it out into a separate routine, if it is bad.
|
// We should consider kicking it out into a separate routine, if it is bad.
|
||||||
err = volumeattach.Delete(cClient, compute_id, disk.ID).ExtractErr()
|
err = volumeattach.Delete(cClient, os.localInstanceID, disk.ID).ExtractErr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, compute_id, err)
|
glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, os.localInstanceID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, compute_id)
|
glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, os.localInstanceID)
|
||||||
} else {
|
} else {
|
||||||
errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, compute_id)
|
errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, os.localInstanceID)
|
||||||
glog.Errorf(errMsg)
|
glog.Errorf(errMsg)
|
||||||
return errors.New(errMsg)
|
return errors.New(errMsg)
|
||||||
}
|
}
|
||||||
|
@ -885,44 +956,3 @@ func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) {
|
||||||
}
|
}
|
||||||
return volume, err
|
return volume, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (os *OpenStack) getComputeIDbyHostname(cClient *gophercloud.ServiceClient) (string, error) {
|
|
||||||
|
|
||||||
hostname, err := ossys.Hostname()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to get Minion's hostname: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
i, ok := os.Instances()
|
|
||||||
if !ok {
|
|
||||||
glog.Errorf("Unable to get instances")
|
|
||||||
return "", errors.New("Unable to get instances")
|
|
||||||
}
|
|
||||||
|
|
||||||
srvs, err := i.List(".")
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to list servers: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(srvs) == 0 {
|
|
||||||
glog.Errorf("Found no servers in the region")
|
|
||||||
return "", errors.New("Found no servers in the region")
|
|
||||||
}
|
|
||||||
glog.V(4).Infof("Found servers: %v", srvs)
|
|
||||||
|
|
||||||
for _, srvname := range srvs {
|
|
||||||
server, err := getServerByName(cClient, srvname)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
if (server.Metadata["hostname"] != nil && server.Metadata["hostname"] == hostname) || (len(server.Name) > 0 && server.Name == hostname) {
|
|
||||||
glog.V(4).Infof("Found server: %s with host :%s", server.Name, hostname)
|
|
||||||
return server.ID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("No server found matching hostname: %s", hostname)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue