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)
188
return env.controllerInstances2(controllerUUID)
191
func (env *maasEnviron) controllerInstances1(controllerUUID string) ([]instance.Id, error) {
185
192
return common.ProviderStateInstances(env.Storage())
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,
203
return nil, errors.Trace(err)
205
if len(instances) == 0 {
206
return nil, environs.ErrNotBootstrapped
208
ids := make([]instance.Id, len(instances))
209
for i := range instances {
210
ids[i] = instances[i].Id()
188
215
// ecfg returns the environment's maasModelConfig, and protects it with a
190
217
func (env *maasEnviron) ecfg() *maasModelConfig {
244
271
return errors.Trace(err)
246
273
env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient)
247
caps, err := GetCapabilities(env.maasClientUnlocked)
274
caps, err := GetCapabilities(env.maasClientUnlocked, maasServer)
249
276
return errors.Trace(err)
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")
255
282
return errors.Trace(err)
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
580
607
result, err = version.CallGet("", 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)"
615
return caps, errors.NewNotSupported(nil, message)
949
981
return nil, errors.Trace(err)
952
cloudcfg, err := environ.newCloudinitConfig(hostname, series)
954
return nil, errors.Trace(err)
984
subnetsMap, err := environ.subnetToSpaceIds()
986
return nil, errors.Trace(err)
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)
994
return nil, errors.Trace(err)
997
cloudcfg, err := environ.newCloudinitConfig(hostname, series, interfaceNamesToBridge)
999
return nil, errors.Trace(err)
956
1002
userdata, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, MAASRenderer{})
958
1004
return nil, errors.Annotatef(err, "could not compose userdata for bootstrap node")
960
1006
logger.Debugf("maas user data; %d bytes", len(userdata))
962
subnetsMap, err := environ.subnetToSpaceIds()
964
return nil, errors.Trace(err)
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
979
1021
interfaces, err = maasObjectNetworkInterfaces(startedNode, subnetsMap)
981
1023
return nil, errors.Trace(err)
1025
environ.tagInstance1(inst1, args.InstanceConfig)
984
startedInst, err := environ.startNode2(*inst.(*maas2Instance), series, userdata)
1027
inst2 := inst.(*maas2Instance)
1028
startedInst, err := environ.startNode2(*inst2, series, userdata)
986
1030
return nil, errors.Trace(err)
990
1034
return nil, errors.Trace(err)
1036
environ.tagInstance2(inst2, args.InstanceConfig)
993
1038
logger.Debugf("started instance %q", inst.Id())
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)
1001
1040
requestedVolumes := make([]names.VolumeTag, len(args.Volumes))
1002
1041
for i, v := range args.Volumes {
1003
1042
requestedVolumes[i] = v.Tag
1065
func instanceConfiguredInterfaceNames(usingMAAS2 bool, inst instance.Instance, subnetsMap map[string]network.Id) ([]string, error) {
1067
interfaces []network.InterfaceInfo
1071
inst1 := inst.(*maas1Instance)
1072
interfaces, err = maasObjectNetworkInterfaces(inst1.maasObject, subnetsMap)
1074
return nil, errors.Trace(err)
1077
inst2 := inst.(*maas2Instance)
1078
interfaces, err = maas2NetworkInterfaces(inst2, subnetsMap)
1080
return nil, errors.Trace(err)
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.
1091
switch iface.ConfigType {
1092
case network.ConfigUnknown, network.ConfigManual:
1093
continue // link is unconfigured
1096
finalName := iface.InterfaceName
1097
numAliases, seen := nameToNumAliases[iface.InterfaceName]
1099
nameToNumAliases[iface.InterfaceName] = 0
1101
numAliases++ // aliases start from 1
1102
finalName += fmt.Sprintf(":%d", numAliases)
1103
nameToNumAliases[iface.InterfaceName] = numAliases
1106
linkedNames = append(linkedNames, finalName)
1108
systemID := extractSystemId(inst.Id())
1109
logger.Infof("interface names to bridge for node %q: %v", systemID, linkedNames)
1111
return linkedNames, nil
1114
func (environ *maasEnviron) tagInstance1(inst *maas1Instance, instanceConfig *instancecfg.InstanceConfig) {
1115
if !multiwatcher.AnyJobNeedsState(instanceConfig.Jobs...) {
1118
err := common.AddStateInstance(environ.Storage(), inst.Id())
1120
logger.Errorf("could not record instance in provider-state: %v", err)
1124
func (environ *maasEnviron) tagInstance2(inst *maas2Instance, instanceConfig *instancecfg.InstanceConfig) {
1125
err := inst.machine.SetOwnerData(instanceConfig.Tags)
1127
logger.Errorf("could not set owner data for instance: %v", err)
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)
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
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
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
1265
1371
bridgeScriptPath,
1266
1372
instancecfg.DefaultBridgePrefix,
1267
1373
instancecfg.DefaultBridgeName,
1268
"/etc/network/interfaces")
1374
"/etc/network/interfaces",
1375
strings.Join(interfacesToBridge, " "),
1271
func renderEtcNetworkInterfacesScript() string {
1272
return setupJujuNetworking()
1379
func renderEtcNetworkInterfacesScript(interfacesToBridge ...string) string {
1380
return setupJujuNetworking(interfacesToBridge)
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
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)
2068
return nil, errors.Annotatef(err, "cannot update device interface %q", interfaces[0].Name)
2173
primaryNICVLANID, hasSubnet := subnetCIDRToVLANID[primaryNICSubnetCIDR]
2175
updatedPrimaryNIC, err := env.updateDeviceInterface(deviceID, primaryNICID, primaryNICName, primaryMACAddress, primaryNICVLANID)
2177
return nil, errors.Annotatef(err, "cannot update device interface %q", interfaces[0].Name)
2179
logger.Debugf("device %q primary interface %q updated: %+v", containerDevice.SystemID, primaryNICName, updatedPrimaryNIC)
2070
logger.Debugf("device %q primary interface %q updated: %+v", containerDevice.SystemID, primaryNICName, updatedPrimaryNIC)
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]
2190
logger.Warningf("NIC %v has no subnet - setting to manual and using untagged VLAN", nic.InterfaceName)
2191
nicVLANID = primaryNICVLANID
2193
logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, nic.CIDR)
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)
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)
2216
logger.Debugf("linked device interface to subnet: %+v", linkedInterface)
2095
logger.Debugf("linked device interface to subnet: %+v", linkedInterface)
2097
2220
finalInterfaces, err := env.deviceInterfaceInfo(deviceID, nameToParentName)
2098
2221
if err != nil {
2099
2222
return nil, errors.Annotate(err, "cannot get device interfaces")
2101
2224
logger.Debugf("allocated device interfaces: %+v", finalInterfaces)
2102
2226
return finalInterfaces, nil
2128
2252
logger.Debugf("primary device NIC prepared info: %+v", primaryNICInfo)
2130
2254
primaryNICSubnetCIDR := primaryNICInfo.CIDR
2131
subnet, ok := subnetCIDRToSubnet[primaryNICSubnetCIDR]
2133
return nil, errors.Errorf("primary NIC subnet %v not found", primaryNICSubnetCIDR)
2255
subnet, hasSubnet := subnetCIDRToSubnet[primaryNICSubnetCIDR]
2257
logger.Debugf("primary device NIC %q has no linked subnet - leaving unconfigured", primaryNICInfo.InterfaceName)
2135
2259
primaryMACAddress := primaryNICInfo.MACAddress
2136
2260
args := gomaasapi.MachinesArgs{
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)
2292
primaryNICVLAN := interface_set[0].VLAN()
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]
2175
return nil, errors.Errorf("NIC %v subnet %v not found", nic.InterfaceName, nic.CIDR)
2177
createdNIC, err := device.CreateInterface(
2178
gomaasapi.CreateInterfaceArgs{
2179
Name: nic.InterfaceName,
2180
MACAddress: nic.MACAddress,
2181
VLAN: subnet.VLAN(),
2298
createArgs := gomaasapi.CreateInterfaceArgs{
2299
Name: nic.InterfaceName,
2301
MACAddress: nic.MACAddress,
2304
subnet, knownSubnet := subnetCIDRToSubnet[nic.CIDR]
2306
logger.Warningf("NIC %v has no subnet - setting to manual and using untagged VLAN", nic.InterfaceName)
2307
createArgs.VLAN = primaryNICVLAN
2309
createArgs.VLAN = subnet.VLAN()
2310
logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, subnet.CIDR())
2313
createdNIC, err := device.CreateInterface(createArgs)
2183
2314
if err != nil {
2184
2315
return nil, errors.Annotate(err, "creating device interface")
2186
2317
logger.Debugf("created device interface: %+v", createdNIC)
2188
2323
linkArgs := gomaasapi.LinkSubnetArgs{
2189
2324
Mode: gomaasapi.LinkModeStatic,
2190
2325
Subnet: subnet,
2192
err = createdNIC.LinkSubnet(linkArgs)
2194
return nil, errors.Annotate(err, "cannot link device interface to subnet")
2328
if err := createdNIC.LinkSubnet(linkArgs); err != nil {
2329
logger.Warningf("linking NIC %v to subnet %v failed: %v", nic.InterfaceName, subnet.CIDR(), err)
2331
logger.Debugf("linked device interface to subnet: %+v", createdNIC)
2196
logger.Debugf("linked device interface to subnet: %+v", createdNIC)
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")
2203
2340
logger.Debugf("allocated device interfaces: %+v", finalInterfaces)
2204
2342
return finalInterfaces, nil