Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions Documentation/dev/libvirt-howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion examples/libvirt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulling this in Go as part of asset generation seems like it would be reasonably straightforward, and in that case we could cache it (with enough information for If-Modified-Since requests on subsequent runs). But I'm fine punting optimizations like that to future work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Let's punt for now though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing this today, it looks like (at least on libvirt 3.9.0 with QEMU 2.9.0) you need to unzip the image before you can use it. But Nginx is currently serving it as an octet-stream:

$ curl -sI http://aos-ostree.rhev-ci-vms.eng.rdu2.redhat.com/rhcos/images/cloud/latest/rhcos-qemu.qcow2.gz | grep -i content-type
Content-Type: application/octet-stream

so there's no way to cleanly distinguish between "caller pointed us at a gzipped image" and "caller pointed us at an uncompressed image". Maybe we can get Nginx updated to use gzip_static so we could request ../latest/rhcos-qemu.qcow2 directly and get gzip as a Content-Encoding? Or look for the gzip and/or QCOW magic to decide if we need to unzip? Or maybe I just have old libraries, and newer ones handle this for me?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this ever work on your system? When I wrote the PR, it worked on mine, but maybe something changed since then. I'll test it again today.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this ever work on your system?

Nope. I should start testing libvirt changes loccally before I approve them...


ca:
# (optional) The content of the PEM-encoded CA certificate, used to generate Tectonic Console's server certificate.
Expand Down
6 changes: 4 additions & 2 deletions installer/pkg/config-generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions installer/pkg/config/libvirt/libvirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 0 additions & 7 deletions installer/pkg/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
55 changes: 12 additions & 43 deletions installer/pkg/config/validate_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package config

import (
"io/ioutil"
"os"
"testing"

Expand Down Expand Up @@ -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
Expand All @@ -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,
},
Expand All @@ -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,
},
Expand All @@ -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,
},
Expand All @@ -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,
},
Expand All @@ -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,
},
}

Expand Down
17 changes: 0 additions & 17 deletions installer/pkg/validate/validate.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package validate

import (
"bytes"
"crypto/x509"
"encoding/json"
"encoding/pem"
Expand Down Expand Up @@ -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
}
49 changes: 0 additions & 49 deletions installer/pkg/validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
3 changes: 1 addition & 2 deletions modules/libvirt/volume/main.tf
Original file line number Diff line number Diff line change
@@ -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}"
}
4 changes: 2 additions & 2 deletions modules/libvirt/volume/variables.tf
Original file line number Diff line number Diff line change
@@ -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"
}
8 changes: 6 additions & 2 deletions pkg/types/machinepools.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ type EC2RootVolume struct {
// LibvirtMachinePoolPlatform stores the configuration for a machine pool
// installed on libvirt.
type LibvirtMachinePoolPlatform struct {
// QCOWImagePath
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, adding comments for your new properties will make it easier for folks to understand the config structure by reading the godocs.

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"`
}
2 changes: 1 addition & 1 deletion steps/infra/libvirt/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
4 changes: 2 additions & 2 deletions steps/variables-libvirt.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
2 changes: 1 addition & 1 deletion tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down