From eca9865048ea2a53a61c15197df6c9e991435260 Mon Sep 17 00:00:00 2001 From: Tomas Karasek Date: Fri, 5 Feb 2021 17:57:46 +0100 Subject: [PATCH 1/2] initial support for bond network types and hybrid-bonded type Signed-off-by: Tomas Karasek --- bond_ports.go | 159 +++++++++++++++++++++++++++++++++++++++++++++ bond_ports_test.go | 137 ++++++++++++++++++++++++++++++++++++++ ports.go | 2 + 3 files changed, 298 insertions(+) create mode 100644 bond_ports.go create mode 100644 bond_ports_test.go diff --git a/bond_ports.go b/bond_ports.go new file mode 100644 index 00000000..e4a62831 --- /dev/null +++ b/bond_ports.go @@ -0,0 +1,159 @@ +package packngo + +import "fmt" + +const NetworkTypeHybridBonded = "hybrid-bonded" + +func (d *Device) GetBondNetworkType(portName string) string { + for _, p := range d.NetworkPorts { + if p.Name == portName { + return p.NetworkType + } + } + return "" +} + +func (d *Device) GetEthPortsInBond(name string) []*Port { + ports := []*Port{} + for _, port := range d.NetworkPorts { + if port.Bond != nil && port.Bond.Name == name { + ports = append(ports, &port) + } + } + return ports +} + +func BondStateTransitionNecessary(type1, type2 string) bool { + if type1 == type2 { + return false + } + if type1 == NetworkTypeHybridBonded && type2 == NetworkTypeL3 { + return false + } + if type2 == NetworkTypeHybridBonded && type1 == NetworkTypeL3 { + return false + } + return true +} + +func (i *DevicePortServiceOp) BondToNetworkType(deviceID, bondPortName, targetType string) (*Device, error) { + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + + curType := d.GetBondNetworkType(bondPortName) + + if !BondStateTransitionNecessary(curType, targetType) { + return nil, fmt.Errorf("Bond doesn't need to be converted from %s to %s", curType, targetType) + } + + err = i.ConvertDeviceBond(d, bondPortName, targetType) + if err != nil { + return nil, err + } + + d, _, err = i.client.Devices.Get(deviceID, nil) + + if err != nil { + return nil, err + } + + finalType := d.GetNetworkType() + + if BondStateTransitionNecessary(finalType, targetType) { + return nil, fmt.Errorf( + "Failed to convert %s on device %s from %s to %s. New type was %s", + bondPortName, deviceID, curType, targetType, finalType) + + } + return d, err +} + +func (i *DevicePortServiceOp) ConvertDeviceBond(d *Device, bondPortName, targetType string) error { + bondPort, err := d.GetPortByName(bondPortName) + if err != nil { + return err + } + + if targetType == NetworkTypeL3 || targetType == NetworkTypeHybridBonded { + _, _, err := i.Bond(bondPort, false) + if err != nil { + return err + } + _, _, err = i.PortToLayerThree(d.ID, bondPortName) + if err != nil { + return err + } + // device needs to be refreshed, the bond and convert calls might bond eths + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return err + } + for _, p := range d.GetEthPortsInBond(bondPortName) { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeHybrid { + _, _, err := i.Bond(bondPort, false) + if err != nil { + return err + } + _, _, err = i.PortToLayerThree(d.ID, bondPortName) + if err != nil { + return err + } + + // device needs to be refreshed, the bond and convert calls might bond eths + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return err + } + ethLatter := d.GetEthPortsInBond(bondPortName)[1] + + _, _, err = i.Disbond(ethLatter, false) + if err != nil { + return err + } + } + if targetType == NetworkTypeL2Individual { + _, _, err := i.PortToLayerTwo(d.ID, bondPortName) + if err != nil { + return err + } + // device needs to be refreshed, the convert call might break the bond + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return err + } + bondPort, err := d.GetPortByName(bondPortName) + if err != nil { + return err + } + _, _, err = i.Disbond(bondPort, true) + if err != nil { + return err + } + } + if targetType == NetworkTypeL2Bonded { + _, _, err := i.PortToLayerTwo(d.ID, bondPortName) + if err != nil { + return err + } + // device needs to be refreshed, the convert call might break the bond + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return err + } + for _, p := range d.GetEthPortsInBond(bondPortName) { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + } + return nil +} diff --git a/bond_ports_test.go b/bond_ports_test.go new file mode 100644 index 00000000..6757732d --- /dev/null +++ b/bond_ports_test.go @@ -0,0 +1,137 @@ +package packngo + +import ( + "log" + "testing" + "time" +) + +func deviceBondToNetworkType(t *testing.T, c *Client, deviceID, bondPortName, targetNetworkType string) { + d, _, err := c.Devices.Get(deviceID, nil) + if err != nil { + t.Fatal(err) + } + oldType := d.GetBondNetworkType(bondPortName) + log.Println("Converting", bondPortName, oldType, "=>", targetNetworkType, "...") + _, err = c.DevicePorts.BondToNetworkType(deviceID, bondPortName, targetNetworkType) + if err != nil { + t.Fatal(err) + } + log.Println(oldType, "=>", targetNetworkType, "OK") + // why sleep here?? + time.Sleep(5 * time.Second) +} + +func TestAccPortBondNetworkStateTransitions(t *testing.T) { + skipUnlessAcceptanceTestsAllowed(t) + t.Parallel() + c, projectID, teardown := setupWithProject(t) + defer teardown() + + fac := testFacility() + + cr := DeviceCreateRequest{ + Hostname: "bondNetworkTypeTest", + Facility: []string{fac}, + Plan: "c3.small.x86", + OS: testOS, + ProjectID: projectID, + BillingCycle: "hourly", + } + d, _, err := c.Devices.Create(&cr) + if err != nil { + t.Fatal(err) + } + defer deleteDevice(t, c, d.ID, false) + deviceID := d.ID + + d = waitDeviceActive(t, c, deviceID) + + bond := "bond0" + + networkType := d.GetBondNetworkType(bond) + if networkType != NetworkTypeL3 { + t.Fatal("network_type should be 'layer3'") + } + + if networkType != NetworkTypeL2Bonded { + deviceBondToNetworkType(t, c, deviceID, bond, NetworkTypeL2Bonded) + } + + deviceBondToNetworkType(t, c, deviceID, bond, NetworkTypeL2Individual) +} + +func TestAccPortBondNetworkStateHybridBonded(t *testing.T) { + skipUnlessAcceptanceTestsAllowed(t) + t.Parallel() + c, projectID, teardown := setupWithProject(t) + defer teardown() + + fac := testFacility() + + cr := DeviceCreateRequest{ + Hostname: "NetworkTypeHybridBondedTest", + Facility: []string{fac}, + Plan: "c3.small.x86", + OS: testOS, + ProjectID: projectID, + BillingCycle: "hourly", + } + d, _, err := c.Devices.Create(&cr) + if err != nil { + t.Fatal(err) + } + defer deleteDevice(t, c, d.ID, false) + deviceID := d.ID + + d = waitDeviceActive(t, c, deviceID) + + bond := "bond0" + + networkType := d.GetBondNetworkType(bond) + if networkType != NetworkTypeL3 { + t.Fatal("network_type should be 'layer3'") + } + + testDesc := "test_desc_" + randString8() + + vncr := VirtualNetworkCreateRequest{ + ProjectID: projectID, + Description: testDesc, + Facility: testFacility(), + } + + vlan, _, err := c.ProjectVirtualNetworks.Create(&vncr) + if err != nil { + t.Fatal(err) + } + defer deleteProjectVirtualNetwork(t, c, vlan.ID) + + bondPort, _ := d.GetPortByName(bond) + + par := PortAssignRequest{ + PortID: bondPort.ID, + VirtualNetworkID: vlan.ID} + + p, _, err := c.DevicePorts.Assign(&par) + if err != nil { + t.Fatal(err) + } + + log.Printf("%#v\n", p) + + d, _, err = c.Devices.Get(d.ID, nil) + if err != nil { + t.Fatal(err) + } + newBondNetworkType := d.GetBondNetworkType(bond) + if newBondNetworkType != NetworkTypeHybridBonded { + t.Fatalf("After bond0 is in L3 and VLAN is assigned to bond0, it's network type should be %s. Was %s", NetworkTypeHybridBonded, newBondNetworkType) + } + + _, _, err = c.DevicePorts.Unassign(&par) + if err != nil { + t.Fatal(err) + } + +} diff --git a/ports.go b/ports.go index 8f15be0d..1c19f178 100644 --- a/ports.go +++ b/ports.go @@ -24,6 +24,8 @@ type DevicePortService interface { GetOddEthPorts(*Device) (map[string]*Port, error) GetAllEthPorts(*Device) (map[string]*Port, error) ConvertDevice(*Device, string) error + BondToNetworkType(string, string, string) (*Device, error) + ConvertDeviceBond(*Device, string, string) error } type PortData struct { From 22130f927c728fe74f551bb90c0d146f1ebfa7d0 Mon Sep 17 00:00:00 2001 From: Tomas Karasek Date: Mon, 8 Feb 2021 15:16:26 +0100 Subject: [PATCH 2/2] don't err on same network type transition Signed-off-by: Tomas Karasek --- bond_ports.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bond_ports.go b/bond_ports.go index e4a62831..644f57d9 100644 --- a/bond_ports.go +++ b/bond_ports.go @@ -45,7 +45,7 @@ func (i *DevicePortServiceOp) BondToNetworkType(deviceID, bondPortName, targetTy curType := d.GetBondNetworkType(bondPortName) if !BondStateTransitionNecessary(curType, targetType) { - return nil, fmt.Errorf("Bond doesn't need to be converted from %s to %s", curType, targetType) + return d, nil } err = i.ConvertDeviceBond(d, bondPortName, targetType)