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  }