github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/fleetmanager/topology/read.go (about) 1 package topology 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "path/filepath" 8 "sort" 9 10 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 11 "github.com/Cloud-Foundations/Dominator/lib/json" 12 "github.com/Cloud-Foundations/Dominator/lib/tags" 13 proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager" 14 ) 15 16 type commonStateType struct { 17 hostnames map[string]struct{} 18 ipAddresses map[string]struct{} 19 macAddresses map[string]struct{} 20 } 21 22 type inheritingState struct { 23 owners *ownersType 24 subnetIds map[string]struct{} 25 tags tags.Tags 26 } 27 28 func checkMacAddressIsZero(macAddr proto.HardwareAddr) bool { 29 for _, b := range macAddr { 30 if b != 0 { 31 return false 32 } 33 } 34 return true 35 } 36 37 func cloneSet(set map[string]struct{}) map[string]struct{} { 38 clone := make(map[string]struct{}, len(set)) 39 for key := range set { 40 clone[key] = struct{}{} 41 } 42 return clone 43 } 44 45 func load(topologyDir string) (*Topology, error) { 46 topology := &Topology{ 47 machineParents: make(map[string]*Directory), 48 reservedIpAddrs: make(map[string]struct{}), 49 } 50 directory, err := topology.readDirectory(topologyDir, "", 51 newInheritingState(), 52 &commonStateType{ 53 hostnames: make(map[string]struct{}), 54 ipAddresses: make(map[string]struct{}), 55 macAddresses: make(map[string]struct{}), 56 }, 57 ) 58 if err != nil { 59 return nil, err 60 } 61 topology.Root = directory 62 return topology, nil 63 } 64 65 func loadMachines(filename string) ([]*proto.Machine, error) { 66 var machines []*proto.Machine 67 if err := json.ReadFromFile(filename, &machines); err != nil { 68 if os.IsNotExist(err) { 69 return nil, nil 70 } 71 return nil, fmt.Errorf("error reading: %s: %s", filename, err) 72 } 73 for _, machine := range machines { 74 if len(machine.HostIpAddress) == 0 { 75 if addrs, err := net.LookupIP(machine.Hostname); err != nil { 76 return nil, err 77 } else if len(addrs) != 1 { 78 return nil, fmt.Errorf("num addresses for: %s: %d!=1", 79 machine.Hostname, len(addrs)) 80 } else { 81 machine.HostIpAddress = addrs[0] 82 } 83 } 84 if len(machine.HostIpAddress) == 16 { 85 machine.HostIpAddress = machine.HostIpAddress.To4() 86 } 87 } 88 return machines, nil 89 } 90 91 func loadOwners(filename string) (*ownersType, error) { 92 var owners ownersType 93 if err := json.ReadFromFile(filename, &owners); err != nil { 94 if os.IsNotExist(err) { 95 return nil, nil 96 } 97 return nil, fmt.Errorf("error reading: %s: %s", filename, err) 98 } 99 return &owners, nil 100 } 101 102 func loadSubnets(filename string) ([]*Subnet, error) { 103 var subnets []*Subnet 104 if err := json.ReadFromFile(filename, &subnets); err != nil { 105 if os.IsNotExist(err) { 106 return nil, nil 107 } 108 return nil, fmt.Errorf("error reading: %s: %s", filename, err) 109 } 110 gatewayIPs := make(map[string]struct{}, len(subnets)) 111 for _, subnet := range subnets { 112 subnet.Shrink() 113 gatewayIp := subnet.IpGateway.String() 114 if _, ok := gatewayIPs[gatewayIp]; ok { 115 return nil, fmt.Errorf("duplicate gateway IP: %s", gatewayIp) 116 } else { 117 gatewayIPs[gatewayIp] = struct{}{} 118 } 119 subnet.reservedIpAddrs = make(map[string]struct{}) 120 for _, ipAddr := range subnet.ReservedIPs { 121 subnet.reservedIpAddrs[ipAddr.String()] = struct{}{} 122 } 123 } 124 return subnets, nil 125 } 126 127 func loadTags(filename string) (tags.Tags, error) { 128 var loadedTags tags.Tags 129 if err := json.ReadFromFile(filename, &loadedTags); err != nil { 130 if os.IsNotExist(err) { 131 return nil, nil 132 } 133 return nil, fmt.Errorf("error reading: %s: %s", filename, err) 134 } 135 return loadedTags, nil 136 } 137 138 func (state *commonStateType) addHostname(name string) error { 139 if name == "" { 140 return nil 141 } 142 if _, ok := state.hostnames[name]; ok { 143 return fmt.Errorf("duplicate hostname: %s", name) 144 } 145 state.hostnames[name] = struct{}{} 146 return nil 147 } 148 149 func (state *commonStateType) addIpAddress(ipAddr net.IP) error { 150 if len(ipAddr) < 1 { 151 return nil 152 } 153 name := ipAddr.String() 154 if _, ok := state.ipAddresses[name]; ok { 155 return fmt.Errorf("duplicate IP address: %s", name) 156 } 157 state.ipAddresses[name] = struct{}{} 158 return nil 159 } 160 161 func (state *commonStateType) addMacAddress(macAddr proto.HardwareAddr) error { 162 if len(macAddr) < 1 { 163 return nil 164 } 165 if checkMacAddressIsZero(macAddr) { 166 return nil 167 } 168 name := macAddr.String() 169 if _, ok := state.macAddresses[name]; ok { 170 return fmt.Errorf("duplicate MAC address: %s", name) 171 } 172 state.macAddresses[name] = struct{}{} 173 return nil 174 } 175 176 func (state *commonStateType) addMachine(machine *proto.Machine, 177 subnets map[string]*Subnet) error { 178 if machine.GatewaySubnetId != "" { 179 if _, ok := subnets[machine.GatewaySubnetId]; !ok { 180 return fmt.Errorf("unknown subnetId: %s", machine.GatewaySubnetId) 181 } 182 } 183 if err := state.addNetworkEntry(machine.NetworkEntry, subnets); err != nil { 184 return err 185 } 186 if err := state.addNetworkEntry(machine.IPMI, nil); err != nil { 187 return err 188 } 189 for _, entry := range machine.SecondaryNetworkEntries { 190 if err := state.addNetworkEntry(entry, subnets); err != nil { 191 return err 192 } 193 } 194 return nil 195 } 196 197 func (state *commonStateType) addNetworkEntry(entry proto.NetworkEntry, 198 subnets map[string]*Subnet) error { 199 if entry.SubnetId != "" { 200 if _, ok := subnets[entry.SubnetId]; !ok { 201 return fmt.Errorf("unknown subnetId: %s", entry.SubnetId) 202 } 203 if entry.Hostname != "" { 204 return fmt.Errorf( 205 "cannot specify SubnetId(%s) and Hostname(%s) together", 206 entry.SubnetId, entry.Hostname) 207 } 208 if len(entry.HostIpAddress) > 0 { 209 return fmt.Errorf( 210 "cannot specify SubnetId(%s) and HostIpAddress(%s) together", 211 entry.SubnetId, entry.HostIpAddress) 212 } 213 } 214 if err := state.addHostname(entry.Hostname); err != nil { 215 return err 216 } 217 if err := state.addIpAddress(entry.HostIpAddress); err != nil { 218 return err 219 } 220 if err := state.addMacAddress(entry.HostMacAddress); err != nil { 221 return err 222 } 223 return nil 224 } 225 226 func newInheritingState() *inheritingState { 227 return &inheritingState{ 228 owners: &ownersType{}, 229 subnetIds: cloneSet(nil), 230 tags: make(tags.Tags), 231 } 232 } 233 234 func (state *inheritingState) copy() *inheritingState { 235 return &inheritingState{ 236 owners: state.owners.copy(), 237 subnetIds: cloneSet(state.subnetIds), 238 tags: state.tags.Copy(), 239 } 240 } 241 242 func (t *Topology) loadSubnets(directory *Directory, dirpath string, 243 subnetIds map[string]struct{}) error { 244 if err := directory.loadSubnets(dirpath, subnetIds); err != nil { 245 return err 246 } 247 for _, subnet := range directory.Subnets { 248 for ipAddr := range subnet.reservedIpAddrs { 249 t.reservedIpAddrs[ipAddr] = struct{}{} 250 } 251 } 252 return nil 253 } 254 255 func (t *Topology) readDirectory(topDir, dirname string, 256 state *inheritingState, commonState *commonStateType) (*Directory, error) { 257 directory := &Directory{ 258 nameToDirectory: make(map[string]*Directory), 259 path: dirname, 260 subnetIdToSubnet: make(map[string]*Subnet), 261 } 262 dirpath := filepath.Join(topDir, dirname) 263 if err := directory.loadOwners(dirpath, state.owners); err != nil { 264 return nil, err 265 } 266 if err := t.loadSubnets(directory, dirpath, state.subnetIds); err != nil { 267 return nil, err 268 } 269 if err := directory.loadTags(dirpath, state.tags); err != nil { 270 return nil, err 271 } 272 if err := t.loadMachines(directory, dirpath, commonState); err != nil { 273 return nil, err 274 } 275 dirnames, err := fsutil.ReadDirnames(dirpath, false) 276 if err != nil { 277 return nil, err 278 } 279 for _, name := range dirnames { 280 if name == ".git" { 281 continue 282 } 283 path := filepath.Join(dirname, name) 284 fi, err := os.Lstat(filepath.Join(topDir, path)) 285 if err != nil { 286 return nil, err 287 } 288 if !fi.IsDir() { 289 continue 290 } 291 state := state.copy() 292 subdir, err := t.readDirectory(topDir, path, state, commonState) 293 if err != nil { 294 return nil, err 295 } else { 296 subdir.Name = name 297 subdir.parent = directory 298 directory.Directories = append(directory.Directories, subdir) 299 directory.nameToDirectory[name] = subdir 300 } 301 } 302 return directory, nil 303 } 304 305 func (directory *Directory) loadMachines(dirname string) error { 306 var err error 307 directory.Machines, err = loadMachines( 308 filepath.Join(dirname, "machines.json")) 309 if err != nil { 310 return err 311 } 312 for _, machine := range directory.Machines { 313 mergedOwners := ownersType{ 314 OwnerGroups: machine.OwnerGroups, 315 OwnerUsers: machine.OwnerUsers, 316 } 317 mergedOwners.merge(directory.owners) 318 machine.OwnerGroups = mergedOwners.OwnerGroups 319 machine.OwnerUsers = mergedOwners.OwnerUsers 320 if machine.Tags == nil { 321 machine.Tags = directory.Tags 322 } else if directory.Tags != nil { 323 mergedTags := directory.Tags.Copy() 324 mergedTags.Merge(machine.Tags) 325 machine.Tags = mergedTags 326 } 327 } 328 return nil 329 } 330 331 func (directory *Directory) loadOwners(dirname string, 332 parentOwners *ownersType) error { 333 owners, err := loadOwners(filepath.Join(dirname, "owners.json")) 334 if err != nil { 335 return err 336 } 337 parentOwners.merge(owners) 338 directory.owners = parentOwners 339 return nil 340 } 341 342 func (directory *Directory) loadSubnets(dirname string, 343 subnetIds map[string]struct{}) error { 344 var err error 345 directory.Subnets, err = loadSubnets(filepath.Join(dirname, "subnets.json")) 346 if err != nil { 347 return err 348 } 349 for _, subnet := range directory.Subnets { 350 if _, ok := subnetIds[subnet.Id]; ok { 351 return fmt.Errorf("duplicate subnet ID: %s", subnet.Id) 352 } else { 353 subnetIds[subnet.Id] = struct{}{} 354 directory.subnetIdToSubnet[subnet.Id] = subnet 355 } 356 } 357 return nil 358 } 359 360 func (directory *Directory) loadTags(dirname string, 361 parentTags tags.Tags) error { 362 loadedTags, err := loadTags(filepath.Join(dirname, "tags.json")) 363 if err != nil { 364 return err 365 } 366 parentTags.Merge(loadedTags) 367 if len(parentTags) > 0 { 368 directory.Tags = parentTags 369 } 370 return nil 371 } 372 373 func (owners *ownersType) copy() *ownersType { 374 newOwners := ownersType{ 375 OwnerGroups: make([]string, 0, len(owners.OwnerGroups)), 376 OwnerUsers: make([]string, 0, len(owners.OwnerUsers)), 377 } 378 for _, group := range owners.OwnerGroups { 379 newOwners.OwnerGroups = append(newOwners.OwnerGroups, group) 380 } 381 for _, user := range owners.OwnerUsers { 382 newOwners.OwnerUsers = append(newOwners.OwnerUsers, user) 383 } 384 return &newOwners 385 } 386 387 func (to *ownersType) merge(from *ownersType) { 388 if from == nil { 389 return 390 } 391 ownerGroups := make(map[string]struct{}, len(to.OwnerGroups)) 392 for _, group := range to.OwnerGroups { 393 ownerGroups[group] = struct{}{} 394 } 395 changedOwnerGroups := false 396 for _, group := range from.OwnerGroups { 397 if _, ok := ownerGroups[group]; !ok { 398 to.OwnerGroups = append(to.OwnerGroups, group) 399 changedOwnerGroups = true 400 } 401 } 402 if changedOwnerGroups { 403 sort.Strings(to.OwnerGroups) 404 } 405 ownerUsers := make(map[string]struct{}, len(to.OwnerUsers)) 406 for _, group := range to.OwnerUsers { 407 ownerUsers[group] = struct{}{} 408 } 409 changedOwnerUsers := false 410 for _, group := range from.OwnerUsers { 411 if _, ok := ownerUsers[group]; !ok { 412 to.OwnerUsers = append(to.OwnerUsers, group) 413 changedOwnerUsers = true 414 } 415 } 416 if changedOwnerUsers { 417 sort.Strings(to.OwnerUsers) 418 } 419 } 420 421 func (t *Topology) loadMachines(directory *Directory, dirname string, 422 commonState *commonStateType) error { 423 if err := directory.loadMachines(dirname); err != nil { 424 return err 425 } 426 for _, machine := range directory.Machines { 427 err := commonState.addMachine(machine, directory.subnetIdToSubnet) 428 if err != nil { 429 return fmt.Errorf("error adding: %s: %s", machine.Hostname, err) 430 } 431 t.machineParents[machine.Hostname] = directory 432 } 433 return nil 434 }