diff --git a/data/data/rhcos-amd64.json b/data/data/rhcos-amd64.json new file mode 100644 index 00000000000..aab2523d866 --- /dev/null +++ b/data/data/rhcos-amd64.json @@ -0,0 +1,135 @@ +{ + "amis": { + "ap-northeast-1": { + "hvm": "ami-0ade724aa9d3514b2" + }, + "ap-northeast-2": { + "hvm": "ami-0465f2a5450aa0257" + }, + "ap-south-1": { + "hvm": "ami-05a3e4b22ecffdf62" + }, + "ap-southeast-1": { + "hvm": "ami-00df6135b05c0a02a" + }, + "ap-southeast-2": { + "hvm": "ami-075295f492dbaa347" + }, + "ca-central-1": { + "hvm": "ami-0a27aa00147a3a2d9" + }, + "eu-central-1": { + "hvm": "ami-0e8ca170012209d72" + }, + "eu-north-1": { + "hvm": "ami-0c736720637f6b42d" + }, + "eu-west-1": { + "hvm": "ami-0770d1d7e95da7ba3" + }, + "eu-west-2": { + "hvm": "ami-08499730d4db69065" + }, + "eu-west-3": { + "hvm": "ami-0658bcfda04098635" + }, + "me-south-1": { + "hvm": "ami-0ea763ffb3e1c62cf" + }, + "sa-east-1": { + "hvm": "ami-0298057a4a12e874a" + }, + "us-east-1": { + "hvm": "ami-0523c75e911667e58" + }, + "us-east-2": { + "hvm": "ami-0d8f77b753c0d96dd" + }, + "us-west-1": { + "hvm": "ami-0782247660ad3a3bb" + }, + "us-west-2": { + "hvm": "ami-0f0fac946d1d31e97" + } + }, + "azure": { + "image": "rhcos-43.81.202003111353.0-azure.x86_64.vhd", + "url": "https://rhcos.blob.core.windows.net/imagebucket/rhcos-43.81.202003111353.0-azure.x86_64.vhd" + }, + "baseURI": "https://releases-art-rhcos.svc.ci.openshift.org/art/storage/releases/rhcos-4.3/43.81.202003111353.0/x86_64/", + "buildid": "43.81.202003111353.0", + "gcp": { + "image": "rhcos-43-81-202003111353-0", + "url": "https://storage.googleapis.com/rhcos/rhcos/43.81.202003111353.0.tar.gz" + }, + "images": { + "aws": { + "path": "rhcos-43.81.202003111353.0-aws.x86_64.vmdk.gz", + "sha256": "e4cbc50409d93fb88d711a89e62c56639579abf804bf2d25b210f43929939000", + "size": 814861898, + "uncompressed-sha256": "2383f9687db4b2f40bf70f2a9750f651c135d09973f7e7f7ed02ac05179e0ea2", + "uncompressed-size": 831565312 + }, + "azure": { + "path": "rhcos-43.81.202003111353.0-azure.x86_64.vhd.gz", + "sha256": "a2c75bfb3f1c75bd21bba1d669631b05692c1a91a88802bbcd7a3218e1834ff6", + "size": 802153013, + "uncompressed-sha256": "bd427aaa3fab89261ac565a89b0b6d066e3a559e2d62d1f6cb749294963162df", + "uncompressed-size": 2189996544 + }, + "gcp": { + "path": "rhcos-43.81.202003111353.0-gcp.x86_64.tar.gz", + "sha256": "8baade8d055181d538f75e00367a860c9197c684924062919eb982f5101d27d1", + "size": 801779472 + }, + "initramfs": { + "path": "rhcos-43.81.202003111353.0-installer-initramfs.x86_64.img", + "sha256": "fa01f1eeeaf6924d8d20bf5834d3853985167b670a0de30a32bc80d3f8c700d4" + }, + "iso": { + "path": "rhcos-43.81.202003111353.0-installer.x86_64.iso", + "sha256": "b10975f240769e6f606981be4fe4740536522f7afefe30c95d93f059db48c756" + }, + "kernel": { + "path": "rhcos-43.81.202003111353.0-installer-kernel-x86_64", + "sha256": "4d7f7b0a631a8f3fd34c9d39e7a037655871f05d503af240e7647a5f4e6490c9" + }, + "metal": { + "path": "rhcos-43.81.202003111353.0-metal.x86_64.raw.gz", + "sha256": "de35f0e0b75c907c805aef687120b34c00e158bd5afaaf7aa60097fe3ed65480", + "size": 803474932, + "uncompressed-sha256": "30e867fb2c2490873276c175e178d77646dd304c91e05d8f99493ebfb16c2fef", + "uncompressed-size": 3369074688 + }, + "openstack": { + "path": "rhcos-43.81.202003111353.0-openstack.x86_64.qcow2.gz", + "sha256": "8f17baa5564450eea4d3b6f817df3df58af7c3294583be62de615663c0ec55a5", + "size": 803742118, + "uncompressed-sha256": "4d204e638d365d9de121f5d513cff2567abd9232710f4bb79992efa4ba718008", + "uncompressed-size": 2148728832 + }, + "ostree": { + "path": "rhcos-43.81.202003111353.0-ostree.x86_64.tar", + "sha256": "c1501350436424ec6d7a805c52b3fc665fe490912f5a16d94bf267c9efa2848f", + "size": 722647040 + }, + "qemu": { + "path": "rhcos-43.81.202003111353.0-qemu.x86_64.qcow2.gz", + "sha256": "cd3260155e494efdb38d0b3019a29980675bff2fee05a80162bd7a587a9bdba6", + "size": 804202741, + "uncompressed-sha256": "bee078cfef57f51d11dcdc7211185e5e85016e044081f3aec9b42637ebd05fec", + "uncompressed-size": 2148663296 + }, + "vmware": { + "path": "rhcos-43.81.202003111353.0-vmware.x86_64.ova", + "sha256": "c60c94b3ee918379230c63ca18ea144fed57088bc51eee5f12cf839ceb6c1fb6", + "size": 831580160 + } + }, + "oscontainer": { + "digest": "sha256:eb81a7625f9fc3d1575f92dd4e825b02ec6e362c88a1bd6e048c789a7f965771", + "image": "quay.io/openshift-release-dev/ocp-v4.0-art-dev" + }, + "ostree-commit": "86e3934e5a039782f1f1df0f827ce00be7572f9be2441e0d7631a20dff9b2933", + "ostree-version": "43.81.202003111353.0" +} \ No newline at end of file diff --git a/data/data/rhcos-ppc64le.json b/data/data/rhcos-ppc64le.json new file mode 100644 index 00000000000..f291f3b32fb --- /dev/null +++ b/data/data/rhcos-ppc64le.json @@ -0,0 +1,50 @@ +{ + "baseURI": "https://releases-art-rhcos.svc.ci.openshift.org/art/storage/releases/rhcos-4.3-ppc64le/43.81.202003172241.0/ppc64le/", + "buildid": "43.81.202003172241.0", + "images": { + "initramfs": { + "path": "rhcos-43.81.202003172241.0-installer-initramfs.ppc64le.img", + "sha256": "451bfc5b1a872b92c3567d0892eac101bc43bc2d9934c4d6933c379d1936b409" + }, + "iso": { + "path": "rhcos-43.81.202003172241.0-installer.ppc64le.iso", + "sha256": "9debdc0bfe2380a2ee057cb1db686f80e95ea9d1c0e45ea8ca70a956467622e8" + }, + "kernel": { + "path": "rhcos-43.81.202003172241.0-installer-kernel-ppc64le", + "sha256": "5a78b5099da27cbe6d22032c8e55542c1b2885e8c1e69206f7fc390dae9897b7" + }, + "metal": { + "path": "rhcos-43.81.202003172241.0-metal.ppc64le.raw.gz", + "sha256": "0f62f700d805e104e6d5f7743a42101050ab390f18005c11991c80e7a8a42fd6", + "size": 775658410, + "uncompressed-sha256": "2d9678ffbd09ea9f1536f981e9104c5bc26e6326e9c75aa1b162957f7c83f661", + "uncompressed-size": 3486515200 + }, + "openstack": { + "path": "rhcos-43.81.202003172241.0-openstack.ppc64le.qcow2.gz", + "sha256": "a9c35106ba9ae2c7bf0543d460b5f9278f0624bd39e121b3baaecead68b1326a", + "size": 774596933, + "uncompressed-sha256": "0af1fecc742a46d376a053783d46210188fc77c93e4186e8aa3586d7b09ed0f1", + "uncompressed-size": 2246377472 + }, + "ostree": { + "path": "rhcos-43.81.202003172241.0-ostree.ppc64le.tar", + "sha256": "57da39c5b22ea077e2f7835ca8b140d55c1154c376b911bbf22cd103480f357e", + "size": 692090880 + }, + "qemu": { + "path": "rhcos-43.81.202003172241.0-qemu.ppc64le.qcow2.gz", + "sha256": "cc7b2ae27936cd67be3338432a11f5b24f36edcef0c6c43c5c2807bb20f4ae1c", + "size": 775059270, + "uncompressed-sha256": "caef1dc9cc9d08f56241c37a77e08f90d4f72d448569caead522ae5aba239c8f", + "uncompressed-size": 2246311936 + } + }, + "oscontainer": { + "digest": "sha256:d61cfef76f88a5d3028d28c80b9ecbbb2a20c3dc152d66566375fb5589e9105e", + "image": "quay.io/openshift-release-dev/ocp-v4.0-art-dev" + }, + "ostree-commit": "2212098f5ea4361b60987472185f0406e8d78253b5b16399610809abe74d26cc", + "ostree-version": "43.81.202003172241.0" +} \ No newline at end of file diff --git a/data/data/rhcos-s390x.json b/data/data/rhcos-s390x.json new file mode 100644 index 00000000000..5100a7f8ea4 --- /dev/null +++ b/data/data/rhcos-s390x.json @@ -0,0 +1,57 @@ +{ + "baseURI": "https://releases-art-rhcos.svc.ci.openshift.org/art/storage/releases/rhcos-4.3-s390x/43.81.202003172338.0/s390x/", + "buildid": "43.81.202003172338.0", + "images": { + "dasd": { + "path": "rhcos-43.81.202003172338.0-dasd.s390x.raw.gz", + "sha256": "6b2e0ac527cc33f47154e125be51542291657fea3c51d7e30000a79b46d27446", + "size": 702271061, + "uncompressed-sha256": "789d52cb84fdfd7d5167f6a6b4e1b0803c7696bf96777e7c4911caf6b5ad31a1", + "uncompressed-size": 3171942400 + }, + "initramfs": { + "path": "rhcos-43.81.202003172338.0-installer-initramfs.s390x.img", + "sha256": "fcb4958ab779bc2d59a451deb62e62b929b7d2fb2b404c698d04968c69ee82ea" + }, + "iso": { + "path": "rhcos-43.81.202003172338.0-installer.s390x.iso", + "sha256": "1df3e0cbe8e15c27b3133e59df4a19fd23b01743f37f7cfc5b2283f2129e4cbc" + }, + "kernel": { + "path": "rhcos-43.81.202003172338.0-installer-kernel-s390x", + "sha256": "02084b138d77182a3a58dc2e290ab47ec412c5a0441f6d2989c02a89d60f613a" + }, + "metal": { + "path": "rhcos-43.81.202003172338.0-metal.s390x.raw.gz", + "sha256": "5e9f11b6c0c310daecabe75fd3435eeb810f9283d322e9efc890e5d8a97c0202", + "size": 702238370, + "uncompressed-sha256": "884be2265e5bc9f310874aed3d093dd4deed187caa240a32672d315bbfeddafa", + "uncompressed-size": 3171942400 + }, + "openstack": { + "path": "rhcos-43.81.202003172338.0-openstack.s390x.qcow2.gz", + "sha256": "9784da7eee7b7c8d07d68da3900634ad17e3d59c86b4eb79554b8a8b2b4fcf11", + "size": 702634988, + "uncompressed-sha256": "59d290e40071298b852d233ebcbae9df527836729d2b35862768e0b7e27857d5", + "uncompressed-size": 1978335232 + }, + "ostree": { + "path": "rhcos-43.81.202003172338.0-ostree.s390x.tar", + "sha256": "e85d4cff77aeb3e8fd4c04e3ae578be017995ba16975a62256f68515186ec3a1", + "size": 642109440 + }, + "qemu": { + "path": "rhcos-43.81.202003172338.0-qemu.s390x.qcow2.gz", + "sha256": "5384458dcaa5e8a8795355046106335fda9a7c079cd165addf7729989afd9377", + "size": 703123878, + "uncompressed-sha256": "717e3d74403e56878961d4e654a161c088c9c1ccf94bbd407fd273b302db376d", + "uncompressed-size": 1978204160 + } + }, + "oscontainer": { + "digest": "sha256:0b513016131fdab7760dee9ffeab65f7b1c4a928123e81367a0c34c4393a2c0e", + "image": "quay.io/openshift-release-dev/ocp-v4.0-art-dev" + }, + "ostree-commit": "fed64caf6e88503e9190e84fbb9a57ab63783e12ab3895bf67e68f66d11b84d1", + "ostree-version": "43.81.202003172338.0" +} \ No newline at end of file diff --git a/docs/user/customization.md b/docs/user/customization.md index d655684b902..2a9ca6e2ea4 100644 --- a/docs/user/customization.md +++ b/docs/user/customization.md @@ -69,6 +69,8 @@ For example, 10.0.0.0/16 represents IP addresses 10.0.0.0 through 10.0.255.255. The following machine-pool properties are available: +* `architecture` (optional string): Determines the instruction set architecture of the machines in the pool. Currently, heteregeneous clusters are not supported, so all pools must specify the same architecture. + Valid values are `amd64` (the default). * `hyperthreading` (optional string): Determines the mode of hyperthreading that machines in the pool will utilize. Valid values are `Enabled` (the default) and `Disabled`. * `name` (required string): The name of the machine pool. diff --git a/hack/update-rhcos-bootimage.py b/hack/update-rhcos-bootimage.py index 97e84c1845d..b1f98439ac8 100755 --- a/hack/update-rhcos-bootimage.py +++ b/hack/update-rhcos-bootimage.py @@ -1,5 +1,5 @@ -#!/usr/bin/python3 -# Usage: ./hack/update-rhcos-bootimage.py https://releases-art-rhcos.svc.ci.openshift.org/storage/releases/ootpa/410.8.20190401.0/meta.json +#!/usr/bin/env python3 +# Usage: ./hack/update-rhcos-bootimage.py https://releases-art-rhcos.svc.ci.openshift.org/storage/releases/ootpa/410.8.20190401.0/meta.json amd64 import codecs,os,sys,json,argparse import urllib.parse import urllib.request @@ -8,12 +8,13 @@ # builds. Do not try to e.g. point to RHT-internal endpoints. RHCOS_RELEASES_APP = 'https://releases-art-rhcos.svc.ci.openshift.org' -dn = os.path.abspath(os.path.dirname(sys.argv[0])) - parser = argparse.ArgumentParser() parser.add_argument("meta", action='store') +parser.add_argument("arch", action='store', choices=['amd64', 's390x', 'ppc64le']) args = parser.parse_args() +metadata_dir = os.path.join(os.path.dirname(sys.argv[0]), "../data/data") + if not args.meta.startswith(RHCOS_RELEASES_APP): raise SystemExit("URL must start with: " + RHCOS_RELEASES_APP) @@ -24,13 +25,24 @@ for k in ['images', 'buildid', 'oscontainer', 'ostree-commit', 'ostree-version', 'azure', 'gcp']: - newmeta[k] = meta[k] -newmeta['amis'] = { - entry['name']: { - 'hvm': entry['hvm'], + if meta.get(k): + newmeta[k] = meta[k] +if meta.get(k): + newmeta['amis'] = { + entry['name']: { + 'hvm': entry['hvm'], + } + for entry in meta['amis'] } - for entry in meta['amis'] -} newmeta['baseURI'] = urllib.parse.urljoin(args.meta, '.') -with open(os.path.join(dn, "../data/data/rhcos.json"), 'w') as f: + +with open(os.path.join(metadata_dir, f"rhcos-{args.arch}.json"), 'w') as f: json.dump(newmeta, f, sort_keys=True, indent=4) + +# Continue to populate the legacy metadata file because there are still +# processes consuming this file directly. This normally could just be a symlink +# but some of these processes reference raw.githubusercontent.com which doesn't +# follow symlinks. +if args.arch == 'amd64': + with open(os.path.join(metadata_dir, "rhcos.json"), 'w') as f: + json.dump(newmeta, f, sort_keys=True, indent=4) diff --git a/pkg/asset/installconfig/installconfig_test.go b/pkg/asset/installconfig/installconfig_test.go index 5be5ae82dfd..345c9c01ebd 100644 --- a/pkg/asset/installconfig/installconfig_test.go +++ b/pkg/asset/installconfig/installconfig_test.go @@ -81,12 +81,14 @@ func TestInstallConfigGenerate_FillsInDefaults(t *testing.T) { Name: "master", Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, }, Compute: []types.MachinePool{ { Name: "worker", Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, }, }, Platform: types.Platform{ @@ -145,12 +147,14 @@ pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" Name: "master", Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, }, Compute: []types.MachinePool{ { Name: "worker", Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, }, }, Platform: types.Platform{ @@ -229,12 +233,14 @@ network: Name: "master", Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, }, Compute: []types.MachinePool{ { Name: "worker", Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, }, }, Platform: types.Platform{ diff --git a/pkg/asset/manifests/operators_test.go b/pkg/asset/manifests/operators_test.go index 25cc50b7635..ba7bc3877cd 100644 --- a/pkg/asset/manifests/operators_test.go +++ b/pkg/asset/manifests/operators_test.go @@ -35,13 +35,15 @@ func TestRedactedInstallConfig(t *testing.T) { ServiceNetwork: []ipnet.IPNet{*ipnet.MustParseCIDR("1.2.3.4/5")}, }, ControlPlane: &types.MachinePool{ - Name: "control-plane", - Replicas: pointer.Int64Ptr(3), + Name: "control-plane", + Replicas: pointer.Int64Ptr(3), + Architecture: types.ArchitectureAMD64, }, Compute: []types.MachinePool{ { - Name: "compute", - Replicas: pointer.Int64Ptr(3), + Name: "compute", + Replicas: pointer.Int64Ptr(3), + Architecture: types.ArchitectureAMD64, }, }, Platform: types.Platform{ @@ -59,10 +61,12 @@ func TestRedactedInstallConfig(t *testing.T) { expectedConfig := createInstallConfig() expectedYaml := `baseDomain: test-domain compute: -- name: compute +- architecture: amd64 + name: compute platform: {} replicas: 3 controlPlane: + architecture: amd64 name: control-plane platform: {} replicas: 3 diff --git a/pkg/asset/rhcos/bootstrap_image.go b/pkg/asset/rhcos/bootstrap_image.go index f8c5805fad1..6fde584b5ba 100644 --- a/pkg/asset/rhcos/bootstrap_image.go +++ b/pkg/asset/rhcos/bootstrap_image.go @@ -51,7 +51,7 @@ func (i *BootstrapImage) Generate(p asset.Parents) error { // Baremetal IPI launches a local VM for the bootstrap node // Hence requires the QEMU image to use the libvirt backend - osimage, err = rhcos.QEMU(ctx) + osimage, err = rhcos.QEMU(ctx, config.ControlPlane.Architecture) default: // other platforms use the same image for all nodes osimage, err = osImage(config) diff --git a/pkg/asset/rhcos/image.go b/pkg/asset/rhcos/image.go index 38800024646..5a067f34a79 100644 --- a/pkg/asset/rhcos/image.go +++ b/pkg/asset/rhcos/image.go @@ -62,6 +62,8 @@ func (i *Image) Generate(p asset.Parents) error { } func osImage(config *types.InstallConfig) (string, error) { + arch := config.ControlPlane.Architecture + var osimage string var err error ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) @@ -72,19 +74,19 @@ func osImage(config *types.InstallConfig) (string, error) { osimage = config.Platform.AWS.AMIID break } - osimage, err = rhcos.AMI(ctx, config.Platform.AWS.Region) + osimage, err = rhcos.AMI(ctx, arch, config.Platform.AWS.Region) case gcp.Name: - osimage, err = rhcos.GCP(ctx) + osimage, err = rhcos.GCP(ctx, arch) case libvirt.Name: - osimage, err = rhcos.QEMU(ctx) + osimage, err = rhcos.QEMU(ctx, arch) case openstack.Name: if oi := config.Platform.OpenStack.ClusterOSImage; oi != "" { osimage = oi break } - osimage, err = rhcos.OpenStack(ctx) + osimage, err = rhcos.OpenStack(ctx, arch) case azure.Name: - osimage, err = rhcos.VHD(ctx) + osimage, err = rhcos.VHD(ctx, arch) case baremetal.Name: // Check for RHCOS image URL override if oi := config.Platform.BareMetal.ClusterOSImage; oi != "" { @@ -95,7 +97,7 @@ func osImage(config *types.InstallConfig) (string, error) { // Note that baremetal IPI currently uses the OpenStack image // because this contains the necessary ironic config drive // ignition support, which isn't enabled in the UPI BM images - osimage, err = rhcos.OpenStack(ctx) + osimage, err = rhcos.OpenStack(ctx, arch) case none.Name, vsphere.Name: default: return "", errors.New("invalid Platform") diff --git a/pkg/rhcos/ami.go b/pkg/rhcos/ami.go index f6d76767b33..625ba9e80ac 100644 --- a/pkg/rhcos/ami.go +++ b/pkg/rhcos/ami.go @@ -4,11 +4,13 @@ import ( "context" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" ) // AMI fetches the HVM AMI ID of the Red Hat Enterprise Linux CoreOS release. -func AMI(ctx context.Context, region string) (string, error) { - meta, err := fetchRHCOSBuild(ctx) +func AMI(ctx context.Context, arch types.Architecture, region string) (string, error) { + meta, err := fetchRHCOSBuild(ctx, arch) if err != nil { return "", errors.Wrap(err, "failed to fetch RHCOS metadata") } diff --git a/pkg/rhcos/azure.go b/pkg/rhcos/azure.go index 63176489d71..14e8fc30bb2 100644 --- a/pkg/rhcos/azure.go +++ b/pkg/rhcos/azure.go @@ -4,11 +4,13 @@ import ( "context" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" ) // VHD fetches the URL of the public Azure storage bucket containing the RHCOS image -func VHD(ctx context.Context) (string, error) { - meta, err := fetchRHCOSBuild(ctx) +func VHD(ctx context.Context, arch types.Architecture) (string, error) { + meta, err := fetchRHCOSBuild(ctx, arch) if err != nil { return "", errors.Wrap(err, "failed to fetch RHCOS metadata") } diff --git a/pkg/rhcos/builds.go b/pkg/rhcos/builds.go index 4bf6b30038b..eaaf2a2558b 100644 --- a/pkg/rhcos/builds.go +++ b/pkg/rhcos/builds.go @@ -3,10 +3,18 @@ package rhcos import ( "context" "encoding/json" + "fmt" "io/ioutil" + "os" "github.com/openshift/installer/data" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" +) + +var ( + errInvalidArch = fmt.Errorf("no build metadata for given architecture") ) type metadata struct { @@ -37,15 +45,17 @@ type metadata struct { OSTreeVersion string `json:"ostree-version"` } -func fetchRHCOSBuild(ctx context.Context) (*metadata, error) { - file, err := data.Assets.Open("rhcos.json") +func fetchRHCOSBuild(ctx context.Context, arch types.Architecture) (*metadata, error) { + file, err := data.Assets.Open(fmt.Sprintf("rhcos-%s.json", arch)) if err != nil { return nil, err } defer file.Close() body, err := ioutil.ReadAll(file) - if err != nil { + if os.IsNotExist(err) { + return nil, errInvalidArch + } else if err != nil { return nil, err } diff --git a/pkg/rhcos/gcp.go b/pkg/rhcos/gcp.go index 782ceba15a2..e1dc4df66cb 100644 --- a/pkg/rhcos/gcp.go +++ b/pkg/rhcos/gcp.go @@ -4,11 +4,13 @@ import ( "context" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" ) // GCP fetches the URL of the public GCP storage bucket containing the RHCOS image -func GCP(ctx context.Context) (string, error) { - meta, err := fetchRHCOSBuild(ctx) +func GCP(ctx context.Context, arch types.Architecture) (string, error) { + meta, err := fetchRHCOSBuild(ctx, arch) if err != nil { return "", errors.Wrap(err, "failed to fetch RHCOS metadata") } diff --git a/pkg/rhcos/openstack.go b/pkg/rhcos/openstack.go index d5485ef40bf..7f25ac7d815 100644 --- a/pkg/rhcos/openstack.go +++ b/pkg/rhcos/openstack.go @@ -5,12 +5,14 @@ import ( "net/url" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" ) // OpenStack fetches the URL of the Red Hat Enterprise Linux CoreOS release, // for the openstack platform -func OpenStack(ctx context.Context) (string, error) { - meta, err := fetchRHCOSBuild(ctx) +func OpenStack(ctx context.Context, arch types.Architecture) (string, error) { + meta, err := fetchRHCOSBuild(ctx, arch) if err != nil { return "", errors.Wrap(err, "failed to fetch RHCOS metadata") } diff --git a/pkg/rhcos/qemu.go b/pkg/rhcos/qemu.go index 5ada808d48c..f0c0026cc80 100644 --- a/pkg/rhcos/qemu.go +++ b/pkg/rhcos/qemu.go @@ -5,11 +5,13 @@ import ( "net/url" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" ) // QEMU fetches the URL of the Red Hat Enterprise Linux CoreOS release. -func QEMU(ctx context.Context) (string, error) { - meta, err := fetchRHCOSBuild(ctx) +func QEMU(ctx context.Context, arch types.Architecture) (string, error) { + meta, err := fetchRHCOSBuild(ctx, arch) if err != nil { return "", errors.Wrap(err, "failed to fetch RHCOS metadata") } diff --git a/pkg/terraform/exec/plugins/Gopkg.lock b/pkg/terraform/exec/plugins/Gopkg.lock index 1e8416074c3..111b239a3bf 100644 --- a/pkg/terraform/exec/plugins/Gopkg.lock +++ b/pkg/terraform/exec/plugins/Gopkg.lock @@ -385,13 +385,16 @@ [[projects]] digest = "1:151f72934298019168adb99a7d17dfa203a65eb49af5da41429775284ea8ce3f" + branch = "multi_arch" + digest = "1:5cf80e7508c8328d4e1d1eec73d4780d5ed075653ab6a34fa5bdd838e089bfae" name = "github.com/dmacvicar/terraform-provider-libvirt" packages = [ "libvirt", "libvirt/helper/suppress", ] pruneopts = "NUT" - revision = "40b0cda5333a29dd9f33d1cfdc206bfad3a8af96" + revision = "312c4360a8bce9e0939b7a1156b848512d311d86" + source = "https://github.com/openshift/terraform-provider-libvirt" [[projects]] branch = "master" diff --git a/pkg/terraform/exec/plugins/Gopkg.toml b/pkg/terraform/exec/plugins/Gopkg.toml index c0319613073..7c82db8ac2b 100644 --- a/pkg/terraform/exec/plugins/Gopkg.toml +++ b/pkg/terraform/exec/plugins/Gopkg.toml @@ -11,7 +11,8 @@ ignored = [ [[constraint]] name = "github.com/dmacvicar/terraform-provider-libvirt" - revision = "40b0cda5333a29dd9f33d1cfdc206bfad3a8af96" + source = "https://github.com/openshift/terraform-provider-libvirt" + branch = "multi_arch" [[constraint]] name = "github.com/terraform-providers/terraform-provider-aws" diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/coreos_ignition_def.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/coreos_ignition_def.go index b8154d14357..87ab3994670 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/coreos_ignition_def.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/coreos_ignition_def.go @@ -8,8 +8,6 @@ import ( "io/ioutil" "log" "os" - "os/exec" - "path/filepath" "strings" libvirt "github.com/libvirt/libvirt-go" @@ -52,55 +50,17 @@ func (ign *defIgnition) CreateAndUpload(client *Client) (string, error) { volumeDef := newDefVolume() volumeDef.Name = ign.Name - tmpDir, err := ioutil.TempDir("", "ignition") + ignFile, err := ign.createFile() if err != nil { - return "", fmt.Errorf("Failed to create Ignition directory: %v", err) + return "", err } defer func() { - if err = os.RemoveAll(tmpDir); err != nil { - log.Printf("Error while removing Ignition directory: %v", err) + if err = os.Remove(ignFile); err != nil { + log.Printf("Error while removing tmp Ignition file: %v", err) } }() - ignPath, err := ign.createFile(tmpDir) - if err != nil { - return "", err - } - - arch, err := getHostArchitecture(client.libvirt) - if err != nil { - return "", fmt.Errorf("Error retrieving host architecture: %s", err) - } - - var userdataPath string - switch arch { - case "i686", "x86_64", "aarch64": - userdataPath = ignPath - case "s390", "s390x": - configDrivePath := filepath.Join(tmpDir, "config.iso") - cmd := exec.Command( - "mkisofs", - "-output", - configDrivePath, - "-volid", - "config-2", - "-root", - "openstack/latest", - "-joliet", - "-rock", - ignPath) - - log.Printf("Executing command: %+v", cmd) - if err = cmd.Run(); err != nil { - return "", fmt.Errorf("Failed to create Config Drive ISO image: %v", err) - } - - userdataPath = configDrivePath - default: - return "", fmt.Errorf("Ignition not supported on %q", arch) - } - - img, err := newImage(userdataPath) + img, err := newImage(ignFile) if err != nil { return "", err } @@ -156,12 +116,12 @@ func getIgnitionVolumeKeyFromTerraformID(id string) (string, error) { } // Dumps the Ignition object - either generated by Terraform or supplied as a file - -// to a userdata Ignition file in the provided directory -func (ign *defIgnition) createFile(dir string) (string, error) { +// to a temporary ignition file +func (ign *defIgnition) createFile() (string, error) { log.Print("Creating Ignition temporary file") - tempFile, err := os.Create(filepath.Join(dir, "user_data")) + tempFile, err := ioutil.TempFile("", ign.Name) if err != nil { - return "", fmt.Errorf("Error creating userdata file: %v", err) + return "", fmt.Errorf("Error creating tmp file: %v", err) } defer tempFile.Close() diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go index cd4c4239ed4..284b13e4d00 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go @@ -198,26 +198,14 @@ func domainGetIfacesInfo(domain libvirt.Domain, rd *schema.ResourceData) ([]libv return interfaces, nil } -func newDiskForCloudInit(virConn *libvirt.Connect, volumeKey string, arch string) (libvirtxml.DomainDisk, error) { - var target *libvirtxml.DomainDiskTarget - switch arch { - case "s390", "s390x": - target = &libvirtxml.DomainDiskTarget{ - // s390 platform doesn't support IDE controllers - Dev: "vdb", - Bus: "scsi", - } - default: - target = &libvirtxml.DomainDiskTarget{ +func newDiskForCloudInit(virConn *libvirt.Connect, volumeKey string) (libvirtxml.DomainDisk, error) { + disk := libvirtxml.DomainDisk{ + Device: "cdrom", + Target: &libvirtxml.DomainDiskTarget{ // Last device letter possible with a single IDE controller on i440FX Dev: "hdd", Bus: "ide", - } - } - - disk := libvirtxml.DomainDisk{ - Device: "cdrom", - Target: target, + }, Driver: &libvirtxml.DomainDiskDriver{ Name: "qemu", Type: "raw", @@ -269,18 +257,33 @@ func setCoreOSIgnition(d *schema.ResourceData, domainDef *libvirtxml.Domain, vir }, } } - case "s390", "s390x": - // System Z does not support any of the same pass-through - // mechanisms as Ignition. As a temporary workaround, the OpenStack - // Config Drive can be used instead. The Ignition volume already - // contains a Config Drive at this point. - - disk, err := newDiskForCloudInit(virConn, ignitionKey, arch) - if err != nil { - return err + case "s390", "s390x", "ppc64", "ppc64le": + // System Z and PowerPC do not support the Firmware Configuration + // device. After a discussion about the best way to support a similar + // method for qemu in https://github.com/coreos/ignition/issues/928, + // decided on creating a virtio-blk device with a serial of ignition + // which contains the ignition config and have ignition support for + // reading from the device which landed in https://github.com/coreos/ignition/pull/936 + + igndisk := libvirtxml.DomainDisk{ + Device: "disk", + Source: &libvirtxml.DomainDiskSource{ + File: &libvirtxml.DomainDiskSourceFile{ + File: ignitionKey, + }, + }, + Target: &libvirtxml.DomainDiskTarget{ + Dev: "vdb", + Bus: "virtio", + }, + Driver: &libvirtxml.DomainDiskDriver{ + Name: "qemu", + Type: "raw", + }, + ReadOnly: &libvirtxml.DomainDiskReadOnly{}, + Serial: "ignition", } - - domainDef.Devices.Disks = append(domainDef.Devices.Disks, disk) + domainDef.Devices.Disks = append(domainDef.Devices.Disks, igndisk) default: return fmt.Errorf("Ignition not supported on %q", arch) } @@ -303,7 +306,7 @@ func setVideo(d *schema.ResourceData, domainDef *libvirtxml.Domain) error { } func setGraphics(d *schema.ResourceData, domainDef *libvirtxml.Domain, arch string) error { - if arch == "s390x" || arch == "ppc64" { + if arch == "s390x" || strings.HasPrefix(arch, "ppc64") { domainDef.Devices.Graphics = nil return nil } @@ -654,13 +657,13 @@ func setFilesystems(d *schema.ResourceData, domainDef *libvirtxml.Domain) error return nil } -func setCloudinit(d *schema.ResourceData, domainDef *libvirtxml.Domain, virConn *libvirt.Connect, arch string) error { +func setCloudinit(d *schema.ResourceData, domainDef *libvirtxml.Domain, virConn *libvirt.Connect) error { if cloudinit, ok := d.GetOk("cloudinit"); ok { cloudinitID, err := getCloudInitVolumeKeyFromTerraformID(cloudinit.(string)) if err != nil { return err } - disk, err := newDiskForCloudInit(virConn, cloudinitID, arch) + disk, err := newDiskForCloudInit(virConn, cloudinitID) if err != nil { return err } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go index a594d152654..bfa01903d97 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go @@ -491,7 +491,7 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error return err } - if err := setCloudinit(d, &domainDef, virConn, arch); err != nil { + if err := setCloudinit(d, &domainDef, virConn); err != nil { return err } @@ -632,12 +632,7 @@ func resourceLibvirtDomainUpdate(d *schema.ResourceData, meta interface{}) error return err } - arch, err := getHostArchitecture(virConn) - if err != nil { - return fmt.Errorf("Error retrieving host architecture: %s", err) - } - - disk, err := newDiskForCloudInit(virConn, cloudinitID, arch) + disk, err := newDiskForCloudInit(virConn, cloudinitID) if err != nil { return err } diff --git a/pkg/types/defaults/machinepools.go b/pkg/types/defaults/machinepools.go index e9810c2e9c5..9573e7950b9 100644 --- a/pkg/types/defaults/machinepools.go +++ b/pkg/types/defaults/machinepools.go @@ -17,4 +17,7 @@ func SetMachinePoolDefaults(p *types.MachinePool, platform string) { if p.Hyperthreading == "" { p.Hyperthreading = types.HyperthreadingEnabled } + if p.Architecture == "" { + p.Architecture = types.ArchitectureAMD64 + } } diff --git a/pkg/types/defaults/machinepools_test.go b/pkg/types/defaults/machinepools_test.go index 4746ca72df5..9eb1e43fd16 100644 --- a/pkg/types/defaults/machinepools_test.go +++ b/pkg/types/defaults/machinepools_test.go @@ -14,6 +14,7 @@ func defaultMachinePool(name string) *types.MachinePool { Name: name, Replicas: pointer.Int64Ptr(3), Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, } } diff --git a/pkg/types/machinepools.go b/pkg/types/machinepools.go index 16665bdcdf5..5183a2d02fb 100644 --- a/pkg/types/machinepools.go +++ b/pkg/types/machinepools.go @@ -20,6 +20,18 @@ const ( HyperthreadingDisabled HyperthreadingMode = "Disabled" ) +// Architecture is the instruction set architecture for the machines in a pool. +type Architecture string + +const ( + // ArchitectureAMD64 indicates AMD64 (x86_64). + ArchitectureAMD64 = "amd64" + // ArchitectureS390X indicates s390x (IBM System Z). + ArchitectureS390X = "s390x" + // ArchitecturePPC64LE indicates ppc64 little endian (Power PC) + ArchitecturePPC64LE = "ppc64le" +) + // MachinePool is a pool of machines to be installed. type MachinePool struct { // Name is the name of the machine pool. @@ -38,6 +50,10 @@ type MachinePool struct { // +optional // Default is for hyperthreading to be enabled. Hyperthreading HyperthreadingMode `json:"hyperthreading,omitempty"` + + // Architecture is the instruction set architecture of the machine pool. + // Defaults to amd64. + Architecture Architecture `json:"architecture,omitempty"` } // MachinePoolPlatform is the platform-specific configuration for a machine diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index 5d6509d9b7b..b1c52c6ef09 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -91,7 +91,7 @@ func ValidateInstallConfig(c *types.InstallConfig, openStackValidValuesFetcher o } else { allErrs = append(allErrs, field.Required(field.NewPath("controlPlane"), "controlPlane is required")) } - allErrs = append(allErrs, validateCompute(&c.Platform, c.Compute, field.NewPath("compute"))...) + allErrs = append(allErrs, validateCompute(&c.Platform, c.ControlPlane, c.Compute, field.NewPath("compute"))...) if err := validate.ImagePullSecret(c.PullSecret); err != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("pullSecret"), c.PullSecret, err.Error())) } @@ -317,7 +317,7 @@ func validateControlPlane(platform *types.Platform, pool *types.MachinePool, fld return allErrs } -func validateCompute(platform *types.Platform, pools []types.MachinePool, fldPath *field.Path) field.ErrorList { +func validateCompute(platform *types.Platform, control *types.MachinePool, pools []types.MachinePool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} poolNames := map[string]bool{} for i, p := range pools { @@ -329,6 +329,9 @@ func validateCompute(platform *types.Platform, pools []types.MachinePool, fldPat allErrs = append(allErrs, field.Duplicate(poolFldPath.Child("name"), p.Name)) } poolNames[p.Name] = true + if control != nil && control.Architecture != p.Architecture { + allErrs = append(allErrs, field.Invalid(poolFldPath.Child("architecture"), p.Architecture, "heteregeneous multi-arch is not supported; compute pool architecture must match control plane")) + } allErrs = append(allErrs, ValidateMachinePool(platform, &p, poolFldPath)...) } return allErrs diff --git a/pkg/types/validation/installconfig_test.go b/pkg/types/validation/installconfig_test.go index 2d37f1d1555..8924e69c23f 100644 --- a/pkg/types/validation/installconfig_test.go +++ b/pkg/types/validation/installconfig_test.go @@ -884,6 +884,24 @@ func TestValidateInstallConfig(t *testing.T) { }(), expectedError: `Invalid value: "DualStack": dual-stack IPv4/IPv6 is not supported for this platform, specify only one type of address`, }, + { + name: "cluster is not heteregenous", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.Compute[0].Architecture = types.ArchitectureS390X + return c + }(), + expectedError: `^compute\[0\].architecture: Invalid value: "s390x": heteregeneous multi-arch is not supported; compute pool architecture must match control plane$`, + }, + { + name: "cluster is not heteregenous", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.Compute[0].Architecture = types.ArchitecturePPC64LE + return c + }(), + expectedError: `^compute\[0\].architecture: Invalid value: "ppc64le": heteregeneous multi-arch is not supported; compute pool architecture must match control plane$`, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/types/validation/machinepools.go b/pkg/types/validation/machinepools.go index 33867a09c14..7be7c9d0af6 100644 --- a/pkg/types/validation/machinepools.go +++ b/pkg/types/validation/machinepools.go @@ -31,6 +31,20 @@ var ( } return v }() + + validArchitectures = map[types.Architecture]bool{ + types.ArchitectureAMD64: true, + types.ArchitectureS390X: true, + types.ArchitecturePPC64LE: true, + } + + validArchitectureValues = func() []string { + v := make([]string, 0, len(validArchitectures)) + for m := range validArchitectures { + v = append(v, string(m)) + } + return v + }() ) // ValidateMachinePool checks that the specified machine pool is valid. @@ -46,6 +60,9 @@ func ValidateMachinePool(platform *types.Platform, p *types.MachinePool, fldPath if !validHyperthreadingModes[p.Hyperthreading] { allErrs = append(allErrs, field.NotSupported(fldPath.Child("hyperthreading"), p.Hyperthreading, validHyperthreadingModeValues)) } + if !validArchitectures[p.Architecture] { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("architecture"), p.Architecture, validArchitectureValues)) + } allErrs = append(allErrs, validateMachinePoolPlatform(platform, &p.Platform, fldPath.Child("platform"))...) return allErrs } diff --git a/pkg/types/validation/machinepools_test.go b/pkg/types/validation/machinepools_test.go index 44517d78c01..0aaf503a875 100644 --- a/pkg/types/validation/machinepools_test.go +++ b/pkg/types/validation/machinepools_test.go @@ -19,6 +19,7 @@ func validMachinePool(name string) *types.MachinePool { Name: name, Replicas: pointer.Int64Ptr(1), Hyperthreading: types.HyperthreadingDisabled, + Architecture: types.ArchitectureAMD64, } }