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