github.com/vmware/govmomi@v0.51.0/cli/cluster/stretch.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package cluster 6 7 import ( 8 "context" 9 "flag" 10 "fmt" 11 "strings" 12 13 "github.com/vmware/govmomi/cli" 14 "github.com/vmware/govmomi/cli/flags" 15 "github.com/vmware/govmomi/object" 16 vim "github.com/vmware/govmomi/vim25/types" 17 "github.com/vmware/govmomi/vsan" 18 "github.com/vmware/govmomi/vsan/methods" 19 "github.com/vmware/govmomi/vsan/types" 20 ) 21 22 type stretch struct { 23 *flags.DatacenterFlag 24 25 WitnessHost string 26 FirstFaultDomainHosts string 27 SecondFaultDomainHosts string 28 FirstFaultDomainName string 29 SecondFaultDomainName string 30 PreferredFaultDomain string 31 } 32 33 func init() { 34 cli.Register("cluster.stretch", &stretch{}) 35 } 36 37 func (cmd *stretch) Usage() string { 38 return "CLUSTER" 39 } 40 41 func (cmd *stretch) Description() string { 42 return `Convert a vSAN cluster into a stretched cluster 43 44 The vSAN cluster is converted to a stretched cluster with a witness host 45 specified by the 'witness' flag. The datastore hosts are placed into one 46 of two fault domains that are specified in each host list. The name of the 47 preferred fault domain can be specified by the 'preferred-fault-domain' flag. 48 49 Examples: 50 govc cluster.stretch -dc remote-site-1 \ 51 -witness /dc-name/host/192.168.112.2 \ 52 -first-fault-domain-hosts 192.168.113.121 \ 53 -second-fault-domain-hosts 192.168.113.45,192.168.113.70 \ 54 cluster-name` 55 } 56 57 func (cmd *stretch) Register(ctx context.Context, f *flag.FlagSet) { 58 cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) 59 cmd.DatacenterFlag.Register(ctx, f) 60 61 f.StringVar(&cmd.WitnessHost, "witness", "", "Witness host for the stretched cluster") 62 f.StringVar(&cmd.FirstFaultDomainHosts, "first-fault-domain-hosts", "", "Hosts to place in the first fault domain") 63 f.StringVar(&cmd.SecondFaultDomainHosts, "second-fault-domain-hosts", "", "Hosts to place in the second fault domain") 64 f.StringVar(&cmd.FirstFaultDomainName, "first-fault-domain-name", "Primary", "Name of the first fault domain") 65 f.StringVar(&cmd.SecondFaultDomainName, "second-fault-domain-name", "Secondary", "Name of the second fault domain") 66 f.StringVar(&cmd.PreferredFaultDomain, "preferred-fault-domain", "Primary", "Name of the preferred fault domain") 67 } 68 69 func (cmd *stretch) Process(ctx context.Context) error { 70 if err := cmd.DatacenterFlag.Process(ctx); err != nil { 71 return err 72 } 73 74 if cmd.WitnessHost == "" || 75 cmd.FirstFaultDomainHosts == "" || 76 cmd.FirstFaultDomainName == "" || 77 cmd.SecondFaultDomainHosts == "" || 78 cmd.SecondFaultDomainName == "" || 79 cmd.PreferredFaultDomain == "" { 80 return flag.ErrHelp 81 } 82 83 return nil 84 } 85 86 func (cmd *stretch) Run(ctx context.Context, f *flag.FlagSet) error { 87 if f.NArg() != 1 { 88 return flag.ErrHelp 89 } 90 91 client, err := cmd.Client() 92 if err != nil { 93 return err 94 } 95 96 vsanClient, err := vsan.NewClient(ctx, client) 97 if err != nil { 98 return err 99 } 100 101 finder, err := cmd.Finder() 102 if err != nil { 103 return err 104 } 105 106 clusterResource, err := finder.ClusterComputeResource(ctx, f.Arg(0)) 107 if err != nil { 108 return err 109 } 110 111 witnessHost, err := finder.HostSystem(ctx, cmd.WitnessHost) 112 if err != nil { 113 return err 114 } 115 116 faultDomainConfig, err := cmd.buildFaultDomainConfig(ctx) 117 if err != nil { 118 return err 119 } 120 121 req := types.VSANVcConvertToStretchedCluster{ 122 This: vsan.VsanVcStretchedClusterSystem, 123 Cluster: clusterResource.Reference(), 124 FaultDomainConfig: *faultDomainConfig, 125 WitnessHost: witnessHost.Reference(), 126 PreferredFd: cmd.PreferredFaultDomain, 127 DiskMapping: nil, 128 } 129 130 res, err := methods.VSANVcConvertToStretchedCluster(ctx, vsanClient, &req) 131 if err != nil { 132 return err 133 } 134 135 logger := cmd.ProgressLogger("stretching cluster... ") 136 defer logger.Wait() 137 138 task := object.NewTask(client, res.Returnval) 139 _, err = task.WaitForResult(ctx, logger) 140 return err 141 } 142 143 func (cmd *stretch) buildFaultDomainConfig(ctx context.Context) (*types.VimClusterVSANStretchedClusterFaultDomainConfig, error) { 144 var faultDomainConfig types.VimClusterVSANStretchedClusterFaultDomainConfig 145 var err error 146 147 faultDomainConfig.FirstFdName = cmd.FirstFaultDomainName 148 faultDomainConfig.FirstFdHosts, err = cmd.getManagedObjectRefs(cmd.FirstFaultDomainHosts, ctx) 149 if err != nil { 150 return nil, err 151 } 152 153 if len(faultDomainConfig.FirstFdHosts) == 0 { 154 return nil, fmt.Errorf("no hosts for fault domain %q", cmd.FirstFaultDomainName) 155 } 156 157 faultDomainConfig.SecondFdName = cmd.SecondFaultDomainName 158 faultDomainConfig.SecondFdHosts, err = cmd.getManagedObjectRefs(cmd.SecondFaultDomainHosts, ctx) 159 if err != nil { 160 return nil, err 161 } 162 163 if len(faultDomainConfig.SecondFdHosts) == 0 { 164 return nil, fmt.Errorf("no hosts for fault domain %q", cmd.SecondFaultDomainName) 165 } 166 167 return &faultDomainConfig, nil 168 } 169 170 func (cmd *stretch) getManagedObjectRefs(domainHosts string, ctx context.Context) ([]vim.ManagedObjectReference, error) { 171 finder, err := cmd.Finder() 172 if err != nil { 173 return nil, err 174 } 175 176 var refs []vim.ManagedObjectReference 177 for _, host := range strings.Split(domainHosts, ",") { 178 h, err := finder.HostSystem(ctx, host) 179 if err != nil { 180 return nil, err 181 } 182 183 refs = append(refs, h.Reference()) 184 } 185 186 return refs, nil 187 }