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  }