~juju-qa/ubuntu/xenial/juju/2.0-rc2

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/maas/environ.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
        "github.com/juju/juju/environs"
30
30
        "github.com/juju/juju/environs/config"
31
31
        "github.com/juju/juju/environs/storage"
 
32
        "github.com/juju/juju/environs/tags"
32
33
        "github.com/juju/juju/instance"
33
34
        "github.com/juju/juju/network"
34
35
        "github.com/juju/juju/provider/common"
181
182
 
182
183
// ControllerInstances is specified in the Environ interface.
183
184
func (env *maasEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) {
184
 
        // TODO(wallyworld) - tag instances with controller UUID so we can use that
 
185
        if !env.usingMAAS2() {
 
186
                return env.controllerInstances1(controllerUUID)
 
187
        }
 
188
        return env.controllerInstances2(controllerUUID)
 
189
}
 
190
 
 
191
func (env *maasEnviron) controllerInstances1(controllerUUID string) ([]instance.Id, error) {
185
192
        return common.ProviderStateInstances(env.Storage())
186
193
}
187
194
 
 
195
func (env *maasEnviron) controllerInstances2(controllerUUID string) ([]instance.Id, error) {
 
196
        instances, err := env.instances2(gomaasapi.MachinesArgs{
 
197
                OwnerData: map[string]string{
 
198
                        tags.JujuIsController: "true",
 
199
                        tags.JujuController:   controllerUUID,
 
200
                },
 
201
        })
 
202
        if err != nil {
 
203
                return nil, errors.Trace(err)
 
204
        }
 
205
        if len(instances) == 0 {
 
206
                return nil, environs.ErrNotBootstrapped
 
207
        }
 
208
        ids := make([]instance.Id, len(instances))
 
209
        for i := range instances {
 
210
                ids[i] = instances[i].Id()
 
211
        }
 
212
        return ids, nil
 
213
}
 
214
 
188
215
// ecfg returns the environment's maasModelConfig, and protects it with a
189
216
// mutex.
190
217
func (env *maasEnviron) ecfg() *maasModelConfig {
244
271
                        return errors.Trace(err)
245
272
                }
246
273
                env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient)
247
 
                caps, err := GetCapabilities(env.maasClientUnlocked)
 
274
                caps, err := GetCapabilities(env.maasClientUnlocked, maasServer)
248
275
                if err != nil {
249
276
                        return errors.Trace(err)
250
277
                }
251
278
                if !caps.Contains(capNetworkDeploymentUbuntu) {
252
 
                        return errors.NotSupportedf("MAAS 1.9 or more recent is required")
 
279
                        return errors.NewNotSupported(nil, "MAAS 1.9 or more recent is required")
253
280
                }
254
281
        case err != nil:
255
282
                return errors.Trace(err)
570
597
 
571
598
// getCapabilities asks the MAAS server for its capabilities, if
572
599
// supported by the server.
573
 
