Skip to content

Commit 5c66cff

Browse files
rvanderp3jcpowermac
authored andcommitted
infrastructure provisioning
1 parent 37a5995 commit 5c66cff

File tree

2 files changed

+358
-0
lines changed

2 files changed

+358
-0
lines changed

pkg/infrastructure/platform/platform.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
awsinfra "github.com/openshift/installer/pkg/infrastructure/aws"
1212
"github.com/openshift/installer/pkg/infrastructure/clusterapi"
1313
gcpcapi "github.com/openshift/installer/pkg/infrastructure/gcp/clusterapi"
14+
vspherecapi "github.com/openshift/installer/pkg/infrastructure/vsphere/clusterapi"
1415
"github.com/openshift/installer/pkg/terraform"
1516
"github.com/openshift/installer/pkg/terraform/stages/aws"
1617
"github.com/openshift/installer/pkg/terraform/stages/azure"
@@ -71,6 +72,9 @@ func ProviderForPlatform(platform string, fg featuregates.FeatureGate) (infrastr
7172
case ovirttypes.Name:
7273
return terraform.InitializeProvider(ovirt.PlatformStages), nil
7374
case vspheretypes.Name:
75+
if fg.Enabled(configv1.FeatureGateClusterAPIInstall) {
76+
return clusterapi.InitializeProvider(vspherecapi.InfraProvider{}), nil
77+
}
7478
return terraform.InitializeProvider(vsphere.PlatformStages), nil
7579
case nonetypes.Name:
7680
// terraform is not used when the platform is "none"
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
package clusterapi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path"
9+
"strings"
10+
11+
"crypto/sha256"
12+
13+
"github.com/openshift/installer/pkg/asset"
14+
vcentercontexts "github.com/openshift/installer/pkg/asset/cluster/vsphere"
15+
"github.com/openshift/installer/pkg/asset/installconfig"
16+
icasset "github.com/openshift/installer/pkg/asset/installconfig"
17+
"github.com/openshift/installer/pkg/asset/rhcos"
18+
"github.com/openshift/installer/pkg/infrastructure"
19+
"github.com/openshift/installer/pkg/rhcos/cache"
20+
ictypes "github.com/openshift/installer/pkg/types"
21+
"github.com/openshift/installer/pkg/types/vsphere"
22+
"github.com/pkg/errors"
23+
"github.com/vmware/govmomi/find"
24+
"github.com/vmware/govmomi/govc/importx"
25+
"github.com/vmware/govmomi/nfc"
26+
"github.com/vmware/govmomi/object"
27+
"github.com/vmware/govmomi/ovf"
28+
"github.com/vmware/govmomi/vim25/mo"
29+
"github.com/vmware/govmomi/vim25/soap"
30+
"github.com/vmware/govmomi/vim25/types"
31+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/session"
32+
)
33+
34+
// InfraProvider is the AWS SDK infra provider.
35+
type InfraProvider struct{}
36+
37+
// InitializeProvider initializes the AWS SDK provider.
38+
func InitializeProvider() infrastructure.Provider {
39+
return InfraProvider{}
40+
}
41+
42+
func attachTag(ctx context.Context, session *session.Session, vmMoRefValue, tagId string) error {
43+
tagManager := session.TagManager
44+
45+
moRef := types.ManagedObjectReference{
46+
Value: vmMoRefValue,
47+
Type: "VirtualMachine",
48+
}
49+
50+
err := tagManager.AttachTag(ctx, tagId, moRef)
51+
52+
if err != nil {
53+
return fmt.Errorf("unable to attach tag: %s", err)
54+
}
55+
return nil
56+
}
57+
58+
func findAvailableHostSystems(ctx context.Context, session *session.Session, clusterHostSystems []*object.HostSystem) (*object.HostSystem, error) {
59+
var hostSystemManagedObject mo.HostSystem
60+
for _, hostObj := range clusterHostSystems {
61+
err := hostObj.Properties(ctx, hostObj.Reference(), []string{"config.product", "network", "datastore", "runtime"}, &hostSystemManagedObject)
62+
if err != nil {
63+
return nil, err
64+
}
65+
if hostSystemManagedObject.Runtime.InMaintenanceMode {
66+
continue
67+
}
68+
return hostObj, nil
69+
}
70+
return nil, errors.New("all hosts unavailable")
71+
}
72+
73+
// Used govc/importx/ovf.go as an example to implement
74+
// resourceVspherePrivateImportOvaCreate and upload functions
75+
// See: https://github.com/vmware/govmomi/blob/cc10a0758d5b4d4873388bcea417251d1ad03e42/govc/importx/ovf.go#L196-L324
76+
func upload(ctx context.Context, archive *importx.ArchiveFlag, lease *nfc.Lease, item nfc.FileItem) error {
77+
file := item.Path
78+
79+
f, size, err := archive.Open(file)
80+
if err != nil {
81+
return err
82+
}
83+
defer f.Close()
84+
85+
opts := soap.Upload{
86+
ContentLength: size,
87+
}
88+
89+
return lease.Upload(ctx, item, f, opts)
90+
}
91+
92+
func importRhcosOva(ctx context.Context, session *session.Session, folder *object.Folder, cachedImage, clusterId, tagId, diskProvisioningType string, failureDomain vsphere.FailureDomain) error {
93+
name := fmt.Sprintf("%s-rhcos-%s-%s", clusterId, failureDomain.Region, failureDomain.Zone)
94+
95+
archive := &importx.ArchiveFlag{Archive: &importx.TapeArchive{Path: cachedImage}}
96+
97+
ovfDescriptor, err := archive.ReadOvf("*.ovf")
98+
if err != nil {
99+
// Open the corrupt OVA file
100+
f, ferr := os.Open(cachedImage)
101+
if ferr != nil {
102+
err = fmt.Errorf("%s, %w", err.Error(), ferr)
103+
}
104+
defer f.Close()
105+
106+
// Get a sha256 on the corrupt OVA file
107+
// and the size of the file
108+
h := sha256.New()
109+
written, cerr := io.Copy(h, f)
110+
if cerr != nil {
111+
err = fmt.Errorf("%s, %w", err.Error(), cerr)
112+
}
113+
114+
return fmt.Errorf("ova %s has a sha256 of %x and a size of %d bytes, failed to read the ovf descriptor %s", cachedImage, h.Sum(nil), written, err)
115+
}
116+
117+
ovfEnvelope, err := archive.ReadEnvelope(ovfDescriptor)
118+
if err != nil {
119+
return fmt.Errorf("failed to parse ovf: %s", err)
120+
}
121+
122+
// The RHCOS OVA only has one network defined by default
123+
// The OVF envelope defines this. We need a 1:1 mapping
124+
// between networks with the OVF and the host
125+
if len(ovfEnvelope.Network.Networks) != 1 {
126+
return fmt.Errorf("expected the OVA to only have a single network adapter")
127+
}
128+
129+
cluster, err := session.Finder.ClusterComputeResource(ctx, failureDomain.Topology.ComputeCluster)
130+
if err != nil {
131+
return fmt.Errorf("failed to find compute cluster: %s", err)
132+
}
133+
134+
clusterHostSystems, err := cluster.Hosts(ctx)
135+
136+
if err != nil {
137+
return fmt.Errorf("failed to get cluster hosts: %s", err)
138+
}
139+
resourcePool, err := session.Finder.ResourcePool(ctx, failureDomain.Topology.ResourcePool)
140+
if err != nil {
141+
return fmt.Errorf("failed to find resource pool: %s", err)
142+
}
143+
144+
networkPath := path.Join(cluster.InventoryPath, failureDomain.Topology.Networks[0])
145+
146+
networkRef, err := session.Finder.Network(ctx, networkPath)
147+
if err != nil {
148+
return fmt.Errorf("failed to find network: %s", err)
149+
150+
}
151+
datastore, err := session.Finder.Datastore(ctx, failureDomain.Topology.Datastore)
152+
if err != nil {
153+
return fmt.Errorf("failed to find datastore: %s", err)
154+
}
155+
156+
// Create mapping between OVF and the network object
157+
// found by Name
158+
networkMappings := []types.OvfNetworkMapping{{
159+
Name: ovfEnvelope.Network.Networks[0].Name,
160+
Network: networkRef.Reference(),
161+
}}
162+
163+
// This is a very minimal spec for importing an OVF.
164+
cisp := types.OvfCreateImportSpecParams{
165+
EntityName: name,
166+
NetworkMapping: networkMappings,
167+
}
168+
169+
m := ovf.NewManager(session.Client.Client)
170+
spec, err := m.CreateImportSpec(ctx,
171+
string(ovfDescriptor),
172+
resourcePool.Reference(),
173+
datastore.Reference(),
174+
cisp)
175+
176+
if err != nil {
177+
return fmt.Errorf("failed to create import spec: %s", err)
178+
}
179+
if spec.Error != nil {
180+
return errors.New(spec.Error[0].LocalizedMessage)
181+
}
182+
183+
hostSystem, err := findAvailableHostSystems(ctx, session, clusterHostSystems)
184+
if err != nil {
185+
return fmt.Errorf("failed to find available host system: %s", err)
186+
}
187+
188+
lease, err := resourcePool.ImportVApp(ctx, spec.ImportSpec, folder, hostSystem)
189+
190+
if err != nil {
191+
return fmt.Errorf("failed to import vapp: %s", err)
192+
}
193+
194+
info, err := lease.Wait(ctx, spec.FileItem)
195+
if err != nil {
196+
return fmt.Errorf("failed to lease wait: %s", err)
197+
}
198+
199+
u := lease.StartUpdater(ctx, info)
200+
defer u.Done()
201+
202+
for _, i := range info.Items {
203+
// upload the vmdk to which ever host that was first
204+
// available with the required network and datastore.
205+
err = upload(ctx, archive, lease, i)
206+
if err != nil {
207+
return fmt.Errorf("failed to upload: %s", err)
208+
}
209+
}
210+
211+
err = lease.Complete(ctx)
212+
if err != nil {
213+
return fmt.Errorf("failed to lease complete: %s", err)
214+
}
215+
216+
vm := object.NewVirtualMachine(session.Client.Client, info.Entity)
217+
if vm == nil {
218+
return fmt.Errorf("error VirtualMachine not found, managed object id: %s", info.Entity.Value)
219+
}
220+
221+
err = vm.MarkAsTemplate(ctx)
222+
if err != nil {
223+
return fmt.Errorf("failed to mark vm as template: %s", err)
224+
}
225+
err = attachTag(ctx, session, vm.Reference().Value, tagId)
226+
if err != nil {
227+
return fmt.Errorf("failed to attach tag: %s", err)
228+
}
229+
230+
return nil
231+
}
232+
233+
func createFolder(ctx context.Context, fullpath string, session *session.Session) (*object.Folder, error) {
234+
dir := path.Dir(fullpath)
235+
base := path.Base(fullpath)
236+
finder := session.Finder
237+
238+
folder, err := finder.Folder(ctx, fullpath)
239+
240+
if folder == nil {
241+
folder, err = finder.Folder(ctx, dir)
242+
243+
var notFoundError *find.NotFoundError
244+
if errors.As(err, &notFoundError) {
245+
folder, err = createFolder(ctx, dir, session)
246+
if err != nil {
247+
return folder, err
248+
}
249+
}
250+
251+
if folder != nil {
252+
folder, err = folder.CreateFolder(ctx, base)
253+
if err != nil {
254+
return folder, err
255+
}
256+
}
257+
}
258+
return folder, err
259+
}
260+
261+
func initializeFoldersAndTemplates(ctx context.Context, rhcosImage *rhcos.Image, installConfig *installconfig.InstallConfig, session *session.Session, clusterId, server string, vcenterContexts *vcentercontexts.VCenterContexts) error {
262+
finder := session.Finder
263+
264+
platform := installConfig.Config.VSphere
265+
failureDomains := platform.FailureDomains
266+
267+
for _, failureDomain := range failureDomains {
268+
dc, err := finder.Datacenter(ctx, failureDomain.Topology.Datacenter)
269+
if err != nil {
270+
return err
271+
}
272+
dcFolders, err := dc.Folders(ctx)
273+
if err != nil {
274+
return fmt.Errorf("unable to get datacenter folder: %v", err)
275+
}
276+
277+
folderPath := path.Join(dcFolders.VmFolder.InventoryPath, clusterId)
278+
279+
// we must set the Folder to the infraId somewhere, we will need to remove that.
280+
// if we are overwriting folderPath it needs to have a slash (path)
281+
folder := failureDomain.Topology.Folder
282+
if strings.Contains(folder, "/") {
283+
folderPath = folder
284+
}
285+
286+
folderMo, err := createFolder(ctx, folderPath, session)
287+
if err != nil {
288+
return fmt.Errorf("unable to create folder: %v", err)
289+
}
290+
291+
cachedImage, err := cache.DownloadImageFile(string(*rhcosImage), cache.InstallerApplicationName)
292+
if err != nil {
293+
return fmt.Errorf("failed to use cached vsphere image: %v", err)
294+
}
295+
296+
// if the template is empty, the ova must be imported
297+
if len(failureDomain.Topology.Template) == 0 {
298+
if err = importRhcosOva(ctx, session, folderMo,
299+
cachedImage, clusterId, vcenterContexts.VCenters[server].TagID, string(platform.DiskType), failureDomain); err != nil {
300+
return fmt.Errorf("failed to import ova: %v", err)
301+
}
302+
}
303+
}
304+
return nil
305+
}
306+
307+
func (a InfraProvider) Name() string {
308+
return vsphere.Name
309+
}
310+
311+
func (a InfraProvider) Provision(dir string, parents asset.Parents) ([]*asset.File, error) {
312+
ctx := context.TODO()
313+
314+
installConfig := &icasset.InstallConfig{}
315+
vcenterContexts := &vcentercontexts.VCenterContexts{}
316+
rhcosImage := new(rhcos.Image)
317+
clusterID := &installconfig.ClusterID{}
318+
319+
parents.Get(
320+
vcenterContexts,
321+
installConfig,
322+
rhcosImage,
323+
clusterID)
324+
325+
for _, vcenter := range installConfig.Config.VSphere.VCenters {
326+
server := vcenter.Server
327+
params := session.NewParams().WithServer(server).WithUserInfo(vcenter.Username, vcenter.Password)
328+
tempConnection, err := session.GetOrCreate(ctx, params)
329+
if err != nil {
330+
return nil, fmt.Errorf("unable to create session: %v", err)
331+
}
332+
333+
defer tempConnection.CloseIdleConnections()
334+
335+
for _, failureDomain := range installConfig.Config.VSphere.FailureDomains {
336+
if failureDomain.Server != server {
337+
continue
338+
}
339+
if err = initializeFoldersAndTemplates(ctx, rhcosImage, installConfig, tempConnection, clusterID.InfraID, server, vcenterContexts); err != nil {
340+
return nil, fmt.Errorf("unable to initialize folders and templates: %v", err)
341+
}
342+
}
343+
}
344+
345+
return nil, nil
346+
}
347+
348+
func (a InfraProvider) DestroyBootstrap(dir string) error {
349+
return nil
350+
}
351+
352+
func (a InfraProvider) ExtractHostAddresses(dir string, ic *ictypes.InstallConfig, ha *infrastructure.HostAddresses) error {
353+
return nil
354+
}

0 commit comments

Comments
 (0)