podman

Форк
0
677 строк · 17.7 Кб
1
package config
2

3
import (
4
	"fmt"
5
	"net"
6
	"os"
7
	"strconv"
8
	"strings"
9
)
10

11
// The VirtioDevice interface is an interface which is implemented by all virtio devices.
12
type VirtioDevice VMComponent
13

14
const (
15
	// Possible values for VirtioInput.InputType
16
	VirtioInputPointingDevice = "pointing"
17
	VirtioInputKeyboardDevice = "keyboard"
18

19
	// Options for VirtioGPUResolution
20
	VirtioGPUResolutionWidth  = "width"
21
	VirtioGPUResolutionHeight = "height"
22

23
	// Default VirtioGPU Resolution
24
	defaultVirtioGPUResolutionWidth  = 800
25
	defaultVirtioGPUResolutionHeight = 600
26
)
27

28
// VirtioInput configures an input device, such as a keyboard or pointing device
29
// (mouse) that the virtual machine can use
30
type VirtioInput struct {
31
	InputType string `json:"inputType"` // currently supports "pointing" and "keyboard" input types
32
}
33

34
type VirtioGPUResolution struct {
35
	Width  int `json:"width"`
36
	Height int `json:"height"`
37
}
38

39
// VirtioGPU configures a GPU device, such as the host computer's display
40
type VirtioGPU struct {
41
	UsesGUI bool `json:"usesGUI"`
42
	VirtioGPUResolution
43
}
44

45
// VirtioVsock configures of a virtio-vsock device allowing 2-way communication
46
// between the host and the virtual machine type
47
type VirtioVsock struct {
48
	// Port is the virtio-vsock port used for this device, see `man vsock` for more
49
	// details.
50
	Port uint
51
	// SocketURL is the path to a unix socket on the host to use for the virtio-vsock communication with the guest.
52
	SocketURL string
53
	// If true, vsock connections will have to be done from guest to host. If false, vsock connections will only be possible
54
	// from host to guest
55
	Listen bool
56
}
57

58
// VirtioBlk configures a disk device.
59
type VirtioBlk struct {
60
	StorageConfig
61
	DeviceIdentifier string
62
}
63

64
type DirectorySharingConfig struct {
65
	MountTag string
66
}
67

68
// VirtioFs configures directory sharing between the guest and the host.
69
type VirtioFs struct {
70
	DirectorySharingConfig
71
	SharedDir string
72
}
73

74
// RosettaShare configures rosetta support in the guest to run Intel binaries on Apple CPUs
75
type RosettaShare struct {
76
	DirectorySharingConfig
77
	InstallRosetta bool
78
}
79

80
// virtioRng configures a random number generator (RNG) device.
81
type VirtioRng struct {
82
}
83

84
// TODO: Add BridgedNetwork support
85
// https://github.com/Code-Hex/vz/blob/d70a0533bf8ed0fa9ab22fa4d4ca554b7c3f3ce5/network.go#L81-L82
86

87
// VirtioNet configures the virtual machine networking.
88
type VirtioNet struct {
89
	Nat        bool
90
	MacAddress net.HardwareAddr
91
	// file parameter is holding a connected datagram socket.
92
	// see https://github.com/Code-Hex/vz/blob/7f648b6fb9205d6f11792263d79876e3042c33ec/network.go#L113-L155
93
	Socket *os.File
94

95
	UnixSocketPath string
96
}
97

98
// VirtioSerial configures the virtual machine serial ports.
99
type VirtioSerial struct {
100
	LogFile   string
101
	UsesStdio bool
102
}
103

104
// TODO: Add VirtioBalloon
105
// https://github.com/Code-Hex/vz/blob/master/memory_balloon.go
106

107
type option struct {
108
	key   string
109
	value string
110
}
111

112
func strToOption(str string) option {
113
	splitStr := strings.SplitN(str, "=", 2)
114

115
	opt := option{
116
		key: splitStr[0],
117
	}
118
	if len(splitStr) > 1 {
119
		opt.value = splitStr[1]
120
	}
121

122
	return opt
123
}
124