func getCapabilities(client *gomaasapi.MAASObject) (set.Strings, error) {
 
600
func getCapabilities(client *gomaasapi.MAASObject, serverURL string) (set.Strings, error) {
574
601
        caps := make(set.Strings)
575
602
        var result gomaasapi.JSONObject
576
603
        var err error
580
607
                result, err = version.CallGet("", nil)
581
608
                if err != nil {
582
609
                        if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == 404 {
583
 
                                return caps, errors.NotSupportedf("MAAS version 1.9 or more recent is required")
 
610
                                message := "could not connect to MAAS controller - check the endpoint is correct"
 
611
                                trimmedUrl := strings.TrimRight(serverURL, "/")
 
612
                                if !strings.HasSuffix(trimmedUrl, "/MAAS") {
 
613
                                        message += " (it normally ends with /MAAS)"
 
614
                                }
 
615
                                return caps, errors.NewNotSupported(nil, message)
584
616
                        }
585
617
                } else {
586
618
                        break
949
981
                return nil, errors.Trace(err)
950
982
        }
951
983
 
952
 
        cloudcfg, err := environ.newCloudinitConfig(hostname, series)
953
 
        if err != nil {
954
 
                return nil, errors.Trace(err)
955
 
        }
 
984
        subnetsMap, err := environ.subnetToSpaceIds()
 
985
        if err != nil {
 
986
                return nil, errors.Trace(err)
 
987
        }
 
988
 
 
989
        // We need to extract the names of all interfaces of the selected node,
 
990
        // which are both linked to subnets, and have an IP address, in order to
 
991
        // pass those to the bridge script.
 
992
        interfaceNamesToBridge, err := instanceConfiguredInterfaceNames(environ.usingMAAS2(), inst, subnetsMap)
 
993
        if err != nil {
 
994
                return nil, errors.Trace(err)
 
995
        }
 
996
 
 
997
        cloudcfg, err := environ.newCloudinitConfig(hostname, series, interfaceNamesToBridge)
 
998
        if err != nil {
 
999
                return nil, errors.Trace(err)
 
1000
        }
 
1001
 
956
1002
        userdata, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, MAASRenderer{})
957
1003
        if err != nil {
958
1004
                return nil, errors.Annotatef(err, "could not compose userdata for bootstrap node")
959
1005
        }
960
1006
        logger.Debugf("maas user data; %d bytes", len(userdata))
961
1007
 
962
 
        subnetsMap, err := environ.subnetToSpaceIds()
963
 
        if err != nil {
964
 
                return nil, errors.Trace(err)
965
 
        }
966
1008
        var interfaces []network.InterfaceInfo
967
1009
        if !environ.usingMAAS2() {
968
1010
                inst1 := inst.(*maas1Instance)
974
1016
                // assigned IP addresses, even when NICs are set to "auto" instead of
975
1017
                // "static". So instead of selectedNode, which only contains the
976
1018
                // acquire-time details (no IP addresses for NICs set to "auto" vs
977
 
                // "static"), we use the up-to-date startedNode response to get the
 
1019
                // "static"),e we use the up-to-date startedNode response to get the
978
1020
                // interfaces.
979
1021
                interfaces, err = maasObjectNetworkInterfaces(startedNode, subnetsMap)
980
1022
                if err != nil {
981
1023
                        return nil, errors.Trace(err)
982
1024
                }
 
1025
                environ.tagInstance1(inst1, args.InstanceConfig)
983
1026
        } else {
984
 
                startedInst, err := environ.startNode2(*inst.(*maas2Instance), series, userdata)
 
1027
                inst2 := inst.(*maas2Instance)
 
1028
                startedInst, err := environ.startNode2(*inst2, series, userdata)
985
1029
                if err != nil {
986
1030
                        return nil, errors.Trace(err)
987
1031
                }
989
1033
                if err != nil {
990
1034
                        return nil, errors.Trace(err)
991
1035
                }
 
1036
                environ.tagInstance2(inst2, args.InstanceConfig)
992
1037
        }
993
1038
        logger.Debugf("started instance %q", inst.Id())
994
1039
 
995
 
        if multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...) {
996
 
                if err := common.AddStateInstance(environ.Storage(), inst.Id()); err != nil {
997
 
                        logger.Errorf("could not record instance in provider-state: %v", err)
998
 
                }
999
 
        }
1000
 
 
1001
1040
        requestedVolumes := make([]names.VolumeTag, len(args.Volumes))
1002
1041
        for i, v := range args.Volumes {
1003
1042
                requestedVolumes[i] = v.Tag
1023
1062
        }, nil
