github.com/coreos/mantle@v0.13.0/platform/machine/qemu/cluster.go (about)

     1  // Copyright 2016 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package qemu
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  	"sync"
    23  
    24  	"github.com/pborman/uuid"
    25  
    26  	"github.com/coreos/mantle/platform"
    27  	"github.com/coreos/mantle/platform/conf"
    28  	"github.com/coreos/mantle/platform/local"
    29  	"github.com/coreos/mantle/system/ns"
    30  )
    31  
    32  // Cluster is a local cluster of QEMU-based virtual machines.
    33  //
    34  // XXX: must be exported so that certain QEMU tests can access struct members
    35  // through type assertions.
    36  type Cluster struct {
    37  	flight *flight
    38  
    39  	mu sync.Mutex
    40  	*local.LocalCluster
    41  }
    42  
    43  func (qc *Cluster) NewMachine(userdata *conf.UserData) (platform.Machine, error) {
    44  	return qc.NewMachineWithOptions(userdata, platform.MachineOptions{})
    45  }
    46  
    47  func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platform.MachineOptions) (platform.Machine, error) {
    48  	id := uuid.New()
    49  
    50  	dir := filepath.Join(qc.RuntimeConf().OutputDir, id)
    51  	if err := os.Mkdir(dir, 0777); err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	// hacky solution for cloud config ip substitution
    56  	// NOTE: escaping is not supported
    57  	qc.mu.Lock()
    58  	netif := qc.flight.Dnsmasq.GetInterface("br0")
    59  	ip := strings.Split(netif.DHCPv4[0].String(), "/")[0]
    60  
    61  	conf, err := qc.RenderUserData(userdata, map[string]string{
    62  		"$public_ipv4":  ip,
    63  		"$private_ipv4": ip,
    64  	})
    65  	if err != nil {
    66  		qc.mu.Unlock()
    67  		return nil, err
    68  	}
    69  	qc.mu.Unlock()
    70  
    71  	var confPath string
    72  	if conf.IsIgnition() {
    73  		confPath = filepath.Join(dir, "ignition.json")
    74  		if err := conf.WriteFile(confPath); err != nil {
    75  			return nil, err
    76  		}
    77  	} else {
    78  		confPath, err = local.MakeConfigDrive(conf, dir)
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  	}
    83  
    84  	journal, err := platform.NewJournal(dir)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	qm := &machine{
    90  		qc:          qc,
    91  		id:          id,
    92  		netif:       netif,
    93  		journal:     journal,
    94  		consolePath: filepath.Join(dir, "console.txt"),
    95  	}
    96  
    97  	qmCmd, extraFiles, err := platform.CreateQEMUCommand(qc.flight.opts.Board, qm.id, qc.flight.opts.BIOSImage, qm.consolePath, confPath, qc.flight.diskImagePath, conf.IsIgnition(), options)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	for _, file := range extraFiles {
   103  		defer file.Close()
   104  	}
   105  	qmMac := qm.netif.HardwareAddr.String()
   106  
   107  	qc.mu.Lock()
   108  
   109  	tap, err := qc.NewTap("br0")
   110  	if err != nil {
   111  		qc.mu.Unlock()
   112  		return nil, err
   113  	}
   114  	defer tap.Close()
   115  	fdnum := 3 + len(extraFiles)
   116  	qmCmd = append(qmCmd, "-netdev", fmt.Sprintf("tap,id=tap,fd=%d", fdnum),
   117  		"-device", platform.Virtio(qc.flight.opts.Board, "net", "netdev=tap,mac="+qmMac))
   118  	fdnum += 1
   119  	extraFiles = append(extraFiles, tap.File)
   120  
   121  	plog.Debugf("NewMachine: %q", qmCmd)
   122  
   123  	qm.qemu = qm.qc.NewCommand(qmCmd[0], qmCmd[1:]...)
   124  
   125  	qc.mu.Unlock()
   126  
   127  	cmd := qm.qemu.(*ns.Cmd)
   128  	cmd.Stderr = os.Stderr
   129  
   130  	cmd.ExtraFiles = append(cmd.ExtraFiles, extraFiles...)
   131  
   132  	if err = qm.qemu.Start(); err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	if err := platform.StartMachine(qm, qm.journal); err != nil {
   137  		qm.Destroy()
   138  		return nil, err
   139  	}
   140  
   141  	qc.AddMach(qm)
   142  
   143  	return qm, nil
   144  }
   145  
   146  func (qc *Cluster) Destroy() {
   147  	qc.LocalCluster.Destroy()
   148  	qc.flight.DelCluster(qc)
   149  }