125
func strvToOptions(opts []string) []option {
126
	parsedOpts := []option{}
127
	for _, opt := range opts {
128
		if len(opt) == 0 {
129
			continue
130
		}
131
		parsedOpts = append(parsedOpts, strToOption(opt))
132
	}
133

134
	return parsedOpts
135
}
136

137
func deviceFromCmdLine(deviceOpts string) (VirtioDevice, error) {
138
	opts := strings.Split(deviceOpts, ",")
139
	if len(opts) == 0 {
140
		return nil, fmt.Errorf("empty option list in command line argument")
141
	}
142
	var dev VirtioDevice
143
	switch opts[0] {
144
	case "rosetta":
145
		dev = &RosettaShare{}
146
	case "virtio-blk":
147
		dev = virtioBlkNewEmpty()
148
	case "virtio-fs":
149
		dev = &VirtioFs{}
150
	case "virtio-net":
151
		dev = &VirtioNet{}
152
	case "virtio-rng":
153
		dev = &VirtioRng{}
154
	case "virtio-serial":
155
		dev = &VirtioSerial{}
156
	case "virtio-vsock":
157
		dev = &VirtioVsock{}
158
	case "usb-mass-storage":
159
		dev = usbMassStorageNewEmpty()
160
	case "virtio-input":
161
		dev = &VirtioInput{}
162
	case "virtio-gpu":
163
		dev = &VirtioGPU{}
164
	default:
165
		return nil, fmt.Errorf("unknown device type: %s", opts[0])
166
	}
167

168
	parsedOpts := strvToOptions(opts[1:])
169
	if err := dev.FromOptions(parsedOpts); err != nil {
170
		return nil, err
171
	}
172

173
	return dev, nil
174
}
175

176
// VirtioSerialNew creates a new serial device for the virtual machine. The
177
// output the virtual machine sent to the serial port will be written to the
178
// file at logFilePath.
179
func VirtioSerialNew(logFilePath string) (VirtioDevice, error) {
180
	return &VirtioSerial{
181
		LogFile: logFilePath,
182
	}, nil
183
}
184

185
func VirtioSerialNewStdio() (VirtioDevice, error) {
186
	return &VirtioSerial{
187
		UsesStdio: true,
188
	}, nil
189
}
190

191
func (dev *VirtioSerial) validate() error {
192
	if dev.LogFile != "" && dev.UsesStdio {
193
		return fmt.Errorf("'logFilePath' and 'stdio' cannot be set at the same time")
194
	}
195
	if dev.LogFile == "" && !dev.UsesStdio {
196
		return fmt.Errorf("one of 'logFilePath' or 'stdio' must be set")
197
	}
198

199
	return nil
200
}
201

202
func (dev *VirtioSerial) ToCmdLine() ([]string, error) {
203
	if err := dev.validate(); err != nil {
204
		return nil, err
205
	}
206
	if dev.UsesStdio {
207
		return []string{"--device", "virtio-serial,stdio"}, nil
208
	}
209

210
	return []string{"--device", fmt.Sprintf("virtio-serial,logFilePath=%s", dev.LogFile)}, nil
211
}
212

213
func (dev *VirtioSerial) FromOptions(options []option) error {
214
	for _, option := range options {
215
		switch option.key {
216
		case "logFilePath":
217
			dev.LogFile = option.value
218
		case "stdio":
219
			if option.value != "" {
220
				return fmt.Errorf("unexpected value for virtio-serial 'stdio' option: %s", option.value)
221
			}
222
			dev.UsesStdio = true
223
		default:
224
			return fmt.Errorf("unknown option for virtio-serial devices: %s", option.key)
225
		}
226
	}
227

228
	return dev.validate()
229
}
230

231
// VirtioInputNew creates a new input device for the virtual machine.
232
// The inputType parameter is the type of virtio-input device that will be added
233
// to the machine.
234
func VirtioInputNew(inputType string) (VirtioDevice, error) {
235
	dev := &VirtioInput{
236
		InputType: inputType,
237
	}
238
	if err := dev.validate(); err != nil {
239
		return nil, err
240
	}
241

242
	return dev, nil
243
}
244