1024
1063
}
1025
1064
 
 
1065
func instanceConfiguredInterfaceNames(usingMAAS2 bool, inst instance.Instance, subnetsMap map[string]network.Id) ([]string, error) {
 
1066
        var (
 
1067
                interfaces []network.InterfaceInfo
 
1068
                err        error
 
1069
        )
 
1070
        if !usingMAAS2 {
 
1071
                inst1 := inst.(*maas1Instance)
 
1072
                interfaces, err = maasObjectNetworkInterfaces(inst1.maasObject, subnetsMap)
 
1073
                if err != nil {
 
1074
                        return nil, errors.Trace(err)
 
1075
                }
 
1076
        } else {
 
1077
                inst2 := inst.(*maas2Instance)
 
1078
                interfaces, err = maas2NetworkInterfaces(inst2, subnetsMap)
 
1079
                if err != nil {
 
1080
                        return nil, errors.Trace(err)
 
1081
                }
 
1082
        }
 
1083
 
 
1084
        nameToNumAliases := make(map[string]int)
 
1085
        var linkedNames []string
 
1086
        for _, iface := range interfaces {
 
1087
                if iface.CIDR == "" { // CIDR comes from a linked subnet.
 
1088
                        continue
 
1089
                }
 
1090
 
 
1091
                switch iface.ConfigType {
 
1092
                case network.ConfigUnknown, network.ConfigManual:
 
1093
                        continue // link is unconfigured
 
1094
                }
 
1095
 
 
1096
                finalName := iface.InterfaceName
 
1097
                numAliases, seen := nameToNumAliases[iface.InterfaceName]
 
1098
                if !seen {
 
1099
                        nameToNumAliases[iface.InterfaceName] = 0
 
1100
                } else {
 
1101
                        numAliases++ // aliases start from 1
 
1102
                        finalName += fmt.Sprintf(":%d", numAliases)
 
1103
                        nameToNumAliases[iface.InterfaceName] = numAliases
 
1104
                }
 
1105
 
 
1106
                linkedNames = append(linkedNames, finalName)
 
1107
        }
 
1108
        systemID := extractSystemId(inst.Id())
 
1109
        logger.Infof("interface names to bridge for node %q: %v", systemID, linkedNames)
 
1110
 
 
1111
        return linkedNames, nil
 
1112
}
 
1113
 
 
1114
func (environ *maasEnviron) tagInstance1(inst *maas1Instance, instanceConfig *instancecfg.InstanceConfig) {
 
1115
        if !multiwatcher.AnyJobNeedsState(instanceConfig.Jobs...) {
 
1116
                return
 
1117
        }
 
1118
        err := common.AddStateInstance(environ.Storage(), inst.Id())
 
1119
        if err != nil {
 
1120
                logger.Errorf("could not record instance in provider-state: %v", err)
 
1121
        }
 
1122
}
 
1123
 
 
1124
func (environ *maasEnviron) tagInstance2(inst *maas2Instance, instanceConfig *instancecfg.InstanceConfig) {
 
1125
        err := inst.machine.SetOwnerData(instanceConfig.Tags)
 
1126
        if err != nil {
 
1127
                logger.Errorf("could not set owner data for instance: %v", err)
 
1128
        }
 
1129
}
 
1130
 
