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  }