Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/core/v1alpha2/virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ const (
)

type NetworksSpec struct {
ID int `json:"id,omitempty"`
Type string `json:"type"`
Name string `json:"name,omitempty"`
VirtualMachineMACAddressName string `json:"virtualMachineMACAddressName,omitempty"`
Expand Down Expand Up @@ -429,6 +430,7 @@ type Versions struct {
}

type NetworksStatus struct {
ID int `json:"id,omitempty"`
Type string `json:"type"`
Name string `json:"name,omitempty"`
MAC string `json:"macAddress,omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions crds/doc-ru-virtualmachines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ spec:
Модуль `sdn` требуется, если в `.spec.networks` указаны дополнительные сети (`Network` или `ClusterNetwork`). Если указана только основная сеть (`Main`), модуль `sdn` не требуется.
items:
properties:
id:
description: |
ID сетевого интерфейса.
type:
description: |
Тип сетевого интерфейса.
Expand Down Expand Up @@ -781,6 +784,9 @@ spec:
Список сетевых интерфейсов, подключенных к ВМ.
items:
properties:
id:
description: |
ID сетевого интерфейса.
type:
description: |
Тип сетевого интерфейса.
Expand Down
8 changes: 8 additions & 0 deletions crds/virtualmachines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,10 @@ spec:
required:
- type
properties:
id:
type: integer
description: |
The ID of the network interface.
type:
type: string
description: |
Expand Down Expand Up @@ -1391,6 +1395,10 @@ spec:
required:
- type
properties:
id:
type: integer
description: |
The ID of the network interface.
type:
type: string
description: |
Expand Down
4 changes: 4 additions & 0 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2933,6 +2933,10 @@ Important considerations when working with additional network interfaces:
- Network security policies (NetworkPolicy) do not apply to additional network interfaces.
- Network parameters (IP addresses, gateways, DNS, etc.) for additional networks are configured manually from within the guest OS (for example, using Cloud-Init).

{{< alert level="info" >}}
Predictable interface order works only on guest OS with `systemd` (e.g. Ubuntu, Debian). On Alpine and other distros without `systemd` the order may not match.
{{< /alert >}}

Example of connecting a VM to the main cluster network and the project network `user-net`:

```yaml
Expand Down
4 changes: 4 additions & 0 deletions docs/USER_GUIDE.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -2967,6 +2967,10 @@ EOF
- политики сетевой безопасности (NetworkPolicy) не применяются к дополнительным сетевым интерфейсам;
- параметры сети (IP-адреса, шлюзы, DNS и т.д.) для дополнительных сетей настраиваются вручную изнутри гостевой ОС (например, с помощью Cloud-Init).

{{< alert level="info" >}}
Предсказуемый порядок интерфейсов соблюдается только в гостевых ОС с `systemd` (например, Ubuntu, Debian). В Alpine и других дистрибутивах без `systemd` порядок может не совпадать.
{{< /alert >}}

Пример подключения ВМ к основной сети кластера и проектной сети `user-net`:

```yaml
Expand Down
159 changes: 111 additions & 48 deletions images/virtualization-artifact/pkg/common/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func HasMainNetworkSpec(networks []v1alpha2.NetworksSpec) bool {
}

type InterfaceSpec struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
InterfaceName string `json:"ifName"`
Expand All @@ -66,65 +67,87 @@ type InterfaceStatus struct {

type InterfaceSpecList []InterfaceSpec

func CreateNetworkSpec(vm *v1alpha2.VirtualMachine, vmmacs []*v1alpha2.VirtualMachineMACAddress) InterfaceSpecList {
var (
all []string
status []struct{ Name, MAC string }
taken = make(map[string]bool)
free []string
res InterfaceSpecList
freeIdx int
)
type MacAddressPool struct {
reservedByName map[string]string
available []string
}

func NewMacAddressPool(vm *v1alpha2.VirtualMachine, vmmacs []*v1alpha2.VirtualMachineMACAddress) *MacAddressPool {
reservedByName := make(map[string]string)
takenMacs := make(map[string]bool)

for _, v := range vmmacs {
if mac := v.Status.Address; mac != "" {
all = append(all, mac)
}
}
for _, n := range vm.Status.Networks {
if n.Type == v1alpha2.NetworksTypeMain {
continue
if n.Type != v1alpha2.NetworksTypeMain && n.MAC != "" {
reservedByName[n.Name] = n.MAC
takenMacs[n.MAC] = true
}
status = append(status, struct{ Name, MAC string }{n.Name, n.MAC})
taken[n.MAC] = true
}
for _, mac := range all {
if !taken[mac] {
free = append(free, mac)

var available []string
for _, v := range vmmacs {
mac := v.Status.Address
if mac != "" && !takenMacs[mac] {
available = append(available, mac)
}
}
for _, n := range vm.Spec.Networks {
if n.Type == v1alpha2.NetworksTypeMain {
res = append(res, InterfaceSpec{
Type: n.Type,
Name: n.Name,
InterfaceName: NameDefaultInterface,
MAC: "",
})

return &MacAddressPool{
reservedByName: reservedByName,
available: available,
}
}

func (p *MacAddressPool) Assign(networkName string) string {
if mac, exists := p.reservedByName[networkName]; exists {
return mac
}

if len(p.available) > 0 {
mac := p.available[0]
p.available = p.available[1:]
return mac
}

return ""
}

func CreateNetworkSpec(vm *v1alpha2.VirtualMachine, vmmacs []*v1alpha2.VirtualMachineMACAddress) InterfaceSpecList {
macPool := NewMacAddressPool(vm, vmmacs)
var specs InterfaceSpecList

for _, net := range vm.Spec.Networks {
if net.Type == v1alpha2.NetworksTypeMain {
specs = append(specs, createMainInterfaceSpec(net))
continue
}
var mac string
for i, s := range status {
if s.Name == n.Name {
mac = s.MAC
status = append(status[:i], status[i+1:]...)
break
}
}
if mac == "" && freeIdx < len(free) {
mac = free[freeIdx]
freeIdx++
}

mac := macPool.Assign(net.Name)
if mac != "" {
res = append(res, InterfaceSpec{
Type: n.Type,
Name: n.Name,
InterfaceName: generateInterfaceName(mac, n.Type),
MAC: mac,
})
specs = append(specs, createAdditionalInterfaceSpec(net, mac))
}
}
return res

return specs
}

func createMainInterfaceSpec(net v1alpha2.NetworksSpec) InterfaceSpec {
return InterfaceSpec{
ID: net.ID,
Type: net.Type,
Name: net.Name,
InterfaceName: NameDefaultInterface,
MAC: "",
}
}

func createAdditionalInterfaceSpec(net v1alpha2.NetworksSpec, mac string) InterfaceSpec {
return InterfaceSpec{
ID: net.ID,
Type: net.Type,
Name: net.Name,
InterfaceName: generateInterfaceName(mac, net.Type),
MAC: mac,
}
}

func (s InterfaceSpecList) ToString() (string, error) {
Expand Down Expand Up @@ -157,3 +180,43 @@ func generateInterfaceName(macAddress, networkType string) string {
}
return name
}

const (
ReservedMainID = 1
StartGenericID = 2
)

type InterfaceIDAllocator struct {
used map[int]bool
cursor int
}

func NewInterfaceIDAllocator() *InterfaceIDAllocator {
return &InterfaceIDAllocator{
used: make(map[int]bool),
cursor: StartGenericID,
}
}

func (a *InterfaceIDAllocator) Reserve(id int) {
if id > 0 {
a.used[id] = true
}
}

func (a *InterfaceIDAllocator) NextAvailable() int {
for {
if a.cursor == ReservedMainID {
a.cursor++
continue
}

if !a.used[a.cursor] {
id := a.cursor
a.used[id] = true
a.cursor++
return id
}
a.cursor++
}
}
Loading