Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: specify an ovn underlay network through MicroOVN #361

Merged
merged 10 commits into from
Sep 6, 2024
Merged
2 changes: 1 addition & 1 deletion api/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func servicesPut(state *state.State, r *http.Request) response.Response {
services := make([]types.ServiceType, len(req.Tokens))
for i, cfg := range req.Tokens {
services[i] = types.ServiceType(cfg.Service)
joinConfigs[cfg.Service] = service.JoinConfig{Token: cfg.JoinToken, LXDConfig: req.LXDConfig, CephConfig: req.CephConfig}
joinConfigs[cfg.Service] = service.JoinConfig{Token: cfg.JoinToken, LXDConfig: req.LXDConfig, CephConfig: req.CephConfig, OVNConfig: req.OVNConfig}
}

// Default to the first iface if none specified.
Expand Down
1 change: 1 addition & 0 deletions api/types/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ServicesPut struct {

LXDConfig []api.ClusterMemberConfigKey `json:"lxd_config" yaml:"lxd_config"`
CephConfig []types.DisksPost `json:"ceph_config" yaml:"ceph_config"`
OVNConfig map[string]string `json:"ovn_config" yaml:"ovn_config"`
}

// ServiceToken represents a join token for a service join request.
Expand Down
89 changes: 86 additions & 3 deletions cmd/microcloud/ask.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ func (c *initConfig) askLocalPool(sh *service.Handler) error {
return nil
}

func validateCephInterfacesForSubnet(lxdService *service.LXDService, systems map[string]InitSystem, availableCephNetworkInterfaces map[string]map[string]service.CephDedicatedInterface, askedCephSubnet string) error {
func validateCephInterfacesForSubnet(lxdService *service.LXDService, systems map[string]InitSystem, availableCephNetworkInterfaces map[string]map[string]service.DedicatedInterface, askedCephSubnet string) error {
validatedCephInterfacesData, err := lxdService.ValidateCephInterfaces(askedCephSubnet, availableCephNetworkInterfaces)
if err != nil {
return err
Expand Down Expand Up @@ -1104,6 +1104,76 @@ func (c *initConfig) askOVNNetwork(sh *service.Handler) error {
}
}

canOVNUnderlay := true
for peer, system := range c.systems {
if len(c.state[system.ServerInfo.Name].AvailableOVNInterfaces) == 0 {
fmt.Printf("Not enough interfaces available on %s to create an underlay network, skipping\n", peer)
canOVNUnderlay = false
break
}
}

var ovnUnderlaySelectedIPs map[string]string
ovnUnderlayData := [][]string{}
for peer, system := range c.systems {
// skip any systems that have already been clustered, but are available for other configuration.
state, ok := c.state[c.name]
if ok {
if state.ExistingServices[types.MicroOVN][peer] != "" {
continue
}
}

for _, net := range c.state[system.ServerInfo.Name].AvailableOVNInterfaces {
for _, addr := range net.Addresses {
ovnUnderlayData = append(ovnUnderlayData, []string{peer, net.Network.Name, net.Network.Type, addr})
}
}
}

if len(ovnUnderlayData) != 0 && canOVNUnderlay {
wantsDedicatedUnderlay, err := c.asker.AskBool("Configure dedicated underlay networking? (yes/no) [default=no]: ", "no")
if err != nil {
return err
}

if wantsDedicatedUnderlay {
header = []string{"LOCATION", "IFACE", "TYPE", "IP ADDRESS (CIDR)"}
fmt.Println("Select exactly one network interface from each cluster member:")

table = NewSelectableTable(header, ovnUnderlayData)
ovnUnderlaySelectedIPs = map[string]string{}
err = c.askRetry("Retry selecting underlay network interfaces?", func() error {
err = table.Render(table.rows)
if err != nil {
return err
}

answers, err := table.GetSelections()
if err != nil {
return err
}

ovnUnderlaySelectedIPs = map[string]string{}
for _, answer := range answers {
target := table.SelectionValue(answer, "LOCATION")
ipAddr := table.SelectionValue(answer, "IP ADDRESS (CIDR)")

if ovnUnderlaySelectedIPs[target] != "" {
return fmt.Errorf("Failed to configure OVN underlay traffic: Selected more than one interface for target %q", target)
}

ovnUnderlaySelectedIPs[target] = ipAddr
}

return nil
})
if err != nil {
return err
}
}
}

for peer, system := range c.systems {
if !askSystems[peer] {
continue
Expand Down Expand Up @@ -1133,6 +1203,19 @@ func (c *initConfig) askOVNNetwork(sh *service.Handler) error {
system.Networks = append(system.Networks, finalConfigs...)
}

if ovnUnderlaySelectedIPs != nil {
ovnUnderlayIpAddr, ok := ovnUnderlaySelectedIPs[peer]
if ok {
ip, _, err := net.ParseCIDR(ovnUnderlayIpAddr)
if err != nil {
return err
}

fmt.Printf("Using %q for OVN underlay traffic on %q\n", ip.String(), peer)
system.OVNGeneveAddr = ip.String()
}
}

c.systems[peer] = system
}

Expand Down Expand Up @@ -1220,15 +1303,15 @@ func (c *initConfig) askCephNetwork(sh *service.Handler) error {
return nil
}

availableCephNetworkInterfaces := map[string]map[string]service.CephDedicatedInterface{}
availableCephNetworkInterfaces := map[string]map[string]service.DedicatedInterface{}
for name, state := range c.state {
if len(state.AvailableCephInterfaces) == 0 {
fmt.Printf("No network interfaces found with IPs on %q to set a dedicated Ceph network, skipping Ceph network setup\n", name)

return nil
}

ifaces := make(map[string]service.CephDedicatedInterface, len(state.AvailableCephInterfaces))
ifaces := make(map[string]service.DedicatedInterface, len(state.AvailableCephInterfaces))
for name, iface := range state.AvailableCephInterfaces {
ifaces[name] = iface
}
Expand Down
20 changes: 20 additions & 0 deletions cmd/microcloud/main_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type InitSystem struct {
TargetStoragePools []lxdAPI.StoragePoolsPost
// Networks is the cluster-wide network configuration.
Networks []lxdAPI.NetworksPost
// OVNGeneveAddr represents an IP address to use for the OVN (if OVN is supported) Geneve tunnel on this system.
// If left empty, the system will choose to route the Geneve traffic through the management network.
OVNGeneveAddr string
// StoragePools is the cluster-wide storage pool configuration.
StoragePools []lxdAPI.StoragePoolsPost
// StorageVolumes is the cluster-wide storage volume configuration.
Expand Down Expand Up @@ -524,6 +527,12 @@ func (c *initConfig) addPeers(sh *service.Handler) (revert.Hook, error) {
LXDConfig: info.JoinConfig,
CephConfig: info.MicroCephDisks,
}

if info.OVNGeneveAddr != "" {
p := joinConfig[peer]
p.OVNConfig = map[string]string{"ovn-encap-ip": info.OVNGeneveAddr}
joinConfig[peer] = p
}
}

clusterSize := map[types.ServiceType]int{}
Expand Down Expand Up @@ -782,6 +791,17 @@ func (c *initConfig) setupCluster(s *service.Handler) error {
}
}

if s.Type() == types.MicroOVN {
microOvnBootstrapConf := make(map[string]string)
if bootstrapSystem.OVNGeneveAddr != "" {
microOvnBootstrapConf["ovn-encap-ip"] = bootstrapSystem.OVNGeneveAddr
}

if len(microOvnBootstrapConf) > 0 {
s.SetConfig(microOvnBootstrapConf)
Copy link
Contributor

Choose a reason for hiding this comment

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

So we only apply the underlay config during bootstrap/join. When running microcloud add or microcloud service add, other nodes may not have underlay networking configured, so even though we present a list of every old and new cluster member, no config will change.

I guess this means we also need a way to set the geneve address over the API.

We could get around this for now if we at least have a way to get the current underlay configuration. We could just skip configuration in the add cases if it was not previously configured during init.

}
}

// set a 2 minute timeout to bootstrap a service in case the node is slow.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
Expand Down
77 changes: 75 additions & 2 deletions cmd/microcloud/main_init_preseed.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Preseed struct {
type System struct {
Name string `yaml:"name"`
UplinkInterface string `yaml:"ovn_uplink_interface"`
UnderlayIP string `yaml:"underlay_ip"`
Storage InitStorage `yaml:"storage"`
}

Expand Down Expand Up @@ -220,6 +221,7 @@ func (c *initConfig) RunPreseed(cmd *cobra.Command) error {
// validate validates the unmarshaled preseed input.
func (p *Preseed) validate(name string, bootstrap bool) error {
uplinkCount := 0
underlayCount := 0
directCephCount := 0
directLocalCount := 0
localInit := false
Expand All @@ -241,6 +243,15 @@ func (p *Preseed) validate(name string, bootstrap bool) error {
uplinkCount++
}

if system.UnderlayIP != "" {
_, _, err := net.ParseCIDR(system.UnderlayIP)
if err != nil {
return fmt.Errorf("Invalid underlay IP: %w", err)
}

underlayCount++
}

if len(system.Storage.Ceph) > 0 {
directCephCount++
}
Expand Down Expand Up @@ -270,6 +281,11 @@ func (p *Preseed) validate(name string, bootstrap bool) error {
return fmt.Errorf("Some systems are missing an uplink interface")
}

containsUnderlay := underlayCount > 0
if containsUnderlay && underlayCount < len(p.Systems) {
return fmt.Errorf("Some systems are missing an underlay interface")
}

containsLocalStorage = directLocalCount > 0
if containsLocalStorage && directLocalCount < len(p.Systems) && len(p.Storage.Local) == 0 {
return fmt.Errorf("Some systems are missing local storage disks")
Expand Down Expand Up @@ -484,10 +500,15 @@ func (p *Preseed) Parse(s *service.Handler, c *initConfig) (map[string]InitSyste

lxd := s.Services[types.LXD].(*service.LXDService)
ifaceByPeer := map[string]string{}
ovnUnderlayNeeded := false
for _, cfg := range p.Systems {
if cfg.UplinkInterface != "" {
ifaceByPeer[cfg.Name] = cfg.UplinkInterface
}

if cfg.UnderlayIP != "" {
ovnUnderlayNeeded = true
}
}

localInfo, err := s.CollectSystemInformation(context.Background(), mdns.ServerInfo{Name: c.name, Address: c.address})
Expand All @@ -498,7 +519,7 @@ func (p *Preseed) Parse(s *service.Handler, c *initConfig) (map[string]InitSyste
// If an uplink interface was explicitly chosen, we will try to set up an OVN network.
explicitOVN := len(ifaceByPeer) > 0

cephInterfaces := map[string]map[string]service.CephDedicatedInterface{}
cephInterfaces := map[string]map[string]service.DedicatedInterface{}
for _, system := range c.systems {
uplinkIfaces, cephIfaces, _, err := lxd.GetNetworkInterfaces(context.Background(), system.ServerInfo.Name, system.ServerInfo.Address, system.ServerInfo.AuthSecret)
if err != nil {
Expand All @@ -517,7 +538,7 @@ func (p *Preseed) Parse(s *service.Handler, c *initConfig) (map[string]InitSyste

for ifaceName, iface := range cephIfaces {
if cephInterfaces[system.ServerInfo.Name] == nil {
cephInterfaces[system.ServerInfo.Name] = map[string]service.CephDedicatedInterface{}
cephInterfaces[system.ServerInfo.Name] = map[string]service.DedicatedInterface{}
}

cephInterfaces[system.ServerInfo.Name][ifaceName] = iface
Expand All @@ -542,6 +563,58 @@ func (p *Preseed) Parse(s *service.Handler, c *initConfig) (map[string]InitSyste

c.systems[peer] = system
}

// Check the preseed underlay network configuration against the available ips.
if ovnUnderlayNeeded {
canOVNUnderlay := true
for peer, system := range c.state {
if len(system.AvailableOVNInterfaces) == 0 {
fmt.Printf("Not enough interfaces available on %s to create an underlay network, skipping\n", peer)
canOVNUnderlay = false
break
}
}
Comment on lines +570 to +576
Copy link
Contributor

Choose a reason for hiding this comment

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

c.state is not populated for preseed since the recipe is precise, we don't want to change any existing system configurations using a preseed file.


if canOVNUnderlay {
// TODO: call `s.Services[types.MicroOVN].(*service.OVNService).SupportsFeature(context.Background(), "custom_encapsulation_ip")`
// when MicroCloud will be updated with microcluster/v2
underlays := make(map[string]string, len(p.Systems))
for _, sys := range p.Systems {
underlays[sys.Name] = sys.UnderlayIP
}

underlayCount := 0
for _, sys := range p.Systems {
for _, net := range c.state[sys.Name].AvailableOVNInterfaces {
if len(net.Addresses) != 0 {
for _, cidrAddr := range net.Addresses {
if underlays[sys.Name] == cidrAddr {
underlayCount = underlayCount + 1
goto out
}
}
}
}

out:
}

if underlayCount != len(p.Systems) {
return nil, fmt.Errorf("Failed to find all underlay IPs on the network")
}

// Apply the underlay IPs to the systems.
for peer, system := range c.systems {
ip, _, err := net.ParseCIDR(underlays[peer])
if err != nil {
return nil, fmt.Errorf("Failed to parse underlay IP: %w", err)
}
Comment on lines +608 to +611
Copy link
Contributor

Choose a reason for hiding this comment

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

The input is not a cidr, so this will fail.


system.OVNGeneveAddr = ip.String()
c.systems[peer] = system
}
}
}
} else {
// Check if FAN networking is usable.
fanUsable, _, err := service.FanNetworkUsable()
Expand Down
13 changes: 13 additions & 0 deletions doc/explanation/microcloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ MicroCloud will still be usable, but you will see some limitations:
As a result of this, network forwarding works at a basic level only, and external addresses must be forwarded to a specific cluster member and don't fail over.
- There is no support for hardware acceleration, load balancers, or ACL functionality within the local network.

(microcloud-networking-underlay)=

Network interface to connect to an OVN underlay network
gabrielmougard marked this conversation as resolved.
Show resolved Hide resolved
: While setting up the distributed networking with OVN, you can also decide to set up a dedicated underlay network for the OVN traffic.
A dedicated underlay network for OVN serves as the physical infrastructure over which the virtual networking (overlay network) is constructed. It can be beneficial to set up a dedicated underlay network for the following reasons:

- A dedicated underlay network isolates the overlay network traffic from other types of traffic (such as management traffic).
- By dedicating a physical network solely for the purpose of the virtual network overlay, network congestion can be reduced.
- A dedicated underlay network can be tuned to provide predictable latency and bandwidth characteristics, which are crucial for the performance of latency-sensitive applications running over the OVN overlay.
- A dedicated underlay can be designed to scale independently of other networks, which allows for more efficient scaling of the overlay network as the demand for virtual network resources increases.

See {ref}`howto-ovn-underlay` for how to set up a dedicated OVN underlay network.
gabrielmougard marked this conversation as resolved.
Show resolved Hide resolved

### Dedicated internal network for Ceph
You can set up a dedicated network for Ceph to separate the internal Ceph traffic from the rest of the MicroCloud cluster traffic.

Expand Down
1 change: 1 addition & 0 deletions doc/how-to/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Install MicroCloud </how-to/install>
Manage the snaps </how-to/snaps>
Initialise MicroCloud </how-to/initialise>
Configure Ceph networking </how-to/ceph_networking>
Configure OVN underlay </how-to/ovn_underlay>
Add a machine </how-to/add_machine>
Remove a machine </how-to/remove_machine>
Add a service </how-to/add_service>
Expand Down
7 changes: 7 additions & 0 deletions doc/how-to/initialise.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ Complete the following steps to initialise MicroCloud:
You must select one network interface per machine.
1. If you want to use IPv4, specify the IPv4 gateway on the uplink network (in CIDR notation) and the first and last IPv4 address in the range that you want to use with LXD.
1. If you want to use IPv6, specify the IPv6 gateway on the uplink network (in CIDR notation).
1. If you chose to set up distributed networking, you can choose to setup an underlay network for the distributed networking:
gabrielmougard marked this conversation as resolved.
Show resolved Hide resolved

If you choose ``yes``, configure the underlay network:

1. Select the network interfaces that you want to use (see {ref}`microcloud-networking-underlay`).

You must select one network interface with an IP address per machine.
gabrielmougard marked this conversation as resolved.
Show resolved Hide resolved
1. MicroCloud now starts to bootstrap the cluster.
Monitor the output to see whether all steps complete successfully.
See {ref}`bootstrapping-process` for more information.
Expand Down
Loading
Loading