From 9ee45fb69a1aa47bd21de82d0056d74aa0057b7d Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Mon, 17 Sep 2018 15:04:30 -0700 Subject: [PATCH] *: provide a default OS image for libvirt Instead of requiring the user to download RHCOS and unpack it, this allows them to be lazy and just let the installer download the image from a default location. Note that there is no caching, so it is recommended that users still manually download and unpack RHCOS. --- Documentation/dev/libvirt-howto.md | 18 ++++--- examples/libvirt.yaml | 2 +- installer/pkg/config-generator/generator.go | 6 ++- installer/pkg/config/libvirt/libvirt.go | 12 ++--- installer/pkg/config/validate.go | 7 --- installer/pkg/config/validate_test.go | 55 +++++---------------- installer/pkg/validate/validate.go | 17 ------- installer/pkg/validate/validate_test.go | 49 ------------------ modules/libvirt/volume/main.tf | 3 +- modules/libvirt/volume/variables.tf | 4 +- pkg/types/machinepools.go | 8 ++- steps/infra/libvirt/main.tf | 2 +- steps/variables-libvirt.tf | 4 +- tests/run.sh | 2 +- 14 files changed, 46 insertions(+), 143 deletions(-) diff --git a/Documentation/dev/libvirt-howto.md b/Documentation/dev/libvirt-howto.md index 4f2732a132c..abbd1622f87 100644 --- a/Documentation/dev/libvirt-howto.md +++ b/Documentation/dev/libvirt-howto.md @@ -16,7 +16,9 @@ git clone https://github.com/openshift/installer.git cd installer ``` -#### 1.3 Download and prepare the operating system image +#### 1.3 (Optional) Download and prepare the operating system image + +*By default, the installer will download the latest RHCOS image every time it is invoked. This may be problematic for users who create a large number of clusters or who have limited network bandwidth. The installer allows a local image to be used instead.* Download the latest RHCOS image (you will need access to the Red Hat internal build systems): @@ -85,14 +87,14 @@ iptables -I INPUT -p tcp -s 192.168.124.0/24 -d 192.168.124.1 --dport 16509 \ #### 1.7 Prepare the configuration file 1. `cp examples/libvirt.yaml ./` -1. Edit the configuration file: +2. Edit the configuration file: 1. Set an email and password in the `admin` section - 1. Set a `baseDomain` (to `tt.testing`) - 1. Set the `sshKey` in the `admin` section to the **contents** of an ssh key (e.g. `ssh-rsa AAAA...`) - 1. Set the `imagePath` to the **absolute** path of the operating system image you downloaded - 1. Set the `name` (e.g. test1) - 1. Look at the `podCIDR` and `serviceCIDR` fields in the `networking` section. Make sure they don't conflict with anything important. - 1. Set the `pullSecret` to your JSON pull secret. + 2. Set a `baseDomain` (to `tt.testing`) + 3. Set the `sshKey` in the `admin` section to the **contents** of an ssh key (e.g. `ssh-rsa AAAA...`) + 4. Set the `name` (e.g. test1) + 5. Look at the `podCIDR` and `serviceCIDR` fields in the `networking` section. Make sure they don't conflict with anything important. + 6. Set the `pullSecret` to your JSON pull secret. + 7. (Optional) Change the `image` to the file URL of the operating system image you downloaded (e.g. `file:///home/user/Downloads/rhcos.qcow`). This will allow the installer to re-use that image instead of having to download it every time. #### 1.8 Set up NetworkManager DNS overlay This step is optional, but useful for being able to resolve cluster-internal hostnames from your host. diff --git a/examples/libvirt.yaml b/examples/libvirt.yaml index 33a06c32659..7065c8ad9ae 100644 --- a/examples/libvirt.yaml +++ b/examples/libvirt.yaml @@ -19,7 +19,7 @@ libvirt: name: tectonic ifName: tt0 ipRange: 192.168.124.0/24 - imagePath: /path/to/image + image: http://aos-ostree.rhev-ci-vms.eng.rdu2.redhat.com/rhcos/images/cloud/latest/rhcos-qemu.qcow2.gz ca: # (optional) The content of the PEM-encoded CA certificate, used to generate Tectonic Console's server certificate. diff --git a/installer/pkg/config-generator/generator.go b/installer/pkg/config-generator/generator.go index 513bbc472eb..bb32367fe57 100644 --- a/installer/pkg/config-generator/generator.go +++ b/installer/pkg/config-generator/generator.go @@ -248,10 +248,12 @@ func (c *ConfigGenerator) installConfig() (*types.InstallConfig, error) { }, } masterPlatform.Libvirt = &types.LibvirtMachinePoolPlatform{ - QCOWImagePath: c.Libvirt.QCOWImagePath, + ImagePool: "default", + ImageVolume: "coreos_base", } workerPlatform.Libvirt = &types.LibvirtMachinePoolPlatform{ - QCOWImagePath: c.Libvirt.QCOWImagePath, + ImagePool: "default", + ImageVolume: "coreos_base", } default: return nil, fmt.Errorf("installconfig: invalid platform %s", c.Platform) diff --git a/installer/pkg/config/libvirt/libvirt.go b/installer/pkg/config/libvirt/libvirt.go index ab1b6233f1b..3d0f9c80061 100644 --- a/installer/pkg/config/libvirt/libvirt.go +++ b/installer/pkg/config/libvirt/libvirt.go @@ -14,12 +14,12 @@ const ( // Libvirt encompasses configuration specific to libvirt. type Libvirt struct { - URI string `json:"tectonic_libvirt_uri,omitempty" yaml:"uri"` - QCOWImagePath string `json:"tectonic_coreos_qcow_path,omitempty" yaml:"imagePath"` - Network `json:",inline" yaml:"network"` - MasterIPs []string `json:"tectonic_libvirt_master_ips,omitempty" yaml:"masterIPs"` - WorkerIPs []string `json:"tectonic_libvirt_worker_ips,omitempty" yaml:"workerIPs"` - BootstrapIP string `json:"tectonic_libvirt_bootstrap_ip,omitempty" yaml:"bootstrapIP"` + URI string `json:"tectonic_libvirt_uri,omitempty" yaml:"uri"` + Image string `json:"tectonic_os_image,omitempty" yaml:"image"` + Network `json:",inline" yaml:"network"` + MasterIPs []string `json:"tectonic_libvirt_master_ips,omitempty" yaml:"masterIPs"` + WorkerIPs []string `json:"tectonic_libvirt_worker_ips,omitempty" yaml:"workerIPs"` + BootstrapIP string `json:"tectonic_libvirt_bootstrap_ip,omitempty" yaml:"bootstrapIP"` } // Network describes a libvirt network configuration. diff --git a/installer/pkg/config/validate.go b/installer/pkg/config/validate.go index 999be89669e..0c8553df25c 100644 --- a/installer/pkg/config/validate.go +++ b/installer/pkg/config/validate.go @@ -19,10 +19,6 @@ const ( maxS3BucketNameLength = 63 ) -var ( - qcowMagic = []byte{'Q', 'F', 'I', 0xfb} -) - // ErrUnmatchedNodePool is returned when a nodePool was specified but not found in the nodePools list. type ErrUnmatchedNodePool struct { name string @@ -160,9 +156,6 @@ func (c *Cluster) validateLibvirt() []error { if err := validate.PrefixError("libvirt uri", validate.NonEmpty(c.Libvirt.URI)); err != nil { errs = append(errs, err) } - if err := validate.PrefixError("libvirt imagePath is not a valid QCOW image", validate.FileHeader(c.Libvirt.QCOWImagePath, qcowMagic)); err != nil { - errs = append(errs, err) - } if err := validate.PrefixError("libvirt network name", validate.NonEmpty(c.Libvirt.Network.Name)); err != nil { errs = append(errs, err) } diff --git a/installer/pkg/config/validate_test.go b/installer/pkg/config/validate_test.go index 962902e2687..ffcde180dc5 100644 --- a/installer/pkg/config/validate_test.go +++ b/installer/pkg/config/validate_test.go @@ -1,7 +1,6 @@ package config import ( - "io/ioutil" "os" "testing" @@ -444,21 +443,6 @@ func TestValidateIgnitionFiles(t *testing.T) { } func TestValidateLibvirt(t *testing.T) { - fValid, err := ioutil.TempFile("", "qcow") - if err != nil { - t.Fatalf("failed to create temporary file: %v", err) - } - if _, err := fValid.Write(qcowMagic); err != nil { - t.Fatalf("failed to write to temporary file: %v", err) - } - fValid.Close() - defer os.Remove(fValid.Name()) - fInvalid, err := ioutil.TempFile("", "qcow") - if err != nil { - t.Fatalf("failed to create temporary file: %v", err) - } - fInvalid.Close() - defer os.Remove(fInvalid.Name()) cases := []struct { cluster Cluster err bool @@ -474,24 +458,9 @@ func TestValidateLibvirt(t *testing.T) { { cluster: Cluster{ Libvirt: libvirt.Libvirt{ - Network: libvirt.Network{}, - QCOWImagePath: "", - URI: "", - }, - Networking: defaultCluster.Networking, - }, - err: true, - }, - { - cluster: Cluster{ - Libvirt: libvirt.Libvirt{ - Network: libvirt.Network{ - Name: "tectonic", - IfName: libvirt.DefaultIfName, - IPRange: "10.0.1.0/24", - }, - QCOWImagePath: fInvalid.Name(), - URI: "baz", + Network: libvirt.Network{}, + Image: "", + URI: "", }, Networking: defaultCluster.Networking, }, @@ -505,8 +474,8 @@ func TestValidateLibvirt(t *testing.T) { IfName: libvirt.DefaultIfName, IPRange: "10.0.1.0/24", }, - QCOWImagePath: fValid.Name(), - URI: "baz", + Image: "file:///foo", + URI: "baz", }, Networking: defaultCluster.Networking, }, @@ -520,8 +489,8 @@ func TestValidateLibvirt(t *testing.T) { IfName: libvirt.DefaultIfName, IPRange: "10.2.1.0/24", }, - QCOWImagePath: fValid.Name(), - URI: "baz", + Image: "file:///foo", + URI: "baz", }, Networking: defaultCluster.Networking, }, @@ -535,8 +504,8 @@ func TestValidateLibvirt(t *testing.T) { IfName: libvirt.DefaultIfName, IPRange: "x", }, - QCOWImagePath: "foo", - URI: "baz", + Image: "file:///foo", + URI: "baz", }, Networking: defaultCluster.Networking, }, @@ -550,12 +519,12 @@ func TestValidateLibvirt(t *testing.T) { IfName: libvirt.DefaultIfName, IPRange: "192.168.0.1/24", }, - QCOWImagePath: "foo", - URI: "baz", + Image: "file:///foo", + URI: "baz", }, Networking: defaultCluster.Networking, }, - err: true, + err: false, }, } diff --git a/installer/pkg/validate/validate.go b/installer/pkg/validate/validate.go index b4966e6835c..416cc02392f 100644 --- a/installer/pkg/validate/validate.go +++ b/installer/pkg/validate/validate.go @@ -1,7 +1,6 @@ package validate import ( - "bytes" "crypto/x509" "encoding/json" "encoding/pem" @@ -469,19 +468,3 @@ func lastIP(cidr *net.IPNet) net.IP { } return last } - -// FileHeader validates that the file at the specified path begins with the given string. -func FileHeader(path string, header []byte) error { - f, err := os.Open(path) - if err != nil { - return err - } - buf := make([]byte, len(header)) - if _, err := f.Read(buf); err != nil { - return err - } - if !bytes.Equal(buf, header) { - return fmt.Errorf("file %q does not begin with %q", path, string(header)) - } - return nil -} diff --git a/installer/pkg/validate/validate_test.go b/installer/pkg/validate/validate_test.go index 0c5e1d98147..8499be2b0fa 100644 --- a/installer/pkg/validate/validate_test.go +++ b/installer/pkg/validate/validate_test.go @@ -610,52 +610,3 @@ func TestFileExists(t *testing.T) { } } } - -func TestFileHeader(t *testing.T) { - cases := []struct { - actual []byte - expected []byte - err bool - }{ - { - actual: []byte{}, - expected: []byte("foo"), - err: true, - }, - { - actual: []byte("foo"), - expected: []byte("bar"), - err: true, - }, - { - actual: []byte("fooooo"), - expected: []byte("foo"), - err: false, - }, - { - actual: []byte("fooooo"), - expected: []byte{}, - err: false, - }, - } - - for i, c := range cases { - f, err := ioutil.TempFile("", "fileheader") - if err != nil { - t.Errorf("test case %d: failed to create temporary file: %v", i, err) - continue - } - if _, err := f.Write(c.actual); err != nil { - t.Errorf("test case %d: failed to write to temporary file: %v", i, err) - } - f.Close() - if err := validate.FileHeader(f.Name(), c.expected); (err != nil) != c.err { - no := "no" - if c.err { - no = "an" - } - t.Errorf("test case %d: expected %s error, got %v", i, no, err) - } - os.Remove(f.Name()) - } -} diff --git a/modules/libvirt/volume/main.tf b/modules/libvirt/volume/main.tf index 97900b278bb..d42cffd9d1e 100644 --- a/modules/libvirt/volume/main.tf +++ b/modules/libvirt/volume/main.tf @@ -1,5 +1,4 @@ -# Create a QCOW volume from the downloaded path resource "libvirt_volume" "coreos_base" { name = "coreos_base" - source = "file://${var.coreos_qcow_path}" + source = "file://${var.image}" } diff --git a/modules/libvirt/volume/variables.tf b/modules/libvirt/volume/variables.tf index 620a839d645..43471f87961 100644 --- a/modules/libvirt/volume/variables.tf +++ b/modules/libvirt/volume/variables.tf @@ -1,4 +1,4 @@ -variable "coreos_qcow_path" { - description = "The path on disk to the coreos disk image" +variable "image" { + description = "The URL of the OS disk image" type = "string" } diff --git a/pkg/types/machinepools.go b/pkg/types/machinepools.go index 59b14181cfd..64b21fa6773 100644 --- a/pkg/types/machinepools.go +++ b/pkg/types/machinepools.go @@ -50,6 +50,10 @@ type EC2RootVolume struct { // LibvirtMachinePoolPlatform stores the configuration for a machine pool // installed on libvirt. type LibvirtMachinePoolPlatform struct { - // QCOWImagePath - QCOWImagePath string `json:"qcowImagePath"` + // ImagePool is the name of the libvirt storage pool to which the storage + // volume containing the OS image belongs. + ImagePool string `json:"imagePool"` + // ImageVolume is the name of the libvirt storage volume containing the OS + // image. + ImageVolume string `json:"imageVolume"` } diff --git a/steps/infra/libvirt/main.tf b/steps/infra/libvirt/main.tf index 2db1733a1db..6dbb7dd4758 100644 --- a/steps/infra/libvirt/main.tf +++ b/steps/infra/libvirt/main.tf @@ -5,7 +5,7 @@ provider "libvirt" { module "libvirt_base_volume" { source = "../../../modules/libvirt/volume" - coreos_qcow_path = "${var.tectonic_coreos_qcow_path}" + image = "${var.tectonic_os_image}" } resource "libvirt_volume" "master" { diff --git a/steps/variables-libvirt.tf b/steps/variables-libvirt.tf index 754d9ae4357..88bed42827e 100644 --- a/steps/variables-libvirt.tf +++ b/steps/variables-libvirt.tf @@ -18,9 +18,9 @@ variable "tectonic_libvirt_ip_range" { description = "IP range for the libvirt machines" } -variable "tectonic_coreos_qcow_path" { +variable "tectonic_os_image" { type = "string" - description = "path to a Red Hat CoreOS qcow image" + description = "The URL of the OS disk image" } variable "tectonic_libvirt_bootstrap_ip" { diff --git a/tests/run.sh b/tests/run.sh index 0c8b496d4a4..be0df6c9003 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -101,7 +101,7 @@ python <<-EOF >"${CLUSTER_NAME}.yaml" config['aws']['master']['iamRoleName'] = 'tf-tectonic-master-node' config['aws']['worker']['iamRoleName'] = 'tf-tectonic-worker-node' elif '${BACKEND}' == 'libvirt': - config['libvirt']['imagePath'] = '${IMAGE_PATH}' + config['libvirt']['image'] = 'file://${IMAGE_PATH}' yaml.safe_dump(config, sys.stdout) EOF