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  }