diff --git a/pkg/hostman/guestfs/fsdriver/linux.go b/pkg/hostman/guestfs/fsdriver/linux.go index a714aad2ab..0b03beed84 100644 --- a/pkg/hostman/guestfs/fsdriver/linux.go +++ b/pkg/hostman/guestfs/fsdriver/linux.go @@ -536,6 +536,14 @@ func (d *sDebianLikeRootFs) PrepareFsForTemplate(rootFs IDiskPartition) error { return errors.Wrap(err, "file put content /etc/network/interface") } } + + // clean /etc/netplan/* + netplanDir := "/etc/netplan/" + if rootFs.Exists(netplanDir, false) { + for _, f := range rootFs.ListDir(netplanDir, false) { + rootFs.Remove(netplanDir+f, false) + } + } return nil } @@ -577,6 +585,16 @@ func getNicTeamingConfigCmds(slaves []*types.SServerNic) string { return cmds.String() } +func (d *sDebianLikeRootFs) deployNetplanConfigFile(rootFs IDiskPartition, nics []*types.SServerNic) error { + netplanDir := "/etc/netplan/" + dirExists := rootFs.Exists(netplanDir, false) + if !dirExists { + return nil + } + + return nil +} + func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []*types.SServerNic) error { if err := d.sLinuxRootFs.DeployNetworkingScripts(rootFs, nics); err != nil { return err @@ -587,7 +605,19 @@ func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics cmds.WriteString("iface lo inet loopback\n\n") // ToServerNics(nics) - allNics, _ := convertNicConfigs(nics) + allNics, bondNics := convertNicConfigs(nics) + + netplanDir := "/etc/netplan" + if rootFs.Exists(netplanDir, false) { + for _, f := range rootFs.ListDir(netplanDir, false) { + rootFs.Remove(netplanDir+f, false) + } + netplanConfig := NewNetplanConfig(allNics, bondNics) + if err := rootFs.FilePutContents(path.Join(netplanDir, "config.yaml"), netplanConfig.YAMLString(), false, false); err != nil { + return errors.Wrap(err, "Put netplan config") + } + } + mainNic, err := getMainNic(allNics) if err != nil { return err diff --git a/pkg/hostman/guestfs/fsdriver/netplan.go b/pkg/hostman/guestfs/fsdriver/netplan.go new file mode 100644 index 0000000000..8a1ada74de --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/netplan.go @@ -0,0 +1,125 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fsdriver + +import ( + "fmt" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/netplan" + "yunion.io/x/onecloud/pkg/util/netutils2" +) + +func NewNetplanConfig(allNics []*types.SServerNic, bondNics []*types.SServerNic) *netplan.Configuration { + network := newNetplanNetwork(allNics, bondNics) + return netplan.NewConfiguration(network) +} + +func newNetplanNetwork(allNics []*types.SServerNic, bondNics []*types.SServerNic) *netplan.Network { + network := netplan.NewNetwork() + + for _, nic := range allNics { + nicConf := getNetplanEthernetConfig(nic, false) + + if nicConf == nil { + continue + } + + network.AddEthernet(nic.Name, nicConf) + } + + for _, bondNic := range bondNics { + if len(bondNic.TeamingSlaves) < 2 { + log.Warningf("BondNic %s slaves nic %#v less than 2", bondNic.Name, bondNic.TeamingSlaves) + continue + } + + var defaultMtu = 1442 + + interfaces := make([]string, len(bondNic.TeamingSlaves)) + for i, sn := range bondNic.TeamingSlaves { + interfaces[i] = sn.Name + + nicConf := &netplan.EthernetConfig{ + DHCP4: false, + MacAddress: sn.Mac, + Match: netplan.NewEthernetConfigMatchMac(sn.Mac), + } + + if sn.Mtu > 0 { + nicConf.Mtu = sn.Mtu + } else { + nicConf.Mtu = defaultMtu + } + + network.AddEthernet(sn.Name, nicConf) + } + + primaryNic := bondNic.TeamingSlaves[0] + netConf := getNetplanEthernetConfig(primaryNic, true) + netConf.MacAddress = primaryNic.Mac + + if netConf.Mtu == 0 { + netConf.Mtu = defaultMtu + } + + // TODO: implement kinds of bond mode config + // bondConf := netplan.NewBondMode4(netConf, interfaces) + bondConf := netplan.NewBondMode1(netConf, interfaces) + + network.AddBond(bondNic.Name, bondConf) + } + + return network +} + +func getNetplanEthernetConfig(nic *types.SServerNic, isBond bool) *netplan.EthernetConfig { + var nicConf *netplan.EthernetConfig + + if !isBond && (nic.TeamingMaster != nil || nic.TeamingSlaves != nil) { + return nil + } else if nic.Virtual { + addr := fmt.Sprintf("%s/32", netutils2.PSEUDO_VIP) + nicConf = netplan.NewStaticEthernetConfig(addr, "", nil, nil, nil) + } else if nic.Manual { + addr := fmt.Sprintf("%s/%d", nic.Ip, nic.Masklen) + gateway := nic.Gateway + var routes []*netplan.Route + + for _, route := range nic.Routes { + routes = append(routes, &netplan.Route{ + To: route[0], + Via: route[1], + }) + } + + nicConf = netplan.NewStaticEthernetConfig( + addr, gateway, + []string{nic.Domain}, + netutils2.GetNicDns(nic), + routes, + ) + if nic.Mtu > 0 { + nicConf.Mtu = nic.Mtu + } + } else { + // dhcp + nicConf = netplan.NewDHCP4EthernetConfig() + } + + return nicConf +} diff --git a/pkg/util/netplan/doc.go b/pkg/util/netplan/doc.go new file mode 100644 index 0000000000..19f8560c6d --- /dev/null +++ b/pkg/util/netplan/doc.go @@ -0,0 +1 @@ +package netplan // import "yunion.io/x/onecloud/pkg/util/netplan" diff --git a/pkg/util/netplan/netplan.go b/pkg/util/netplan/netplan.go new file mode 100644 index 0000000000..71909384c6 --- /dev/null +++ b/pkg/util/netplan/netplan.go @@ -0,0 +1,242 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netplan + +import "yunion.io/x/jsonutils" + +// Configuration examples reference from https://netplan.io/examples/ +// manpage: http://manpages.ubuntu.com/manpages/cosmic/man5/netplan.5.html +type Configuration struct { + Network *Network `json:"network"` +} + +func NewConfiguration(network *Network) *Configuration { + return &Configuration{ + Network: network, + } +} + +func (c *Configuration) YAMLString() string { + return toYAMLString(c) +} + +type NetworkRenderer string + +const ( + VERSION2 = 2 + NetworkRendererNetworkd NetworkRenderer = "networkd" +) + +type Network struct { + Version uint `json:"version"` + Renderer NetworkRenderer `json:"renderer"` + Ethernets map[string]*EthernetConfig `json:"ethernets"` + Bonds map[string]*Bond `json:"bonds"` +} + +type EthernetConfigMatch struct { + MacAddress string `json:"macaddress"` +} + +func NewEthernetConfigMatchMac(macAddr string) *EthernetConfigMatch { + return &EthernetConfigMatch{ + MacAddress: macAddr, + } +} + +type EthernetConfig struct { + DHCP4 bool `json:"dhcp4"` + Addresses []string `json:"addresses"` + Match *EthernetConfigMatch `json:"match"` + MacAddress string `json:"macaddress"` + Gateway4 string `json:"gateway4"` + Routes []*Route `json:"routes"` + Nameservers *Nameservers `json:"nameservers"` + Mtu int `json:"mtu,omitzero"` +} + +type Route struct { + To string `json:"to"` + Via string `json:"via"` + Metric uint `json:"metric"` + // OnLink bool `json:"on-link"` +} + +type Nameservers struct { + Search []string `json:"search"` + Addresses []string `json:"addresses"` +} + +type Bond struct { + EthernetConfig + Interfaces []string `json:"interfaces"` + Parameters IBondModeParams `json:"parameters"` +} + +func toYAMLString(obj interface{}) string { + return jsonutils.Marshal(obj).YAMLString() +} + +func (b *Bond) YAMLString() string { + return toYAMLString(b) +} + +type BondMode string + +const ( + // ref: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/networking_guide/overview-of-bonding-modes-and-the-required-settings-on-the-switch + // mode0 + bondModeBalanceRR = "balance-rr" + // mode1 + bondModeActiveBackup = "active-backup" + // mode4 + bondMode8023AD = "802.3ad" +) + +type IBondModeParams interface { + GetMode() string +} + +type BondModeBaseParams struct { + Mode string `json:"mode"` + MiiMonitorInterval int `json:"mii-monitor-interval,omitzero"` + GratuitiousArp int `json:"gratuitiousi-arp,omitzero"` +} + +func (c BondModeBaseParams) GetMode() string { + return c.Mode +} + +func (c *BondModeBaseParams) SetMiiMonitorInterval(i int) { + c.MiiMonitorInterval = i +} + +func (c *BondModeBaseParams) SetGratutiousArp(g int) { + c.GratuitiousArp = g +} + +type BondModeActiveBackupParams struct { + *BondModeBaseParams + + Primary string `json:"primary"` +} + +func NewBondMode0Params() *BondModeBaseParams { + return &BondModeBaseParams{ + Mode: bondModeBalanceRR, + } +} + +func NewBondModeActiveBackupParams(primary string) *BondModeActiveBackupParams { + return &BondModeActiveBackupParams{ + BondModeBaseParams: &BondModeBaseParams{ + Mode: bondModeActiveBackup, + }, + Primary: primary, + } +} + +type BondMode4Params struct { + *BondModeBaseParams +} + +func NewBondMode4Params() *BondMode4Params { + return &BondMode4Params{ + BondModeBaseParams: &BondModeBaseParams{ + Mode: bondMode8023AD, + }, + } +} + +func NewNetwork() *Network { + return &Network{ + Version: VERSION2, + Renderer: NetworkRendererNetworkd, + Ethernets: make(map[string]*EthernetConfig), + Bonds: make(map[string]*Bond), + } +} + +func (n *Network) AddEthernet(name string, ether *EthernetConfig) *Network { + n.Ethernets[name] = ether + return n +} + +func (n *Network) AddBond(name string, bond *Bond) *Network { + n.Bonds[name] = bond + return n +} + +func (n *Network) YAMLString() string { + return toYAMLString(n) +} + +func NewDHCP4EthernetConfig() *EthernetConfig { + return &EthernetConfig{ + DHCP4: true, + } +} + +func NewStaticEthernetConfig( + addr string, + gateway string, + search []string, + nameservers []string, + routes []*Route, +) *EthernetConfig { + return &EthernetConfig{ + DHCP4: false, + Addresses: []string{addr}, + Gateway4: gateway, + Routes: routes, + Nameservers: &Nameservers{ + Search: search, + Addresses: nameservers, + }, + } +} + +func (c *EthernetConfig) YAMLString() string { + return toYAMLString(c) +} + +func newBondModeByParams(conf *EthernetConfig, interfaces []string, params IBondModeParams) *Bond { + return &Bond{ + EthernetConfig: *conf, + Interfaces: interfaces, + Parameters: params, + } +} + +func NewBondMode0(conf *EthernetConfig, interfaces []string) *Bond { + params := NewBondMode0Params() + params.SetMiiMonitorInterval(100) + return newBondModeByParams(conf, interfaces, params) +} + +func NewBondMode1(conf *EthernetConfig, interfaces []string) *Bond { + params := NewBondModeActiveBackupParams(interfaces[0]) + params.SetMiiMonitorInterval(100) + return newBondModeByParams(conf, interfaces, params) +} +func NewBondMode4(conf *EthernetConfig, interfaces []string) *Bond { + params := NewBondMode4Params() + // TODO: figure out what follows options related to netplan config + // miimon: 1 + // lacp_rate: 1 + // xmit_hash_policy: 1 + params.SetMiiMonitorInterval(100) + return newBondModeByParams(conf, interfaces, params) +} diff --git a/pkg/util/netplan/netplan_test.go b/pkg/util/netplan/netplan_test.go new file mode 100644 index 0000000000..a2b24fd5f8 --- /dev/null +++ b/pkg/util/netplan/netplan_test.go @@ -0,0 +1,101 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netplan + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewEthernetConfig(t *testing.T) { + c := NewDHCP4EthernetConfig() + + assert := assert.New(t) + assert.YAMLEq("dhcp4: true", c.YAMLString()) + +} + +func TestNewBondMode4(t *testing.T) { + c := NewBondMode4( + &EthernetConfig{ + DHCP4: false, + Gateway4: "192.168.1.1", + Addresses: []string{"192.168.1.252/24"}, + Nameservers: &Nameservers{ + Search: []string{"local"}, + Addresses: []string{"8.8.8.8", "8.8.4.4"}, + }, + }, + []string{"enp2s0", "enp3s0"}, + ) + + yamlStr := ` +addresses: +- 192.168.1.252/24 +dhcp4: false +gateway4: 192.168.1.1 +interfaces: +- enp2s0 +- enp3s0 +nameservers: + addresses: + - 8.8.8.8 + - 8.8.4.4 + search: + - local +parameters: + mii-monitor-interval: 100 + mode: "802.3ad" +` + + assert := assert.New(t) + assert.YAMLEq(yamlStr, c.YAMLString()) +} + +func TestNewNetwork(t *testing.T) { + n := NewNetwork() + n.AddEthernet("eth0", NewDHCP4EthernetConfig()) + n.AddEthernet("eth1", NewStaticEthernetConfig( + "10.10.10.2/24", + "10.10.10.1", + []string{"mydomain", "otherdomain"}, + []string{"114.114.114.114"}, + nil, + )) + + c := NewConfiguration(n) + + yamlStr := ` +network: + ethernets: + eth0: + dhcp4: true + eth1: + addresses: + - 10.10.10.2/24 + dhcp4: false + gateway4: 10.10.10.1 + nameservers: + addresses: + - 114.114.114.114 + search: ["mydomain", "otherdomain"] + renderer: networkd + version: 2 +` + + assert := assert.New(t) + assert.YAMLEq(yamlStr, c.YAMLString()) +}