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