245
func (dev *VirtioInput) validate() error {
246
	if dev.InputType != VirtioInputPointingDevice && dev.InputType != VirtioInputKeyboardDevice {
247
		return fmt.Errorf("unknown option for virtio-input devices: %s", dev.InputType)
248
	}
249

250
	return nil
251
}
252

253
func (dev *VirtioInput) ToCmdLine() ([]string, error) {
254
	if err := dev.validate(); err != nil {
255
		return nil, err
256
	}
257

258
	return []string{"--device", fmt.Sprintf("virtio-input,%s", dev.InputType)}, nil
259
}
260

261
func (dev *VirtioInput) FromOptions(options []option) error {
262
	for _, option := range options {
263
		switch option.key {
264
		case VirtioInputPointingDevice, VirtioInputKeyboardDevice:
265
			if option.value != "" {
266
				return fmt.Errorf(fmt.Sprintf("unexpected value for virtio-input %s option: %s", option.key, option.value))
267
			}
268
			dev.InputType = option.key
269
		default:
270
			return fmt.Errorf("unknown option for virtio-input devices: %s", option.key)
271
		}
272
	}
273
	return dev.validate()
274
}
275

276
// VirtioGPUNew creates a new gpu device for the virtual machine.
277
// The usesGUI parameter determines whether a graphical application window will
278
// be displayed
279
func VirtioGPUNew() (VirtioDevice, error) {
280
	return &VirtioGPU{
281
		UsesGUI: false,
282
		VirtioGPUResolution: VirtioGPUResolution{
283
			Width:  defaultVirtioGPUResolutionWidth,
284
			Height: defaultVirtioGPUResolutionHeight,
285
		},
286
	}, nil
287
}
288

289
func (dev *VirtioGPU) validate() error {
290
	if dev.Height < 1 || dev.Width < 1 {
291
		return fmt.Errorf("invalid dimensions for virtio-gpu device resolution: %dx%d", dev.Width, dev.Height)
292
	}
293

294
	return nil
295
}
296

297
func (dev *VirtioGPU) ToCmdLine() ([]string, error) {
298
	if err := dev.validate(); err != nil {
299
		return nil, err
300
	}
301

302
	return []string{"--device", fmt.Sprintf("virtio-gpu,width=%d,height=%d", dev.Width, dev.Height)}, nil
303
}
304

305
func (dev *VirtioGPU) FromOptions(options []option) error {
306
	for _, option := range options {
307
		switch option.key {
308
		case VirtioGPUResolutionHeight:
309
			height, err := strconv.Atoi(option.value)
310
			if err != nil || height < 1 {
311
				return fmt.Errorf(fmt.Sprintf("Invalid value for virtio-gpu %s: %s", option.key, option.value))
312
			}
313

314
			dev.Height = height
315
		case VirtioGPUResolutionWidth:
316
			width, err := strconv.Atoi(option.value)
317
			if err != nil || width < 1 {
318
				return fmt.Errorf(fmt.Sprintf("Invalid value for virtio-gpu %s: %s", option.key, option.value))
319
			}
320

321
			dev.Width = width
322
		default:
323
			return fmt.Errorf("unknown option for virtio-gpu devices: %s", option.key)
324
		}
325
	}
326

327
	if dev.Width == 0 && dev.Height == 0 {
328
		dev.Width = defaultVirtioGPUResolutionWidth
329
		dev.Height = defaultVirtioGPUResolutionHeight
330
	}
331

332
	return dev.validate()
333
}
334

335
// VirtioNetNew creates a new network device for the virtual machine. It will
336
// use macAddress as its MAC address.
337
func VirtioNetNew(macAddress string) (*VirtioNet, error) {
338
	var hwAddr net.HardwareAddr
339

340
	if macAddress != "" {
341
		var err error
342
		if hwAddr, err = net.ParseMAC(macAddress); err != nil {
343
			return nil, err
344
		}
345
	}
346
	return &VirtioNet{
347
		Nat:        true,
348
		MacAddress: hwAddr,
349
	}, nil
350
}
351

