github.com/Cloud-Foundations/Dominator@v0.3.4/cmd/vm-control/createVm.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "math/rand" 10 "net" 11 "os" 12 "path/filepath" 13 "strings" 14 "time" 15 16 hyperclient "github.com/Cloud-Foundations/Dominator/hypervisor/client" 17 "github.com/Cloud-Foundations/Dominator/lib/filesystem/util" 18 "github.com/Cloud-Foundations/Dominator/lib/format" 19 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 20 "github.com/Cloud-Foundations/Dominator/lib/images/virtualbox" 21 "github.com/Cloud-Foundations/Dominator/lib/json" 22 "github.com/Cloud-Foundations/Dominator/lib/log" 23 "github.com/Cloud-Foundations/Dominator/lib/srpc" 24 "github.com/Cloud-Foundations/Dominator/lib/tags" 25 hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor" 26 ) 27 28 var sysfsDirectory = "/sys/block" 29 30 type volumeInitParams struct { 31 hyper_proto.VolumeInitialisationInfo 32 MountPoint string 33 } 34 35 type wrappedReadCloser struct { 36 real io.Closer 37 wrap io.Reader 38 } 39 40 func init() { 41 rand.Seed(time.Now().Unix() + time.Now().UnixNano()) 42 } 43 44 func approximateVolumesForCreateRequest() hyper_proto.VmInfo { 45 vmInfo := hyper_proto.VmInfo{} 46 vmInfo.Volumes = make([]hyper_proto.Volume, 1, len(secondaryVolumeSizes)+1) 47 vmInfo.Volumes[0] = hyper_proto.Volume{Size: uint64(minFreeBytes) + 2<<30} 48 for _, size := range secondaryVolumeSizes { 49 vmInfo.Volumes = append(vmInfo.Volumes, hyper_proto.Volume{ 50 Size: uint64(size), 51 }) 52 } 53 return vmInfo 54 } 55 56 func createVmSubcommand(args []string, logger log.DebugLogger) error { 57 if err := createVm(logger); err != nil { 58 return fmt.Errorf("error creating VM: %s", err) 59 } 60 return nil 61 } 62 63 func callCreateVm(client *srpc.Client, request hyper_proto.CreateVmRequest, 64 reply *hyper_proto.CreateVmResponse, imageReader, userDataReader io.Reader, 65 imageSize, userDataSize int64, logger log.DebugLogger) error { 66 conn, err := client.Call("Hypervisor.CreateVm") 67 if err != nil { 68 return fmt.Errorf("error calling Hypervisor.CreateVm: %s", err) 69 } 70 defer conn.Close() 71 if err := conn.Encode(request); err != nil { 72 return fmt.Errorf("error encoding request: %s", err) 73 } 74 // Stream any required data. 75 if imageReader != nil { 76 logger.Debugln(0, "uploading image") 77 startTime := time.Now() 78 if nCopied, err := io.CopyN(conn, imageReader, imageSize); err != nil { 79 return fmt.Errorf("error uploading image: %s got %d of %d bytes", 80 err, nCopied, imageSize) 81 } else { 82 duration := time.Since(startTime) 83 speed := uint64(float64(nCopied) / duration.Seconds()) 84 logger.Debugf(0, "uploaded image in %s (%s/s)\n", 85 format.Duration(duration), format.FormatBytes(speed)) 86 } 87 } 88 if userDataReader != nil { 89 logger.Debugln(0, "uploading user data") 90 nCopied, err := io.CopyN(conn, userDataReader, userDataSize) 91 if err != nil { 92 return fmt.Errorf( 93 "error uploading user data: %s got %d of %d bytes", 94 err, nCopied, userDataSize) 95 } 96 } 97 response, err := processCreateVmResponses(conn, logger) 98 *reply = response 99 return err 100 } 101 102 func createVm(logger log.DebugLogger) error { 103 if *vmHostname == "" { 104 if name := vmTags["Name"]; name == "" { 105 return errors.New("no hostname specified") 106 } else { 107 *vmHostname = name 108 } 109 } else { 110 if name := vmTags["Name"]; name == "" { 111 if vmTags == nil { 112 vmTags = make(tags.Tags) 113 } 114 vmTags["Name"] = *vmHostname 115 } 116 } 117 request := hyper_proto.CreateVmRequest{ 118 DhcpTimeout: *dhcpTimeout, 119 DoNotStart: *doNotStart, 120 EnableNetboot: *enableNetboot, 121 MinimumFreeBytes: uint64(minFreeBytes), 122 RoundupPower: *roundupPower, 123 SkipMemoryCheck: *skipMemoryCheck, 124 VmInfo: createVmInfoFromFlags(), 125 } 126 if request.VmInfo.MemoryInMiB < 1 { 127 request.VmInfo.MemoryInMiB = 1024 128 } 129 if request.VmInfo.MilliCPUs < 1 { 130 request.VmInfo.MilliCPUs = 250 131 } 132 minimumCPUs := request.VmInfo.MilliCPUs / 1000 133 if request.VmInfo.VirtualCPUs > 0 && 134 request.VmInfo.VirtualCPUs < minimumCPUs { 135 return fmt.Errorf("vCPUs must be at least %d", minimumCPUs) 136 } 137 if len(requestIPs) > 0 && requestIPs[0] != "" { 138 ipAddr := net.ParseIP(requestIPs[0]) 139 if ipAddr == nil { 140 return fmt.Errorf("invalid IP address: %s", requestIPs[0]) 141 } 142 request.Address.IpAddress = ipAddr 143 } 144 if len(requestIPs) > 1 && len(secondarySubnetIDs) > 0 { 145 request.SecondaryAddresses = make([]hyper_proto.Address, 146 len(secondarySubnetIDs)) 147 for index, addr := range requestIPs[1:] { 148 if addr == "" { 149 continue 150 } 151 ipAddr := net.ParseIP(addr) 152 if ipAddr == nil { 153 return fmt.Errorf("invalid IP address: %s", requestIPs[0]) 154 } 155 request.SecondaryAddresses[index] = hyper_proto.Address{ 156 IpAddress: ipAddr} 157 } 158 } 159 tmpVmInfo := approximateVolumesForCreateRequest() 160 if hypervisor, err := getHypervisorAddress(tmpVmInfo); err != nil { 161 return err 162 } else { 163 logger.Debugf(0, "creating VM on %s\n", hypervisor) 164 return createVmOnHypervisor(hypervisor, request, logger) 165 } 166 } 167 168 func createVmInfoFromFlags() hyper_proto.VmInfo { 169 var volumes []hyper_proto.Volume 170 if len(volumeTypes) > 0 { 171 // If provided, set for root volume. Secondaries are done later. 172 volumes = append(volumes, hyper_proto.Volume{Type: volumeTypes[0]}) 173 } 174 return hyper_proto.VmInfo{ 175 ConsoleType: consoleType, 176 DestroyOnPowerdown: *destroyOnPowerdown, 177 DestroyProtection: *destroyProtection, 178 DisableVirtIO: *disableVirtIO, 179 ExtraKernelOptions: *extraKernelOptions, 180 Hostname: *vmHostname, 181 MemoryInMiB: uint64(memory >> 20), 182 MilliCPUs: *milliCPUs, 183 OwnerGroups: ownerGroups, 184 OwnerUsers: ownerUsers, 185 Tags: vmTags, 186 SecondarySubnetIDs: secondarySubnetIDs, 187 SubnetId: *subnetId, 188 VirtualCPUs: *virtualCPUs, 189 Volumes: volumes, 190 } 191 } 192 193 func createVmOnHypervisor(hypervisor string, 194 request hyper_proto.CreateVmRequest, logger log.DebugLogger) error { 195 secondaryFstab := &bytes.Buffer{} 196 var vinitParams []volumeInitParams 197 if *secondaryVolumesInitParams == "" { 198 vinitParams = makeVolumeInitParams(uint(len(secondaryVolumeSizes))) 199 } else { 200 err := json.ReadFromFile(*secondaryVolumesInitParams, &vinitParams) 201 if err != nil { 202 return err 203 } 204 } 205 for index, size := range secondaryVolumeSizes { 206 volume := hyper_proto.Volume{Size: uint64(size)} 207 if index+1 < len(volumeTypes) { 208 volume.Type = volumeTypes[index+1] 209 } 210 request.SecondaryVolumes = append(request.SecondaryVolumes, volume) 211 if *initialiseSecondaryVolumes && 212 index < len(vinitParams) { 213 vinit := vinitParams[index] 214 if vinit.Label == "" { 215 return fmt.Errorf("VolumeInit[%d] missing Label", index) 216 } 217 if vinit.MountPoint == "" { 218 return fmt.Errorf("VolumeInit[%d] missing MountPoint", index) 219 } 220 request.OverlayDirectories = append(request.OverlayDirectories, 221 vinit.MountPoint) 222 request.SecondaryVolumesInit = append(request.SecondaryVolumesInit, 223 vinit.VolumeInitialisationInfo) 224 util.WriteFstabEntry(secondaryFstab, "LABEL="+vinit.Label, 225 vinit.MountPoint, "ext4", "discard", 0, 2) 226 } 227 } 228 if *identityCertFile != "" && *identityKeyFile != "" { 229 identityCert, err := ioutil.ReadFile(*identityCertFile) 230 if err != nil { 231 return err 232 } 233 identityKey, err := ioutil.ReadFile(*identityKeyFile) 234 if err != nil { 235 return err 236 } 237 request.IdentityCertificate = identityCert 238 request.IdentityKey = identityKey 239 } 240 var imageReader, userDataReader io.Reader 241 if *imageName != "" { 242 request.ImageName = *imageName 243 request.ImageTimeout = *imageTimeout 244 request.SkipBootloader = *skipBootloader 245 if overlayFiles, err := loadOverlayFiles(); err != nil { 246 return err 247 } else { 248 request.OverlayFiles = overlayFiles 249 } 250 secondaryFstab.Write(request.OverlayFiles["/etc/fstab"]) 251 if secondaryFstab.Len() > 0 { 252 if request.OverlayFiles == nil { 253 request.OverlayFiles = make(map[string][]byte) 254 } 255 request.OverlayFiles["/etc/fstab"] = secondaryFstab.Bytes() 256 } 257 } else if *imageURL != "" { 258 request.ImageURL = *imageURL 259 } else if *imageFile != "" { 260 file, size, err := getReader(*imageFile) 261 if err != nil { 262 return err 263 } else { 264 defer file.Close() 265 request.ImageDataSize = uint64(size) 266 imageReader = file 267 } 268 } else { 269 return errors.New("no image specified") 270 } 271 if *userDataFile != "" { 272 file, size, err := getReader(*userDataFile) 273 if err != nil { 274 return err 275 } else { 276 defer file.Close() 277 request.UserDataSize = uint64(size) 278 userDataReader = file 279 } 280 } 281 client, err := dialHypervisor(hypervisor) 282 if err != nil { 283 return err 284 } 285 defer client.Close() 286 var reply hyper_proto.CreateVmResponse 287 err = callCreateVm(client, request, &reply, imageReader, userDataReader, 288 int64(request.ImageDataSize), int64(request.UserDataSize), logger) 289 if err != nil { 290 return err 291 } 292 if err := hyperclient.AcknowledgeVm(client, reply.IpAddress); err != nil { 293 return fmt.Errorf("error acknowledging VM: %s", err) 294 } 295 fmt.Println(reply.IpAddress) 296 if *doNotStart { 297 return nil 298 } 299 if reply.DhcpTimedOut { 300 return errors.New("DHCP ACK timed out") 301 } 302 if *dhcpTimeout > 0 { 303 logger.Debugln(0, "Received DHCP ACK") 304 } 305 return maybeWatchVm(client, hypervisor, reply.IpAddress, logger) 306 } 307 308 func getReader(filename string) (io.ReadCloser, int64, error) { 309 if file, err := os.Open(filename); err != nil { 310 return nil, -1, err 311 } else if filepath.Ext(filename) == ".vdi" { 312 vdi, err := virtualbox.NewReader(file) 313 if err != nil { 314 file.Close() 315 return nil, -1, err 316 } 317 return &wrappedReadCloser{real: file, wrap: vdi}, int64(vdi.Size), nil 318 } else { 319 fi, err := file.Stat() 320 if err != nil { 321 file.Close() 322 return nil, -1, err 323 } 324 switch fi.Mode() & os.ModeType { 325 case 0: 326 return file, fi.Size(), nil 327 case os.ModeDevice: 328 if size, err := readBlockDeviceSize(filename); err != nil { 329 file.Close() 330 return nil, -1, err 331 } else { 332 return file, size, nil 333 } 334 default: 335 file.Close() 336 return nil, -1, errors.New("unsupported file type") 337 } 338 } 339 } 340 341 func loadOverlayFiles() (map[string][]byte, error) { 342 if *overlayDirectory == "" { 343 return nil, nil 344 } 345 return fsutil.ReadFileTree(*overlayDirectory, *overlayPrefix) 346 } 347 348 func makeVolumeInitParams(numVolumes uint) []volumeInitParams { 349 vinitParams := make([]volumeInitParams, numVolumes) 350 for index := 0; index < int(numVolumes); index++ { 351 label := fmt.Sprintf("/data/%d", index) 352 vinitParams[index].Label = label 353 vinitParams[index].MountPoint = label 354 } 355 return vinitParams 356 } 357 358 func processCreateVmResponses(conn *srpc.Conn, 359 logger log.DebugLogger) (hyper_proto.CreateVmResponse, error) { 360 var zeroResponse hyper_proto.CreateVmResponse 361 if err := conn.Flush(); err != nil { 362 return zeroResponse, fmt.Errorf("error flushing: %s", err) 363 } 364 for { 365 var response hyper_proto.CreateVmResponse 366 if err := conn.Decode(&response); err != nil { 367 return zeroResponse, fmt.Errorf("error decoding: %s", err) 368 } 369 if response.Error != "" { 370 return zeroResponse, errors.New(response.Error) 371 } 372 if response.ProgressMessage != "" { 373 logger.Debugln(0, response.ProgressMessage) 374 } 375 if response.Final { 376 return response, nil 377 } 378 } 379 } 380 381 func readBlockDeviceSize(filename string) (int64, error) { 382 if strings.HasPrefix(filename, "/dev/") { 383 filename = filename[5:] 384 } 385 deviceBlocks, err := readSysfsInt64( 386 filepath.Join(sysfsDirectory, filename, "size")) 387 if err != nil { 388 return 0, err 389 } 390 return deviceBlocks * 512, nil 391 } 392 393 func readSysfsInt64(filename string) (int64, error) { 394 file, err := os.Open(filename) 395 if err != nil { 396 return 0, err 397 } 398 defer file.Close() 399 var value int64 400 nScanned, err := fmt.Fscanf(file, "%d", &value) 401 if err != nil { 402 return 0, err 403 } 404 if nScanned < 1 { 405 return 0, fmt.Errorf("only read %d values from: %s", nScanned, filename) 406 } 407 return value, nil 408 } 409 410 func (r *wrappedReadCloser) Close() error { 411 return r.real.Close() 412 } 413 414 func (r *wrappedReadCloser) Read(p []byte) (n int, err error) { 415 return r.wrap.Read(p) 416 }