github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/maas/interfaces.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/gomaasapi" 13 14 "github.com/juju/juju/instance" 15 "github.com/juju/juju/network" 16 ) 17 18 //////////////////////////////////////////////////////////////////////////////// 19 // TODO(dimitern): The types below should be part of gomaasapi. 20 // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119310616 21 22 type maasLinkMode string 23 24 const ( 25 modeUnknown maasLinkMode = "" 26 modeStatic maasLinkMode = "static" 27 modeDHCP maasLinkMode = "dhcp" 28 modeLinkUp maasLinkMode = "link_up" 29 modeAuto maasLinkMode = "auto" 30 ) 31 32 type maasInterfaceLink struct { 33 ID int `json:"id"` 34 Subnet *maasSubnet `json:"subnet,omitempty"` 35 IPAddress string `json:"ip_address,omitempty"` 36 Mode maasLinkMode `json:"mode"` 37 } 38 39 type maasInterfaceType string 40 41 const ( 42 typeUnknown maasInterfaceType = "" 43 typePhysical maasInterfaceType = "physical" 44 typeVLAN maasInterfaceType = "vlan" 45 typeBond maasInterfaceType = "bond" 46 ) 47 48 type maasInterface struct { 49 ID int `json:"id"` 50 Name string `json:"name"` 51 Type maasInterfaceType `json:"type"` 52 Enabled bool `json:"enabled"` 53 54 MACAddress string `json:"mac_address"` 55 VLAN maasVLAN `json:"vlan"` 56 EffectveMTU int `json:"effective_mtu"` 57 58 Links []maasInterfaceLink `json:"links"` 59 60 Parents []string `json:"parents"` 61 Children []string `json:"children"` 62 63 ResourceURI string `json:"resource_uri"` 64 } 65 66 type maasVLAN struct { 67 ID int `json:"id"` 68 Name string `json:"name"` 69 VID int `json:"vid"` 70 MTU int `json:"mtu"` 71 Fabric string `json:"fabric"` 72 ResourceURI string `json:"resource_uri"` 73 } 74 75 type maasSubnet struct { 76 ID int `json:"id"` 77 Name string `json:"name"` 78 Space string `json:"space"` 79 VLAN maasVLAN `json:"vlan"` 80 GatewayIP string `json:"gateway_ip"` 81 DNSServers []string `json:"dns_servers"` 82 CIDR string `json:"cidr"` 83 ResourceURI string `json:"resource_uri"` 84 } 85 86 func parseInterfaces(jsonBytes []byte) ([]maasInterface, error) { 87 var interfaces []maasInterface 88 if err := json.Unmarshal(jsonBytes, &interfaces); err != nil { 89 return nil, errors.Annotate(err, "parsing interfaces") 90 } 91 return interfaces, nil 92 } 93 94 // maasObjectNetworkInterfaces implements environs.NetworkInterfaces() using the 95 // new (1.9+) MAAS API, parsing the node details JSON embedded into the given 96 // maasObject to extract all the relevant InterfaceInfo fields. It returns an 97 // error satisfying errors.IsNotSupported() if it cannot find the required 98 // "interface_set" node details field. 99 func maasObjectNetworkInterfaces(maasObject *gomaasapi.MAASObject, subnetsMap map[string]network.Id) ([]network.InterfaceInfo, error) { 100 interfaceSet, ok := maasObject.GetMap()["interface_set"] 101 if !ok || interfaceSet.IsNil() { 102 // This means we're using an older MAAS API. 103 return nil, errors.NotSupportedf("interface_set") 104 } 105 106 // TODO(dimitern): Change gomaasapi JSONObject to give access to the raw 107 // JSON bytes directly, rather than having to do call MarshalJSON just so 108 // the result can be unmarshaled from it. 109 // 110 // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119311323 111 112 rawBytes, err := interfaceSet.MarshalJSON() 113 if err != nil { 114 return nil, errors.Annotate(err, "cannot get interface_set JSON bytes") 115 } 116 117 interfaces, err := parseInterfaces(rawBytes) 118 if err != nil { 119 return nil, errors.Trace(err) 120 } 121 122 infos := make([]network.InterfaceInfo, 0, len(interfaces)) 123 for i, iface := range interfaces { 124 // The below works for all types except bonds and their members. 125 parentName := strings.Join(iface.Parents, "") 126 var nicType network.InterfaceType 127 switch iface.Type { 128 case typePhysical: 129 nicType = network.EthernetInterface 130 children := strings.Join(iface.Children, "") 131 if parentName == "" && len(iface.Children) == 1 && strings.HasPrefix(children, "bond") { 132 // FIXME: Verify the bond exists, regardless of its name. 133 // This is a bond member, set the parent correctly (from 134 // Juju's perspective) - to the bond itself. 135 parentName = children 136 } 137 case typeBond: 138 parentName = "" 139 nicType = network.BondInterface 140 case typeVLAN: 141 nicType = network.VLAN_8021QInterface 142 } 143 144 nicInfo := network.InterfaceInfo{ 145 DeviceIndex: i, 146 MACAddress: iface.MACAddress, 147 ProviderId: network.Id(fmt.Sprintf("%v", iface.ID)), 148 VLANTag: iface.VLAN.VID, 149 InterfaceName: iface.Name, 150 InterfaceType: nicType, 151 ParentInterfaceName: parentName, 152 Disabled: !iface.Enabled, 153 NoAutoStart: !iface.Enabled, 154 } 155 156 if len(iface.Links) == 0 { 157 logger.Debugf("interface %q has no links", iface.Name) 158 infos = append(infos, nicInfo) 159 continue 160 } 161 162 for _, link := range iface.Links { 163 nicInfo.ConfigType = maasLinkToInterfaceConfigType(string(link.Mode)) 164 165 if link.IPAddress == "" && link.Subnet == nil { 166 logger.Debugf("interface %q link %d has neither subnet nor address", iface.Name, link.ID) 167 infos = append(infos, nicInfo) 168 } else { 169 // We set it here initially without a space, just so we don't 170 // lose it when we have no linked subnet below. 171 nicInfo.Address = network.NewAddress(link.IPAddress) 172 nicInfo.ProviderAddressId = network.Id(fmt.Sprintf("%v", link.ID)) 173 } 174 175 if link.Subnet == nil { 176 logger.Debugf("interface %q link %d missing subnet", iface.Name, link.ID) 177 infos = append(infos, nicInfo) 178 continue 179 } 180 181 sub := link.Subnet 182 nicInfo.CIDR = sub.CIDR 183 nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID)) 184 nicInfo.ProviderVLANId = network.Id(fmt.Sprintf("%v", sub.VLAN.ID)) 185 186 // Now we know the subnet and space, we can update the address to 187 // store the space with it. 188 nicInfo.Address = network.NewAddressOnSpace(sub.Space, link.IPAddress) 189 spaceId, ok := subnetsMap[string(sub.CIDR)] 190 if !ok { 191 // The space we found is not recognised, no 192 // provider id available. 193 logger.Warningf("interface %q link %d has unrecognised space %q", iface.Name, link.ID, sub.Space) 194 } else { 195 nicInfo.Address.SpaceProviderId = spaceId 196 nicInfo.ProviderSpaceId = spaceId 197 } 198 199 gwAddr := network.NewAddressOnSpace(sub.Space, sub.GatewayIP) 200 nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space, sub.DNSServers...) 201 if ok { 202 gwAddr.SpaceProviderId = spaceId 203 for i := range nicInfo.DNSServers { 204 nicInfo.DNSServers[i].SpaceProviderId = spaceId 205 } 206 } 207 nicInfo.GatewayAddress = gwAddr 208 nicInfo.MTU = sub.VLAN.MTU 209 210 // Each link we represent as a separate InterfaceInfo, but with the 211 // same name and device index, just different addres, subnet, etc. 212 infos = append(infos, nicInfo) 213 } 214 } 215 return infos, nil 216 } 217 218 func maas2NetworkInterfaces(instance *maas2Instance, subnetsMap map[string]network.Id) ([]network.InterfaceInfo, error) { 219 interfaces := instance.machine.InterfaceSet() 220 infos := make([]network.InterfaceInfo, 0, len(interfaces)) 221 for i, iface := range interfaces { 222 223 // The below works for all types except bonds and their members. 224 parentName := strings.Join(iface.Parents(), "") 225 var nicType network.InterfaceType 226 switch maasInterfaceType(iface.Type()) { 227 case typePhysical: 228 nicType = network.EthernetInterface 229 children := strings.Join(iface.Children(), "") 230 if parentName == "" && len(iface.Children()) == 1 && strings.HasPrefix(children, "bond") { 231 // FIXME: Verify the bond exists, regardless of its name. 232 // This is a bond member, set the parent correctly (from 233 // Juju's perspective) - to the bond itself. 234 parentName = children 235 } 236 case typeBond: 237 parentName = "" 238 nicType = network.BondInterface 239 case typeVLAN: 240 nicType = network.VLAN_8021QInterface 241 } 242 243 vlanTag := 0 244 if iface.VLAN() != nil { 245 vlanTag = iface.VLAN().VID() 246 } 247 nicInfo := network.InterfaceInfo{ 248 DeviceIndex: i, 249 MACAddress: iface.MACAddress(), 250 ProviderId: network.Id(fmt.Sprintf("%v", iface.ID())), 251 VLANTag: vlanTag, 252 InterfaceName: iface.Name(), 253 InterfaceType: nicType, 254 ParentInterfaceName: parentName, 255 Disabled: !iface.Enabled(), 256 NoAutoStart: !iface.Enabled(), 257 } 258 259 if len(iface.Links()) == 0 { 260 logger.Debugf("interface %q has no links", iface.Name()) 261 infos = append(infos, nicInfo) 262 continue 263 } 264 265 for _, link := range iface.Links() { 266 nicInfo.ConfigType = maasLinkToInterfaceConfigType(link.Mode()) 267 268 if link.IPAddress() == "" && link.Subnet() == nil { 269 logger.Debugf("interface %q link %d has neither subnet nor address", iface.Name(), link.ID()) 270 infos = append(infos, nicInfo) 271 } else { 272 // We set it here initially without a space, just so we don't 273 // lose it when we have no linked subnet below. 274 nicInfo.Address = network.NewAddress(link.IPAddress()) 275 nicInfo.ProviderAddressId = network.Id(fmt.Sprintf("%v", link.ID())) 276 } 277 278 if link.Subnet() == nil { 279 logger.Debugf("interface %q link %d missing subnet", iface.Name(), link.ID()) 280 infos = append(infos, nicInfo) 281 continue 282 } 283 284 sub := link.Subnet() 285 nicInfo.CIDR = sub.CIDR() 286 nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID())) 287 nicInfo.ProviderVLANId = network.Id(fmt.Sprintf("%v", sub.VLAN().ID())) 288 289 // Now we know the subnet and space, we can update the address to 290 // store the space with it. 291 nicInfo.Address = network.NewAddressOnSpace(sub.Space(), link.IPAddress()) 292 spaceId, ok := subnetsMap[string(sub.CIDR())] 293 if !ok { 294 // The space we found is not recognised, no 295 // provider id available. 296 logger.Warningf("interface %q link %d has unrecognised space %q", iface.Name(), link.ID(), sub.Space()) 297 } else { 298 nicInfo.Address.SpaceProviderId = spaceId 299 nicInfo.ProviderSpaceId = spaceId 300 } 301 302 gwAddr := network.NewAddressOnSpace(sub.Space(), sub.Gateway()) 303 nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space(), sub.DNSServers()...) 304 if ok { 305 gwAddr.SpaceProviderId = spaceId 306 for i := range nicInfo.DNSServers { 307 nicInfo.DNSServers[i].SpaceProviderId = spaceId 308 } 309 } 310 nicInfo.GatewayAddress = gwAddr 311 nicInfo.MTU = sub.VLAN().MTU() 312 313 // Each link we represent as a separate InterfaceInfo, but with the 314 // same name and device index, just different addres, subnet, etc. 315 infos = append(infos, nicInfo) 316 } 317 } 318 return infos, nil 319 } 320 321 // NetworkInterfaces implements Environ.NetworkInterfaces. 322 func (environ *maasEnviron) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) { 323 inst, err := environ.getInstance(instId) 324 if err != nil { 325 return nil, errors.Trace(err) 326 } 327 subnetsMap, err := environ.subnetToSpaceIds() 328 if err != nil { 329 return nil, errors.Trace(err) 330 } 331 if environ.usingMAAS2() { 332 return maas2NetworkInterfaces(inst.(*maas2Instance), subnetsMap) 333 } else { 334 mi := inst.(*maas1Instance) 335 return maasObjectNetworkInterfaces(mi.maasObject, subnetsMap) 336 } 337 } 338 339 func maasLinkToInterfaceConfigType(mode string) network.InterfaceConfigType { 340 switch maasLinkMode(mode) { 341 case modeUnknown: 342 return network.ConfigUnknown 343 case modeDHCP: 344 return network.ConfigDHCP 345 case modeStatic, modeAuto: 346 return network.ConfigStatic 347 case modeLinkUp: 348 default: 349 } 350 351 return network.ConfigManual 352 }