352
// Set the socket to use for the network communication
353
//
354
// This maps the virtual machine network interface to a connected datagram
355
// socket. This means all network traffic on this interface will go through
356
// file.
357
// file must be a connected datagram (SOCK_DGRAM) socket.
358
func (dev *VirtioNet) SetSocket(file *os.File) {
359
	dev.Socket = file
360
	dev.Nat = false
361
}
362

363
func (dev *VirtioNet) SetUnixSocketPath(path string) {
364
	dev.UnixSocketPath = path
365
	dev.Nat = false
366
}
367

368
func (dev *VirtioNet) validate() error {
369
	if dev.Nat && dev.Socket != nil {
370
		return fmt.Errorf("'nat' and 'fd' cannot be set at the same time")
371
	}
372
	if dev.Nat && dev.UnixSocketPath != "" {
373
		return fmt.Errorf("'nat' and 'unixSocketPath' cannot be set at the same time")
374
	}
375
	if dev.Socket != nil && dev.UnixSocketPath != "" {
376
		return fmt.Errorf("'fd' and 'unixSocketPath' cannot be set at the same time")
377
	}
378
	if !dev.Nat && dev.Socket == nil && dev.UnixSocketPath == "" {
379
		return fmt.Errorf("one of 'nat' or 'fd' or 'unixSocketPath' must be set")
380
	}
381

382
	return nil
383
}
384

385
func (dev *VirtioNet) ToCmdLine() ([]string, error) {
386
	if err := dev.validate(); err != nil {
387
		return nil, err
388
	}
389

390
	builder := strings.Builder{}
391
	builder.WriteString("virtio-net")
392
	switch {
393
	case dev.Nat:
394
		builder.WriteString(",nat")
395
	case dev.UnixSocketPath != "":
396
		fmt.Fprintf(&builder, ",unixSocketPath=%s", dev.UnixSocketPath)
397
	default:
398
		fmt.Fprintf(&builder, ",fd=%d", dev.Socket.Fd())
399
	}
400

401
	if len(dev.MacAddress) != 0 {
402
		builder.WriteString(fmt.Sprintf(",mac=%s", dev.MacAddress))
403
	}
404

405
	return []string{"--device", builder.String()}, nil
406
}
407

408
func (dev *VirtioNet) FromOptions(options []option) error {
409
	for _, option := range options {
410
		switch option.key {
411
		case "nat":
412
			if option.value != "" {
413
				return fmt.Errorf("unexpected value for virtio-net 'nat' option: %s", option.value)
414
			}
415
			dev.Nat = true
416
		case "mac":
417
			macAddress, err := net.ParseMAC(option.value)
418
			if err != nil {
419
				return err
420
			}
421
			dev.MacAddress = macAddress
422
		case "fd":
423
			fd, err := strconv.Atoi(option.value)
424
			if err != nil {
425
				return err
426
			}
427
			dev.Socket = os.NewFile(uintptr(fd), "vfkit virtio-net socket")
428
		case "unixSocketPath":
429
			dev.UnixSocketPath = option.value
430
		default:
431
			return fmt.Errorf("unknown option for virtio-net devices: %s", option.key)
432
		}
433
	}
434

435
	return dev.validate()
436
}
437

438
// VirtioRngNew creates a new random number generator device to feed entropy
439
// into the virtual machine.
440
func VirtioRngNew() (VirtioDevice, error) {
441
	return &VirtioRng{}, nil
442
}
443

444
func (dev *VirtioRng) ToCmdLine() ([]string, error) {
445
	return []string{"--device", "virtio-rng"}, nil
446
}
447

448
func (dev *VirtioRng) FromOptions(options []option) error {
449
	if len(options) != 0 {
450
		return fmt.Errorf("unknown options for virtio-rng devices: %s", options)
451
	}
452
	return nil
453
}
454

455
func virtioBlkNewEmpty() *VirtioBlk {
456
	return &VirtioBlk{
457
		StorageConfig: StorageConfig{
458
			DevName: "virtio-blk",
459
		},
460
		DeviceIdentifier: "",
461
	}
462
}
463

