Skip to content
Merged
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
204 changes: 135 additions & 69 deletions ipam/mas.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,129 +5,195 @@ package ipam

import (
"encoding/json"
"errors"
"io/ioutil"
"net"
"net/http"
"time"
"runtime"
"strings"

"github.com/Azure/azure-container-networking/common"
"github.com/Azure/azure-container-networking/log"
)

const (
// Host URL to query.
masQueryUrl = "http://169.254.169.254:6642/ListNetwork"

// Minimum time interval between consecutive queries.
masQueryInterval = 10 * time.Second
defaultLinuxFilePath = "/etc/kubernetes/interfaces.json"
defaultWindowsFilePath = `c:\k\interfaces.json`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a small question -> does this json file exists after reboot?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

windows = "windows"
name = "MAS"
)

// Microsoft Azure Stack IPAM configuration source.
type masSource struct {
name string
sink addressConfigSink
queryUrl string
queryInterval time.Duration
lastRefresh time.Time
name string
sink addressConfigSink
fileLoaded bool
filePath string
}

// MAS host agent JSON object format.
type jsonObject struct {
Isolation string
IPs []struct {
IP string
IsolationId string
Mask string
DefaultGateways []string
DnsServers []string
}
type NetworkInterfaces struct {
Interfaces []Interface
}

type Interface struct {
MacAddress string
IsPrimary bool
IPSubnets []IPSubnet
}

type IPSubnet struct {
Prefix string
IPAddresses []IPAddress
}

type IPAddress struct {
Address string
IsPrimary bool
}

// Creates the MAS source.
func newMasSource(options map[string]interface{}) (*masSource, error) {
queryUrl, _ := options[common.OptIpamQueryUrl].(string)
if queryUrl == "" {
queryUrl = masQueryUrl
}

i, _ := options[common.OptIpamQueryInterval].(int)
queryInterval := time.Duration(i) * time.Second
if queryInterval == 0 {
queryInterval = masQueryInterval
var filePath string
if runtime.GOOS == windows {
filePath = defaultWindowsFilePath
} else {
filePath = defaultLinuxFilePath
}

return &masSource{
name: "MAS",
queryUrl: queryUrl,
queryInterval: queryInterval,
name: name,
filePath: filePath,
}, nil
}

// Starts the MAS source.
func (s *masSource) start(sink addressConfigSink) error {
s.sink = sink
func (source *masSource) start(sink addressConfigSink) error {
source.sink = sink
return nil
}

// Stops the MAS source.
func (s *masSource) stop() {
s.sink = nil
return
func (source *masSource) stop() {
source.sink = nil
}

// Refreshes configuration.
func (s *masSource) refresh() error {
func (source *masSource) refresh() error {
if source == nil {
return errors.New("masSource is nil")
}

// Refresh only if enough time has passed since the last query.
if time.Since(s.lastRefresh) < s.queryInterval {
if source.fileLoaded {
return nil
}
s.lastRefresh = time.Now()

// Configure the local default address space.
local, err := s.sink.newAddressSpace(LocalDefaultAddressSpaceId, LocalScope)
// Query the list of local interfaces.
localInterfaces, err := net.Interfaces()
if err != nil {
return err
}

// Fetch configuration.
resp, err := http.Get(s.queryUrl)
// Query the list of Azure Network Interfaces
sdnInterfaces, err := getSDNInterfaces(source.filePath)
if err != nil {
return err
}

defer resp.Body.Close()

// Decode JSON object.
var obj jsonObject
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&obj)
// Configure the local default address space.
local, err := source.sink.newAddressSpace(LocalDefaultAddressSpaceId, LocalScope)
if err != nil {
return err
}

// Add the IP addresses to the local address space.
for _, v := range obj.IPs {
address := net.ParseIP(v.IP)
subnet := net.IPNet{
IP: net.ParseIP(v.IP),
Mask: net.IPMask(net.ParseIP(v.Mask)),
if err = populateAddressSpace(local, sdnInterfaces, localInterfaces); err != nil {
return err
}

// Set the local address space as active.
if err = source.sink.setAddressSpace(local); err != nil {
return err
}

log.Printf("[ipam] Address space successfully populated from config file")
source.fileLoaded = true

return nil
}

func getSDNInterfaces(fileLocation string) (*NetworkInterfaces, error) {
data, err := ioutil.ReadFile(fileLocation)
if err != nil {
return nil, err
}

interfaces := &NetworkInterfaces{}
if err = json.Unmarshal(data, interfaces); err != nil {
return nil, err
}

return interfaces, nil
}

func populateAddressSpace(localAddressSpace *addressSpace, sdnInterfaces *NetworkInterfaces, localInterfaces []net.Interface) error {
//Find the interface with matching MacAddress or Name
for _, sdnIf := range sdnInterfaces.Interfaces {
ifName := ""

for _, localIf := range localInterfaces {
if macAddressesEqual(sdnIf.MacAddress, localIf.HardwareAddr.String()) {
ifName = localIf.Name
break
}
}

ap, err := local.newAddressPool("eth0", 0, &subnet)
if err != nil {
log.Printf("[ipam] Failed to create pool:%v err:%v.", subnet, err)
// Skip if interface is not found.
if ifName == "" {
log.Printf("[ipam] Failed to find interface with MAC address:%v", sdnIf.MacAddress)
continue
}

_, err = ap.newAddressRecord(&address)
if err != nil {
log.Printf("[ipam] Failed to create address:%v err:%v.", address, err)
continue
// Prioritize secondary interfaces.
priority := 0
if !sdnIf.IsPrimary {
priority = 1
}
}

// Set the local address space as active.
s.sink.setAddressSpace(local)
for _, subnet := range sdnIf.IPSubnets {
_, network, err := net.ParseCIDR(subnet.Prefix)
if err != nil {
log.Printf("[ipam] Failed to parse subnet:%v err:%v.", subnet.Prefix, err)
continue
}

addressPool, err := localAddressSpace.newAddressPool(ifName, priority, network)
if err != nil {
log.Printf("[ipam] Failed to create pool:%v ifName:%v err:%v.", subnet, ifName, err)
continue
}

// Add the IP addresses to the localAddressSpace address space.
for _, ipAddr := range subnet.IPAddresses {
// Primary addresses are reserved for the host.
if ipAddr.IsPrimary {
continue
}

address := net.ParseIP(ipAddr.Address)

_, err = addressPool.newAddressRecord(&address)
if err != nil {
log.Printf("[ipam] Failed to create address:%v err:%v.", address, err)
continue
}
}
}
}

return nil
}

func macAddressesEqual(macAddress1 string, macAddress2 string) bool {
macAddress1 = strings.ToLower(strings.Replace(macAddress1, ":", "", -1))
macAddress2 = strings.ToLower(strings.Replace(macAddress2, ":", "", -1))

return macAddress1 == macAddress2
}
Loading