123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- /*
- Copyright 2016 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 openstack
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "k8s.io/klog"
- "k8s.io/kubernetes/pkg/util/mount"
- "k8s.io/utils/exec"
- )
- const (
- // metadataURLTemplate allows building an OpenStack Metadata service URL.
- // It's a hardcoded IPv4 link-local address as documented in "OpenStack Cloud
- // Administrator Guide", chapter Compute - Networking with nova-network.
- //https://docs.openstack.org/nova/latest/admin/networking-nova.html#metadata-service
- defaultMetadataVersion = "2012-08-10"
- metadataURLTemplate = "http://169.254.169.254/openstack/%s/meta_data.json"
- // metadataID is used as an identifier on the metadata search order configuration.
- metadataID = "metadataService"
- // Config drive is defined as an iso9660 or vfat (deprecated) drive
- // with the "config-2" label.
- //https://docs.openstack.org/nova/latest/user/config-drive.html
- configDriveLabel = "config-2"
- configDrivePathTemplate = "openstack/%s/meta_data.json"
- // configDriveID is used as an identifier on the metadata search order configuration.
- configDriveID = "configDrive"
- )
- // ErrBadMetadata is used to indicate a problem parsing data from metadata server
- var ErrBadMetadata = errors.New("invalid OpenStack metadata, got empty uuid")
- // DeviceMetadata is a single/simplified data structure for all kinds of device metadata types.
- type DeviceMetadata struct {
- Type string `json:"type"`
- Bus string `json:"bus,omitempty"`
- Serial string `json:"serial,omitempty"`
- Address string `json:"address,omitempty"`
- // .. and other fields.
- }
- // Metadata has the information fetched from OpenStack metadata service or
- // config drives. Assumes the "2012-08-10" meta_data.json format.
- // See http://docs.openstack.org/user-guide/cli_config_drive.html
- type Metadata struct {
- UUID string `json:"uuid"`
- Name string `json:"name"`
- AvailabilityZone string `json:"availability_zone"`
- Devices []DeviceMetadata `json:"devices,omitempty"`
- // .. and other fields we don't care about. Expand as necessary.
- }
- // parseMetadata reads JSON from OpenStack metadata server and parses
- // instance ID out of it.
- func parseMetadata(r io.Reader) (*Metadata, error) {
- var metadata Metadata
- json := json.NewDecoder(r)
- if err := json.Decode(&metadata); err != nil {
- return nil, err
- }
- if metadata.UUID == "" {
- return nil, ErrBadMetadata
- }
- return &metadata, nil
- }
- func getMetadataURL(metadataVersion string) string {
- return fmt.Sprintf(metadataURLTemplate, metadataVersion)
- }
- func getConfigDrivePath(metadataVersion string) string {
- return fmt.Sprintf(configDrivePathTemplate, metadataVersion)
- }
- func getMetadataFromConfigDrive(metadataVersion string) (*Metadata, error) {
- // Try to read instance UUID from config drive.
- dev := "/dev/disk/by-label/" + configDriveLabel
- if _, err := os.Stat(dev); os.IsNotExist(err) {
- out, err := exec.New().Command(
- "blkid", "-l",
- "-t", "LABEL="+configDriveLabel,
- "-o", "device",
- ).CombinedOutput()
- if err != nil {
- return nil, fmt.Errorf("unable to run blkid: %v", err)
- }
- dev = strings.TrimSpace(string(out))
- }
- mntdir, err := ioutil.TempDir("", "configdrive")
- if err != nil {
- return nil, err
- }
- defer os.Remove(mntdir)
- klog.V(4).Infof("Attempting to mount configdrive %s on %s", dev, mntdir)
- mounter := mount.New("" /* default mount path */)
- err = mounter.Mount(dev, mntdir, "iso9660", []string{"ro"})
- if err != nil {
- err = mounter.Mount(dev, mntdir, "vfat", []string{"ro"})
- }
- if err != nil {
- return nil, fmt.Errorf("error mounting configdrive %s: %v", dev, err)
- }
- defer mounter.Unmount(mntdir)
- klog.V(4).Infof("Configdrive mounted on %s", mntdir)
- configDrivePath := getConfigDrivePath(metadataVersion)
- f, err := os.Open(
- filepath.Join(mntdir, configDrivePath))
- if err != nil {
- return nil, fmt.Errorf("error reading %s on config drive: %v", configDrivePath, err)
- }
- defer f.Close()
- return parseMetadata(f)
- }
- func getMetadataFromMetadataService(metadataVersion string) (*Metadata, error) {
- // Try to get JSON from metadata server.
- metadataURL := getMetadataURL(metadataVersion)
- klog.V(4).Infof("Attempting to fetch metadata from %s", metadataURL)
- resp, err := http.Get(metadataURL)
- if err != nil {
- return nil, fmt.Errorf("error fetching %s: %v", metadataURL, err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- err = fmt.Errorf("unexpected status code when reading metadata from %s: %s", metadataURL, resp.Status)
- return nil, err
- }
- return parseMetadata(resp.Body)
- }
- // Metadata is fixed for the current host, so cache the value process-wide
- var metadataCache *Metadata
- func getMetadata(order string) (*Metadata, error) {
- if metadataCache == nil {
- var md *Metadata
- var err error
- elements := strings.Split(order, ",")
- for _, id := range elements {
- id = strings.TrimSpace(id)
- switch id {
- case configDriveID:
- md, err = getMetadataFromConfigDrive(defaultMetadataVersion)
- case metadataID:
- md, err = getMetadataFromMetadataService(defaultMetadataVersion)
- default:
- err = fmt.Errorf("%s is not a valid metadata search order option. Supported options are %s and %s", id, configDriveID, metadataID)
- }
- if err == nil {
- break
- }
- }
- if err != nil {
- return nil, err
- }
- metadataCache = md
- }
- return metadataCache, nil
- }
|