package manifests import ( "context" "fmt" "path" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" configv1 "github.com/openshift/api/config/v1" operatorv1 "github.com/openshift/api/operator/v1" "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" "github.com/openshift/installer/pkg/types/aws" "github.com/openshift/installer/pkg/types/powervs" ) var ( noCfgFilename = path.Join(manifestDir, "cluster-network-02-config.yml") cnoCfgFilename = path.Join(manifestDir, "cluster-network-03-config.yml") // Cluster Network MTU for AWS Local Zone deployments on edge machine pools. ovnKubernetesNetworkMtuEdge uint32 = 1200 ) // Networking generates the cluster-network-*.yml files. type Networking struct { Config *configv1.Network FileList []*asset.File } var _ asset.WritableAsset = (*Networking)(nil) // Name returns a human friendly name for the operator. func (no *Networking) Name() string { return "Network Config" } // Dependencies returns all of the dependencies directly needed to generate // network configuration. func (no *Networking) Dependencies() []asset.Asset { return []asset.Asset{ &installconfig.InstallConfig{}, } } // Generate generates the network operator config. func (no *Networking) Generate(_ context.Context, dependencies asset.Parents) error { installConfig := &installconfig.InstallConfig{} dependencies.Get(installConfig) netConfig := installConfig.Config.Networking clusterNet := []configv1.ClusterNetworkEntry{} if len(netConfig.ClusterNetwork) > 0 { for _, net := range netConfig.ClusterNetwork { clusterNet = append(clusterNet, configv1.ClusterNetworkEntry{ CIDR: net.CIDR.String(), HostPrefix: uint32(net.HostPrefix), }) } } else { return errors.Errorf("ClusterNetworks must be specified") } serviceNet := []string{} for _, sn := range netConfig.ServiceNetwork { serviceNet = append(serviceNet, sn.String()) } no.Config = &configv1.Network{ TypeMeta: metav1.TypeMeta{ APIVersion: configv1.SchemeGroupVersion.String(), Kind: "Network", }, ObjectMeta: metav1.ObjectMeta{ Name: "cluster", // not namespaced }, Spec: configv1.NetworkSpec{ ClusterNetwork: clusterNet, ServiceNetwork: serviceNet, NetworkType: netConfig.NetworkType, // Block all Service.ExternalIPs by default ExternalIP: &configv1.ExternalIPConfig{ Policy: &configv1.ExternalIPPolicy{}, }, }, } configData, err := yaml.Marshal(no.Config) if err != nil { return errors.Wrapf(err, "failed to create %s manifests from InstallConfig", no.Name()) } no.FileList = []*asset.File{ { Filename: noCfgFilename, Data: configData, }, } cnoCfg, err := clusterNetworkOperatorConfig(installConfig, clusterNet, serviceNet) if err != nil { return fmt.Errorf("error generating cluster network operator config: %w", err) } if cnoCfg != nil { cnoData, err := yaml.Marshal(cnoCfg) if err != nil { return fmt.Errorf("error marshaling cluster network operator manifest %w", err) } no.FileList = append(no.FileList, &asset.File{ Filename: cnoCfgFilename, Data: cnoData, }) } return nil } // Files returns the files generated by the asset. func (no *Networking) Files() []*asset.File { return no.FileList } // Load returns false since this asset is not written to disk by the installer. func (no *Networking) Load(f asset.FileFetcher) (bool, error) { return false, nil } // clusterNetworkOperatorConfig conditionally generates the operatorv1.Networking config if customizations // are needed, otherwise it is omitted to fallback to default behavior. func clusterNetworkOperatorConfig(ic *installconfig.InstallConfig, cns []configv1.ClusterNetworkEntry, sn []string) (*operatorv1.Network, error) { var cnoCfg *operatorv1.Network var err error switch ic.Config.Platform.Name() { case aws.Name: cnoCfg, err = generateCustomNetworkConfigMTU(ic, cns, sn) if err != nil { return nil, err } case powervs.Name: if ic.Config.NetworkType == "OVNKubernetes" { cnoCfg = ovnNetworkOperatorConfig(cns, sn) cnoCfg.Spec.DefaultNetwork.OVNKubernetesConfig.GatewayConfig = &operatorv1.GatewayConfig{RoutingViaHost: true} } } if ovnCfg := ic.Config.OVNKubernetesConfig; ovnCfg != nil && ovnCfg.IPv4 != nil && ovnCfg.IPv4.InternalJoinSubnet != nil { if cnoCfg == nil { cnoCfg = ovnNetworkOperatorConfig(cns, sn) } cnoCfg.Spec.DefaultNetwork.OVNKubernetesConfig.IPv4 = &operatorv1.IPv4OVNKubernetesConfig{InternalJoinSubnet: ovnCfg.IPv4.InternalJoinSubnet.String()} } return cnoCfg, nil } // generateCustomNetworkConfigMTU generates and return the DefaultNetwork configuration, when there are // customizations in the install-config.yaml. func generateCustomNetworkConfigMTU(ic *installconfig.InstallConfig, cns []configv1.ClusterNetworkEntry, sn []string) (*operatorv1.Network, error) { if ic.Config == nil { return nil, nil } if ic.Config.Networking == nil { return nil, nil } mtu := uint32(0) hasCustomMTU := false hasEdgePool := false netConfig := ic.Config.Networking if ic.Config.Platform.Name() == aws.Name { for _, mp := range ic.Config.Compute { // Check if there is an edge compute pool in install config, and generate the // CNO object to set DefaultNetwork for CNI with custom MTU. // EC2 Instances running on AWS Local and Wavelength zones generally // requires (newer zones are supporting higger) MTU set to 1300 to // communicate with regular zones in the Region. // The number of MTU must be decreased from the network plugin overhead. // https://docs.aws.amazon.com/local-zones/latest/ug/how-local-zones-work.html if mp.Name == types.MachinePoolEdgeRoleName { hasCustomMTU = true hasEdgePool = true } } if ic.Config.Networking != nil && ic.Config.Networking.ClusterNetworkMTU > 0 { hasCustomMTU = true mtu = ic.Config.Networking.ClusterNetworkMTU } } if !hasCustomMTU { return nil, nil } var cnoCfg *operatorv1.Network if netConfig.NetworkType == string(operatorv1.NetworkTypeOVNKubernetes) { // User-defined Cluster MTU has precedence over standard edge zone for each plugin. if hasEdgePool && mtu == 0 { mtu = ovnKubernetesNetworkMtuEdge } cnoCfg = ovnNetworkOperatorConfig(cns, sn) cnoCfg.Spec.DefaultNetwork.OVNKubernetesConfig.MTU = &mtu } return cnoCfg, nil } // ovnNetworkOperatorConfig generates a network operator configuration manifest // using ovn-kubernetes as the SDN. func ovnNetworkOperatorConfig(cns []configv1.ClusterNetworkEntry, sn []string) *operatorv1.Network { operCNs := []operatorv1.ClusterNetworkEntry{} for _, cn := range cns { ocn := operatorv1.ClusterNetworkEntry{ CIDR: cn.CIDR, HostPrefix: cn.HostPrefix, } operCNs = append(operCNs, ocn) } return &operatorv1.Network{ TypeMeta: metav1.TypeMeta{ APIVersion: "operator.openshift.io/v1", Kind: "Network", }, ObjectMeta: metav1.ObjectMeta{ Name: "cluster", }, Spec: operatorv1.NetworkSpec{ OperatorSpec: operatorv1.OperatorSpec{ManagementState: operatorv1.Managed}, ClusterNetwork: operCNs, ServiceNetwork: sn, DefaultNetwork: operatorv1.DefaultNetworkDefinition{ Type: operatorv1.NetworkTypeOVNKubernetes, OVNKubernetesConfig: &operatorv1.OVNKubernetesConfig{}, }, }, Status: operatorv1.NetworkStatus{}, } }