diff --git a/datasource/vmware/vmware.go b/datasource/vmware/vmware.go new file mode 100644 index 0000000..9eb7c05 --- /dev/null +++ b/datasource/vmware/vmware.go @@ -0,0 +1,154 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 vmware + +import ( + "fmt" + "log" + "net" + + "github.com/coreos/coreos-cloudinit/config" + "github.com/coreos/coreos-cloudinit/datasource" + + "github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/sigma/vmw-guestinfo/rpcvmx" + "github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/sigma/vmw-guestinfo/vmcheck" +) + +type vmware struct { + readConfig func(key string) (string, error) +} + +func NewDatasource() *vmware { + return &vmware{readConfig} +} + +func (v vmware) IsAvailable() bool { + return vmcheck.IsVirtualWorld() +} + +func (v vmware) AvailabilityChanges() bool { + return false +} + +func (v vmware) ConfigRoot() string { + return "/" +} + +func (v vmware) FetchMetadata() (metadata datasource.Metadata, err error) { + metadata.Hostname, _ = v.readConfig("hostname") + + netconf := map[string]string{} + saveConfig := func(key string, args ...interface{}) string { + key = fmt.Sprintf(key, args...) + val, _ := v.readConfig(key) + if val != "" { + netconf[key] = val + } + return val + } + + for i := 0; ; i++ { + if nameserver := saveConfig("dns.server.%d", i); nameserver == "" { + break + } + } + + found := true + for i := 0; found; i++ { + found = false + + found = (saveConfig("interface.%d.name", i) != "") || found + found = (saveConfig("interface.%d.mac", i) != "") || found + found = (saveConfig("interface.%d.dhcp", i) != "") || found + + role, _ := v.readConfig(fmt.Sprintf("interface.%d.role", i)) + for a := 0; ; a++ { + address := saveConfig("interface.%d.ip.%d.address", i, a) + if address == "" { + break + } else { + found = true + } + + ip, _, err := net.ParseCIDR(address) + if err != nil { + return metadata, err + } + + switch role { + case "public": + if ip.To4() != nil { + metadata.PublicIPv4 = ip + } else { + metadata.PublicIPv6 = ip + } + case "private": + if ip.To4() != nil { + metadata.PrivateIPv4 = ip + } else { + metadata.PrivateIPv6 = ip + } + case "": + default: + return metadata, fmt.Errorf("unrecognized role: %q", role) + } + } + + for r := 0; ; r++ { + gateway := saveConfig("interface.%d.route.%d.gateway", i, r) + destination := saveConfig("interface.%d.route.%d.destination", i, r) + + if gateway == "" && destination == "" { + break + } else { + found = true + } + } + } + metadata.NetworkConfig = netconf + + return +} + +func (v vmware) FetchUserdata() ([]byte, error) { + encoding, err := v.readConfig("coreos.config.data.encoding") + if err != nil { + return nil, err + } + + data, err := v.readConfig("coreos.config.data") + if err != nil { + return nil, err + } + + if encoding != "" { + return config.DecodeContent(data, encoding) + } + return []byte(data), nil +} + +func (v vmware) Type() string { + return "vmware" +} + +func readConfig(key string) (string, error) { + data, err := rpcvmx.NewConfig().GetString(key, "") + if err == nil { + log.Printf("Read from %q: %q\n", key, data) + } else { + log.Printf("Failed to read from %q: %v\n", key, err) + } + return data, err +} diff --git a/datasource/vmware/vmware_test.go b/datasource/vmware/vmware_test.go new file mode 100644 index 0000000..8ef5399 --- /dev/null +++ b/datasource/vmware/vmware_test.go @@ -0,0 +1,189 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 vmware + +import ( + "errors" + "net" + "reflect" + "testing" + + "github.com/coreos/coreos-cloudinit/datasource" +) + +type MockHypervisor map[string]string + +func (h MockHypervisor) ReadConfig(key string) (string, error) { + return h[key], nil +} + +func TestFetchMetadata(t *testing.T) { + tests := []struct { + variables MockHypervisor + + metadata datasource.Metadata + err error + }{ + { + variables: map[string]string{ + "interface.0.mac": "test mac", + "interface.0.dhcp": "yes", + }, + metadata: datasource.Metadata{ + NetworkConfig: map[string]string{ + "interface.0.mac": "test mac", + "interface.0.dhcp": "yes", + }, + }, + }, + { + variables: map[string]string{ + "interface.0.name": "test name", + "interface.0.dhcp": "yes", + }, + metadata: datasource.Metadata{ + NetworkConfig: map[string]string{ + "interface.0.name": "test name", + "interface.0.dhcp": "yes", + }, + }, + }, + { + variables: map[string]string{ + "hostname": "test host", + "interface.0.mac": "test mac", + "interface.0.role": "private", + "interface.0.ip.0.address": "fe00::100/64", + "interface.0.route.0.gateway": "fe00::1", + "interface.0.route.0.destination": "::", + }, + metadata: datasource.Metadata{ + Hostname: "test host", + PrivateIPv6: net.ParseIP("fe00::100"), + NetworkConfig: map[string]string{ + "interface.0.mac": "test mac", + "interface.0.ip.0.address": "fe00::100/64", + "interface.0.route.0.gateway": "fe00::1", + "interface.0.route.0.destination": "::", + }, + }, + }, + { + variables: map[string]string{ + "hostname": "test host", + "interface.0.name": "test name", + "interface.0.role": "public", + "interface.0.ip.0.address": "10.0.0.100/24", + "interface.0.ip.1.address": "10.0.0.101/24", + "interface.0.route.0.gateway": "10.0.0.1", + "interface.0.route.0.destination": "0.0.0.0", + "interface.1.mac": "test mac", + "interface.1.role": "private", + "interface.1.route.0.gateway": "10.0.0.2", + "interface.1.route.0.destination": "0.0.0.0", + "interface.1.ip.0.address": "10.0.0.102/24", + }, + metadata: datasource.Metadata{ + Hostname: "test host", + PublicIPv4: net.ParseIP("10.0.0.101"), + PrivateIPv4: net.ParseIP("10.0.0.102"), + NetworkConfig: map[string]string{ + "interface.0.name": "test name", + "interface.0.ip.0.address": "10.0.0.100/24", + "interface.0.ip.1.address": "10.0.0.101/24", + "interface.0.route.0.gateway": "10.0.0.1", + "interface.0.route.0.destination": "0.0.0.0", + "interface.1.mac": "test mac", + "interface.1.route.0.gateway": "10.0.0.2", + "interface.1.route.0.destination": "0.0.0.0", + "interface.1.ip.0.address": "10.0.0.102/24", + }, + }, + }, + } + + for i, tt := range tests { + v := vmware{tt.variables.ReadConfig} + metadata, err := v.FetchMetadata() + if !reflect.DeepEqual(tt.err, err) { + t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err) + } + if !reflect.DeepEqual(tt.metadata, metadata) { + t.Errorf("bad metadata (#%d): want %#v, got %#v", i, tt.metadata, metadata) + } + } +} + +func TestFetchUserdata(t *testing.T) { + tests := []struct { + variables MockHypervisor + + userdata string + err error + }{ + {}, + { + variables: map[string]string{"coreos.config.data": "test config"}, + userdata: "test config", + }, + { + variables: map[string]string{ + "coreos.config.data.encoding": "", + "coreos.config.data": "test config", + }, + userdata: "test config", + }, + { + variables: map[string]string{ + "coreos.config.data.encoding": "base64", + "coreos.config.data": "dGVzdCBjb25maWc=", + }, + userdata: "test config", + }, + { + variables: map[string]string{ + "coreos.config.data.encoding": "gzip+base64", + "coreos.config.data": "H4sIABaoWlUAAytJLS5RSM7PS8tMBwCQiHNZCwAAAA==", + }, + userdata: "test config", + }, + { + variables: map[string]string{ + "coreos.config.data.encoding": "test encoding", + }, + err: errors.New(`Unsupported encoding "test encoding"`), + }, + } + + for i, tt := range tests { + v := vmware{tt.variables.ReadConfig} + userdata, err := v.FetchUserdata() + if !reflect.DeepEqual(tt.err, err) { + t.Errorf("bad error (#%d): want %v, got %v", i, nil, err) + } + if tt.userdata != string(userdata) { + t.Errorf("bad userdata (#%d): want %q, got %q", i, tt.userdata, userdata) + } + } +} + +func TestFetchUserdataError(t *testing.T) { + testErr := errors.New("test error") + _, err := vmware{func(_ string) (string, error) { return "", testErr }}.FetchUserdata() + + if testErr != err { + t.Errorf("bad error: want %v, got %v", testErr, err) + } +} diff --git a/network/vmware.go b/network/vmware.go new file mode 100644 index 0000000..230be42 --- /dev/null +++ b/network/vmware.go @@ -0,0 +1,174 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 network + +import ( + "fmt" + "log" + "net" +) + +func ProcessVMwareNetconf(config map[string]string) ([]InterfaceGenerator, error) { + log.Println("Processing VMware network config") + + log.Println("Parsing nameservers") + var nameservers []net.IP + for i := 0; ; i++ { + if ipStr, ok := config[fmt.Sprintf("dns.server.%d", i)]; ok { + if ip := net.ParseIP(ipStr); ip != nil { + nameservers = append(nameservers, ip) + } else { + return nil, fmt.Errorf("invalid nameserver: %q", ipStr) + } + } else { + break + } + } + log.Printf("Parsed %d nameservers", len(nameservers)) + + var interfaces []InterfaceGenerator + for i := 0; ; i++ { + var addresses []net.IPNet + var routes []route + var err error + var dhcp bool + iface := &physicalInterface{} + + log.Printf("Proccessing interface %d", i) + + log.Println("Processing DHCP") + if dhcp, err = processDHCPConfig(config, fmt.Sprintf("interface.%d.", i)); err != nil { + return nil, err + } + + log.Println("Processing addresses") + if as, err := processAddressConfig(config, fmt.Sprintf("interface.%d.", i)); err == nil { + addresses = append(addresses, as...) + } else { + return nil, err + } + + log.Println("Processing routes") + if rs, err := processRouteConfig(config, fmt.Sprintf("interface.%d.", i)); err == nil { + routes = append(routes, rs...) + } else { + return nil, err + } + + if mac, ok := config[fmt.Sprintf("interface.%d.mac", i)]; ok { + log.Printf("Parsing interface %d MAC address: %q", i, mac) + if hwaddr, err := net.ParseMAC(mac); err == nil { + iface.hwaddr = hwaddr + } else { + return nil, fmt.Errorf("error while parsing MAC address: %v", err) + } + } + + if name, ok := config[fmt.Sprintf("interface.%d.name", i)]; ok { + log.Printf("Parsing interface %d name: %q", i, name) + iface.name = name + } + + if len(addresses) > 0 || len(routes) > 0 { + iface.config = configMethodStatic{ + hwaddress: iface.hwaddr, + addresses: addresses, + nameservers: nameservers, + routes: routes, + } + } else if dhcp { + iface.config = configMethodDHCP{ + hwaddress: iface.hwaddr, + } + } else { + break + } + + interfaces = append(interfaces, iface) + } + + return interfaces, nil +} + +func processAddressConfig(config map[string]string, prefix string) (addresses []net.IPNet, err error) { + for a := 0; ; a++ { + prefix := fmt.Sprintf("%sip.%d.", prefix, a) + + addressStr, ok := config[prefix+"address"] + if !ok { + break + } + + ip, network, err := net.ParseCIDR(addressStr) + if err != nil { + return nil, fmt.Errorf("invalid address: %q", addressStr) + } + addresses = append(addresses, net.IPNet{ + IP: ip, + Mask: network.Mask, + }) + } + + return +} + +func processRouteConfig(config map[string]string, prefix string) (routes []route, err error) { + for r := 0; ; r++ { + prefix := fmt.Sprintf("%sroute.%d.", prefix, r) + + gatewayStr, gok := config[prefix+"gateway"] + destinationStr, dok := config[prefix+"destination"] + if gok && !dok { + return nil, fmt.Errorf("missing destination key") + } else if !gok && dok { + return nil, fmt.Errorf("missing gateway key") + } else if !gok && !dok { + break + } + + gateway := net.ParseIP(gatewayStr) + if gateway == nil { + return nil, fmt.Errorf("invalid gateway: %q", gatewayStr) + } + + _, destination, err := net.ParseCIDR(destinationStr) + if err != nil { + return nil, err + } + + routes = append(routes, route{ + destination: *destination, + gateway: gateway, + }) + } + + return +} + +func processDHCPConfig(config map[string]string, prefix string) (dhcp bool, err error) { + dhcpStr, ok := config[prefix+"dhcp"] + if !ok { + return false, nil + } + + switch dhcpStr { + case "yes": + return true, nil + case "no": + return false, nil + default: + return false, fmt.Errorf("invalid DHCP option: %q", dhcpStr) + } +} diff --git a/network/vmware_test.go b/network/vmware_test.go new file mode 100644 index 0000000..010568c --- /dev/null +++ b/network/vmware_test.go @@ -0,0 +1,361 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 network + +import ( + "errors" + "net" + "reflect" + "testing" +) + +func mustParseMac(mac net.HardwareAddr, err error) net.HardwareAddr { + if err != nil { + panic(err) + } + return mac +} + +func TestProcessVMwareNetconf(t *testing.T) { + tests := []struct { + config map[string]string + + interfaces []InterfaceGenerator + err error + }{ + {}, + { + config: map[string]string{ + "interface.0.dhcp": "yes", + }, + interfaces: []InterfaceGenerator{ + &physicalInterface{logicalInterface{ + config: configMethodDHCP{}, + }}, + }, + }, + { + config: map[string]string{ + "interface.0.mac": "00:11:22:33:44:55", + "interface.0.dhcp": "yes", + }, + interfaces: []InterfaceGenerator{ + &physicalInterface{logicalInterface{ + hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")), + config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55"))}, + }}, + }, + }, + { + config: map[string]string{ + "interface.0.name": "eth0", + "interface.0.dhcp": "yes", + }, + interfaces: []InterfaceGenerator{ + &physicalInterface{logicalInterface{ + name: "eth0", + config: configMethodDHCP{}, + }}, + }, + }, + { + config: map[string]string{ + "interface.0.mac": "00:11:22:33:44:55", + "interface.0.ip.0.address": "10.0.0.100/24", + "interface.0.route.0.gateway": "10.0.0.1", + "interface.0.route.0.destination": "0.0.0.0/0", + }, + interfaces: []InterfaceGenerator{ + &physicalInterface{logicalInterface{ + hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")), + config: configMethodStatic{ + hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")), + addresses: []net.IPNet{net.IPNet{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}}, + // I realize how upset you must be that I am shoving an IPMask into an IP. This is because net.IPv4zero is + // actually a magic IPv6 address which ruins our equality check. What's that? Just use IP::Equal()? I'd rather + // DeepEqual just handle that for me, but until Go gets operator overloading, we are stuck with this. + routes: []route{route{ + destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, + gateway: net.ParseIP("10.0.0.1")}, + }, + }, + }}, + }, + }, + { + config: map[string]string{ + "dns.server.0": "1.2.3.4", + "dns.server.1": "5.6.7.8", + "interface.0.mac": "00:11:22:33:44:55", + "interface.0.ip.0.address": "10.0.0.100/24", + "interface.0.ip.1.address": "10.0.0.101/24", + "interface.0.route.0.gateway": "10.0.0.1", + "interface.0.route.0.destination": "0.0.0.0/0", + "interface.1.name": "eth0", + "interface.1.ip.0.address": "10.0.1.100/24", + "interface.1.route.0.gateway": "10.0.1.1", + "interface.1.route.0.destination": "0.0.0.0/0", + "interface.2.dhcp": "yes", + "interface.2.mac": "00:11:22:33:44:77", + }, + interfaces: []InterfaceGenerator{ + &physicalInterface{logicalInterface{ + hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")), + config: configMethodStatic{ + hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")), + addresses: []net.IPNet{ + net.IPNet{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}, + net.IPNet{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)}, + }, + routes: []route{route{ + destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, + gateway: net.ParseIP("10.0.0.1")}, + }, + nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")}, + }, + }}, + &physicalInterface{logicalInterface{ + name: "eth0", + config: configMethodStatic{ + addresses: []net.IPNet{net.IPNet{IP: net.ParseIP("10.0.1.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}}, + routes: []route{route{ + destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, + gateway: net.ParseIP("10.0.1.1")}, + }, + nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")}, + }, + }}, + &physicalInterface{logicalInterface{ + hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:77")), + config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:77"))}, + }}, + }, + }, + { + config: map[string]string{"dns.server.0": "test dns"}, + err: errors.New(`invalid nameserver: "test dns"`), + }, + } + + for i, tt := range tests { + interfaces, err := ProcessVMwareNetconf(tt.config) + if !reflect.DeepEqual(tt.err, err) { + t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err) + } + if !reflect.DeepEqual(tt.interfaces, interfaces) { + t.Errorf("bad interfaces (#%d): want %#v, got %#v", i, tt.interfaces, interfaces) + for _, iface := range tt.interfaces { + t.Logf(" want: %#v", iface) + } + for _, iface := range interfaces { + t.Logf(" got: %#v", iface) + } + } + } +} + +func TestProcessAddressConfig(t *testing.T) { + tests := []struct { + config map[string]string + prefix string + + addresses []net.IPNet + err error + }{ + {}, + + // static - ipv4 + { + config: map[string]string{ + "ip.0.address": "10.0.0.100/24", + }, + + addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}}, + }, + { + config: map[string]string{ + "this.is.a.prefix.ip.0.address": "10.0.0.100/24", + }, + prefix: "this.is.a.prefix.", + + addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}}, + }, + { + config: map[string]string{ + "ip.0.address": "10.0.0.100/24", + "ip.1.address": "10.0.0.101/24", + "ip.2.address": "10.0.0.102/24", + }, + + addresses: []net.IPNet{ + {IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}, + {IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)}, + {IP: net.ParseIP("10.0.0.102"), Mask: net.CIDRMask(24, net.IPv4len*8)}, + }, + }, + + // static - ipv6 + { + config: map[string]string{ + "ip.0.address": "fe00::100/64", + }, + + addresses: []net.IPNet{{IP: net.ParseIP("fe00::100"), Mask: net.IPMask(net.CIDRMask(64, net.IPv6len*8))}}, + }, + { + config: map[string]string{ + "ip.0.address": "fe00::100/64", + "ip.1.address": "fe00::101/64", + "ip.2.address": "fe00::102/64", + }, + + addresses: []net.IPNet{ + {IP: net.ParseIP("fe00::100"), Mask: net.CIDRMask(64, net.IPv6len*8)}, + {IP: net.ParseIP("fe00::101"), Mask: net.CIDRMask(64, net.IPv6len*8)}, + {IP: net.ParseIP("fe00::102"), Mask: net.CIDRMask(64, net.IPv6len*8)}, + }, + }, + + // invalid + { + config: map[string]string{ + "ip.0.address": "test address", + }, + + err: errors.New(`invalid address: "test address"`), + }, + } + + for i, tt := range tests { + addresses, err := processAddressConfig(tt.config, tt.prefix) + if !reflect.DeepEqual(tt.err, err) { + t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err) + } + if err != nil { + continue + } + + if !reflect.DeepEqual(tt.addresses, addresses) { + t.Errorf("bad addresses (#%d): want %#v, got %#v", i, tt.addresses, addresses) + } + } +} + +func TestProcessRouteConfig(t *testing.T) { + tests := []struct { + config map[string]string + prefix string + + routes []route + err error + }{ + {}, + + { + config: map[string]string{ + "route.0.gateway": "10.0.0.1", + "route.0.destination": "0.0.0.0/0", + }, + + routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}}, + }, + { + config: map[string]string{ + "this.is.a.prefix.route.0.gateway": "10.0.0.1", + "this.is.a.prefix.route.0.destination": "0.0.0.0/0", + }, + prefix: "this.is.a.prefix.", + + routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}}, + }, + { + config: map[string]string{ + "route.0.gateway": "fe00::1", + "route.0.destination": "::/0", + }, + + routes: []route{{destination: net.IPNet{IP: net.IPv6zero, Mask: net.IPMask(net.IPv6zero)}, gateway: net.ParseIP("fe00::1")}}, + }, + + // invalid + { + config: map[string]string{ + "route.0.gateway": "test gateway", + "route.0.destination": "0.0.0.0/0", + }, + + err: errors.New(`invalid gateway: "test gateway"`), + }, + { + config: map[string]string{ + "route.0.gateway": "10.0.0.1", + "route.0.destination": "test destination", + }, + + err: &net.ParseError{Type: "CIDR address", Text: "test destination"}, + }, + } + + for i, tt := range tests { + routes, err := processRouteConfig(tt.config, tt.prefix) + if !reflect.DeepEqual(tt.err, err) { + t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err) + } + if err != nil { + continue + } + + if !reflect.DeepEqual(tt.routes, routes) { + t.Errorf("bad routes (#%d): want %#v, got %#v", i, tt.routes, routes) + } + } +} + +func TestProcessDHCPConfig(t *testing.T) { + tests := []struct { + config map[string]string + prefix string + + dhcp bool + err error + }{ + {}, + + // prefix + {config: map[string]string{"this.is.a.prefix.mac": ""}, prefix: "this.is.a.prefix.", dhcp: false}, + {config: map[string]string{"this.is.a.prefix.dhcp": "yes"}, prefix: "this.is.a.prefix.", dhcp: true}, + + // dhcp + {config: map[string]string{"dhcp": "yes"}, dhcp: true}, + {config: map[string]string{"dhcp": "no"}, dhcp: false}, + + // invalid + {config: map[string]string{"dhcp": "blah"}, err: errors.New(`invalid DHCP option: "blah"`)}, + } + + for i, tt := range tests { + dhcp, err := processDHCPConfig(tt.config, tt.prefix) + if !reflect.DeepEqual(tt.err, err) { + t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err) + } + if err != nil { + continue + } + + if tt.dhcp != dhcp { + t.Errorf("bad dhcp (#%d): want %v, got %v", i, tt.dhcp, dhcp) + } + } +} diff --git a/test b/test index 3065a93..8853dfa 100755 --- a/test +++ b/test @@ -15,6 +15,7 @@ SRC=" datasource/proc_cmdline datasource/test datasource/url + datasource/vmware datasource/waagent initialize network