github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/vsphere/metadata.go (about) 1 package vsphere 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "path" 8 "strings" 9 "sync" 10 11 "github.com/vmware/govmomi/object" 12 "github.com/vmware/govmomi/vim25/soap" 13 "sigs.k8s.io/cluster-api-provider-vsphere/pkg/session" 14 15 "github.com/openshift/installer/pkg/types/vsphere" 16 ) 17 18 // NetworkNameMap contains the vCenter cluster name, resource pools and network names. 19 type NetworkNameMap struct { 20 Cluster string 21 ResourcePools map[string]*object.ResourcePool 22 NetworkNames map[string]string 23 } 24 25 // VCenterContext maintains context of known vCenters to be used in CAPI manifest reconciliation. 26 type VCenterContext struct { 27 VCenter string 28 Datacenters []string 29 ClusterNetworkMap map[string]NetworkNameMap 30 } 31 32 // VCenterCredential contains the vCenter username and password. 33 type VCenterCredential struct { 34 Username string 35 Password string 36 } 37 38 // Metadata holds vcenter stuff. 39 type Metadata struct { 40 sessions map[string]*session.Session 41 credentials map[string]*session.Params 42 43 VCenterContexts map[string]VCenterContext 44 45 VCenterCredentials map[string]VCenterCredential 46 47 mutex sync.Mutex 48 } 49 50 // NewMetadata initializes a new Metadata object. 51 func NewMetadata() *Metadata { 52 return &Metadata{ 53 sessions: make(map[string]*session.Session), 54 credentials: make(map[string]*session.Params), 55 VCenterContexts: make(map[string]VCenterContext), 56 VCenterCredentials: make(map[string]VCenterCredential), 57 } 58 } 59 60 // AddCredentials creates a session param from the vCenter server, username and password 61 // to the Credentials Map. 62 func (m *Metadata) AddCredentials(server, username, password string) *session.Params { 63 if _, ok := m.VCenterCredentials[server]; !ok { 64 m.VCenterCredentials[server] = VCenterCredential{ 65 Username: username, 66 Password: password, 67 } 68 } 69 70 // m.credentials is not stored in the json state file - there is no real reason to do this 71 // but upon returning to AddCredentials (create manifest, create cluster) the credentials map is 72 // nil, re-make it. 73 if m.credentials == nil { 74 m.credentials = make(map[string]*session.Params) 75 } 76 77 if _, ok := m.credentials[server]; !ok { 78 m.credentials[server] = session.NewParams().WithServer(server).WithUserInfo(username, password) 79 } 80 return m.credentials[server] 81 } 82 83 // Session returns a session from unlockedSession based on the server (vCenter URL). 84 func (m *Metadata) Session(ctx context.Context, server string) (*session.Session, error) { 85 m.mutex.Lock() 86 defer m.mutex.Unlock() 87 88 // m.sessions is not stored in the json state file - there is no real reason to do this 89 // but upon returning to Session (create manifest, create cluster) the sessions map is 90 // nil, re-make it. 91 if m.sessions == nil { 92 m.sessions = make(map[string]*session.Session) 93 } 94 95 return m.unlockedSession(ctx, server) 96 } 97 98 func (m *Metadata) unlockedSession(ctx context.Context, server string) (*session.Session, error) { 99 var err error 100 var ok bool 101 var params *session.Params 102 103 if params, ok = m.credentials[server]; !ok { 104 if creds, ok := m.VCenterCredentials[server]; ok { 105 params = m.AddCredentials(server, creds.Username, creds.Password) 106 } else { 107 return nil, fmt.Errorf("credentials for %s not found", server) 108 } 109 } 110 111 // if nil we haven't created a session 112 if _, ok := m.sessions[server]; ok { 113 // is the session still valid? if not re-run GetOrCreate. 114 if !m.sessions[server].Valid() { 115 m.sessions[server], err = session.GetOrCreate(ctx, params) 116 if err != nil { 117 return nil, err 118 } 119 } 120 return m.sessions[server], nil 121 } 122 123 // If we have gotten here there is no session for the server name, create. 124 m.sessions[server], err = session.GetOrCreate(ctx, params) 125 return m.sessions[server], err 126 } 127 128 // unwrapToSoapFault is required because soapErrorFaul is not exported 129 // and are unable to use errors.As() 130 // https://github.com/vmware/govmomi/blob/main/vim25/soap/error.go#L38 131 func unwrapToSoapFault(err error) error { 132 if err != nil { 133 if soapFault := soap.IsSoapFault(err); !soapFault { 134 return unwrapToSoapFault(errors.Unwrap(err)) 135 } 136 return err 137 } 138 return err 139 } 140 141 // Networks populates VCenterContext and the ClusterNetworkMap based on the vCenter server url and the FailureDomains. 142 func (m *Metadata) Networks(ctx context.Context, vcenter vsphere.VCenter, failureDomains []vsphere.FailureDomain) error { 143 _, err := m.Session(ctx, vcenter.Server) 144 if err != nil { 145 // Defense against potential issues with assisted installer 146 if soapErr := unwrapToSoapFault(err); soapErr != nil { 147 soapFault := soap.ToSoapFault(soapErr) 148 // The assisted installer provides bogus username and password 149 // values. Only return the soap error (fault) if it matches incorrect username or password. 150 if strings.Contains(soapFault.String, "Cannot complete login due to an incorrect user name or password") { 151 return soapErr 152 } 153 } 154 // if soapErr is nil then this is not a SOAP fault, return err 155 return err 156 } 157 158 m.VCenterContexts[vcenter.Server] = VCenterContext{ 159 VCenter: vcenter.Server, 160 Datacenters: vcenter.Datacenters, 161 ClusterNetworkMap: make(map[string]NetworkNameMap), 162 } 163 164 for _, fd := range failureDomains { 165 if fd.Server != vcenter.Server { 166 continue 167 } 168 if err := m.populateNetworks(ctx, fd); err != nil { 169 return fmt.Errorf("unable to retrieve network names: %w", err) 170 } 171 } 172 173 return nil 174 } 175 176 func (m *Metadata) populateNetworks(ctx context.Context, failureDomain vsphere.FailureDomain) error { 177 var ok bool 178 var sess *session.Session 179 180 if sess, ok = m.sessions[failureDomain.Server]; !ok { 181 return fmt.Errorf("unable to find session") 182 } 183 184 finder := sess.Finder 185 clusterPath := failureDomain.Topology.ComputeCluster 186 187 clusterRef, err := finder.ClusterComputeResource(ctx, clusterPath) 188 if err != nil { 189 return fmt.Errorf("unable to retrieve compute cluster: %w", err) 190 } 191 192 rpFindPath := path.Join(clusterRef.InventoryPath, "...") 193 pools, err := finder.ResourcePoolList(ctx, rpFindPath) 194 if err != nil { 195 return fmt.Errorf("unable to retrieve resource pools relative to compute cluster: %w", err) 196 } 197 198 for _, network := range failureDomain.Topology.Networks { 199 clusterMap, present := m.VCenterContexts[failureDomain.Server].ClusterNetworkMap[clusterPath] 200 if !present { 201 clusterMap = NetworkNameMap{ 202 Cluster: clusterPath, 203 NetworkNames: map[string]string{}, 204 ResourcePools: map[string]*object.ResourcePool{}, 205 } 206 for _, pool := range pools { 207 clusterMap.ResourcePools[path.Clean(pool.InventoryPath)] = pool 208 } 209 210 m.VCenterContexts[failureDomain.Server].ClusterNetworkMap[clusterPath] = clusterMap 211 } 212 213 networkPath := path.Join(clusterRef.InventoryPath, network) 214 215 // Added check to confirm that the path to the network exists. 216 networkRef, err := finder.Network(ctx, networkPath) 217 if err != nil { 218 return err 219 } 220 221 clusterMap.NetworkNames[network] = networkRef.GetInventoryPath() 222 } 223 return nil 224 }