github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/vm-control/createVm.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "math/rand" 8 "net" 9 "os" 10 "path/filepath" 11 "time" 12 13 hyperclient "github.com/Cloud-Foundations/Dominator/hypervisor/client" 14 "github.com/Cloud-Foundations/Dominator/lib/format" 15 "github.com/Cloud-Foundations/Dominator/lib/images/virtualbox" 16 "github.com/Cloud-Foundations/Dominator/lib/log" 17 "github.com/Cloud-Foundations/Dominator/lib/srpc" 18 "github.com/Cloud-Foundations/Dominator/lib/tags" 19 fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager" 20 hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor" 21 ) 22 23 type wrappedReadCloser struct { 24 real io.Closer 25 wrap io.Reader 26 } 27 28 func init() { 29 rand.Seed(time.Now().Unix() + time.Now().UnixNano()) 30 } 31 32 func createVmSubcommand(args []string, logger log.DebugLogger) error { 33 if err := createVm(logger); err != nil { 34 return fmt.Errorf("Error creating VM: %s", err) 35 } 36 return nil 37 } 38 39 func callCreateVm(client *srpc.Client, request hyper_proto.CreateVmRequest, 40 reply *hyper_proto.CreateVmResponse, imageReader, userDataReader io.Reader, 41 imageSize, userDataSize int64, logger log.DebugLogger) error { 42 conn, err := client.Call("Hypervisor.CreateVm") 43 if err != nil { 44 return fmt.Errorf("error calling Hypervisor.CreateVm: %s", err) 45 } 46 defer conn.Close() 47 if err := conn.Encode(request); err != nil { 48 return fmt.Errorf("error encoding request: %s", err) 49 } 50 // Stream any required data. 51 if imageReader != nil { 52 logger.Debugln(0, "uploading image") 53 startTime := time.Now() 54 if nCopied, err := io.CopyN(conn, imageReader, imageSize); err != nil { 55 return fmt.Errorf("error uploading image: %s got %d of %d bytes", 56 err, nCopied, imageSize) 57 } else { 58 duration := time.Since(startTime) 59 speed := uint64(float64(nCopied) / duration.Seconds()) 60 logger.Debugf(0, "uploaded image in %s (%s/s)\n", 61 format.Duration(duration), format.FormatBytes(speed)) 62 } 63 } 64 if userDataReader != nil { 65 logger.Debugln(0, "uploading user data") 66 nCopied, err := io.CopyN(conn, userDataReader, userDataSize) 67 if err != nil { 68 return fmt.Errorf( 69 "error uploading user data: %s got %d of %d bytes", 70 err, nCopied, userDataSize) 71 } 72 } 73 response, err := processCreateVmResponses(conn, logger) 74 *reply = response 75 return err 76 } 77 78 func createVm(logger log.DebugLogger) error { 79 if *vmHostname == "" { 80 if name := vmTags["Name"]; name == "" { 81 return errors.New("no hostname specified") 82 } else { 83 *vmHostname = name 84 } 85 } else { 86 if name := vmTags["Name"]; name == "" { 87 if vmTags == nil { 88 vmTags = make(tags.Tags) 89 } 90 vmTags["Name"] = *vmHostname 91 } 92 } 93 if hypervisor, err := getHypervisorAddress(); err != nil { 94 return err 95 } else { 96 logger.Debugf(0, "creating VM on %s\n", hypervisor) 97 return createVmOnHypervisor(hypervisor, logger) 98 } 99 } 100 101 func createVmInfoFromFlags() hyper_proto.VmInfo { 102 return hyper_proto.VmInfo{ 103 ConsoleType: consoleType, 104 DestroyProtection: *destroyProtection, 105 DisableVirtIO: *disableVirtIO, 106 Hostname: *vmHostname, 107 MemoryInMiB: uint64(memory >> 20), 108 MilliCPUs: *milliCPUs, 109 OwnerGroups: ownerGroups, 110 OwnerUsers: ownerUsers, 111 Tags: vmTags, 112 SecondarySubnetIDs: secondarySubnetIDs, 113 SubnetId: *subnetId, 114 } 115 } 116 117 func createVmOnHypervisor(hypervisor string, logger log.DebugLogger) error { 118 request := hyper_proto.CreateVmRequest{ 119 DhcpTimeout: *dhcpTimeout, 120 EnableNetboot: *enableNetboot, 121 MinimumFreeBytes: uint64(minFreeBytes), 122 RoundupPower: *roundupPower, 123 VmInfo: createVmInfoFromFlags(), 124 } 125 if request.VmInfo.MemoryInMiB < 1 { 126 request.VmInfo.MemoryInMiB = 1024 127 } 128 if request.VmInfo.MilliCPUs < 1 { 129 request.VmInfo.MilliCPUs = 250 130 } 131 if len(requestIPs) > 0 && requestIPs[0] != "" { 132 ipAddr := net.ParseIP(requestIPs[0]) 133 if ipAddr == nil { 134 return fmt.Errorf("invalid IP address: %s", requestIPs[0]) 135 } 136 request.Address.IpAddress = ipAddr 137 } 138 if len(requestIPs) > 1 && len(secondarySubnetIDs) > 0 { 139 request.SecondaryAddresses = make([]hyper_proto.Address, 140 len(secondarySubnetIDs)) 141 for index, addr := range requestIPs[1:] { 142 if addr == "" { 143 continue 144 } 145 ipAddr := net.ParseIP(addr) 146 if ipAddr == nil { 147 return fmt.Errorf("invalid IP address: %s", requestIPs[0]) 148 } 149 request.SecondaryAddresses[index] = hyper_proto.Address{ 150 IpAddress: ipAddr} 151 } 152 } 153 for _, size := range secondaryVolumeSizes { 154 request.SecondaryVolumes = append(request.SecondaryVolumes, 155 hyper_proto.Volume{Size: uint64(size)}) 156 } 157 var imageReader, userDataReader io.Reader 158 if *imageName != "" { 159 request.ImageName = *imageName 160 request.ImageTimeout = *imageTimeout 161 request.SkipBootloader = *skipBootloader 162 } else if *imageURL != "" { 163 request.ImageURL = *imageURL 164 } else if *imageFile != "" { 165 file, size, err := getReader(*imageFile) 166 if err != nil { 167 return err 168 } else { 169 defer file.Close() 170 request.ImageDataSize = uint64(size) 171 imageReader = file 172 } 173 } else { 174 return errors.New("no image specified") 175 } 176 if *userDataFile != "" { 177 file, size, err := getReader(*userDataFile) 178 if err != nil { 179 return err 180 } else { 181 defer file.Close() 182 request.UserDataSize = uint64(size) 183 userDataReader = file 184 } 185 } 186 client, err := dialHypervisor(hypervisor) 187 if err != nil { 188 return err 189 } 190 defer client.Close() 191 var reply hyper_proto.CreateVmResponse 192 err = callCreateVm(client, request, &reply, imageReader, userDataReader, 193 int64(request.ImageDataSize), int64(request.UserDataSize), logger) 194 if err != nil { 195 return err 196 } 197 if err := hyperclient.AcknowledgeVm(client, reply.IpAddress); err != nil { 198 return fmt.Errorf("error acknowledging VM: %s", err) 199 } 200 fmt.Println(reply.IpAddress) 201 if reply.DhcpTimedOut { 202 return errors.New("DHCP ACK timed out") 203 } 204 if *dhcpTimeout > 0 { 205 logger.Debugln(0, "Received DHCP ACK") 206 } 207 return maybeWatchVm(client, hypervisor, reply.IpAddress, logger) 208 } 209 210 func getHypervisorAddress() (string, error) { 211 if *hypervisorHostname != "" { 212 return fmt.Sprintf("%s:%d", *hypervisorHostname, *hypervisorPortNum), 213 nil 214 } 215 client, err := dialFleetManager(fmt.Sprintf("%s:%d", 216 *fleetManagerHostname, *fleetManagerPortNum)) 217 if err != nil { 218 return "", err 219 } 220 defer client.Close() 221 if *adjacentVM != "" { 222 if adjacentVmIpAddr, err := lookupIP(*adjacentVM); err != nil { 223 return "", err 224 } else { 225 return findHypervisorClient(client, adjacentVmIpAddr) 226 } 227 } 228 request := fm_proto.ListHypervisorsInLocationRequest{ 229 Location: *location, 230 SubnetId: *subnetId, 231 } 232 var reply fm_proto.ListHypervisorsInLocationResponse 233 err = client.RequestReply("FleetManager.ListHypervisorsInLocation", 234 request, &reply) 235 if err != nil { 236 return "", err 237 } 238 if reply.Error != "" { 239 return "", errors.New(reply.Error) 240 } 241 if numHyper := len(reply.HypervisorAddresses); numHyper < 1 { 242 return "", errors.New("no active Hypervisors in location") 243 } else if numHyper < 2 { 244 return reply.HypervisorAddresses[0], nil 245 } else { 246 return reply.HypervisorAddresses[rand.Intn(numHyper-1)], nil 247 } 248 } 249 250 func getReader(filename string) (io.ReadCloser, int64, error) { 251 if file, err := os.Open(filename); err != nil { 252 return nil, -1, err 253 } else if filepath.Ext(filename) == ".vdi" { 254 vdi, err := virtualbox.NewReader(file) 255 if err != nil { 256 file.Close() 257 return nil, -1, err 258 } 259 return &wrappedReadCloser{real: file, wrap: vdi}, int64(vdi.Size), nil 260 } else { 261 fi, err := file.Stat() 262 if err != nil { 263 file.Close() 264 return nil, -1, err 265 } 266 return file, fi.Size(), nil 267 } 268 } 269 270 func processCreateVmResponses(conn *srpc.Conn, 271 logger log.DebugLogger) (hyper_proto.CreateVmResponse, error) { 272 var zeroResponse hyper_proto.CreateVmResponse 273 if err := conn.Flush(); err != nil { 274 return zeroResponse, fmt.Errorf("error flushing: %s", err) 275 } 276 for { 277 var response hyper_proto.CreateVmResponse 278 if err := conn.Decode(&response); err != nil { 279 return zeroResponse, fmt.Errorf("error decoding: %s", err) 280 } 281 if response.Error != "" { 282 return zeroResponse, errors.New(response.Error) 283 } 284 if response.ProgressMessage != "" { 285 logger.Debugln(0, response.ProgressMessage) 286 } 287 if response.Final { 288 return response, nil 289 } 290 } 291 } 292 293 func (r *wrappedReadCloser) Close() error { 294 return r.real.Close() 295 } 296 297 func (r *wrappedReadCloser) Read(p []byte) (n int, err error) { 298 return r.wrap.Read(p) 299 }