1026
1131
func (environ *maasEnviron) waitForNodeDeployment(id instance.Id, timeout time.Duration) error {
1027
1132
        if environ.usingMAAS2() {
1028
1133
                return environ.waitForNodeDeployment2(id, timeout)
1065
1170
                        return errors.Trace(err)
1066
1171
                }
1067
1172
                stat := machine.Status()
1068
 
                if stat.Status == status.StatusRunning {
 
1173
                if stat.Status == status.Running {
1069
1174
                        return nil
1070
1175
                }
1071
 
                if stat.Status == status.StatusProvisioningError {
 
1176
                if stat.Status == status.ProvisioningError {
1072
1177
                        return errors.Errorf("instance %q failed to deploy", id)
1073
1178
 
1074
1179
                }
1218
1323
 
1219
1324
// setupJujuNetworking returns a string representing the script to run
1220
1325
// in order to prepare the Juju-specific networking config on a node.
1221
 
func setupJujuNetworking() string {
 
1326
func setupJujuNetworking(interfacesToBridge []string) string {
1222
1327
        // For ubuntu series < xenial we prefer python2 over python3
1223
1328
        // as we don't want to invalidate lots of testing against
1224
1329
        // known cloud-image contents. A summary of Ubuntu releases
1247
1352
 
1248
1353
if [ ! -z "${juju_networking_preferred_python_binary:-}" ]; then
1249
1354
    if [ -f %[1]q ]; then
1250
 
# We are sharing this code between master, maas-spaces2 and 1.25.
1251
 
# For the moment we want master and 1.25 to not bridge all interfaces.
 
1355
# We are sharing this code between 2.0 and 1.25.
 
1356
# For the moment we want 2.0 to bridge all interfaces linked
 
1357
# to a subnet, while for 1.25 we only bridge the default route interface.
1252
1358
# This setting allows us to easily switch the behaviour when merging
1253
1359
# the code between those various branches.
1254
 
        juju_bridge_all_interfaces=1
1255
 
        if [ $juju_bridge_all_interfaces -eq 1 ]; then
1256
 
            $juju_networking_preferred_python_binary %[1]q --bridge-prefix=%[2]q --one-time-backup --activate %[4]q
 
1360
        juju_bridge_linked_interfaces=1
 
1361
        if [ $juju_bridge_linked_interfaces -eq 1 ]; then
 
1362
            $juju_networking_preferred_python_binary %[1]q --bridge-prefix=%[2]q --interfaces-to-bridge=%[5]q --one-time-backup --activate %[4]q
1257
1363
        else
1258
1364
            juju_ipv4_interface_to_bridge=$(ip -4 route list exact default | head -n1 | cut -d' ' -f5)
1259
 
            $juju_networking_preferred_python_binary %[1]q --bridge-name=%[3]q --interface-to-bridge="${juju_ipv4_interface_to_bridge:-unknown}" --one-time-backup --activate %[4]q
 
1365
            $juju_networking_preferred_python_binary %[1]q --bridge-name=%[3]q --interfaces-to-bridge="${juju_ipv4_interface_to_bridge:-unknown}" --one-time-backup --activate %[4]q
1260
1366
        fi
1261
1367
    fi
1262
1368
else
1265
1371
                bridgeScriptPath,
1266
1372
                instancecfg.DefaultBridgePrefix,
1267
1373
                instancecfg.DefaultBridgeName,
1268
 
                "/etc/network/interfaces")
 
1374
                "/etc/network/interfaces",
 
1375
                strings.Join(interfacesToBridge, " "),
 
1376
        )
1269
1377
}
1270
1378
 
1271
 
func renderEtcNetworkInterfacesScript() string {
1272
 
        return setupJujuNetworking()
 
1379
func renderEtcNetworkInterfacesScript(interfacesToBridge ...string) string {
 
1380
        return setupJujuNetworking(interfacesToBridge)
1273
1381
}
1274
1382
 
1275
1383
// newCloudinitConfig creates a cloudinit.Config structure suitable as a base
1276
1384
// for initialising a MAAS node.
1277
 
func (environ *maasEnviron) newCloudinitConfig(hostname, forSeries string) (cloudinit.CloudConfig, error) {
 
1385
func (environ *maasEnviron) newCloudinitConfig(hostname, forSeries string, interfacesToBridge []string) (cloudinit.CloudConfig, error) {
1278
1386
        cloudcfg, err := cloudinit.New(forSeries)
1279
1387
        if err != nil {
1280
1388
                return nil, err
1306
1414
                }
1307
1415
                cloudcfg.AddPackage("bridge-utils")
1308
1416
                cloudcfg.AddBootTextFile(bridgeScriptPath, bridgeScriptPython, 0755)
1309
 
                cloudcfg.AddScripts(setupJujuNetworking())
 
1417
                cloudcfg.AddScripts(setupJujuNetworking(interfacesToBridge))
1310
1418
        }
1311
1419
        return cloudcfg, nil
1312
1420
}
2062
2170
        primaryNICName := interfaces[0].Name
2063
2171
        primaryNICID := strconv.Itoa(interfaces[0].ID)
2064
2172
        primaryNICSubnetCIDR := primaryNICInfo.CIDR
2065
 
        primaryNICVLANID := subnetCIDRToVLANID[primaryNICSubnetCIDR]
2066
 
        updatedPrimaryNIC, err := env.updateDeviceInterface(deviceID, primaryNICID, primaryNICName, primaryMACAddress, primaryNICVLANID)
2067
 
        if err != nil {
2068
 
                return nil, errors.Annotatef(err, "cannot update device interface %q", interfaces[0].Name)
 
2173
        primaryNICVLANID, hasSubnet := subnetCIDRToVLANID[primaryNICSubnetCIDR]
 
2174
        if hasSubnet {
 
2175
                updatedPrimaryNIC, err := env.updateDeviceInterface(deviceID, primaryNICID, primaryNICName, primaryMACAddress, primaryNICVLANID)
 
2176
                if err != nil {
 
2177
                        return nil, errors.Annotatef(err, "cannot update device interface %q", interfaces[0].Name)
 
2178
                }
 
2179
                logger.Debugf("device %q primary interface %q updated: %+v", containerDevice.SystemID, primaryNICName, updatedPrimaryNIC)
2069
2180
        }
2070
 
        logger.Debugf("device %q primary interface %q updated: %+v", containerDevice.SystemID, primaryNICName, updatedPrimaryNIC)
2071
2181
 
2072
2182
        deviceNICIDs := make([]string, len(preparedInfo))
2073
2183
        nameToParentName := make(map[string]string)
2074
2184
        for i, nic := range preparedInfo {
2075
2185
                maasNICID := ""
2076
2186
                nameToParentName[nic.InterfaceName] = nic.ParentInterfaceName
 
2187
                nicVLANID, knownSubnet := subnetCIDRToVLANID[nic.CIDR]
2077
2188
                if nic.InterfaceName != primaryNICName {
2078
 
                        nicVLANID := subnetCIDRToVLANID[nic.CIDR]
 
2189
                        if !knownSubnet {
 
2190
                                logger.Warningf("NIC %v has no subnet - setting to manual and using untagged VLAN", nic.InterfaceName)
 
2191
                                nicVLANID = primaryNICVLANID
 
2192
                        } else {
 
2193
                                logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, nic.CIDR)
 
2194
                        }
 
2195
 
2079
2196
                        createdNIC, err := env.createDeviceInterface(deviceID, nic.InterfaceName, nic.MACAddress, nicVLANID)
2080
2197
                        if err != nil {
2081
2198
                                return nil, errors.Annotate(err, "creating device interface")
2088
2205
                deviceNICIDs[i] = maasNICID
2089
2206
                subnetID := string(nic.ProviderSubnetId)
2090
2207
 
 
2208
                if !knownSubnet {
 
2209
                        continue
 
2210
                }
 
2211
 
2091
2212
                linkedInterface, err := env.linkDeviceInterfaceToSubnet(deviceID, maasNICID, subnetID, modeStatic)
2092
2213
                if err != nil {
2093
 
                        return nil, errors.Annotate(err, "cannot link device interface to subnet")
 
2214
                        logger.Warningf("linking NIC %v to subnet %v failed: %v", nic.InterfaceName, nic.CIDR, err)
 
2215
                } else {
 
2216
                        logger.Debugf("linked device interface to subnet: %+v", linkedInterface)
2094
2217
                }
2095
 
                logger.Debugf("linked device interface to subnet: %+v", linkedInterface)
2096
2218
        }
 
2219
 
2097
2220
        finalInterfaces, err := env.deviceInterfaceInfo(deviceID, nameToParentName)
2098
2221
        if err != nil {
2099
2222
                return nil, errors.Annotate(err, "cannot get device interfaces")
2100
2223
        }
2101
2224
        logger.Debugf("allocated device interfaces: %+v", finalInterfaces)
 
2225
 
2102
2226
        return finalInterfaces, nil
2103
2227
}
2104
2228
 
2128
2252
        logger.Debugf("primary device NIC prepared info: %+v", primaryNICInfo)
2129
2253
 
2130
2254
        primaryNICSubnetCIDR := primaryNICInfo.CIDR
2131
 
        subnet, ok := subnetCIDRToSubnet[primaryNICSubnetCIDR]
2132
 
        if !ok {
2133
 
                return nil, errors.Errorf("primary NIC subnet %v not found", primaryNICSubnetCIDR)
 
2255
        subnet, hasSubnet := subnetCIDRToSubnet[primaryNICSubnetCIDR]
 
2256
        if !hasSubnet {
 
2257
                logger.Debugf("primary device NIC %q has no linked subnet - leaving unconfigured", primaryNICInfo.InterfaceName)
2134
2258
        }
2135
2259
        primaryMACAddress := primaryNICInfo.MACAddress
2136
2260
        args := gomaasapi.MachinesArgs{
2152
2276
        createDeviceArgs := gomaasapi.CreateMachineDeviceArgs{
2153
2277
                Hostname:      deviceName,
2154
2278
                MACAddress:    primaryMACAddress,
2155
 
                Subnet:        subnet,
 
2279
                Subnet:        subnet, // can be nil
2156
2280
                InterfaceName: primaryNICName,
2157
2281
        }
2158
2282
        device, err := machine.CreateDevice(createDeviceArgs)
2163
2287
        if len(interface_set) != 1 {
2164
2288
                // Shouldn't be possible as machine.CreateDevice always returns us
2165
2289
                // one interface.
2166
 
                return nil, errors.Errorf("unexpected number of interfaces inresponse from creating device: %v", interface_set)
 
2290
                return nil, errors.Errorf("unexpected number of interfaces in response from creating device: %v", interface_set)
2167
2291
        }
 
2292
        primaryNICVLAN := interface_set[0].VLAN()
2168
2293
 
2169
2294
        nameToParentName := make(map[string]string)
2170
2295
        for _, nic := range preparedInfo {
2171
2296
                nameToParentName[nic.InterfaceName] = nic.ParentInterfaceName
2172
2297
                if nic.InterfaceName != primaryNICName {
2173
 
                        subnet, ok := subnetCIDRToSubnet[nic.CIDR]
2174
 
                        if !ok {
2175
 
                                return nil, errors.Errorf("NIC %v subnet %v not found", nic.InterfaceName, nic.CIDR)
2176
 
                        }
2177
 
                        createdNIC, err := device.CreateInterface(
2178
 
                                gomaasapi.CreateInterfaceArgs{
2179
 
                                        Name:       nic.InterfaceName,
2180
 
                                        MACAddress: nic.MACAddress,
2181
 
                                        VLAN:       subnet.VLAN(),
2182
 
                                })
 
2298
                        createArgs := gomaasapi.CreateInterfaceArgs{
 
2299
                                Name:       nic.InterfaceName,
 
2300
                                MTU:        nic.MTU,
 
2301
                                MACAddress: nic.MACAddress,
 
2302
                        }
 
2303
 
 
2304
                        subnet, knownSubnet := subnetCIDRToSubnet[nic.CIDR]
 
2305
                        if !knownSubnet {
 
2306
                                logger.Warningf("NIC %v has no subnet - setting to manual and using untagged VLAN", nic.InterfaceName)
 
2307
                                createArgs.VLAN = primaryNICVLAN
 
2308
                        } else {
 
2309
                                createArgs.VLAN = subnet.VLAN()
 
2310
                                logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, subnet.CIDR())
 
2311
                        }
 
2312
 
 
2313
                        createdNIC, err := device.CreateInterface(createArgs)
2183
2314
                        if err != nil {
2184
2315
                                return nil, errors.Annotate(err, "creating device interface")
2185
2316
                        }
2186
2317
                        logger.Debugf("created device interface: %+v", createdNIC)
2187
2318
 
 
2319
                        if !knownSubnet {
 
2320
                                continue
 
2321
                        }
 
2322
 
2188
2323
                        linkArgs := gomaasapi.LinkSubnetArgs{
2189
2324
                                Mode:   gomaasapi.LinkModeStatic,
2190
2325
                                Subnet: subnet,
2191
2326
                        }
2192
 
                        err = createdNIC.LinkSubnet(linkArgs)
2193
 
                        if err != nil {
2194
 
                                return nil, errors.Annotate(err, "cannot link device interface to subnet")
 
2327
 
 
2328
                        if err := createdNIC.LinkSubnet(linkArgs); err != nil {
 
2329
                                logger.Warningf("linking NIC %v to subnet %v failed: %v", nic.InterfaceName, subnet.CIDR(), err)
 
2330
                        } else {
 
2331
                                logger.Debugf("linked device interface to subnet: %+v", createdNIC)
2195
2332
                        }
2196
 
                        logger.Debugf("linked device interface to subnet: %+v", createdNIC)
2197
2333
                }
2198
2334
        }
 
2335
 
2199
2336
        finalInterfaces, err := env.deviceInterfaceInfo2(device.SystemID(), nameToParentName)
2200
2337
        if err != nil {
2201
2338
                return nil, errors.Annotate(err, "cannot get device interfaces")
2202
2339
        }
2203
2340
        logger.Debugf("allocated device interfaces: %+v", finalInterfaces)
 
2341
 
2204
2342
        return finalInterfaces, nil
2205
2343
}
2206
2344