github.com/openshift/installer@v1.4.17/pkg/asset/agent/manifests/agentclusterinstall.go (about) 1 package manifests 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/go-openapi/swag" 13 "github.com/pkg/errors" 14 "github.com/sirupsen/logrus" 15 corev1 "k8s.io/api/core/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/util/validation/field" 18 "sigs.k8s.io/yaml" 19 20 operv1 "github.com/openshift/api/operator/v1" 21 hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" 22 aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" 23 hivev1 "github.com/openshift/hive/apis/hive/v1" 24 "github.com/openshift/installer/pkg/asset" 25 "github.com/openshift/installer/pkg/asset/agent" 26 "github.com/openshift/installer/pkg/asset/agent/agentconfig" 27 "github.com/openshift/installer/pkg/asset/agent/workflow" 28 "github.com/openshift/installer/pkg/ipnet" 29 "github.com/openshift/installer/pkg/types" 30 "github.com/openshift/installer/pkg/types/baremetal" 31 "github.com/openshift/installer/pkg/types/defaults" 32 "github.com/openshift/installer/pkg/types/external" 33 "github.com/openshift/installer/pkg/types/none" 34 "github.com/openshift/installer/pkg/types/vsphere" 35 ) 36 37 const ( 38 installConfigOverrides = aiv1beta1.Group + "/install-config-overrides" 39 ) 40 41 var ( 42 agentClusterInstallFilename = filepath.Join(clusterManifestDir, "agent-cluster-install.yaml") 43 ) 44 45 // AgentClusterInstall generates the agent-cluster-install.yaml file. 46 type AgentClusterInstall struct { 47 File *asset.File 48 Config *hiveext.AgentClusterInstall 49 } 50 51 type agentClusterInstallOnPremPlatform struct { 52 // APIVIPs contains the VIP(s) to use for internal API communication. In 53 // dual stack clusters it contains an IPv4 and IPv6 address, otherwise only 54 // one VIP 55 APIVIPs []string `json:"apiVIPs,omitempty"` 56 57 // IngressVIPs contains the VIP(s) to use for ingress traffic. In dual stack 58 // clusters it contains an IPv4 and IPv6 address, otherwise only one VIP 59 IngressVIPs []string `json:"ingressVIPs,omitempty"` 60 61 // Host, including BMC, configuration. 62 Hosts []baremetal.Host `json:"hosts,omitempty"` 63 64 // ClusterProvisioningIP is the IP on the dedicated provisioning network. 65 ClusterProvisioningIP string `json:"clusterProvisioningIP,omitempty"` 66 67 // ProvisioningNetwork is used to indicate if we will have a provisioning network, and how it will be managed. 68 ProvisioningNetwork baremetal.ProvisioningNetwork `json:"provisioningNetwork,omitempty"` 69 70 // ProvisioningNetworkInterface is the name of the network interface on a control plane 71 // baremetal host that is connected to the provisioning network. 72 ProvisioningNetworkInterface string `json:"provisioningNetworkInterface,omitempty"` 73 74 // ProvisioningNetworkCIDR defines the network to use for provisioning. 75 ProvisioningNetworkCIDR *ipnet.IPNet `json:"provisioningNetworkCIDR,omitempty"` 76 77 // ProvisioningDHCPRange is used to provide DHCP services to hosts 78 // for provisioning. 79 ProvisioningDHCPRange string `json:"provisioningDHCPRange,omitempty"` 80 } 81 82 type agentClusterInstallOnPremExternalPlatform struct { 83 // PlatformName holds the arbitrary string representing the infrastructure provider name, expected to be set at the installation time. 84 PlatformName string `json:"platformName,omitempty"` 85 // CloudControllerManager when set to external, this property will enable an external cloud provider. 86 CloudControllerManager external.CloudControllerManager `json:"cloudControllerManager,omitempty"` 87 } 88 89 type agentClusterInstallPlatform struct { 90 // BareMetal is the configuration used when installing on bare metal. 91 // +optional 92 BareMetal *agentClusterInstallOnPremPlatform `json:"baremetal,omitempty"` 93 // VSphere is the configuration used when installing on vSphere. 94 // +optional 95 VSphere *vsphere.Platform `json:"vsphere,omitempty"` 96 // External is the configuration used when installing on external cloud provider. 97 // +optional 98 External *agentClusterInstallOnPremExternalPlatform `json:"external,omitempty"` 99 } 100 101 // Used to generate InstallConfig overrides for Assisted Service to apply 102 type agentClusterInstallInstallConfigOverrides struct { 103 // FIPS configures https://www.nist.gov/itl/fips-general-information 104 // 105 // +kubebuilder:default=false 106 // +optional 107 FIPS bool `json:"fips,omitempty"` 108 // Platform is the configuration for the specific platform upon which to 109 // perform the installation. 110 Platform *agentClusterInstallPlatform `json:"platform,omitempty"` 111 // Capabilities selects the managed set of optional, core cluster components. 112 Capabilities *types.Capabilities `json:"capabilities,omitempty"` 113 // Allow override of network type 114 Networking *types.Networking `json:"networking,omitempty"` 115 // Allow override of CPUPartitioning 116 CPUPartitioning types.CPUPartitioningMode `json:"cpuPartitioningMode,omitempty"` 117 } 118 119 var _ asset.WritableAsset = (*AgentClusterInstall)(nil) 120 121 // Name returns a human friendly name for the asset. 122 func (*AgentClusterInstall) Name() string { 123 return "AgentClusterInstall Config" 124 } 125 126 // Dependencies returns all of the dependencies directly needed to generate 127 // the asset. 128 func (*AgentClusterInstall) Dependencies() []asset.Asset { 129 return []asset.Asset{ 130 &workflow.AgentWorkflow{}, 131 &agent.OptionalInstallConfig{}, 132 &agentconfig.AgentHosts{}, 133 } 134 } 135 136 // Generate generates the AgentClusterInstall manifest. 137 // 138 //nolint:gocyclo 139 func (a *AgentClusterInstall) Generate(_ context.Context, dependencies asset.Parents) error { 140 agentWorkflow := &workflow.AgentWorkflow{} 141 installConfig := &agent.OptionalInstallConfig{} 142 agentHosts := &agentconfig.AgentHosts{} 143 dependencies.Get(agentWorkflow, agentHosts, installConfig) 144 145 // This manifest is not required for AddNodes workflow 146 if agentWorkflow.Workflow == workflow.AgentWorkflowTypeAddNodes { 147 return nil 148 } 149 150 if installConfig.Config != nil { 151 var numberOfWorkers int = 0 152 for _, compute := range installConfig.Config.Compute { 153 numberOfWorkers = numberOfWorkers + int(*compute.Replicas) 154 } 155 156 clusterNetwork := []hiveext.ClusterNetworkEntry{} 157 for _, cn := range installConfig.Config.Networking.ClusterNetwork { 158 entry := hiveext.ClusterNetworkEntry{ 159 CIDR: cn.CIDR.String(), 160 HostPrefix: cn.HostPrefix, 161 } 162 clusterNetwork = append(clusterNetwork, entry) 163 } 164 165 serviceNetwork := []string{} 166 for _, sn := range installConfig.Config.Networking.ServiceNetwork { 167 serviceNetwork = append(serviceNetwork, sn.String()) 168 } 169 170 machineNetwork := []hiveext.MachineNetworkEntry{} 171 for _, mn := range installConfig.Config.Networking.MachineNetwork { 172 entry := hiveext.MachineNetworkEntry{ 173 CIDR: mn.CIDR.String(), 174 } 175 machineNetwork = append(machineNetwork, entry) 176 } 177 178 agentClusterInstall := &hiveext.AgentClusterInstall{ 179 TypeMeta: metav1.TypeMeta{ 180 Kind: "AgentClusterInstall", 181 APIVersion: hiveext.GroupVersion.String(), 182 }, 183 ObjectMeta: metav1.ObjectMeta{ 184 Name: getAgentClusterInstallName(installConfig), 185 Namespace: installConfig.ClusterNamespace(), 186 }, 187 Spec: hiveext.AgentClusterInstallSpec{ 188 ImageSetRef: &hivev1.ClusterImageSetReference{ 189 Name: getClusterImageSetReferenceName(), 190 }, 191 ClusterDeploymentRef: corev1.LocalObjectReference{ 192 Name: getClusterDeploymentName(installConfig), 193 }, 194 Networking: hiveext.Networking{ 195 MachineNetwork: machineNetwork, 196 ClusterNetwork: clusterNetwork, 197 ServiceNetwork: serviceNetwork, 198 }, 199 SSHPublicKey: strings.Trim(installConfig.Config.SSHKey, "|\n\t"), 200 ProvisionRequirements: hiveext.ProvisionRequirements{ 201 ControlPlaneAgents: int(*installConfig.Config.ControlPlane.Replicas), 202 WorkerAgents: numberOfWorkers, 203 }, 204 PlatformType: agent.HivePlatformType(installConfig.Config.Platform), 205 }, 206 } 207 208 if agentClusterInstall.Spec.PlatformType == hiveext.ExternalPlatformType { 209 agentClusterInstall.Spec.ExternalPlatformSpec = &hiveext.ExternalPlatformSpec{ 210 PlatformName: installConfig.Config.Platform.External.PlatformName, 211 } 212 } 213 214 if installConfig.Config.Platform.Name() == none.Name || installConfig.Config.Platform.Name() == external.Name { 215 logrus.Debugf("Setting UserManagedNetworking to true for %s platform", installConfig.Config.Platform.Name()) 216 agentClusterInstall.Spec.Networking.UserManagedNetworking = swag.Bool(true) 217 } 218 219 icOverridden := false 220 icOverrides := agentClusterInstallInstallConfigOverrides{} 221 if installConfig.Config.FIPS { 222 icOverridden = true 223 icOverrides.FIPS = installConfig.Config.FIPS 224 } 225 226 if installConfig.Config.Proxy != nil { 227 agentClusterInstall.Spec.Proxy = (*hiveext.Proxy)(getProxy(installConfig.Config.Proxy)) 228 } 229 230 if installConfig.Config.Platform.BareMetal != nil { 231 baremetalPlatform := agentClusterInstallOnPremPlatform{} 232 bmIcOverridden := false 233 234 for _, host := range agentHosts.Hosts { 235 // Override if BMC values are not the same as default 236 if host.BMC.Username != "" || host.BMC.Password != "" || host.BMC.Address != "" { 237 bmhost := baremetal.Host{ 238 Name: host.Hostname, 239 Role: host.Role, 240 } 241 if len(host.Interfaces) > 0 { 242 // Boot MAC address is stored as first interface 243 bmhost.BootMACAddress = host.Interfaces[0].MacAddress 244 } else { 245 logrus.Infof("Could not obtain baremetal BootMacAddress for %s", installConfig.Config.Platform.Name()) 246 } 247 bmIcOverridden = true 248 bmhost.BMC = host.BMC 249 baremetalPlatform.Hosts = append(baremetalPlatform.Hosts, bmhost) 250 251 // Set provisioning network configuration 252 baremetalPlatform.ClusterProvisioningIP = installConfig.Config.Platform.BareMetal.ClusterProvisioningIP 253 baremetalPlatform.ProvisioningNetwork = installConfig.Config.Platform.BareMetal.ProvisioningNetwork 254 baremetalPlatform.ProvisioningNetworkInterface = installConfig.Config.Platform.BareMetal.ProvisioningNetworkInterface 255 baremetalPlatform.ProvisioningNetworkCIDR = installConfig.Config.Platform.BareMetal.ProvisioningNetworkCIDR 256 baremetalPlatform.ProvisioningDHCPRange = installConfig.Config.Platform.BareMetal.ProvisioningDHCPRange 257 } 258 } 259 if bmIcOverridden { 260 icOverridden = true 261 icOverrides.Platform = &agentClusterInstallPlatform{} 262 icOverrides.Platform = &agentClusterInstallPlatform{ 263 BareMetal: &baremetalPlatform, 264 } 265 } 266 267 agentClusterInstall.Spec.APIVIPs = installConfig.Config.Platform.BareMetal.APIVIPs 268 agentClusterInstall.Spec.IngressVIPs = installConfig.Config.Platform.BareMetal.IngressVIPs 269 agentClusterInstall.Spec.APIVIP = installConfig.Config.Platform.BareMetal.APIVIPs[0] 270 agentClusterInstall.Spec.IngressVIP = installConfig.Config.Platform.BareMetal.IngressVIPs[0] 271 } else if installConfig.Config.Platform.VSphere != nil { 272 vspherePlatform := vsphere.Platform{} 273 if len(installConfig.Config.Platform.VSphere.APIVIPs) > 1 { 274 icOverridden = true 275 vspherePlatform.APIVIPs = installConfig.Config.Platform.VSphere.APIVIPs 276 vspherePlatform.IngressVIPs = installConfig.Config.Platform.VSphere.IngressVIPs 277 } 278 hasCredentials := false 279 if len(installConfig.Config.Platform.VSphere.VCenters) > 0 { 280 for _, vcenter := range installConfig.Config.Platform.VSphere.VCenters { 281 if agent.VCenterCredentialsAreProvided(vcenter) { 282 icOverridden = true 283 hasCredentials = true 284 vspherePlatform.VCenters = append(vspherePlatform.VCenters, vcenter) 285 } 286 } 287 } 288 if hasCredentials && len(installConfig.Config.Platform.VSphere.FailureDomains) > 0 { 289 icOverridden = true 290 vspherePlatform.FailureDomains = append(vspherePlatform.FailureDomains, installConfig.Config.VSphere.FailureDomains...) 291 } 292 if icOverridden { 293 icOverrides.Platform = &agentClusterInstallPlatform{ 294 VSphere: &vspherePlatform, 295 } 296 } 297 agentClusterInstall.Spec.APIVIPs = installConfig.Config.Platform.VSphere.APIVIPs 298 agentClusterInstall.Spec.IngressVIPs = installConfig.Config.Platform.VSphere.IngressVIPs 299 } else if installConfig.Config.Platform.External != nil { 300 icOverridden = true 301 icOverrides.Platform = &agentClusterInstallPlatform{ 302 External: &agentClusterInstallOnPremExternalPlatform{ 303 PlatformName: installConfig.Config.External.PlatformName, 304 CloudControllerManager: installConfig.Config.External.CloudControllerManager, 305 }, 306 } 307 } 308 309 networkOverridden := setNetworkType(agentClusterInstall, installConfig.Config, "NetworkType is not specified in InstallConfig.") 310 if networkOverridden { 311 icOverridden = true 312 icOverrides.Networking = installConfig.Config.Networking 313 } 314 315 if installConfig.Config.Capabilities != nil { 316 icOverrides.Capabilities = installConfig.Config.Capabilities 317 icOverridden = true 318 } 319 320 if installConfig.Config.CPUPartitioning != "" { 321 icOverridden = true 322 icOverrides.CPUPartitioning = installConfig.Config.CPUPartitioning 323 } 324 325 if icOverridden { 326 overrides, err := json.Marshal(icOverrides) 327 if err != nil { 328 return errors.Wrap(err, "failed to marshal AgentClusterInstall installConfigOverrides") 329 } 330 agentClusterInstall.SetAnnotations(map[string]string{ 331 installConfigOverrides: string(overrides), 332 }) 333 } 334 335 a.Config = agentClusterInstall 336 337 } 338 return a.finish() 339 } 340 341 // Files returns the files generated by the asset. 342 func (a *AgentClusterInstall) Files() []*asset.File { 343 if a.File != nil { 344 return []*asset.File{a.File} 345 } 346 return []*asset.File{} 347 } 348 349 // Load returns agentclusterinstall asset from the disk. 350 func (a *AgentClusterInstall) Load(f asset.FileFetcher) (bool, error) { 351 352 agentClusterInstallFile, err := f.FetchByName(agentClusterInstallFilename) 353 if err != nil { 354 if os.IsNotExist(err) { 355 return false, nil 356 } 357 return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentClusterInstallFilename)) 358 } 359 360 agentClusterInstall := &hiveext.AgentClusterInstall{} 361 if err := yaml.UnmarshalStrict(agentClusterInstallFile.Data, agentClusterInstall); err != nil { 362 err = errors.Wrapf(err, "failed to unmarshal %s", agentClusterInstallFilename) 363 return false, err 364 } 365 366 setNetworkType(agentClusterInstall, &types.InstallConfig{}, "NetworkType is not specified in AgentClusterInstall.") 367 368 // Due to OCPBUGS-7495 we previously required lowercase platform names here, 369 // even though that is incorrect. Rewrite to the correct mixed case names 370 // for backward compatibility. 371 switch string(agentClusterInstall.Spec.PlatformType) { 372 case baremetal.Name: 373 agentClusterInstall.Spec.PlatformType = hiveext.BareMetalPlatformType 374 case external.Name: 375 agentClusterInstall.Spec.PlatformType = hiveext.ExternalPlatformType 376 case none.Name: 377 agentClusterInstall.Spec.PlatformType = hiveext.NonePlatformType 378 case vsphere.Name: 379 agentClusterInstall.Spec.PlatformType = hiveext.VSpherePlatformType 380 } 381 382 // Set the default value for userManagedNetworking, as would be done by the 383 // mutating webhook in ZTP. 384 if agentClusterInstall.Spec.Networking.UserManagedNetworking == nil { 385 switch agentClusterInstall.Spec.PlatformType { 386 case hiveext.NonePlatformType, hiveext.ExternalPlatformType: 387 logrus.Debugf("Setting UserManagedNetworking to true for %s platform", agentClusterInstall.Spec.PlatformType) 388 agentClusterInstall.Spec.Networking.UserManagedNetworking = swag.Bool(true) 389 } 390 } 391 392 a.Config = agentClusterInstall 393 394 if err = a.finish(); err != nil { 395 return false, err 396 } 397 return true, nil 398 } 399 400 func (a *AgentClusterInstall) finish() error { 401 402 if a.Config == nil { 403 return errors.New("missing configuration or manifest file") 404 } 405 406 if err := a.validateIPAddressAndNetworkType().ToAggregate(); err != nil { 407 return errors.Wrapf(err, "invalid NetworkType configured") 408 } 409 410 if err := a.validateSupportedPlatforms().ToAggregate(); err != nil { 411 return errors.Wrapf(err, "invalid PlatformType configured") 412 } 413 414 agentClusterInstallData, err := yaml.Marshal(a.Config) 415 if err != nil { 416 return errors.Wrap(err, "failed to marshal agent installer AgentClusterInstall") 417 } 418 419 a.File = &asset.File{ 420 Filename: agentClusterInstallFilename, 421 Data: agentClusterInstallData, 422 } 423 return nil 424 } 425 426 // Sets the default network type to OVNKubernetes if it is unspecified in the 427 // AgentClusterInstall or InstallConfig. 428 func setNetworkType(aci *hiveext.AgentClusterInstall, installConfig *types.InstallConfig, 429 warningMessage string) bool { 430 if aci.Spec.Networking.NetworkType != "" { 431 return false 432 } 433 434 if installConfig != nil && installConfig.Networking != nil && 435 installConfig.Networking.NetworkType != "" { 436 if installConfig.Networking.NetworkType == string(operv1.NetworkTypeOVNKubernetes) || installConfig.Networking.NetworkType == string(operv1.NetworkTypeOpenShiftSDN) { 437 aci.Spec.Networking.NetworkType = installConfig.NetworkType 438 return false 439 } 440 441 // Set OVNKubernetes in AgentClusterInstall and return true to indicate InstallConfigOverride should be used 442 aci.Spec.Networking.NetworkType = string(operv1.NetworkTypeOVNKubernetes) 443 return true 444 } 445 446 defaults.SetInstallConfigDefaults(installConfig) 447 logrus.Infof("%s Defaulting NetworkType to %s.", warningMessage, installConfig.NetworkType) 448 aci.Spec.Networking.NetworkType = installConfig.NetworkType 449 return false 450 } 451 452 func isIPv6(ipAddress net.IP) bool { 453 // Using To16() on IPv4 addresses does not return nil so it cannot be used to determine if 454 // IP addresses are IPv6. Instead we are checking if the address is IPv6 by using To4(). 455 // Same as https://github.com/openshift/installer/blob/6eca978b89fc0be17f70fc8a28fa20aab1316843/pkg/types/validation/installconfig.go#L193 456 ip := ipAddress.To4() 457 return ip == nil 458 } 459 460 func (a *AgentClusterInstall) validateIPAddressAndNetworkType() field.ErrorList { 461 var allErrs field.ErrorList 462 463 fieldPath := field.NewPath("spec", "networking", "networkType") 464 clusterNetworkPath := field.NewPath("spec", "networking", "clusterNetwork") 465 serviceNetworkPath := field.NewPath("spec", "networking", "serviceNetwork") 466 467 if a.Config.Spec.Networking.NetworkType == string(operv1.NetworkTypeOpenShiftSDN) { 468 hasIPv6 := false 469 for _, cn := range a.Config.Spec.Networking.ClusterNetwork { 470 ipNet, errCIDR := ipnet.ParseCIDR(cn.CIDR) 471 if errCIDR != nil { 472 allErrs = append(allErrs, field.Required(clusterNetworkPath, "error parsing the clusterNetwork CIDR")) 473 continue 474 } 475 if isIPv6(ipNet.IP) { 476 hasIPv6 = true 477 } 478 } 479 if hasIPv6 { 480 allErrs = append(allErrs, field.Required(fieldPath, 481 fmt.Sprintf("clusterNetwork CIDR is IPv6 and is not compatible with networkType %s", 482 operv1.NetworkTypeOpenShiftSDN))) 483 } 484 485 hasIPv6 = false 486 for _, cidr := range a.Config.Spec.Networking.ServiceNetwork { 487 ipNet, errCIDR := ipnet.ParseCIDR(cidr) 488 if errCIDR != nil { 489 allErrs = append(allErrs, field.Required(serviceNetworkPath, "error parsing the clusterNetwork CIDR")) 490 continue 491 } 492 if isIPv6(ipNet.IP) { 493 hasIPv6 = true 494 } 495 } 496 if hasIPv6 { 497 allErrs = append(allErrs, field.Required(fieldPath, 498 fmt.Sprintf("serviceNetwork CIDR is IPv6 and is not compatible with networkType %s", 499 operv1.NetworkTypeOpenShiftSDN))) 500 } 501 } 502 503 return allErrs 504 } 505 506 func (a *AgentClusterInstall) validateSupportedPlatforms() field.ErrorList { 507 var allErrs field.ErrorList 508 509 if a.Config.Spec.PlatformType != "" && !agent.IsSupportedPlatform(a.Config.Spec.PlatformType) { 510 fieldPath := field.NewPath("spec", "platformType") 511 allErrs = append(allErrs, field.NotSupported(fieldPath, a.Config.Spec.PlatformType, agent.SupportedHivePlatforms())) 512 } 513 514 switch a.Config.Spec.PlatformType { 515 case hiveext.NonePlatformType, hiveext.ExternalPlatformType: 516 if a.Config.Spec.Networking.UserManagedNetworking != nil && !*a.Config.Spec.Networking.UserManagedNetworking { 517 fieldPath := field.NewPath("spec", "networking", "userManagedNetworking") 518 allErrs = append(allErrs, field.Forbidden(fieldPath, 519 fmt.Sprintf("%s platform requires user-managed networking", 520 a.Config.Spec.PlatformType))) 521 } 522 } 523 return allErrs 524 } 525 526 // FIPSEnabled returns whether FIPS is enabled in the cluster configuration. 527 func (a *AgentClusterInstall) FIPSEnabled() bool { 528 icOverrides := agentClusterInstallInstallConfigOverrides{} 529 if err := json.Unmarshal([]byte(a.Config.Annotations[installConfigOverrides]), &icOverrides); err == nil { 530 return icOverrides.FIPS 531 } 532 return false 533 } 534 535 // GetExternalPlatformName returns the platform name for the external platform. 536 func (a *AgentClusterInstall) GetExternalPlatformName() string { 537 if a.Config != nil && a.Config.Spec.ExternalPlatformSpec != nil { 538 return a.Config.Spec.ExternalPlatformSpec.PlatformName 539 } 540 return "" 541 }