github.com/vmware/govmomi@v0.37.1/govc/namespace/cluster/enable.go (about) 1 /* 2 Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cluster 18 19 import ( 20 "context" 21 "flag" 22 "fmt" 23 "strconv" 24 "strings" 25 26 "github.com/vmware/govmomi/govc/cli" 27 "github.com/vmware/govmomi/govc/flags" 28 "github.com/vmware/govmomi/govc/storage/policy" 29 "github.com/vmware/govmomi/vapi/namespace" 30 ) 31 32 type enableCluster struct { 33 ControlPlaneDNSSearchDomains string 34 ImageStoragePolicy string 35 NcpClusterNetworkSpec workloadNetwork 36 ControlPlaneManagementNetwork masterManagementNetwork 37 ControlPlaneDNSNames string 38 ControlPlaneNTPServers string 39 EphemeralStoragePolicy string 40 DefaultImageRepository string 41 ServiceCidr string 42 LoginBanner string 43 SizeHint string 44 WorkerDNS string 45 DefaultImageRegistry string 46 ControlPlaneDNS string 47 NetworkProvider string 48 ControlPlaneStoragePolicy string 49 DefaultKubernetesServiceContentLibrary string 50 51 *flags.ClusterFlag 52 } 53 54 type masterManagementNetwork struct { 55 Mode string 56 FloatingIP string 57 AddressRange *namespace.AddressRange 58 Network string 59 } 60 61 type workloadNetwork struct { 62 NsxEdgeCluster string 63 PodCidrs string 64 EgressCidrs string 65 Switch string 66 IngressCidrs string 67 } 68 69 type objectReferences struct { 70 Cluster string 71 Network string 72 ImageStoragePolicy string 73 ControlPlaneStoragePolicy string 74 EphemeralStoragePolicy string 75 WorkerNetworkSwitch string 76 EdgeCluster string 77 } 78 79 func init() { 80 newEnableCluster := &enableCluster{ 81 ControlPlaneManagementNetwork: masterManagementNetwork{ 82 AddressRange: &namespace.AddressRange{}, 83 }, 84 } 85 cli.Register("namespace.cluster.enable", newEnableCluster) 86 } 87 88 func (cmd *enableCluster) Register(ctx context.Context, f *flag.FlagSet) { 89 cmd.ClusterFlag, ctx = flags.NewClusterFlag(ctx) 90 // this brings in some additional flags 91 // Datacenter flag, which we do want in order to configure a Finder we use to do some lookups 92 // ClientFlag, which we do need to control auth and connection details 93 // OutputFlag, which doesn't have any effect in this case as there's no response to output 94 cmd.ClusterFlag.Register(ctx, f) 95 // Descriptions are mostly extracted from: 96 // https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/operations/com/vmware/vcenter/namespace_management/clusters.enable-operation.html 97 f.StringVar(&cmd.SizeHint, "size", "", 98 "The size of the Kubernetes API server and the worker nodes. Value is one of: TINY, SMALL, MEDIUM, LARGE.") 99 f.StringVar(&cmd.ServiceCidr, "service-cidr", "", 100 "CIDR block from which Kubernetes allocates service cluster IP addresses. Shouldn't overlap with pod, ingress or egress CIDRs") 101 f.StringVar(&cmd.NetworkProvider, "network-provider", "NSXT_CONTAINER_PLUGIN", 102 "Optional. Provider of cluster networking for this vSphere Namespaces cluster. Currently only value supported is: NSXT_CONTAINER_PLUGIN.") 103 f.StringVar(&cmd.NcpClusterNetworkSpec.PodCidrs, "pod-cidrs", "", 104 "CIDR blocks from which Kubernetes allocates pod IP addresses. Comma-separated list. Shouldn't overlap with service, ingress or egress CIDRs.") 105 f.StringVar(&cmd.NcpClusterNetworkSpec.IngressCidrs, "workload-network.ingress-cidrs", "", 106 "CIDR blocks from which NSX assigns IP addresses for Kubernetes Ingresses and Kubernetes Services of type LoadBalancer. Comma-separated list. Shouldn't overlap with pod, service or egress CIDRs.") 107 f.StringVar(&cmd.NcpClusterNetworkSpec.EgressCidrs, "workload-network.egress-cidrs", "", 108 "CIDR blocks from which NSX assigns IP addresses used for performing SNAT from container IPs to external IPs. Comma-separated list. Shouldn't overlap with pod, service or ingress CIDRs.") 109 f.StringVar(&cmd.NcpClusterNetworkSpec.Switch, "workload-network.switch", "", 110 "vSphere Distributed Switch used to connect this cluster.") 111 f.StringVar(&cmd.NcpClusterNetworkSpec.NsxEdgeCluster, "workload-network.edge-cluster", "", 112 "NSX Edge Cluster to be used for Kubernetes Services of type LoadBalancer, Kubernetes Ingresses, and NSX SNAT.") 113 f.StringVar(&cmd.ControlPlaneManagementNetwork.Network, "mgmt-network.network", "", 114 "Identifier for the management network.") 115 f.StringVar(&cmd.ControlPlaneManagementNetwork.Mode, "mgmt-network.mode", "STATICRANGE", 116 "IPv4 address assignment modes. Value is one of: DHCP, STATICRANGE") 117 f.StringVar(&cmd.ControlPlaneManagementNetwork.FloatingIP, "mgmt-network.floating-IP", "", 118 "Optional. The Floating IP used by the HA master cluster in the when network Mode is DHCP.") 119 f.StringVar(&cmd.ControlPlaneManagementNetwork.AddressRange.StartingAddress, "mgmt-network.starting-address", "", 120 "Denotes the start of the IP range to be used. Optional, but required with network mode STATICRANGE.") 121 f.IntVar(&cmd.ControlPlaneManagementNetwork.AddressRange.AddressCount, "mgmt-network.address-count", 5, 122 "The number of IP addresses in the management range. Optional, but required with network mode STATICRANGE.") 123 f.StringVar(&cmd.ControlPlaneManagementNetwork.AddressRange.SubnetMask, "mgmt-network.subnet-mask", "", 124 "Subnet mask of the management network. Optional, but required with network mode STATICRANGE.") 125 f.StringVar(&cmd.ControlPlaneManagementNetwork.AddressRange.Gateway, "mgmt-network.gateway", "", 126 "Gateway to be used for the management IP range") 127 f.StringVar(&cmd.ControlPlaneDNS, "control-plane-dns", "", 128 "Comma-separated list of DNS server IP addresses to use on Kubernetes API server, specified in order of preference.") 129 f.StringVar(&cmd.ControlPlaneDNSNames, "control-plane-dns-names", "", 130 "Comma-separated list of DNS names to associate with the Kubernetes API server. These DNS names are embedded in the TLS certificate presented by the API server.") 131 f.StringVar(&cmd.ControlPlaneDNSSearchDomains, "control-plane-dns-search-domains", "", 132 "Comma-separated list of domains to be searched when trying to lookup a host name on Kubernetes API server, specified in order of preference.") 133 f.StringVar(&cmd.ControlPlaneNTPServers, "control-plane-ntp-servers", "", 134 "Optional. Comma-separated list of NTP server DNS names or IP addresses to use on Kubernetes API server, specified in order of preference. If unset, VMware Tools based time synchronization is enabled.") 135 f.StringVar(&cmd.WorkerDNS, "worker-dns", "", 136 "Comma-separated list of DNS server IP addresses to use on the worker nodes, specified in order of preference.") 137 f.StringVar(&cmd.ControlPlaneStoragePolicy, "control-plane-storage-policy", "", 138 "Storage Policy associated with Kubernetes API server.") 139 f.StringVar(&cmd.EphemeralStoragePolicy, "ephemeral-storage-policy", "", 140 "Storage Policy associated with ephemeral disks of all the Kubernetes Pods in the cluster.") 141 f.StringVar(&cmd.ImageStoragePolicy, "image-storage-policy", "", 142 "Storage Policy to be used for container images.") 143 f.StringVar(&cmd.LoginBanner, "login-banner", "", 144 "Optional. Disclaimer to be displayed prior to login via the Kubectl plugin.") 145 // documented API is currently ambiguous with these duplicated fields, need to wait for this to be resolved 146 //f.StringVar(&cmd.DefaultImageRegistry, "default-image-registry", "", 147 // "Optional. Default image registry to use when unspecified in the container image name. Defaults to Docker Hub.") 148 //f.StringVar(&cmd.DefaultImageRepository, "default-image-repository", "", 149 // "Optional. Default image registry to use when unspecified in the container image name. Defaults to Docker Hub.") 150 // TODO 151 // f.StringVar(&cmd.DefaultKubernetesServiceContentLibrary, "default-kubernetes-service-content-library", "", 152 // "Optional. Content Library which holds the VM Images for vSphere Kubernetes Service. This Content Library should be subscribed to VMware's hosted vSphere Kubernetes Service Repository.") 153 } 154 155 func (cmd *enableCluster) Description() string { 156 return `Enable vSphere Namespaces on the cluster. 157 This operation sets up Kubernetes instance for the cluster along with worker nodes. 158 159 Examples: 160 govc namespace.cluster.enable \ 161 -cluster "Workload-Cluster" \ 162 -service-cidr 10.96.0.0/23 \ 163 -pod-cidrs 10.244.0.0/20 \ 164 -control-plane-dns 10.10.10.10 \ 165 -control-plane-dns-names wcp.example.com \ 166 -workload-network.egress-cidrs 10.0.0.128/26 \ 167 -workload-network.ingress-cidrs "10.0.0.64/26" \ 168 -workload-network.switch VDS \ 169 -workload-network.edge-cluster Edge-Cluster-1 \ 170 -size TINY \ 171 -mgmt-network.network "DVPG-Management Network" \ 172 -mgmt-network.gateway 10.0.0.1 \ 173 -mgmt-network.starting-address 10.0.0.45 \ 174 -mgmt-network.subnet-mask 255.255.255.0 \ 175 -ephemeral-storage-policy "vSAN Default Storage Policy" \ 176 -control-plane-storage-policy "vSAN Default Storage Policy" \ 177 -image-storage-policy "vSAN Default Storage Policy"` 178 } 179 180 func (cmd *enableCluster) Run(ctx context.Context, f *flag.FlagSet) error { 181 c, err := cmd.RestClient() 182 if err != nil { 183 return err 184 } 185 186 // Cluster object reference lookup 187 cluster, err := cmd.Cluster() 188 if err != nil { 189 return err 190 } 191 id := cluster.Reference().Value 192 193 finder, err := cmd.ClusterFlag.Finder() 194 if err != nil { 195 return err 196 } 197 198 // Network object reference lookup. 199 networkRef, err := finder.Network(ctx, cmd.ControlPlaneManagementNetwork.Network) 200 if err != nil { 201 return err 202 } 203 204 // Storage policy object references lookup 205 pbmc, err := cmd.PbmClient() 206 if err != nil { 207 return fmt.Errorf("error creating client for storage policy lookup: %s", err) 208 } 209 210 storagePolicyNames := make(map[string]string) 211 storagePolicyNames["control-plane"] = cmd.ControlPlaneStoragePolicy 212 storagePolicyNames["ephemeral"] = cmd.EphemeralStoragePolicy 213 storagePolicyNames["image"] = cmd.ImageStoragePolicy 214 // keep track of names we looked up, so we don't repeat lookups 215 visited := make(map[string]string) 216 storagePolicyRefs := make(map[string]string) 217 for k, v := range storagePolicyNames { 218 if _, exists := visited[v]; !exists { 219 policies, err := policy.ListProfiles(ctx, pbmc, v) 220 if err != nil { 221 return fmt.Errorf("error looking up storage policy %q: %s", v, err) 222 } else if len(policies) != 1 { 223 return fmt.Errorf("could not find a unique storage policy ID for query %q", v) 224 } 225 visited[v] = policies[0].GetPbmProfile().ProfileId.UniqueId 226 storagePolicyRefs[k] = policies[0].GetPbmProfile().ProfileId.UniqueId 227 } else { 228 storagePolicyRefs[k] = visited[v] 229 } 230 231 } 232 233 // DVS Object reference lookup 234 // We need an id returned from the namespace lookup here, not a regular managed object reference. 235 // Similar approach in powerCLI here: 236 // https://github.com/lamw/PowerCLI-Example-Scripts/blob/7e4b9b9c93c5ffaa0ac2fefa8e02e5f751c044b7/Modules/VMware.WorkloadManagement/VMware.WorkloadManagement.psm1#L123 237 // Note that the data model returned means we get no chance to choose the switch by name. 238 // We assume there's just one switch per cluster and bail out otherwise. 239 m := namespace.NewManager(c) 240 clusterId := cluster.Reference().Value 241 switches, err := m.ListCompatibleDistributedSwitches(ctx, clusterId) 242 if err != nil { 243 return fmt.Errorf("error in compatible switch lookup: %s", err) 244 } else if len(switches) != 1 { 245 return fmt.Errorf("expected to find 1 namespace compatible switch in cluster %q, found %d", 246 clusterId, len(switches)) 247 } 248 249 switchId := switches[0].DistributedSwitch 250 251 edgeClusterDisplayName := cmd.NcpClusterNetworkSpec.NsxEdgeCluster 252 edgeClusters, err := m.ListCompatibleEdgeClusters(ctx, clusterId, switchId) 253 if err != nil { 254 return fmt.Errorf("error in compatible edge cluster lookup: %s", err) 255 } 256 257 matchingEdgeClusters := make([]string, 0) 258 for _, v := range edgeClusters { 259 if v.DisplayName == edgeClusterDisplayName { 260 matchingEdgeClusters = append(matchingEdgeClusters, v.EdgeCluster) 261 } 262 } 263 if len(matchingEdgeClusters) != 1 { 264 return fmt.Errorf("Didn't find unique match for edge cluster %q, found %d objects", 265 edgeClusterDisplayName, len(matchingEdgeClusters)) 266 } 267 268 resolvedObjectRefs := objectReferences{ 269 Cluster: cluster.Reference().Value, 270 Network: networkRef.Reference().Value, 271 ControlPlaneStoragePolicy: storagePolicyRefs["control-plane"], 272 EphemeralStoragePolicy: storagePolicyRefs["ephemeral"], 273 ImageStoragePolicy: storagePolicyRefs["image"], 274 WorkerNetworkSwitch: switchId, 275 EdgeCluster: matchingEdgeClusters[0], 276 } 277 278 enableClusterSpec, err := cmd.toVapiSpec(resolvedObjectRefs) 279 if err != nil { 280 return err 281 } 282 283 err = m.EnableCluster(ctx, id, enableClusterSpec) 284 if err != nil { 285 return err 286 } 287 288 return nil 289 } 290 291 func (cmd *enableCluster) toVapiSpec(refs objectReferences) (*namespace.EnableClusterSpec, error) { 292 293 podCidrs, err := splitCidrList(splitCommaSeparatedList(cmd.NcpClusterNetworkSpec.PodCidrs)) 294 if err != nil { 295 return nil, fmt.Errorf("invalid workload-network.pod-cidrs value: %s", err) 296 } 297 egressCidrs, err := splitCidrList(splitCommaSeparatedList(cmd.NcpClusterNetworkSpec.EgressCidrs)) 298 if err != nil { 299 return nil, fmt.Errorf("invalid workload-network.egress-cidrs value: %s", err) 300 } 301 ingressCidrs, err := splitCidrList(splitCommaSeparatedList(cmd.NcpClusterNetworkSpec.IngressCidrs)) 302 if err != nil { 303 return nil, fmt.Errorf("invalid workload-network.ingress-cidrs value: %s", err) 304 } 305 serviceCidr, err := splitCidr(cmd.ServiceCidr) 306 if err != nil { 307 return nil, fmt.Errorf("invalid service-cidr value: %s", err) 308 } 309 var masterManagementNetwork *namespace.MasterManagementNetwork 310 if (cmd.ControlPlaneManagementNetwork.Mode != "") || 311 (cmd.ControlPlaneManagementNetwork.FloatingIP != "") || 312 (cmd.ControlPlaneManagementNetwork.Network != "") { 313 masterManagementNetwork = &namespace.MasterManagementNetwork{} 314 masterManagementNetwork.AddressRange = cmd.ControlPlaneManagementNetwork.AddressRange 315 masterManagementNetwork.FloatingIP = cmd.ControlPlaneManagementNetwork.FloatingIP 316 ipam := namespace.IpAssignmentModeFromString(cmd.ControlPlaneManagementNetwork.Mode) 317 masterManagementNetwork.Mode = &ipam 318 masterManagementNetwork.Network = cmd.ControlPlaneManagementNetwork.Network 319 } 320 if masterManagementNetwork != nil { 321 if (masterManagementNetwork.AddressRange.SubnetMask == "") && 322 (masterManagementNetwork.AddressRange.StartingAddress == "") && 323 (masterManagementNetwork.AddressRange.Gateway == "") && 324 (masterManagementNetwork.AddressRange.AddressCount == 0) { 325 masterManagementNetwork.AddressRange = nil 326 } 327 masterManagementNetwork.Network = refs.Network 328 } 329 330 sh := namespace.SizingHintFromString(cmd.SizeHint) 331 np := namespace.ClusterNetworkProviderFromString(cmd.NetworkProvider) 332 333 spec := namespace.EnableClusterSpec{ 334 MasterDNSSearchDomains: splitCommaSeparatedList(cmd.ControlPlaneDNSSearchDomains), 335 ImageStorage: namespace.ImageStorageSpec{StoragePolicy: refs.ImageStoragePolicy}, 336 NcpClusterNetworkSpec: &namespace.NcpClusterNetworkSpec{ 337 NsxEdgeCluster: refs.EdgeCluster, 338 PodCidrs: podCidrs, 339 EgressCidrs: egressCidrs, 340 ClusterDistributedSwitch: refs.WorkerNetworkSwitch, 341 IngressCidrs: ingressCidrs, 342 }, 343 MasterManagementNetwork: masterManagementNetwork, 344 MasterDNSNames: splitCommaSeparatedList(cmd.ControlPlaneDNSNames), 345 MasterNTPServers: splitCommaSeparatedList(cmd.ControlPlaneNTPServers), 346 EphemeralStoragePolicy: refs.EphemeralStoragePolicy, 347 DefaultImageRepository: cmd.DefaultImageRepository, 348 ServiceCidr: serviceCidr, 349 LoginBanner: cmd.LoginBanner, 350 SizeHint: &sh, 351 WorkerDNS: splitCommaSeparatedList(cmd.WorkerDNS), 352 DefaultImageRegistry: nil, 353 MasterDNS: splitCommaSeparatedList(cmd.ControlPlaneDNS), 354 NetworkProvider: &np, 355 MasterStoragePolicy: refs.ControlPlaneStoragePolicy, 356 DefaultKubernetesServiceContentLibrary: cmd.DefaultKubernetesServiceContentLibrary, 357 } 358 return &spec, nil 359 } 360 361 func splitCidr(input string) (*namespace.Cidr, error) { 362 parts := strings.Split(input, "/") 363 if !(len(parts) == 2) || parts[0] == "" { 364 return nil, fmt.Errorf("invalid cidr %q supplied, needs to be in form '192.168.1.0/24'", input) 365 } 366 367 prefix, err := strconv.Atoi(parts[1]) 368 if err != nil { 369 return nil, err 370 } 371 372 result := namespace.Cidr{ 373 Address: parts[0], 374 Prefix: prefix, 375 } 376 return &result, nil 377 } 378 379 func splitCidrList(input []string) ([]namespace.Cidr, error) { 380 var result []namespace.Cidr 381 for i, cidrIn := range input { 382 cidr, err := splitCidr(cidrIn) 383 if err != nil { 384 return nil, fmt.Errorf("parsing cidr %q in list position %d : %q", cidrIn, i, err) 385 } 386 result = append(result, *cidr) 387 } 388 return result, nil 389 } 390 391 func splitCommaSeparatedList(cslist string) []string { 392 return deleteEmpty(strings.Split(cslist, ",")) 393 } 394 395 func deleteEmpty(s []string) []string { 396 var r []string 397 for _, str := range s { 398 if str != "" { 399 r = append(r, str) 400 } 401 } 402 return r 403 }