464
// VirtioBlkNew creates a new disk to use in the virtual machine. It will use
465
// the file at imagePath as the disk image. This image must be in raw format.
466
func VirtioBlkNew(imagePath string) (*VirtioBlk, error) {
467
	virtioBlk := virtioBlkNewEmpty()
468
	virtioBlk.ImagePath = imagePath
469

470
	return virtioBlk, nil
471
}
472

473
func (dev *VirtioBlk) SetDeviceIdentifier(devID string) {
474
	dev.DeviceIdentifier = devID
475
}
476

477
func (dev *VirtioBlk) FromOptions(options []option) error {
478
	unhandledOpts := []option{}
479
	for _, option := range options {
480
		switch option.key {
481
		case "deviceId":
482
			dev.DeviceIdentifier = option.value
483
		default:
484
			unhandledOpts = append(unhandledOpts, option)
485
		}
486
	}
487

488
	return dev.StorageConfig.FromOptions(unhandledOpts)
489
}
490

491
func (dev *VirtioBlk) ToCmdLine() ([]string, error) {
492
	cmdLine, err := dev.StorageConfig.ToCmdLine()
493
	if err != nil {
494
		return []string{}, err
495
	}
496
	if len(cmdLine) != 2 {
497
		return []string{}, fmt.Errorf("unexpected storage config commandline")
498
	}
499
	if dev.DeviceIdentifier != "" {
500
		cmdLine[1] = fmt.Sprintf("%s,deviceId=%s", cmdLine[1], dev.DeviceIdentifier)
501
	}
502
	return cmdLine, nil
503
}
504

505
// VirtioVsockNew creates a new virtio-vsock device for 2-way communication
506
// between the host and the virtual machine. The communication will happen on
507
// vsock port, and on the host it will use the unix socket at socketURL.
508
// When listen is true, the host will be listening for connections over vsock.
509
// When listen  is false, the guest will be listening for connections over vsock.
510
func VirtioVsockNew(port uint, socketURL string, listen bool) (VirtioDevice, error) {
511
	return &VirtioVsock{
512
		Port:      port,
513
		SocketURL: socketURL,
514
		Listen:    listen,
515
	}, nil
516
}
517

518
func (dev *VirtioVsock) ToCmdLine() ([]string, error) {
519
	if dev.Port == 0 || dev.SocketURL == "" {
520
		return nil, fmt.Errorf("virtio-vsock needs both a port and a socket URL")
521
	}
522
	var listenStr string
523
	if dev.Listen {
524
		listenStr = "listen"
525
	} else {
526
		listenStr = "connect"
527
	}
528
	return []string{"--device", fmt.Sprintf("virtio-vsock,port=%d,socketURL=%s,%s", dev.Port, dev.SocketURL, listenStr)}, nil
529
}
530

531
func (dev *VirtioVsock) FromOptions(options []option) error {
532
	// default to listen for backwards compatibliity
533
	dev.Listen = true
534
	for _, option := range options {
535
		switch option.key {
536
		case "socketURL":
537
			dev.SocketURL = option.value
538
		case "port":
539
			port, err := strconv.Atoi(option.value)
540
			if err != nil {
541
				return err
542
			}
543
			dev.Port = uint(port)
544
		case "listen":
545
			dev.Listen = true
546
		case "connect":
547
			dev.Listen = false
548
		default:
549
			return fmt.Errorf("unknown option for virtio-vsock devices: %s", option.key)
550
		}
551
	}
552
	return nil
553
}
554

555
// VirtioFsNew creates a new virtio-fs device for file sharing. It will share
556
// the directory at sharedDir with the virtual machine. This directory can be
557
// mounted in the VM using `mount -t virtiofs mountTag /some/dir`
558
func VirtioFsNew(sharedDir string, mountTag string) (VirtioDevice, error) {
559
	return &VirtioFs{
560
		DirectorySharingConfig: DirectorySharingConfig{
561
			MountTag: mountTag,
562
		},
563
		SharedDir: sharedDir,
564
	}, nil
565
}
566

