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