diff --git a/build.conf b/build.conf index ed21bb5b..9ed1edb4 100644 --- a/build.conf +++ b/build.conf @@ -1,3 +1,3 @@ IMAGE_NAME=rancher/os VERSION=v0.4.3-dev -DFS_IMAGE=rancher/docker:1.10.0 +DFS_IMAGE=rancher/docker:v1.10.0-1 diff --git a/cmd/cloudinit/packet.go b/cmd/cloudinit/packet.go index 58cab269..ebf54537 100644 --- a/cmd/cloudinit/packet.go +++ b/cmd/cloudinit/packet.go @@ -3,6 +3,8 @@ package cloudinit import ( "fmt" "net/http" + "os" + "path" "strings" yaml "github.com/cloudfoundry-incubator/candiedyaml" @@ -40,7 +42,7 @@ func enablePacketNetwork(cfg *rancherConfig.RancherConfig) { bondCfg := netconf.InterfaceConfig{ Addresses: []string{}, BondOpts: map[string]string{ - "lacp-rate": "1", + "lacp_rate": "1", "xmit_hash_policy": "layer3+4", "downdelay": "200", "updelay": "200", @@ -83,6 +85,10 @@ func enablePacketNetwork(cfg *rancherConfig.RancherConfig) { }, } + if err := os.MkdirAll(path.Dir(rancherConfig.CloudConfigNetworkFile), 0700); err != nil { + logrus.Errorf("Failed to create directory for file %s: %v", rancherConfig.CloudConfigNetworkFile, err) + } + if err := rancherConfig.WriteToFile(cc, rancherConfig.CloudConfigNetworkFile); err != nil { logrus.Errorf("Failed to save config file %s: %v", rancherConfig.CloudConfigNetworkFile, err) } diff --git a/os-config.yml b/os-config.yml index 9f7bd5d1..cc3af212 100644 --- a/os-config.yml +++ b/os-config.yml @@ -64,11 +64,6 @@ rancher: network: dns: nameservers: [8.8.8.8, 8.8.4.4] - interfaces: - eth*: - dhcp: true - lo: - address: 127.0.0.1/8 repositories: core: url: https://raw.githubusercontent.com/rancher/os-services/v0.4.3-dev diff --git a/tests/integration/assets/test_01/cloud-config.yml b/tests/integration/assets/test_01/cloud-config.yml index 20175b16..853223d4 100644 --- a/tests/integration/assets/test_01/cloud-config.yml +++ b/tests/integration/assets/test_01/cloud-config.yml @@ -7,6 +7,8 @@ rancher: kernel-headers: true network: interfaces: + eth*: + dhcp: true eth1: address: 10.10.2.17/24 gateway: 10.10.2.2 diff --git a/tests/integration/assets/test_09/cloud-config.yml b/tests/integration/assets/test_09/cloud-config.yml new file mode 100644 index 00000000..b11304d9 --- /dev/null +++ b/tests/integration/assets/test_09/cloud-config.yml @@ -0,0 +1,25 @@ +#cloud-config +rancher: + network: + interfaces: + eth0: + address: 10.0.2.15/24 + gateway: 10.0.2.2 + br0: + vlans: 100 + eth1: + vlans: 100,200:foobar + eth1.100: + bridge: br0 + br0.100: + address: 123.123.123.123/32 + eth6: + bond: bond0 + eth7: + bond: bond0 + bond0: + bond_opts: + mode: 1 + address: 123.123.123.124/32 +ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC85w9stZyiLQp/DkVO6fqwiShYcj1ClKdtCqgHtf+PLpJkFReSFu8y21y+ev09gsSMRRrjF7yt0pUHV6zncQhVeqsZtgc5WbELY2DOYUGmRn/CCvPbXovoBrQjSorqlBmpuPwsStYLr92Xn+VVsMNSUIegHY22DphGbDKG85vrKB8HxUxGIDxFBds/uE8FhSy+xsoyT/jUZDK6pgq2HnGl6D81ViIlKecpOpWlW3B+fea99ADNyZNVvDzbHE5pcI3VRw8u59WmpWOUgT6qacNVACl8GqpBvQk8sw7O/X9DSZHCKafeD9G5k+GYbAUz92fKWrx/lOXfUXPS3+c8dRIF diff --git a/tests/integration/rostest/test_09_network.py b/tests/integration/rostest/test_09_network.py new file mode 100644 index 00000000..74e1b0b0 --- /dev/null +++ b/tests/integration/rostest/test_09_network.py @@ -0,0 +1,36 @@ +import pytest +import rostest.util as u +from rostest.util import SSH + + +cloud_config_path = './tests/integration/assets/test_09/cloud-config.yml' + + +@pytest.fixture(scope="module") +def qemu(request): + q = u.run_qemu(request, run_args=['--cloud-config', cloud_config_path, + '-net', 'nic,vlan=0,model=virtio', + '-net', 'nic,vlan=0,model=virtio', + '-net', 'nic,vlan=0,model=virtio', + '-net', 'nic,vlan=0,model=virtio', + '-net', 'nic,vlan=0,model=virtio', + '-net', 'nic,vlan=0,model=virtio', + '-net', 'nic,vlan=0,model=virtio']) + u.flush_out(q.stdout) + return q + + +def test_network_conf(qemu): + SSH(qemu).check_call('bash', '-c', '''cat > test-merge << "SCRIPT" +set -x -e + +ip link show dev br0 +ip link show dev br0.100 | grep br0.100@br0 +ip link show dev eth1.100 | grep 'master br0' +ip link show dev eth6 | grep 'master bond0' +ip link show dev eth7 | grep 'master bond0' +[ "$( 4 && strings.ToLower(netConf.Match[:3]) == "mac" { - haAddr, err := net.ParseMAC(netConf.Match[4:]) - if err != nil { - return err - } - // Don't match mac address of the bond because it is the same as the slave - if bytes.Compare(haAddr, link.Attrs().HardwareAddr) == 0 && link.Attrs().Name != netConf.Bond { - // MAC address match is used over all other matches - match = netConf - break - } - } - - // "" means match has not been found - if match.Match == "" && matches(linkName, netConf.Match) { - match = netConf - } - - if netConf.Match == linkName { - // Found exact match, use it over wildcard match - match = netConf - } - } - - if match.Match != "" { + if match, ok := findMatch(link, netCfg); ok { if match.DHCP { - dhcpLinks = append(dhcpLinks, link.Attrs().Name) - } else if err = applyNetConf(link, match); err != nil { + dhcpLinks[link.Attrs().Name] = match.DHCPArgs + } else if err = applyInterfaceConfig(link, match); err != nil { log.Errorf("Failed to apply settings to %s : %v", linkName, err) } } } - if len(dhcpLinks) > 0 { - log.Infof("Running DHCP on %v", dhcpLinks) - dhcpcdArgs := append([]string{"-MA4", "-e", "force_hostname=true"}, dhcpLinks...) - cmd := exec.Command("dhcpcd", dhcpcdArgs...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - log.Error(err) + //run dhcp + wg := sync.WaitGroup{} + for iface, args := range dhcpLinks { + wg.Add(1) + go func(iface, args string) { + runDhcp(iface, args) + wg.Done() + }(iface, args) + } + wg.Wait() + + runCmds(netCfg.PostCmds, "") + return err +} + +func runDhcp(iface string, argstr string) { + log.Infof("Running DHCP on %s", iface) + args := []string{} + if argstr != "" { + var err error + args, err = shlex.Split(argstr) + if err != nil { + log.Errorf("Failed to parse [%s]: %v", argstr, err) } } - - if err != nil { - return err + if len(args) == 0 { + args = defaultDhcpArgs } - return nil + args = append(args, iface) + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Error(err) + } } func linkUp(link netlink.Link, netConf InterfaceConfig) error { @@ -207,15 +271,27 @@ func setGateway(gateway string) error { return nil } -func applyNetConf(link netlink.Link, netConf InterfaceConfig) error { +func applyInterfaceConfig(link netlink.Link, netConf InterfaceConfig) error { if netConf.Bond != "" { - b := Bond(netConf.Bond) - b.AddSlave(link.Attrs().Name) - if b.Error() != nil { - return b.Error() + b, err := Bond(netConf.Bond) + if err != nil { + return err } + if err := b.AddSlave(link.Attrs().Name); err != nil { + return err + } + return nil + } - return linkUp(link, netConf) + if netConf.Bridge != "" && netConf.Bridge != "true" { + b, err := NewBridge(netConf.Bridge) + if err != nil { + return err + } + if err := b.AddLink(link); err != nil { + return err + } + return nil } if netConf.IPV4LL { @@ -223,16 +299,18 @@ func applyNetConf(link netlink.Link, netConf InterfaceConfig) error { log.Errorf("IPV4LL set failed: %v", err) return err } - } else if netConf.Address == "" && len(netConf.Addresses) == 0 { - return nil } else { + addresses := []string{} + if netConf.Address != "" { - err := applyAddress(netConf.Address, link, netConf) - if err != nil { - log.Errorf("Failed to apply address %s to %s: %v", netConf.Address, link.Attrs().Name, err) - } + addresses = append(addresses, netConf.Address) } - for _, address := range netConf.Addresses { + + if len(netConf.Addresses) > 0 { + addresses = append(addresses, netConf.Addresses...) + } + + for _, address := range addresses { err := applyAddress(address, link, netConf) if err != nil { log.Errorf("Failed to apply address %s to %s: %v", address, link.Attrs().Name, err) @@ -247,6 +325,8 @@ func applyNetConf(link netlink.Link, netConf InterfaceConfig) error { } } + runCmds(netConf.PreUp, link.Attrs().Name) + if err := linkUp(link, netConf); err != nil { return err } @@ -259,30 +339,31 @@ func applyNetConf(link netlink.Link, netConf InterfaceConfig) error { log.Errorf("Fail to set gateway %s", netConf.Gateway) } - for _, postUp := range netConf.PostUp { - postUp = strings.TrimSpace(postUp) - if postUp == "" { - continue - } - - args, err := shlex.Split(strings.Replace(postUp, "$iface", link.Attrs().Name, -1)) - if err != nil { - log.Errorf("Failed to parse command [%s]: %v", postUp, err) - continue - } - - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - log.Errorf("Failed to run command [%s]: %v", postUp, err) - continue - } - } + runCmds(netConf.PostUp, link.Attrs().Name) return nil } -func matches(link, conf string) bool { - return glob.Glob(conf, link) +func runCmds(cmds []string, iface string) { + for _, cmd := range cmds { + cmd = strings.TrimSpace(cmd) + if cmd == "" { + continue + } + + args, err := shlex.Split(strings.Replace(cmd, "$iface", iface, -1)) + if err != nil { + log.Errorf("Failed to parse command [%s]: %v", cmd, err) + continue + } + + log.Infof("Running command %s %v", args[0], args[1:]) + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Errorf("Failed to run command [%s]: %v", cmd, err) + continue + } + } } diff --git a/vendor/github.com/rancher/netconf/types.go b/vendor/github.com/rancher/netconf/types.go index 2b86e2ca..fa7f2d0c 100644 --- a/vendor/github.com/rancher/netconf/types.go +++ b/vendor/github.com/rancher/netconf/types.go @@ -1,24 +1,28 @@ package netconf type NetworkConfig struct { - Script string `yaml:"script,omitempty"` + PreCmds []string `yaml:"pre_cmds,omitempty"` Dns DnsConfig `yaml:"dns,omitempty"` Interfaces map[string]InterfaceConfig `yaml:"interfaces,omitempty"` + PostCmds []string `yaml:"post_cmds,omitempty"` } type InterfaceConfig struct { Match string `yaml:"match,omitempty"` DHCP bool `yaml:"dhcp,omitempty"` + DHCPArgs string `yaml:"dhcp_args,omitempty"` Address string `yaml:"address,omitempty"` Addresses []string `yaml:"addresses,omitempty"` IPV4LL bool `yaml:"ipv4ll,omitempty"` Gateway string `yaml:"gateway,omitempty"` GatewayIpv6 string `yaml:"gateway_ipv6,omitempty"` MTU int `yaml:"mtu,omitempty"` - Bridge bool `yaml:"bridge,omitempty"` + Bridge string `yaml:"bridge,omitempty"` Bond string `yaml:"bond,omitempty"` BondOpts map[string]string `yaml:"bond_opts,omitempty"` PostUp []string `yaml:"post_up,omitempty"` + PreUp []string `yaml:"pre_up,omitempty"` + Vlans string `yaml:"vlans,omitempty"` } type DnsConfig struct { diff --git a/vendor/github.com/rancher/netconf/vlan.go b/vendor/github.com/rancher/netconf/vlan.go new file mode 100644 index 00000000..75821d82 --- /dev/null +++ b/vendor/github.com/rancher/netconf/vlan.go @@ -0,0 +1,79 @@ +package netconf + +import ( + "fmt" + "strconv" + "strings" + + "github.com/vishvananda/netlink" +) + +type VlanDefinition struct { + Id int + Name string +} + +type Vlan struct { + name string + link netlink.Link + id int +} + +func NewVlan(link netlink.Link, name string, id int) (*Vlan, error) { + if name == "" { + name = fmt.Sprintf("%s.%d", link.Attrs().Name, id) + } + + v := &Vlan{ + name: name, + link: link, + id: id, + } + return v, v.init() +} + +func (v *Vlan) init() error { + link, err := netlink.LinkByName(v.name) + if err == nil { + if _, ok := link.(*netlink.Vlan); !ok { + return fmt.Errorf("%s is not a VLAN device", v.name) + } + return nil + } + + vlan := netlink.Vlan{} + vlan.ParentIndex = v.link.Attrs().Index + vlan.Name = v.name + vlan.VlanId = v.id + + return netlink.LinkAdd(&vlan) +} + +func ParseVlanDefinitions(vlans string) ([]VlanDefinition, error) { + vlans = strings.TrimSpace(vlans) + if vlans == "" { + return nil, nil + } + + result := []VlanDefinition{} + + for _, vlan := range strings.Split(vlans, ",") { + idName := strings.SplitN(strings.TrimSpace(vlan), ":", 2) + id, err := strconv.Atoi(idName[0]) + if err != nil { + return nil, fmt.Errorf("Invalid format in %s: %v", vlans, err) + } + + def := VlanDefinition{ + Id: id, + } + + if len(idName) > 1 { + def.Name = idName[1] + } + + result = append(result, def) + } + + return result, nil +}