github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/hyper-control/netbootHost.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "net" 10 11 imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client" 12 "github.com/Cloud-Foundations/Dominator/lib/errors" 13 libjson "github.com/Cloud-Foundations/Dominator/lib/json" 14 "github.com/Cloud-Foundations/Dominator/lib/log" 15 "github.com/Cloud-Foundations/Dominator/lib/srpc" 16 fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager" 17 hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor" 18 installer_proto "github.com/Cloud-Foundations/Dominator/proto/installer" 19 ) 20 21 type hostAddressType struct { 22 address hyper_proto.Address 23 hostname string 24 } 25 26 type leaseType struct { 27 address hostAddressType 28 subnet *hyper_proto.Subnet 29 } 30 31 func netbootHostSubcommand(args []string, logger log.DebugLogger) error { 32 err := netbootHost(args[0], logger) 33 if err != nil { 34 return fmt.Errorf("Error netbooting host: %s", err) 35 } 36 return nil 37 } 38 39 func findMatchingSubnet(subnets []*hyper_proto.Subnet, 40 ipAddr net.IP) *hyper_proto.Subnet { 41 for _, subnet := range subnets { 42 subnetMask := net.IPMask(subnet.IpMask) 43 subnetAddr := subnet.IpGateway.Mask(subnetMask) 44 if ipAddr.Mask(subnetMask).Equal(subnetAddr) { 45 return subnet 46 } 47 } 48 return nil 49 } 50 51 func getHostAddress(networkEntries []fm_proto.NetworkEntry) []hostAddressType { 52 hostAddresses := make([]hostAddressType, 0, len(networkEntries)) 53 for _, networkEntry := range networkEntries { 54 if len(networkEntry.HostIpAddress) > 0 && 55 len(networkEntry.HostMacAddress) > 0 { 56 hostAddresses = append(hostAddresses, hostAddressType{ 57 address: hyper_proto.Address{ 58 IpAddress: networkEntry.HostIpAddress, 59 MacAddress: networkEntry.HostMacAddress.String(), 60 }, 61 hostname: networkEntry.Hostname, 62 }) 63 } 64 } 65 return hostAddresses 66 } 67 68 func getInstallConfig(fmCR *srpc.ClientResource, imageClient *srpc.Client, 69 hostname string, addRandomData bool, 70 logger log.DebugLogger) (*fm_proto.GetMachineInfoResponse, []leaseType, 71 map[string][]byte, error) { 72 info, err := getInfoForMachine(fmCR, hostname) 73 if err != nil { 74 return nil, nil, nil, err 75 } 76 imageName := info.Machine.Tags["RequiredImage"] 77 subnets := make([]*hyper_proto.Subnet, 0, len(info.Subnets)) 78 for _, subnet := range info.Subnets { 79 if subnet.VlanId == 0 { 80 subnets = append(subnets, subnet) 81 } 82 } 83 if len(subnets) < 1 { 84 return nil, nil, nil, errors.New("no non-VLAN subnets known") 85 } 86 networkEntries := getNetworkEntries(info) 87 hostAddresses := getHostAddress(networkEntries) 88 if len(hostAddresses) < 1 { 89 return nil, nil, nil, 90 errors.New("no IP and MAC addresses known for host") 91 } 92 leases := make([]leaseType, 0, len(hostAddresses)) 93 for _, address := range hostAddresses { 94 subnet := findMatchingSubnet(subnets, address.address.IpAddress) 95 if subnet != nil { 96 leases = append(leases, leaseType{address: address, subnet: subnet}) 97 } 98 } 99 if len(leases) < 1 { 100 return nil, nil, nil, 101 errors.New("no IP and MAC addresses matching a subnet") 102 } 103 if imageName != "" { 104 img, err := imageclient.GetImage(imageClient, imageName) 105 if err != nil { 106 return nil, nil, nil, err 107 } 108 if img == nil { 109 logger.Printf("warning: image: %s does not exist", imageName) 110 } else if len(img.FileSystem.InodeTable) < 1000 { 111 return nil, nil, nil, fmt.Errorf( 112 "only %d inodes, this is likely not bootable", 113 len(img.FileSystem.InodeTable)) 114 } 115 } 116 configFiles, err := makeConfigFiles(info, imageName, networkEntries, 117 addRandomData) 118 if err != nil { 119 return nil, nil, nil, err 120 } 121 return &info, leases, configFiles, nil 122 } 123 124 func getNetworkEntries( 125 info fm_proto.GetMachineInfoResponse) []fm_proto.NetworkEntry { 126 networkEntries := make([]fm_proto.NetworkEntry, 1, 127 len(info.Machine.SecondaryNetworkEntries)+1) 128 networkEntries[0] = info.Machine.NetworkEntry 129 for _, networkEntry := range info.Machine.SecondaryNetworkEntries { 130 networkEntries = append(networkEntries, networkEntry) 131 } 132 return networkEntries 133 } 134 135 func getStorageLayout() (installer_proto.StorageLayout, error) { 136 if *storageLayoutFilename == "" { 137 return makeDefaultStorageLayout(), nil 138 } 139 var val installer_proto.StorageLayout 140 if err := libjson.ReadFromFile(*storageLayoutFilename, &val); err != nil { 141 return installer_proto.StorageLayout{}, err 142 } 143 return val, nil 144 } 145 146 func makeConfigFiles(info fm_proto.GetMachineInfoResponse, imageName string, 147 networkEntries []fm_proto.NetworkEntry, 148 addRandomData bool) (map[string][]byte, error) { 149 filesMap := make(map[string][]byte, len(netbootFiles)+1) 150 for tftpFilename, localFilename := range netbootFiles { 151 if data, err := ioutil.ReadFile(localFilename); err != nil { 152 return nil, err 153 } else { 154 filesMap[tftpFilename] = data 155 } 156 } 157 if data, err := json.MarshalIndent(info, "", " "); err != nil { 158 return nil, err 159 } else { 160 filesMap["config.json"] = append(data, '\n') 161 } 162 if *targetImageName != "" { 163 filesMap["imagename"] = []byte(*targetImageName + "\n") 164 } else if imageName != "" { 165 filesMap["imagename"] = []byte(imageName + "\n") 166 } 167 buffer := new(bytes.Buffer) 168 fmt.Fprintf(buffer, "%s:%d\n", *imageServerHostname, *imageServerPortNum) 169 filesMap["imageserver"] = buffer.Bytes() 170 var primarySubnet *hyper_proto.Subnet 171 for _, networkEntry := range networkEntries { 172 subnet := findMatchingSubnet(info.Subnets, networkEntry.HostIpAddress) 173 if subnet == nil { 174 continue 175 } 176 primarySubnet = subnet 177 break 178 } 179 if primarySubnet == nil { 180 return nil, errors.New("no primary subnet found") 181 } 182 if addRandomData && *randomSeedBytes > 0 { 183 randomData := make([]byte, *randomSeedBytes) 184 if _, err := rand.Read(randomData); err != nil { 185 return nil, err 186 } 187 filesMap["random-seed"] = randomData 188 } 189 buffer = new(bytes.Buffer) 190 if primarySubnet.DomainName != "" { 191 fmt.Fprintf(buffer, "domain %s\n", primarySubnet.DomainName) 192 fmt.Fprintf(buffer, "search %s\n", primarySubnet.DomainName) 193 fmt.Fprintln(buffer) 194 } 195 for _, nameserver := range primarySubnet.DomainNameServers { 196 fmt.Fprintf(buffer, "nameserver %s\n", nameserver) 197 } 198 filesMap["resolv.conf"] = buffer.Bytes() 199 if layout, err := getStorageLayout(); err != nil { 200 return nil, err 201 } else { 202 if data, err := json.MarshalIndent(layout, "", " "); err != nil { 203 return nil, err 204 } else { 205 filesMap["storage-layout.json"] = append(data, '\n') 206 } 207 } 208 return filesMap, nil 209 } 210 211 func makeDefaultStorageLayout() installer_proto.StorageLayout { 212 return installer_proto.StorageLayout{ 213 BootDriveLayout: []installer_proto.Partition{ 214 { 215 MountPoint: "/", 216 MinimumFreeBytes: 2 << 30, 217 }, 218 { 219 MountPoint: "/home", 220 MinimumFreeBytes: 1 << 30, 221 }, 222 { 223 MountPoint: "/var/log", 224 MinimumFreeBytes: 512 << 20, 225 }, 226 }, 227 ExtraMountPointsBasename: "/data/", 228 UseKexec: *useKexec, 229 } 230 } 231 232 func netbootHost(hostname string, logger log.DebugLogger) error { 233 fmCR := srpc.NewClientResource("tcp", 234 fmt.Sprintf("%s:%d", *fleetManagerHostname, *fleetManagerPortNum)) 235 defer fmCR.ScheduleClose() 236 imageClient, err := srpc.DialHTTP("tcp", fmt.Sprintf("%s:%d", 237 *imageServerHostname, *imageServerPortNum), 0) 238 if err != nil { 239 return fmt.Errorf("%s: %s", *imageServerHostname, err) 240 } 241 defer imageClient.Close() 242 info, leases, configFiles, err := getInstallConfig(fmCR, imageClient, 243 hostname, true, logger) 244 if err != nil { 245 return err 246 } 247 var hypervisorAddresses []string 248 if *hypervisorHostname != "" { 249 hypervisorAddresses = append(hypervisorAddresses, 250 fmt.Sprintf("%s:%d", *hypervisorHostname, *hypervisorPortNum)) 251 } else { 252 hypervisorAddresses, err = listConnectedHypervisorsInLocation(fmCR, 253 info.Location) 254 if err != nil { 255 return err 256 } 257 } 258 if len(hypervisorAddresses) < 1 { 259 return errors.New("no nearby Hypervisors available") 260 } 261 logger.Debugf(0, "Selected %s as boot server on subnet: %s\n", 262 hypervisorAddresses[0], leases[0].subnet.Id) 263 hyperCR := srpc.NewClientResource("tcp", hypervisorAddresses[0]) 264 defer hyperCR.ScheduleClose() 265 request := hyper_proto.NetbootMachineRequest{ 266 Address: leases[0].address.address, 267 Files: configFiles, 268 FilesExpiration: *netbootFilesTimeout, 269 Hostname: hostname, 270 NumAcknowledgementsToWaitFor: *numAcknowledgementsToWaitFor, 271 OfferExpiration: *offerTimeout, 272 WaitTimeout: *netbootTimeout, 273 } 274 var reply hyper_proto.NetbootMachineResponse 275 client, err := hyperCR.GetHTTP(nil, 0) 276 if err != nil { 277 return err 278 } 279 defer client.Put() 280 err = client.RequestReply("Hypervisor.NetbootMachine", request, &reply) 281 if err != nil { 282 return err 283 } 284 return errors.New(reply.Error) 285 }