Skip to content
This repository was archived by the owner on Feb 5, 2020. It is now read-only.
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
10 changes: 5 additions & 5 deletions examples/tectonic.libvirt.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
admin:
email: "a@b.c"
password: "verysecure"
email: a@b.c
password: verysecure
# The base DNS domain of the cluster. It must NOT contain a trailing period. Some
# DNS providers will automatically add this if necessary.
#
Expand All @@ -15,12 +15,12 @@ admin:
baseDomain:

libvirt:
uri: "qemu:///system"
uri: qemu:///system
network:
name: tectonic
ifName: tt0
dnsServer: "8.8.8.8"
ipRange: "192.168.124.0/24"
dnsServer: 8.8.8.8
ipRange: 192.168.124.0/24
sshKey: "ssh-rsa ..."
imagePath: /path/to/image

Expand Down
2 changes: 1 addition & 1 deletion installer/pkg/config-generator/ignition.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (c *ConfigGenerator) embedUserBlock(ignCfg *ignconfigtypes.Config) {
userBlock := ignconfigtypes.User{
Name: "core",
SSHAuthorizedKeys: []string{
c.Libvirt.SshKey,
c.Libvirt.SSHKey,
},
}

Expand Down
6 changes: 6 additions & 0 deletions installer/pkg/config/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ var defaultCluster = Cluster{
Channel: ContainerLinuxChannelStable,
Version: ContainerLinuxVersionLatest,
},
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{
DNSServer: libvirt.DefaultDNSServer,
IfName: libvirt.DefaultIfName,
},
},
Networking: Networking{
MTU: "1480",
PodCIDR: "10.2.0.0/16",
Expand Down
29 changes: 18 additions & 11 deletions installer/pkg/config/libvirt/libvirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,39 @@ import (
"github.com/apparentlymart/go-cidr/cidr"
)

const (
// DefaultDNSServer is the default DNS server for libvirt.
DefaultDNSServer = "8.8.8.8"
// DefaultIfName is the default interface name for libvirt.
DefaultIfName = "osbr0"
)

// Libvirt-specific configuration
type Libvirt struct {
URI string `json:"tectonic_libvirt_uri,omitempty" yaml:"uri"`
SshKey string `json:"tectonic_libvirt_ssh_key,omitempty" yaml:"sshKey"`
QowImagePath string `json:"tectonic_coreos_qow_path,omitempty" yaml:"imagePath"`
Network `json:",inline" yaml:"network"`
MasterIPs []string `json:"tectonic_libvirt_master_ips,omitempty" yaml:"masterIps"`
URI string `json:"tectonic_libvirt_uri,omitempty" yaml:"uri"`
SSHKey string `json:"tectonic_libvirt_ssh_key,omitempty" yaml:"sshKey"`
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"`
}

type Network struct {
Name string `json:"tectonic_libvirt_network_name,omitempty" yaml:"name"`
IfName string `json:"tectonic_libvirt_network_if,omitempty" yaml"ifName"`
DnsServer string `json:"tectonic_libvirt_resolver,omitempty" yaml:"dnsServer"`
IpRange string `json:"tectonic_libvirt_ip_range,omitempty" yaml:"ipRange"`
DNSServer string `json:"tectonic_libvirt_resolver,omitempty" yaml:"dnsServer"`
IPRange string `json:"tectonic_libvirt_ip_range,omitempty" yaml:"ipRange"`
}

// Fill in any variables for terraform
func (l *Libvirt) TFVars(masterCount int) error {
_, network, err := net.ParseCIDR(l.Network.IpRange)
_, network, err := net.ParseCIDR(l.Network.IPRange)
if err != nil {
return fmt.Errorf("failed to parse libvirt.network.iprange: %v", err)
return fmt.Errorf("failed to parse libvirt network ipRange: %v", err)
}

if len(l.MasterIPs) > 0 {
if len(l.MasterIPs) != masterCount {
return fmt.Errorf("length of MasterIPs does't match master count")
return fmt.Errorf("length of MasterIPs doesn't match master count")
} else {
return nil
}
Expand All @@ -41,7 +48,7 @@ func (l *Libvirt) TFVars(masterCount int) error {
for i := 0; i < masterCount; i++ {
ip, err := cidr.Host(network, i+10)
if err != nil {
return fmt.Errorf("failed to generate masterips: %v", err)
return fmt.Errorf("failed to generate master IPs: %v", err)
}
l.MasterIPs = append(l.MasterIPs, ip.String())
}
Expand Down
74 changes: 53 additions & 21 deletions installer/pkg/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"regexp"
"strings"

Expand All @@ -20,6 +19,10 @@ 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 @@ -81,6 +84,7 @@ func (c *Cluster) Validate() []error {
errs = append(errs, c.validateAWS()...)
errs = append(errs, c.validateCL()...)
errs = append(errs, c.validateTectonicFiles()...)
errs = append(errs, c.validateLibvirt()...)
if err := validate.PrefixError("cluster name", validate.ClusterName(c.Name)); err != nil {
errs = append(errs, err)
}
Expand Down Expand Up @@ -130,6 +134,52 @@ func (c *Cluster) validateCL() []error {
return errs
}

// validateLibvirt validates all fields specific to libvirt.
func (c *Cluster) validateLibvirt() []error {
var errs []error
if c.Platform != PlatformLibvirt {
return errs
}
if err := validate.PrefixError("libvirt network ipRange", validate.SubnetCIDR(c.Libvirt.Network.IPRange)); err != nil {
Copy link

Choose a reason for hiding this comment

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

It's not 100% necessary, but if you want to validate that this doesn't overlap with the cluster and service cidrs, that would be awesome!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

errs = append(errs, err)
}
if len(c.Libvirt.MasterIPs) > 0 {
if len(c.Libvirt.MasterIPs) != c.NodeCount(c.Master.NodePools) {
errs = append(errs, fmt.Errorf("length of masterIPs does't match master count"))
}
for i, ip := range c.Libvirt.MasterIPs {
if err := validate.PrefixError(fmt.Sprintf("libvirt masterIPs[%d] %q", i, ip), validate.IPv4(ip)); err != nil {
errs = append(errs, err)
}
}
}
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 sshKey", validate.NonEmpty(c.Libvirt.SSHKey)); 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)
}
if err := validate.PrefixError("libvirt network ifName", validate.NonEmpty(c.Libvirt.Network.IfName)); err != nil {
errs = append(errs, err)
}
if err := validate.PrefixError("libvirt network dnsServer", validate.IPv4(c.Libvirt.Network.DNSServer)); err != nil {
errs = append(errs, err)
}
if err := validate.PrefixError("libvirt ipRange and podCIDR", validate.CIDRsDontOverlap(c.Libvirt.Network.IPRange, c.Networking.PodCIDR)); err != nil {
errs = append(errs, err)
}
if err := validate.PrefixError("libvirt ipRange and serviceCIDR", validate.CIDRsDontOverlap(c.Libvirt.Network.IPRange, c.Networking.ServiceCIDR)); err != nil {
errs = append(errs, err)
}
return errs
}

func (c *Cluster) validateNetworking() []error {
var errs []error
// https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media
Expand All @@ -145,26 +195,8 @@ func (c *Cluster) validateNetworking() []error {
if err := c.validateNetworkType(); err != nil {
errs = append(errs, err)
}

var podOK, serviceOK bool
_, pod, err := net.ParseCIDR(c.Networking.PodCIDR)
if err != nil {
errs = append(errs, fmt.Errorf("invalid pod CIDR %q: %v", c.Networking.PodCIDR, err))
} else if err := validate.CanonicalizeIP(&pod.IP); err != nil {
errs = append(errs, fmt.Errorf("invalid pod CIDR %q: %v", c.Networking.PodCIDR, err))
} else {
podOK = true
}
_, service, err := net.ParseCIDR(c.Networking.ServiceCIDR)
if err != nil {
errs = append(errs, fmt.Errorf("invalid service CIDR %q: %v", c.Networking.ServiceCIDR, err))
} else if err := validate.CanonicalizeIP(&service.IP); err != nil {
errs = append(errs, fmt.Errorf("invalid service CIDR %q: %v", c.Networking.ServiceCIDR, err))
} else {
serviceOK = true
}
if podOK && serviceOK && validate.CIDRsOverlap(pod, service) {
errs = append(errs, errors.New("pod and service CIDRs overlap"))
if err := validate.PrefixError("pod and service CIDRs", validate.CIDRsDontOverlap(c.Networking.PodCIDR, c.Networking.ServiceCIDR)); err != nil {
errs = append(errs, err)
}
return errs
}
Expand Down
141 changes: 141 additions & 0 deletions installer/pkg/config/validate_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package config

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

"github.com/coreos/tectonic-installer/installer/pkg/config/aws"
"github.com/coreos/tectonic-installer/installer/pkg/config/libvirt"
)

func TestMissingNodePool(t *testing.T) {
Expand Down Expand Up @@ -570,3 +572,142 @@ func TestValidateCL(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
}{
{
cluster: Cluster{},
err: true,
},
{
cluster: defaultCluster,
err: true,
},
{
cluster: Cluster{
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{},
QCOWImagePath: "",
SSHKey: "",
URI: "",
},
Networking: defaultCluster.Networking,
},
err: true,
},
{
cluster: Cluster{
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{
Name: "tectonic",
IfName: libvirt.DefaultIfName,
DNSServer: libvirt.DefaultDNSServer,
IPRange: "10.0.1.0/24",
},
QCOWImagePath: fInvalid.Name(),
SSHKey: "bar",
URI: "baz",
},
Networking: defaultCluster.Networking,
},
err: true,
},
{
cluster: Cluster{
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{
Name: "tectonic",
IfName: libvirt.DefaultIfName,
DNSServer: libvirt.DefaultDNSServer,
IPRange: "10.0.1.0/24",
},
QCOWImagePath: fValid.Name(),
SSHKey: "bar",
URI: "baz",
},
Networking: defaultCluster.Networking,
},
err: false,
},
{
cluster: Cluster{
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{
Name: "tectonic",
IfName: libvirt.DefaultIfName,
DNSServer: libvirt.DefaultDNSServer,
IPRange: "10.2.1.0/24",
},
QCOWImagePath: fValid.Name(),
SSHKey: "bar",
URI: "baz",
},
Networking: defaultCluster.Networking,
},
err: true,
},
{
cluster: Cluster{
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{
Name: "tectonic",
IfName: libvirt.DefaultIfName,
DNSServer: libvirt.DefaultDNSServer,
IPRange: "x",
},
QCOWImagePath: "foo",
SSHKey: "bar",
URI: "baz",
},
Networking: defaultCluster.Networking,
},
err: true,
},
{
cluster: Cluster{
Libvirt: libvirt.Libvirt{
Network: libvirt.Network{
Name: "tectonic",
IfName: libvirt.DefaultIfName,
DNSServer: "foo",
IPRange: "192.168.0.1/24",
},
QCOWImagePath: "foo",
SSHKey: "bar",
URI: "baz",
},
Networking: defaultCluster.Networking,
},
err: true,
},
}

for i, c := range cases {
c.cluster.Platform = PlatformLibvirt
if err := c.cluster.validateLibvirt(); (err != nil) != c.err {
no := "no"
if c.err {
no = "an"
}
t.Errorf("test case %d: expected %s error, got %v", i, no, err)
}
}
}
Loading