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 }