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  }