// ManipulateFiles exercises the /api/1.0/nodes/ API endpoint. Most precisely, // it lists the existing nodes, creates a new node, updates it and then // deletes it. func ManipulateNodes(maas *gomaasapi.MAASObject) { nodeListing := maas.GetSubObject("nodes") // List nodes. fmt.Println("Fetching list of nodes...") listNodeObjects, err := nodeListing.CallGet("list", url.Values{}) checkError(err) listNodes, err := listNodeObjects.GetArray() checkError(err) fmt.Printf("Got list of %v nodes\n", len(listNodes)) for index, nodeObj := range listNodes { node, err := nodeObj.GetMAASObject() checkError(err) hostname, err := node.GetField("hostname") checkError(err) fmt.Printf("Node #%d is named '%v' (%v)\n", index, hostname, node.URL()) } // Create a node. fmt.Println("Creating a new node...") params := url.Values{"architecture": {"i386/generic"}, "mac_addresses": {"AA:BB:CC:DD:EE:FF"}} newNodeObj, err := nodeListing.CallPost("new", params) checkError(err) newNode, err := newNodeObj.GetMAASObject() checkError(err) newNodeName, err := newNode.GetField("hostname") checkError(err) fmt.Printf("New node created: %s (%s)\n", newNodeName, newNode.URL()) // Update the new node. fmt.Println("Updating the new node...") updateParams := url.Values{"hostname": {"mynewname"}} newNodeObj2, err := newNode.Update(updateParams) checkError(err) newNodeName2, err := newNodeObj2.GetField("hostname") checkError(err) fmt.Printf("New node updated, now named: %s\n", newNodeName2) // Count the nodes. listNodeObjects2, err := nodeListing.CallGet("list", url.Values{}) checkError(err) listNodes2, err := listNodeObjects2.GetArray() checkError(err) fmt.Printf("We've got %v nodes\n", len(listNodes2)) // Delete the new node. fmt.Println("Deleting the new node...") errDelete := newNode.Delete() checkError(errDelete) // Count the nodes. listNodeObjects3, err := nodeListing.CallGet("list", url.Values{}) checkError(err) listNodes3, err := listNodeObjects3.GetArray() checkError(err) fmt.Printf("We've got %v nodes\n", len(listNodes3)) }
// ManipulateFiles exercises the /api/1.0/files/ API endpoint. Most precisely, // it uploads a files and then fetches it, making sure the received content // is the same as the one that was sent. func ManipulateFiles(maas *gomaasapi.MAASObject) { files := maas.GetSubObject("files") fileContent := []byte("test file content") fileName := "filename" filesToUpload := map[string][]byte{"file": fileContent} // Upload a file. fmt.Println("Uploading a file...") _, err := files.CallPostFiles("add", url.Values{"filename": {fileName}}, filesToUpload) checkError(err) fmt.Println("File sent.") // Fetch the file. fmt.Println("Fetching the file...") fileResult, err := files.CallGet("get", url.Values{"filename": {fileName}}) checkError(err) receivedFileContent, err := fileResult.GetBytes() checkError(err) if bytes.Compare(receivedFileContent, fileContent) != 0 { panic("Received content differs from the content sent!") } fmt.Println("Got file.") // Fetch list of files. listFiles, err := files.CallGet("list", url.Values{}) checkError(err) listFilesArray, err := listFiles.GetArray() checkError(err) fmt.Printf("We've got %v file(s)\n", len(listFilesArray)) // Delete the file. fmt.Println("Deleting the file...") fileObject, err := listFilesArray[0].GetMAASObject() checkError(err) errDelete := fileObject.Delete() checkError(errDelete) // Count the files. listFiles, err = files.CallGet("list", url.Values{}) checkError(err) listFilesArray, err = listFiles.GetArray() checkError(err) fmt.Printf("We've got %v file(s)\n", len(listFilesArray)) }
// fetchNodes do a HTTP GET to the MAAS server to query all the nodes func fetchNodes(client *maas.MAASObject) ([]MaasNode, error) { nodeListing := client.GetSubObject("nodes") listNodeObjects, err := nodeListing.CallGet("list", url.Values{}) if checkWarn(err, "unable to get the list of all nodes: %s", err) { return nil, err } listNodes, err := listNodeObjects.GetArray() if checkWarn(err, "unable to get the node objects for the list: %s", err) { return nil, err } var nodes = make([]MaasNode, len(listNodes)) for index, nodeObj := range listNodes { node, err := nodeObj.GetMAASObject() if !checkWarn(err, "unable to retrieve object for node: %s", err) { nodes[index] = MaasNode{node} } } return nodes, nil }
// updateName - changes the name of the MAAS node based on the configuration file func updateNodeName(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error { macs := node.MACs() // Get current node name and strip off domain name current := node.Hostname() if i := strings.IndexRune(current, '.'); i != -1 { current = current[:i] } for _, mac := range macs { if entry, ok := options.Mappings[mac]; ok { if name, ok := entry.(map[string]interface{})["hostname"]; ok && current != name.(string) { nodesObj := client.GetSubObject("nodes") nodeObj := nodesObj.GetSubObject(node.ID()) log.Printf("RENAME '%s' to '%s'\n", node.Hostname(), name.(string)) if !options.Preview { nodeObj.Update(url.Values{"hostname": []string{name.(string)}}) } } } } return nil }
func maasObjectId(maasObject *gomaasapi.MAASObject) instance.Id { // Use the node's 'resource_uri' value. return instance.Id(maasObject.URI().String()) }
// maasObjectNetworkInterfaces implements environs.NetworkInterfaces() using the // new (1.9+) MAAS API, parsing the node details JSON embedded into the given // maasObject to extract all the relevant InterfaceInfo fields. It returns an // error satisfying errors.IsNotSupported() if it cannot find the required // "interface_set" node details field. func maasObjectNetworkInterfaces(maasObject *gomaasapi.MAASObject, subnetsMap map[string]network.Id) ([]network.InterfaceInfo, error) { interfaceSet, ok := maasObject.GetMap()["interface_set"] if !ok || interfaceSet.IsNil() { // This means we're using an older MAAS API. return nil, errors.NotSupportedf("interface_set") } // TODO(dimitern): Change gomaasapi JSONObject to give access to the raw // JSON bytes directly, rather than having to do call MarshalJSON just so // the result can be unmarshaled from it. // // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119311323 rawBytes, err := interfaceSet.MarshalJSON() if err != nil { return nil, errors.Annotate(err, "cannot get interface_set JSON bytes") } interfaces, err := parseInterfaces(rawBytes) if err != nil { return nil, errors.Trace(err) } infos := make([]network.InterfaceInfo, 0, len(interfaces)) for i, iface := range interfaces { // The below works for all types except bonds and their members. parentName := strings.Join(iface.Parents, "") var nicType network.InterfaceType switch iface.Type { case typePhysical: nicType = network.EthernetInterface children := strings.Join(iface.Children, "") if parentName == "" && len(iface.Children) == 1 && strings.HasPrefix(children, "bond") { // FIXME: Verify the bond exists, regardless of its name. // This is a bond member, set the parent correctly (from // Juju's perspective) - to the bond itself. parentName = children } case typeBond: parentName = "" nicType = network.BondInterface case typeVLAN: nicType = network.VLAN_8021QInterface } nicInfo := network.InterfaceInfo{ DeviceIndex: i, MACAddress: iface.MACAddress, ProviderId: network.Id(fmt.Sprintf("%v", iface.ID)), VLANTag: iface.VLAN.VID, InterfaceName: iface.Name, InterfaceType: nicType, ParentInterfaceName: parentName, Disabled: !iface.Enabled, NoAutoStart: !iface.Enabled, } for _, link := range iface.Links { switch link.Mode { case modeUnknown: nicInfo.ConfigType = network.ConfigUnknown case modeDHCP: nicInfo.ConfigType = network.ConfigDHCP case modeStatic, modeLinkUp: nicInfo.ConfigType = network.ConfigStatic default: nicInfo.ConfigType = network.ConfigManual } if link.IPAddress == "" { logger.Debugf("interface %q has no address", iface.Name) } else { // We set it here initially without a space, just so we don't // lose it when we have no linked subnet below. nicInfo.Address = network.NewAddress(link.IPAddress) nicInfo.ProviderAddressId = network.Id(fmt.Sprintf("%v", link.ID)) } if link.Subnet == nil { logger.Debugf("interface %q link %d missing subnet", iface.Name, link.ID) infos = append(infos, nicInfo) continue } sub := link.Subnet nicInfo.CIDR = sub.CIDR nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID)) nicInfo.ProviderVLANId = network.Id(fmt.Sprintf("%v", sub.VLAN.ID)) // Now we know the subnet and space, we can update the address to // store the space with it. nicInfo.Address = network.NewAddressOnSpace(sub.Space, link.IPAddress) spaceId, ok := subnetsMap[string(sub.CIDR)] if !ok { // The space we found is not recognised, no // provider id available. logger.Warningf("interface %q link %d has unrecognised space %q", iface.Name, link.ID, sub.Space) } else { nicInfo.Address.SpaceProviderId = spaceId nicInfo.ProviderSpaceId = spaceId } gwAddr := network.NewAddressOnSpace(sub.Space, sub.GatewayIP) nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space, sub.DNSServers...) if ok { gwAddr.SpaceProviderId = spaceId for i := range nicInfo.DNSServers { nicInfo.DNSServers[i].SpaceProviderId = spaceId } } nicInfo.GatewayAddress = gwAddr nicInfo.MTU = sub.VLAN.MTU // Each link we represent as a separate InterfaceInfo, but with the // same name and device index, just different addres, subnet, etc. infos = append(infos, nicInfo) } } return infos, nil }
// maasObjectNetworkInterfaces implements environs.NetworkInterfaces() using the // new (1.9+) MAAS API, parsing the node details JSON embedded into the given // maasObject to extract all the relevant InterfaceInfo fields. It returns an // error satisfying errors.IsNotSupported() if it cannot find the required // "interface_set" node details field. func maasObjectNetworkInterfaces(maasObject *gomaasapi.MAASObject) ([]network.InterfaceInfo, error) { interfaceSet, ok := maasObject.GetMap()["interface_set"] if !ok || interfaceSet.IsNil() { // This means we're using an older MAAS API. return nil, errors.NotSupportedf("interface_set") } // TODO(dimitern): Change gomaasapi JSONObject to give access to the raw // JSON bytes directly, rather than having to do call MarshalJSON just so // the result can be unmarshaled from it. // // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119311323 rawBytes, err := interfaceSet.MarshalJSON() if err != nil { return nil, errors.Annotate(err, "cannot get interface_set JSON bytes") } interfaces, err := parseInterfaces(rawBytes) if err != nil { return nil, errors.Trace(err) } infos := make([]network.InterfaceInfo, 0, len(interfaces)) for i, iface := range interfaces { nicInfo := network.InterfaceInfo{ DeviceIndex: i, MACAddress: iface.MACAddress, ProviderId: network.Id(fmt.Sprintf("%v", iface.ID)), VLANTag: iface.VLAN.VID, InterfaceName: iface.Name, Disabled: !iface.Enabled, NoAutoStart: !iface.Enabled, // This is not needed anymore, but the provisioner still validates it's set. NetworkName: network.DefaultPrivate, } for _, link := range iface.Links { switch link.Mode { case modeUnknown: nicInfo.ConfigType = network.ConfigUnknown case modeDHCP: nicInfo.ConfigType = network.ConfigDHCP case modeStatic, modeLinkUp: nicInfo.ConfigType = network.ConfigStatic default: nicInfo.ConfigType = network.ConfigManual } if link.IPAddress == "" { logger.Warningf("interface %q has no address", iface.Name) } else { // We set it here initially without a space, just so we don't // lose it when we have no linked subnet below. nicInfo.Address = network.NewAddress(link.IPAddress) } if link.Subnet == nil { logger.Warningf("interface %q link %d missing subnet", iface.Name, link.ID) infos = append(infos, nicInfo) continue } sub := link.Subnet nicInfo.CIDR = sub.CIDR nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID)) // Now we know the subnet and space, we can update the address to // store the space with it. nicInfo.Address = network.NewAddressOnSpace(sub.Space, link.IPAddress) gwAddr := network.NewAddressOnSpace(sub.Space, sub.GatewayIP) nicInfo.GatewayAddress = gwAddr nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space, sub.DNSServers...) nicInfo.MTU = sub.VLAN.MTU // Each link we represent as a separate InterfaceInfo, but with the // same name and device index, just different addres, subnet, etc. infos = append(infos, nicInfo) } } return infos, nil }