567
func (dev *VirtioFs) ToCmdLine() ([]string, error) {
568
	if dev.SharedDir == "" {
569
		return nil, fmt.Errorf("virtio-fs needs the path to the directory to share")
570
	}
571
	if dev.MountTag != "" {
572
		return []string{"--device", fmt.Sprintf("virtio-fs,sharedDir=%s,mountTag=%s", dev.SharedDir, dev.MountTag)}, nil
573
	}
574

575
	return []string{"--device", fmt.Sprintf("virtio-fs,sharedDir=%s", dev.SharedDir)}, nil
576
}
577

578
func (dev *VirtioFs) FromOptions(options []option) error {
579
	for _, option := range options {
580
		switch option.key {
581
		case "sharedDir":
582
			dev.SharedDir = option.value
583
		case "mountTag":
584
			dev.MountTag = option.value
585
		default:
586
			return fmt.Errorf("unknown option for virtio-fs devices: %s", option.key)
587
		}
588
	}
589
	return nil
590
}
591

592
// RosettaShare creates a new rosetta share for running x86_64 binaries on M1 machines.
593
// It will share a directory containing the linux rosetta binaries with the
594
// virtual machine. This directory can be mounted in the VM using `mount -t
595
// virtiofs mountTag /some/dir`
596
func RosettaShareNew(mountTag string) (VirtioDevice, error) {
597
	return &RosettaShare{
598
		DirectorySharingConfig: DirectorySharingConfig{
599
			MountTag: mountTag,
600
		},
601
	}, nil
602
}
603

604
func (dev *RosettaShare) ToCmdLine() ([]string, error) {
605
	if dev.MountTag == "" {
606
		return nil, fmt.Errorf("rosetta shares require a mount tag to be specified")
607
	}
608
	builder := strings.Builder{}
609
	builder.WriteString("rosetta")
610
	fmt.Fprintf(&builder, ",mountTag=%s", dev.MountTag)
611
	if dev.InstallRosetta {
612
		builder.WriteString(",install")
613
	}
614

615
	return []string{"--device", builder.String()}, nil
616
}
617

618
func (dev *RosettaShare) FromOptions(options []option) error {
619
	for _, option := range options {
620
		switch option.key {
621
		case "mountTag":
622
			dev.MountTag = option.value
623
		case "install":
624
			dev.InstallRosetta = true
625
		default:
626
			return fmt.Errorf("unknown option for rosetta share: %s", option.key)
627
		}
628
	}
629
	return nil
630
}
631

632
type USBMassStorage struct {
633
	StorageConfig
634
}
635

636
func usbMassStorageNewEmpty() *USBMassStorage {
637
	return &USBMassStorage{
638
		StorageConfig{
639
			DevName: "usb-mass-storage",
640
		},
641
	}
642
}
643

644
// USBMassStorageNew creates a new USB disk to use in the virtual machine. It will use
645
// the file at imagePath as the disk image. This image must be in raw or ISO format.
646
func USBMassStorageNew(imagePath string) (VMComponent, error) {
647
	usbMassStorage := usbMassStorageNewEmpty()
648
	usbMassStorage.ImagePath = imagePath
649

650
	return usbMassStorage, nil
651
}
652

653
// StorageConfig configures a disk device.
654
type StorageConfig struct {
655
	DevName   string
656
	ImagePath string
657
	ReadOnly  bool
658
}
659

660
func (config *StorageConfig) ToCmdLine() ([]string, error) {
661
	if config.ImagePath == "" {
662
		return nil, fmt.Errorf("%s devices need the path to a disk image", config.DevName)
663
	}
664
	return []string{"--device", fmt.Sprintf("%s,path=%s", config.DevName, config.ImagePath)}, nil
665
}
666

667
func (config *StorageConfig) FromOptions(options []option) error {
668
	for _, option := range options {
669
		switch option.key {
670
		case "path":
671
			config.ImagePath = option.value
672
		default:
673
			return fmt.Errorf("unknown option for %s devices: %s", config.DevName, option.key)
674
		}
675
	}
676
	return nil
677
}
678

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.