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