istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/ambient/helpers.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ambient
    16  
    17  import (
    18  	"fmt"
    19  	"net/netip"
    20  	"strings"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  
    24  	"istio.io/istio/pilot/pkg/model"
    25  	"istio.io/istio/pkg/config/labels"
    26  	"istio.io/istio/pkg/workloadapi"
    27  )
    28  
    29  // name format: <cluster>/<group>/<kind>/<namespace>/<name></section-name>
    30  func (a *index) generatePodUID(p *v1.Pod) string {
    31  	return a.ClusterID.String() + "//" + "Pod/" + p.Namespace + "/" + p.Name
    32  }
    33  
    34  // name format: <cluster>/<group>/<kind>/<namespace>/<name></section-name>
    35  // if the WorkloadEntry is inlined in the ServiceEntry, we may need section name. caller should use generateServiceEntryUID
    36  func (a *index) generateWorkloadEntryUID(wkEntryNamespace, wkEntryName string) string {
    37  	return a.ClusterID.String() + "/networking.istio.io/WorkloadEntry/" + wkEntryNamespace + "/" + wkEntryName
    38  }
    39  
    40  // name format: <cluster>/<group>/<kind>/<namespace>/<name></section-name>
    41  // section name should be the WE address, which needs to be stable across SE updates (it is assumed WE addresses are unique)
    42  func (a *index) generateServiceEntryUID(svcEntryNamespace, svcEntryName, addr string) string {
    43  	return a.ClusterID.String() + "/networking.istio.io/ServiceEntry/" + svcEntryNamespace + "/" + svcEntryName + "/" + addr
    44  }
    45  
    46  func workloadToAddressInfo(w *workloadapi.Workload) model.AddressInfo {
    47  	return model.AddressInfo{
    48  		Address: &workloadapi.Address{
    49  			Type: &workloadapi.Address_Workload{
    50  				Workload: w,
    51  			},
    52  		},
    53  	}
    54  }
    55  
    56  func modelWorkloadToAddressInfo(w model.WorkloadInfo) model.AddressInfo {
    57  	return workloadToAddressInfo(w.Workload)
    58  }
    59  
    60  func serviceToAddressInfo(s *workloadapi.Service) model.AddressInfo {
    61  	return model.AddressInfo{
    62  		Address: &workloadapi.Address{
    63  			Type: &workloadapi.Address_Service{
    64  				Service: s,
    65  			},
    66  		},
    67  	}
    68  }
    69  
    70  func mustByteIPToString(b []byte) string {
    71  	ip, _ := netip.AddrFromSlice(b) // Address only comes from objects we create, so it must be valid
    72  	return ip.String()
    73  }
    74  
    75  func byteIPToAddr(b []byte) netip.Addr {
    76  	ip, _ := netip.AddrFromSlice(b) // Address only comes from objects we create, so it must be valid
    77  	return ip
    78  }
    79  
    80  func (a index) getWaypointAddress(w *Waypoint) *workloadapi.GatewayAddress {
    81  	// probably overly cautious... I don't think the ambient index impl counts something with zero addresses as waypoint
    82  	if w != nil && len(w.Addresses) >= 1 {
    83  		return &workloadapi.GatewayAddress{
    84  			Destination: &workloadapi.GatewayAddress_Address{
    85  				// probably use from Cidr instead?
    86  				Address: a.toNetworkAddressFromIP(w.Addresses[0]),
    87  			},
    88  			// TODO: look up the HBONE port instead of hardcoding it
    89  			HboneMtlsPort: 15008,
    90  		}
    91  	}
    92  	return nil
    93  }
    94  
    95  func (a *index) toNetworkAddress(vip string) (*workloadapi.NetworkAddress, error) {
    96  	ip, err := netip.ParseAddr(vip)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("parse %v: %v", vip, err)
    99  	}
   100  	return &workloadapi.NetworkAddress{
   101  		Network: a.Network(vip, make(labels.Instance, 0)).String(),
   102  		Address: ip.AsSlice(),
   103  	}, nil
   104  }
   105  
   106  func (a *index) toNetworkAddressFromIP(ip netip.Addr) *workloadapi.NetworkAddress {
   107  	return &workloadapi.NetworkAddress{
   108  		Network: a.Network(ip.String(), make(labels.Instance, 0)).String(),
   109  		Address: ip.AsSlice(),
   110  	}
   111  }
   112  
   113  func (a *index) toNetworkAddressFromCidr(vip string) (*workloadapi.NetworkAddress, error) {
   114  	ip, err := parseCidrOrIP(vip)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return &workloadapi.NetworkAddress{
   119  		Network: a.Network(vip, make(labels.Instance, 0)).String(),
   120  		Address: ip.AsSlice(),
   121  	}, nil
   122  }
   123  
   124  // parseCidrOrIP parses an IP or a CIDR of a exactly 1 IP (e.g. /32).
   125  // This is to support ServiceEntry which supports CIDRs, but we don't currently support more than 1 IP
   126  func parseCidrOrIP(ip string) (netip.Addr, error) {
   127  	if strings.Contains(ip, "/") {
   128  		prefix, err := netip.ParsePrefix(ip)
   129  		if err != nil {
   130  			return netip.Addr{}, err
   131  		}
   132  		if !prefix.IsSingleIP() {
   133  			return netip.Addr{}, fmt.Errorf("only single IP CIDR is allowed")
   134  		}
   135  		return prefix.Addr(), nil
   136  	}
   137  	return netip.ParseAddr(ip)
   138  }
   139  
   140  func AppendNonNil[T any](data []T, i *T) []T {
   141  	if i != nil {
   142  		data = append(data, *i)
   143  	}
   144  	return data
   145  }
   146  
   147  func IsPodRunning(pod *v1.Pod) bool {
   148  	return pod.Status.Phase == v1.PodRunning
   149  }
   150  
   151  func IsPodPending(pod *v1.Pod) bool {
   152  	return pod.Status.Phase == v1.PodPending
   153  }
   154  
   155  // IsPodReady is copied from kubernetes/pkg/api/v1/pod/utils.go
   156  func IsPodReady(pod *v1.Pod) bool {
   157  	return IsPodReadyConditionTrue(pod.Status)
   158  }
   159  
   160  // IsPodReadyConditionTrue returns true if a pod is ready; false otherwise.
   161  func IsPodReadyConditionTrue(status v1.PodStatus) bool {
   162  	condition := GetPodReadyCondition(status)
   163  	return condition != nil && condition.Status == v1.ConditionTrue
   164  }
   165  
   166  func GetPodReadyCondition(status v1.PodStatus) *v1.PodCondition {
   167  	_, condition := GetPodCondition(&status, v1.PodReady)
   168  	return condition
   169  }
   170  
   171  func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
   172  	if status == nil {
   173  		return -1, nil
   174  	}
   175  	return GetPodConditionFromList(status.Conditions, conditionType)
   176  }
   177  
   178  // GetPodConditionFromList extracts the provided condition from the given list of condition and
   179  // returns the index of the condition and the condition. Returns -1 and nil if the condition is not present.
   180  func GetPodConditionFromList(conditions []v1.PodCondition, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
   181  	if conditions == nil {
   182  		return -1, nil
   183  	}
   184  	for i := range conditions {
   185  		if conditions[i].Type == conditionType {
   186  			return i, &conditions[i]
   187  		}
   188  	}
   189  	return -1, nil
   190  }
   191  
   192  func FindPortName(pod *v1.Pod, name string) (int32, bool) {
   193  	for _, container := range pod.Spec.Containers {
   194  		for _, port := range container.Ports {
   195  			if port.Name == name && port.Protocol == v1.ProtocolTCP {
   196  				return port.ContainerPort, true
   197  			}
   198  		}
   199  	}
   200  	return 0, false
   201  }
   202  
   203  func namespacedHostname(namespace, hostname string) string {
   204  	return namespace + "/" + hostname
   205  }
   206  
   207  func networkAddressFromWorkload(wl model.WorkloadInfo) []networkAddress {
   208  	networkAddrs := make([]networkAddress, 0, len(wl.Addresses))
   209  	for _, addr := range wl.Addresses {
   210  		// mustByteIPToString is ok since this is from our IP constructed
   211  		networkAddrs = append(networkAddrs, networkAddress{network: wl.Network, ip: mustByteIPToString(addr)})
   212  	}
   213  	return networkAddrs
   214  }
   215  
   216  func networkAddressFromService(s model.ServiceInfo) []networkAddress {
   217  	networkAddrs := make([]networkAddress, 0, len(s.Addresses))
   218  	for _, addr := range s.Addresses {
   219  		// mustByteIPToString is ok since this is from our IP constructed
   220  		networkAddrs = append(networkAddrs, networkAddress{network: addr.Network, ip: mustByteIPToString(addr.Address)})
   221  	}
   222  	return networkAddrs
   223  }
   224  
   225  // internal object used for indexing in ambientindex maps
   226  type networkAddress struct {
   227  	network string
   228  	ip      string
   229  }
   230  
   231  func (n *networkAddress) String() string {
   232  	return n.network + "/" + n.ip
   233  }