github.com/cilium/cilium@v1.16.2/pkg/node/types/node.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package types
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"net"
    11  	"path"
    12  	"slices"
    13  
    14  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  
    16  	"github.com/cilium/cilium/api/v1/models"
    17  	"github.com/cilium/cilium/pkg/annotation"
    18  	"github.com/cilium/cilium/pkg/cidr"
    19  	cmtypes "github.com/cilium/cilium/pkg/clustermesh/types"
    20  	"github.com/cilium/cilium/pkg/defaults"
    21  	ipamTypes "github.com/cilium/cilium/pkg/ipam/types"
    22  	ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    23  	"github.com/cilium/cilium/pkg/kvstore/store"
    24  	"github.com/cilium/cilium/pkg/node/addressing"
    25  	"github.com/cilium/cilium/pkg/option"
    26  	"github.com/cilium/cilium/pkg/source"
    27  )
    28  
    29  // Identity represents the node identity of a node.
    30  type Identity struct {
    31  	Name    string
    32  	Cluster string
    33  }
    34  
    35  // String returns the string representation on NodeIdentity.
    36  func (nn Identity) String() string {
    37  	return path.Join(nn.Cluster, nn.Name)
    38  }
    39  
    40  // appendAllocCDIR sets or appends the given podCIDR to the node.
    41  // If the IPv4/IPv6AllocCIDR is already set, we add the podCIDR as a secondary
    42  // alloc CIDR.
    43  func (n *Node) appendAllocCDIR(podCIDR *cidr.CIDR) {
    44  	if podCIDR.IP.To4() != nil {
    45  		if n.IPv4AllocCIDR == nil {
    46  			n.IPv4AllocCIDR = podCIDR
    47  		} else {
    48  			n.IPv4SecondaryAllocCIDRs = append(n.IPv4SecondaryAllocCIDRs, podCIDR)
    49  		}
    50  	} else {
    51  		if n.IPv6AllocCIDR == nil {
    52  			n.IPv6AllocCIDR = podCIDR
    53  		} else {
    54  			n.IPv6SecondaryAllocCIDRs = append(n.IPv6SecondaryAllocCIDRs, podCIDR)
    55  		}
    56  	}
    57  }
    58  
    59  // ParseCiliumNode parses a CiliumNode custom resource and returns a Node
    60  // instance. Invalid IP and CIDRs are silently ignored
    61  func ParseCiliumNode(n *ciliumv2.CiliumNode) (node Node) {
    62  	wireguardPubKey, _ := annotation.Get(n, annotation.WireguardPubKey, annotation.WireguardPubKeyAlias)
    63  	node = Node{
    64  		Name:            n.Name,
    65  		EncryptionKey:   uint8(n.Spec.Encryption.Key),
    66  		Cluster:         option.Config.ClusterName,
    67  		ClusterID:       option.Config.ClusterID,
    68  		Source:          source.CustomResource,
    69  		Labels:          n.ObjectMeta.Labels,
    70  		Annotations:     n.ObjectMeta.Annotations,
    71  		NodeIdentity:    uint32(n.Spec.NodeIdentity),
    72  		WireguardPubKey: wireguardPubKey,
    73  		BootID:          n.Spec.BootID,
    74  	}
    75  
    76  	for _, cidrString := range n.Spec.IPAM.PodCIDRs {
    77  		ipnet, err := cidr.ParseCIDR(cidrString)
    78  		if err == nil {
    79  			node.appendAllocCDIR(ipnet)
    80  		}
    81  	}
    82  
    83  	for _, pool := range n.Spec.IPAM.Pools.Allocated {
    84  		for _, podCIDR := range pool.CIDRs {
    85  			ipnet, err := cidr.ParseCIDR(string(podCIDR))
    86  			if err == nil {
    87  				node.appendAllocCDIR(ipnet)
    88  			}
    89  		}
    90  	}
    91  
    92  	node.IPv4HealthIP = net.ParseIP(n.Spec.HealthAddressing.IPv4)
    93  	node.IPv6HealthIP = net.ParseIP(n.Spec.HealthAddressing.IPv6)
    94  
    95  	node.IPv4IngressIP = net.ParseIP(n.Spec.IngressAddressing.IPV4)
    96  	node.IPv6IngressIP = net.ParseIP(n.Spec.IngressAddressing.IPV6)
    97  
    98  	for _, address := range n.Spec.Addresses {
    99  		if ip := net.ParseIP(address.IP); ip != nil {
   100  			node.IPAddresses = append(node.IPAddresses, Address{Type: address.Type, IP: ip})
   101  		}
   102  	}
   103  
   104  	return
   105  }
   106  
   107  // ToCiliumNode converts the node to a CiliumNode
   108  func (n *Node) ToCiliumNode() *ciliumv2.CiliumNode {
   109  	var (
   110  		podCIDRs                 []string
   111  		ipAddrs                  []ciliumv2.NodeAddress
   112  		healthIPv4, healthIPv6   string
   113  		ingressIPv4, ingressIPv6 string
   114  	)
   115  
   116  	if n.IPv4AllocCIDR != nil {
   117  		podCIDRs = append(podCIDRs, n.IPv4AllocCIDR.String())
   118  	}
   119  	if n.IPv6AllocCIDR != nil {
   120  		podCIDRs = append(podCIDRs, n.IPv6AllocCIDR.String())
   121  	}
   122  	for _, ipv4AllocCIDR := range n.IPv4SecondaryAllocCIDRs {
   123  		podCIDRs = append(podCIDRs, ipv4AllocCIDR.String())
   124  	}
   125  	for _, ipv6AllocCIDR := range n.IPv6SecondaryAllocCIDRs {
   126  		podCIDRs = append(podCIDRs, ipv6AllocCIDR.String())
   127  	}
   128  	if n.IPv4HealthIP != nil {
   129  		healthIPv4 = n.IPv4HealthIP.String()
   130  	}
   131  	if n.IPv6HealthIP != nil {
   132  		healthIPv6 = n.IPv6HealthIP.String()
   133  	}
   134  	if n.IPv4IngressIP != nil {
   135  		ingressIPv4 = n.IPv4IngressIP.String()
   136  	}
   137  	if n.IPv6IngressIP != nil {
   138  		ingressIPv6 = n.IPv6IngressIP.String()
   139  	}
   140  
   141  	for _, address := range n.IPAddresses {
   142  		ipAddrs = append(ipAddrs, ciliumv2.NodeAddress{
   143  			Type: address.Type,
   144  			IP:   address.IP.String(),
   145  		})
   146  	}
   147  
   148  	return &ciliumv2.CiliumNode{
   149  		ObjectMeta: v1.ObjectMeta{
   150  			Name:        n.Name,
   151  			Labels:      n.Labels,
   152  			Annotations: n.Annotations,
   153  		},
   154  		Spec: ciliumv2.NodeSpec{
   155  			Addresses: ipAddrs,
   156  			HealthAddressing: ciliumv2.HealthAddressingSpec{
   157  				IPv4: healthIPv4,
   158  				IPv6: healthIPv6,
   159  			},
   160  			IngressAddressing: ciliumv2.AddressPair{
   161  				IPV4: ingressIPv4,
   162  				IPV6: ingressIPv6,
   163  			},
   164  			Encryption: ciliumv2.EncryptionSpec{
   165  				Key: int(n.EncryptionKey),
   166  			},
   167  			IPAM: ipamTypes.IPAMSpec{
   168  				PodCIDRs: podCIDRs,
   169  			},
   170  			NodeIdentity: uint64(n.NodeIdentity),
   171  			BootID:       n.BootID,
   172  		},
   173  	}
   174  }
   175  
   176  // RegisterNode overloads GetKeyName to ignore the cluster name, as cluster name may not be stable during node registration.
   177  //
   178  // +k8s:deepcopy-gen=true
   179  type RegisterNode struct {
   180  	Node
   181  }
   182  
   183  // GetKeyName Overloaded key name w/o cluster name
   184  func (n *RegisterNode) GetKeyName() string {
   185  	return n.Name
   186  }
   187  
   188  // DeepKeyCopy creates a deep copy of the LocalKey
   189  func (n *RegisterNode) DeepKeyCopy() store.LocalKey {
   190  	return n.DeepCopy()
   191  }
   192  
   193  func (n *RegisterNode) Unmarshal(_ string, data []byte) error {
   194  	newNode := Node{}
   195  	if err := json.Unmarshal(data, &newNode); err != nil {
   196  		return err
   197  	}
   198  
   199  	n.Node = newNode
   200  	return nil
   201  }
   202  
   203  // Node contains the nodes name, the list of addresses to this address
   204  //
   205  // +k8s:deepcopy-gen=true
   206  type Node struct {
   207  	// Name is the name of the node. This is typically the hostname of the node.
   208  	Name string
   209  
   210  	// Cluster is the name of the cluster the node is associated with
   211  	Cluster string
   212  
   213  	IPAddresses []Address
   214  
   215  	// IPv4AllocCIDR if set, is the IPv4 address pool out of which the node
   216  	// allocates IPs for local endpoints from
   217  	IPv4AllocCIDR *cidr.CIDR
   218  
   219  	// IPv4SecondaryAllocCIDRs contains additional IPv4 CIDRs from which this
   220  	//node allocates IPs for its local endpoints from
   221  	IPv4SecondaryAllocCIDRs []*cidr.CIDR
   222  
   223  	// IPv6AllocCIDR if set, is the IPv6 address pool out of which the node
   224  	// allocates IPs for local endpoints from
   225  	IPv6AllocCIDR *cidr.CIDR
   226  
   227  	// IPv6SecondaryAllocCIDRs contains additional IPv6 CIDRs from which this
   228  	// node allocates IPs for its local endpoints from
   229  	IPv6SecondaryAllocCIDRs []*cidr.CIDR
   230  
   231  	// IPv4HealthIP if not nil, this is the IPv4 address of the
   232  	// cilium-health endpoint located on the node.
   233  	IPv4HealthIP net.IP
   234  
   235  	// IPv6HealthIP if not nil, this is the IPv6 address of the
   236  	// cilium-health endpoint located on the node.
   237  	IPv6HealthIP net.IP
   238  
   239  	// IPv4IngressIP if not nil, this is the IPv4 address of the
   240  	// Ingress listener on the node.
   241  	IPv4IngressIP net.IP
   242  
   243  	// IPv6IngressIP if not nil, this is the IPv6 address of the
   244  	// Ingress listener located on the node.
   245  	IPv6IngressIP net.IP
   246  
   247  	// ClusterID is the unique identifier of the cluster
   248  	ClusterID uint32
   249  
   250  	// Source is the source where the node configuration was generated / created.
   251  	Source source.Source
   252  
   253  	// Key index used for transparent encryption or 0 for no encryption
   254  	EncryptionKey uint8
   255  
   256  	// Node labels
   257  	Labels map[string]string
   258  
   259  	// Node annotations
   260  	Annotations map[string]string
   261  
   262  	// NodeIdentity is the numeric identity allocated for the node
   263  	NodeIdentity uint32
   264  
   265  	// WireguardPubKey is the WireGuard public key of this node
   266  	WireguardPubKey string
   267  
   268  	// BootID is a unique node identifier generated on boot
   269  	BootID string
   270  }
   271  
   272  // Fullname returns the node's full name including the cluster name if a
   273  // cluster name value other than the default value has been specified
   274  func (n *Node) Fullname() string {
   275  	if n.Cluster != defaults.ClusterName {
   276  		return path.Join(n.Cluster, n.Name)
   277  	}
   278  
   279  	return n.Name
   280  }
   281  
   282  // Address is a node address which contains an IP and the address type.
   283  //
   284  // +k8s:deepcopy-gen=true
   285  type Address struct {
   286  	Type addressing.AddressType
   287  	IP   net.IP
   288  }
   289  
   290  func (a Address) ToString() string {
   291  	return a.IP.String()
   292  }
   293  
   294  func (a Address) AddrType() addressing.AddressType {
   295  	return a.Type
   296  }
   297  
   298  // GetNodeIP returns one of the node's IP addresses available with the
   299  // following priority:
   300  // - NodeInternalIP
   301  // - NodeExternalIP
   302  // - other IP address type
   303  // Nil is returned if GetNodeIP fails to extract an IP from the Node based
   304  // on the provided address family.
   305  func (n *Node) GetNodeIP(ipv6 bool) net.IP {
   306  	return addressing.ExtractNodeIP[Address](n.IPAddresses, ipv6)
   307  }
   308  
   309  // GetExternalIP returns ExternalIP of k8s Node. If not present, then it
   310  // returns nil;
   311  func (n *Node) GetExternalIP(ipv6 bool) net.IP {
   312  	for _, addr := range n.IPAddresses {
   313  		if (ipv6 && addr.IP.To4() != nil) || (!ipv6 && addr.IP.To4() == nil) {
   314  			continue
   315  		}
   316  		if addr.Type == addressing.NodeExternalIP {
   317  			return addr.IP
   318  		}
   319  	}
   320  
   321  	return nil
   322  }
   323  
   324  // GetK8sNodeIPs returns k8s Node IP (either InternalIP or ExternalIP or nil;
   325  // the former is preferred).
   326  func (n *Node) GetK8sNodeIP() net.IP {
   327  	var externalIP net.IP
   328  
   329  	for _, addr := range n.IPAddresses {
   330  		if addr.Type == addressing.NodeInternalIP {
   331  			return addr.IP
   332  		} else if addr.Type == addressing.NodeExternalIP {
   333  			externalIP = addr.IP
   334  		}
   335  	}
   336  
   337  	return externalIP
   338  }
   339  
   340  // GetNodeInternalIP returns the Internal IPv4 of node or nil.
   341  func (n *Node) GetNodeInternalIPv4() net.IP {
   342  	for _, addr := range n.IPAddresses {
   343  		if addr.IP.To4() == nil {
   344  			continue
   345  		}
   346  		if addr.Type == addressing.NodeInternalIP {
   347  			return addr.IP
   348  		}
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  // GetNodeInternalIP returns the Internal IPv6 of node or nil.
   355  func (n *Node) GetNodeInternalIPv6() net.IP {
   356  	for _, addr := range n.IPAddresses {
   357  		if addr.IP.To4() != nil {
   358  			continue
   359  		}
   360  		if addr.Type == addressing.NodeInternalIP {
   361  			return addr.IP
   362  		}
   363  	}
   364  
   365  	return nil
   366  }
   367  
   368  // GetCiliumInternalIP returns the CiliumInternalIP e.g. the IP associated
   369  // with cilium_host on the node.
   370  func (n *Node) GetCiliumInternalIP(ipv6 bool) net.IP {
   371  	for _, addr := range n.IPAddresses {
   372  		if (ipv6 && addr.IP.To4() != nil) ||
   373  			(!ipv6 && addr.IP.To4() == nil) {
   374  			continue
   375  		}
   376  		if addr.Type == addressing.NodeCiliumInternalIP {
   377  			return addr.IP
   378  		}
   379  	}
   380  	return nil
   381  }
   382  
   383  // SetCiliumInternalIP sets the CiliumInternalIP e.g. the IP associated
   384  // with cilium_host on the node.
   385  func (n *Node) SetCiliumInternalIP(newAddr net.IP) {
   386  	n.setAddress(addressing.NodeCiliumInternalIP, newAddr)
   387  }
   388  
   389  // SetNodeExternalIP sets the NodeExternalIP.
   390  func (n *Node) SetNodeExternalIP(newAddr net.IP) {
   391  	n.setAddress(addressing.NodeExternalIP, newAddr)
   392  }
   393  
   394  // SetNodeInternalIP sets the NodeInternalIP.
   395  func (n *Node) SetNodeInternalIP(newAddr net.IP) {
   396  	n.setAddress(addressing.NodeInternalIP, newAddr)
   397  }
   398  
   399  func (n *Node) RemoveAddresses(typ addressing.AddressType) {
   400  	newAddresses := []Address{}
   401  	for _, addr := range n.IPAddresses {
   402  		if addr.Type != typ {
   403  			newAddresses = append(newAddresses, addr)
   404  		}
   405  	}
   406  	n.IPAddresses = newAddresses
   407  }
   408  
   409  func (n *Node) setAddress(typ addressing.AddressType, newIP net.IP) {
   410  	newAddr := Address{Type: typ, IP: newIP}
   411  
   412  	if newIP == nil {
   413  		n.RemoveAddresses(typ)
   414  		return
   415  	}
   416  
   417  	// Create a copy of the slice, so that we don't modify the
   418  	// current one, which may be captured by any of the observers.
   419  	n.IPAddresses = slices.Clone(n.IPAddresses)
   420  
   421  	ipv6 := newIP.To4() == nil
   422  	// Try first to replace an existing address with same type
   423  	for i, addr := range n.IPAddresses {
   424  		if addr.Type != typ {
   425  			continue
   426  		}
   427  		if ipv6 != (addr.IP.To4() == nil) {
   428  			// Don't replace if address family is different.
   429  			continue
   430  		}
   431  		n.IPAddresses[i] = newAddr
   432  		return
   433  	}
   434  	n.IPAddresses = append(n.IPAddresses, newAddr)
   435  
   436  }
   437  
   438  func (n *Node) GetIPByType(addrType addressing.AddressType, ipv6 bool) net.IP {
   439  	for _, addr := range n.IPAddresses {
   440  		if addr.Type != addrType {
   441  			continue
   442  		}
   443  		if is4 := addr.IP.To4() != nil; (!ipv6 && is4) || (ipv6 && !is4) {
   444  			return addr.IP
   445  		}
   446  	}
   447  	return nil
   448  }
   449  
   450  func (n *Node) getPrimaryAddress() *models.NodeAddressing {
   451  	v4 := n.GetNodeIP(false)
   452  	v6 := n.GetNodeIP(true)
   453  
   454  	var ipv4AllocStr, ipv6AllocStr string
   455  	if n.IPv4AllocCIDR != nil {
   456  		ipv4AllocStr = n.IPv4AllocCIDR.String()
   457  	}
   458  	if n.IPv6AllocCIDR != nil {
   459  		ipv6AllocStr = n.IPv6AllocCIDR.String()
   460  	}
   461  
   462  	var v4Str, v6Str string
   463  	if v4 != nil {
   464  		v4Str = v4.String()
   465  	}
   466  	if v6 != nil {
   467  		v6Str = v6.String()
   468  	}
   469  
   470  	return &models.NodeAddressing{
   471  		IPV4: &models.NodeAddressingElement{
   472  			Enabled:    option.Config.EnableIPv4,
   473  			IP:         v4Str,
   474  			AllocRange: ipv4AllocStr,
   475  		},
   476  		IPV6: &models.NodeAddressingElement{
   477  			Enabled:    option.Config.EnableIPv6,
   478  			IP:         v6Str,
   479  			AllocRange: ipv6AllocStr,
   480  		},
   481  	}
   482  }
   483  
   484  func (n *Node) isPrimaryAddress(addr Address, ipv4 bool) bool {
   485  	return addr.IP.String() == n.GetNodeIP(!ipv4).String()
   486  }
   487  
   488  func (n *Node) getSecondaryAddresses() []*models.NodeAddressingElement {
   489  	result := []*models.NodeAddressingElement{}
   490  
   491  	for _, addr := range n.IPAddresses {
   492  		ipv4 := false
   493  		if addr.IP.To4() != nil {
   494  			ipv4 = true
   495  		}
   496  		if !n.isPrimaryAddress(addr, ipv4) {
   497  			result = append(result, &models.NodeAddressingElement{
   498  				IP: addr.IP.String(),
   499  			})
   500  		}
   501  	}
   502  
   503  	return result
   504  }
   505  
   506  func (n *Node) getHealthAddresses() *models.NodeAddressing {
   507  	if n.IPv4HealthIP == nil && n.IPv6HealthIP == nil {
   508  		return nil
   509  	}
   510  
   511  	var v4Str, v6Str string
   512  	if n.IPv4HealthIP != nil {
   513  		v4Str = n.IPv4HealthIP.String()
   514  	}
   515  	if n.IPv6HealthIP != nil {
   516  		v6Str = n.IPv6HealthIP.String()
   517  	}
   518  
   519  	return &models.NodeAddressing{
   520  		IPV4: &models.NodeAddressingElement{
   521  			Enabled: option.Config.EnableIPv4,
   522  			IP:      v4Str,
   523  		},
   524  		IPV6: &models.NodeAddressingElement{
   525  			Enabled: option.Config.EnableIPv6,
   526  			IP:      v6Str,
   527  		},
   528  	}
   529  }
   530  
   531  func (n *Node) getIngressAddresses() *models.NodeAddressing {
   532  	if n.IPv4IngressIP == nil && n.IPv6IngressIP == nil {
   533  		return nil
   534  	}
   535  
   536  	var v4Str, v6Str string
   537  	if n.IPv4IngressIP != nil {
   538  		v4Str = n.IPv4IngressIP.String()
   539  	}
   540  	if n.IPv6IngressIP != nil {
   541  		v6Str = n.IPv6IngressIP.String()
   542  	}
   543  
   544  	return &models.NodeAddressing{
   545  		IPV4: &models.NodeAddressingElement{
   546  			Enabled: option.Config.EnableIPv4,
   547  			IP:      v4Str,
   548  		},
   549  		IPV6: &models.NodeAddressingElement{
   550  			Enabled: option.Config.EnableIPv6,
   551  			IP:      v6Str,
   552  		},
   553  	}
   554  }
   555  
   556  // GetModel returns the API model representation of a node.
   557  func (n *Node) GetModel() *models.NodeElement {
   558  	return &models.NodeElement{
   559  		Name:                  n.Fullname(),
   560  		PrimaryAddress:        n.getPrimaryAddress(),
   561  		SecondaryAddresses:    n.getSecondaryAddresses(),
   562  		HealthEndpointAddress: n.getHealthAddresses(),
   563  		IngressAddress:        n.getIngressAddresses(),
   564  		Source:                string(n.Source),
   565  	}
   566  }
   567  
   568  // Identity returns the identity of the node
   569  func (n *Node) Identity() Identity {
   570  	return Identity{
   571  		Name:    n.Name,
   572  		Cluster: n.Cluster,
   573  	}
   574  }
   575  
   576  func getCluster() string {
   577  	return option.Config.ClusterName
   578  }
   579  
   580  // IsLocal returns true if this is the node on which the agent itself is
   581  // running on
   582  func (n *Node) IsLocal() bool {
   583  	return n != nil && n.Name == GetName() && n.Cluster == getCluster()
   584  }
   585  
   586  func (n *Node) GetIPv4AllocCIDRs() []*cidr.CIDR {
   587  	result := make([]*cidr.CIDR, 0, len(n.IPv4SecondaryAllocCIDRs)+1)
   588  	if n.IPv4AllocCIDR != nil {
   589  		result = append(result, n.IPv4AllocCIDR)
   590  	}
   591  	if len(n.IPv4SecondaryAllocCIDRs) > 0 {
   592  		result = append(result, n.IPv4SecondaryAllocCIDRs...)
   593  	}
   594  	return result
   595  }
   596  
   597  func (n *Node) GetIPv6AllocCIDRs() []*cidr.CIDR {
   598  	result := make([]*cidr.CIDR, 0, len(n.IPv6SecondaryAllocCIDRs)+1)
   599  	if n.IPv6AllocCIDR != nil {
   600  		result = append(result, n.IPv6AllocCIDR)
   601  	}
   602  	if len(n.IPv6SecondaryAllocCIDRs) > 0 {
   603  		result = append(result, n.IPv6SecondaryAllocCIDRs...)
   604  	}
   605  	return result
   606  }
   607  
   608  // GetKeyNodeName constructs the API name for the given cluster and node name.
   609  func GetKeyNodeName(cluster, node string) string {
   610  	// WARNING - STABLE API: Changing the structure of the key may break
   611  	// backwards compatibility
   612  	return path.Join(cluster, node)
   613  }
   614  
   615  // GetKeyName returns the kvstore key to be used for the node
   616  func (n *Node) GetKeyName() string {
   617  	return GetKeyNodeName(n.Cluster, n.Name)
   618  }
   619  
   620  // DeepKeyCopy creates a deep copy of the LocalKey
   621  func (n *Node) DeepKeyCopy() store.LocalKey {
   622  	return n.DeepCopy()
   623  }
   624  
   625  // Marshal returns the node object as JSON byte slice
   626  func (n *Node) Marshal() ([]byte, error) {
   627  	return json.Marshal(n)
   628  }
   629  
   630  // Unmarshal parses the JSON byte slice and updates the node receiver
   631  func (n *Node) Unmarshal(key string, data []byte) error {
   632  	newNode := Node{}
   633  	if err := json.Unmarshal(data, &newNode); err != nil {
   634  		return err
   635  	}
   636  
   637  	if err := newNode.validate(); err != nil {
   638  		return err
   639  	}
   640  
   641  	*n = newNode
   642  
   643  	return nil
   644  }
   645  
   646  // LogRepr returns a representation of the node to be used for logging
   647  func (n *Node) LogRepr() string {
   648  	b, err := n.Marshal()
   649  	if err != nil {
   650  		return fmt.Sprintf("%#v", n)
   651  	}
   652  	return string(b)
   653  }
   654  
   655  func (n *Node) validate() error {
   656  	switch {
   657  	case n.Cluster == "":
   658  		return errors.New("cluster is unset")
   659  	case n.Name == "":
   660  		return errors.New("name is unset")
   661  	}
   662  
   663  	// Skip the ClusterID check if it matches the local one, as we assume that
   664  	// it has already been validated, and to allow it to be zero.
   665  	if n.ClusterID != option.Config.ClusterID {
   666  		if err := cmtypes.ValidateClusterID(n.ClusterID); err != nil {
   667  			return err
   668  		}
   669  	}
   670  
   671  	return nil
   672  }