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

Add support for VLANs #145

Merged
merged 4 commits into from
Feb 19, 2024
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
1 change: 1 addition & 0 deletions Eryph.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=realizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uninstallation/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Virtualization/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vlan/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Workflows/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
7 changes: 7 additions & 0 deletions src/core/src/Eryph.Core/Network/BridgeHostIpMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Eryph.Core.Network;

public enum BridgeHostIpMode
{
Disabled,
Dhcp
}
9 changes: 9 additions & 0 deletions src/core/src/Eryph.Core/Network/BridgeVlanMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Eryph.Core.Network;

public enum BridgeVlanMode
{
Invalid,
Access,
NativeUntagged,
NativeTagged
}
9 changes: 9 additions & 0 deletions src/core/src/Eryph.Core/Network/NetworkProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class NetworkProvider
public string BridgeName { get; set; }
public string SwitchName { get; set; }

public int? Vlan { get; set; }

[CanBeNull] public NetworkProviderBridgeOptions BridgeOptions { get; set; }

public string[] Adapters { get; set; }

public NetworkProviderSubnet[] Subnets { get; set; }
Expand Down Expand Up @@ -43,7 +47,12 @@ public static Validation<Error, NetworkProvider> Validate(NetworkProvider provid
from nonInvalidType in provider.Type == NetworkProviderType.Invalid
? Prelude.Fail<Error, NetworkProvider>($"network_provider {provider.Name}: network provider type has to of value overlay, nat_overlay or flat")
: Prelude.Success<Error, NetworkProvider>(provider)
let hasProviderTag = provider.Vlan > 0
from nonInvalidProviderVlan in provider.Type == NetworkProviderType.NatOverLay && hasProviderTag
? Prelude.Fail<Error, NetworkProvider>($"network_provider {provider.Name}: provider vlan tag is not supported for nat_overlay network providers")
: Prelude.Success<Error, NetworkProvider>(provider)
select provider;

}

}
53 changes: 53 additions & 0 deletions src/core/src/Eryph.Core/Network/NetworkProviderBridgeOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using JetBrains.Annotations;
using YamlDotNet.Serialization;

namespace Eryph.Core.Network;

public class NetworkProviderBridgeOptions
{
// this is intentionally not named vlan to avoid confusion with the vlan of the provider
public int? BridgeVlan { get; set; }

[YamlMember(Alias = "vlan_mode")]
public string VLanModeString { get; set; }

[YamlIgnore] public BridgeVlanMode VLanMode => ParseVLanMode(VLanModeString);

public static BridgeVlanMode ParseVLanMode([CanBeNull] string modeString)
{
if (string.IsNullOrWhiteSpace(modeString))
return BridgeVlanMode.Invalid;

return modeString switch
{
"access" => BridgeVlanMode.Access,
"native_untagged" => BridgeVlanMode.NativeUntagged,
"native_tagged" => BridgeVlanMode.NativeTagged,
_ => BridgeVlanMode.Invalid
};
}

// the ip mode is only used when the bridge is created, therefore it is
// named default_ip_mode
[YamlMember(Alias = "default_ip_mode")]
public string DefaultIpModeString { get; set; }

[YamlIgnore] public BridgeHostIpMode DefaultIpMode => ParseHostIpMode(DefaultIpModeString);

public static BridgeHostIpMode ParseHostIpMode([CanBeNull] string modeString)
{
if (string.IsNullOrWhiteSpace(modeString))
return BridgeHostIpMode.Disabled;

return modeString switch
{
// for future use
//"static" => BridgeHostIpMode.Static,
"dhcp" => BridgeHostIpMode.Dhcp,
"disabled" => BridgeHostIpMode.Disabled,
_ => BridgeHostIpMode.Disabled
};
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ public class NetworkProvidersConfiguration
{
public NetworkProvider[] NetworkProviders { get; set; }

public string[] EnabledBridges { get; set; }


public const string DefaultConfig = @"
network_providers:
- name: default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dbosoft.OVN.Core" Version="1.0.0-rc.1" />
<PackageReference Include="Dbosoft.OVN.Core" Version="1.0.0-rc.2" />
<PackageReference Include="GitVersion.MsBuild" Version="5.11.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private static NetworkPlan AddExternalNetSwitches(NetworkPlan networkPlan,
from provider in overlayProviders.Where(p => providerNames.Contains(p.Name))
let p1 = networkPlan.AddSwitch($"externalNet-{networkPlan.Id}-{provider.Name}")
let p2 = p1.AddExternalNetworkPort($"externalNet-{networkPlan.Id}-{provider.Name}",
provider.Name)
provider.Name, provider.Vlan)

select p2).Apply(s => JoinPlans(s, networkPlan));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dbosoft.OVN.Hosting" Version="1.0.0-rc.1" />
<PackageReference Include="Dbosoft.OVN.Hosting" Version="1.0.0-rc.2" />
<PackageReference Include="GitVersion.MsBuild" Version="5.11.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dbosoft.OVN.Hosting" Version="1.0.0-rc.1" />
<PackageReference Include="Dbosoft.OVN.Hosting" Version="1.0.0-rc.2" />
<PackageReference Include="Eryph.GenePool.Client" Version="0.1.0-ci.22" />
<PackageReference Include="GitVersion.MsBuild" Version="5.11.1">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public Aff<RT, Unit> EnableBridgeAdapter(string adapterName) =>
.AddCommand("Enable-NetAdapter"))
.ToAff());

public Aff<RT, Unit> ConfigureNATAdapter(string adapterName, IPAddress ipAddress, IPNetwork network)
public Aff<RT, Unit> ConfigureAdapterIp(string adapterName, IPAddress ipAddress, IPNetwork network)
{
var ipCommand = PsCommandBuilder.Create()
.AddCommand("New-NetIPAddress")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface IHostNetworkCommands<RT> where RT : struct, HasCancel<RT>

Aff<RT, Seq<NetNat>> GetNetNat();
Aff<RT, Unit> EnableBridgeAdapter(string adapterName);
Aff<RT, Unit> ConfigureNATAdapter(string adapterName, IPAddress ipAddress, IPNetwork network);
Aff<RT, Unit> ConfigureAdapterIp(string adapterName, IPAddress ipAddress, IPNetwork network);
Aff<RT, Seq<TypedPsObject<VMNetworkAdapter>>> GetNetAdaptersBySwitch(Guid switchId);
Aff<RT, Unit> DisconnectNetworkAdapters(Seq<TypedPsObject<VMNetworkAdapter>> adapters);
Aff<RT, Unit> ReconnectNetworkAdapters(Seq<TypedPsObject<VMNetworkAdapter>> adapters, string switchName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public enum NetworkChangeOperation

RemoveAdapterPort,
AddAdapterPort,
UpdateBridgePort,

ConfigureNatIp,
UpdateBridgeMapping
Expand Down Expand Up @@ -275,6 +276,22 @@ public Aff<RT, OVSBridgeInfo> RemoveUnusedBridges(OVSBridgeInfo ovsBridges, Seq<
}
}

private (int? VlanTag, string? VlanMode) GetBridgePortSettings(NetworkProviderBridgeOptions? options)
{
var bridgeVlanTag = options?.BridgeVlan;
var vlanMode = options?.VLanMode switch
{
BridgeVlanMode.Invalid => null,
BridgeVlanMode.Access => "access",
BridgeVlanMode.NativeUntagged => "native-untagged",
BridgeVlanMode.NativeTagged => "native-tagged",
null => null,
_ => null
};

return (bridgeVlanTag, vlanMode);
}

public Aff<RT, Seq<string>> AddMissingBridges(
bool hadSwitchBefore,
Seq<string> enableBridges,
Expand All @@ -292,11 +309,15 @@ public Aff<RT, Seq<string>> AddMissingBridges(

_logger.LogDebug("Adding operation to add bridge {bridge}", newBridge);

var (vlanTag, vlanMode) = GetBridgePortSettings(newBridge.Options);

AddOperation(() =>
from c in default(RT).HostNetworkCommands
from ovs in default(RT).OVS
let cancelAddBridge = new CancellationTokenSource(TimeSpan.FromSeconds(30))
from uAddBridge in ovs.AddBridge(newBridge.BridgeName, cancelAddBridge.Token).ToAff(l => l)
let cancelSetBridge = new CancellationTokenSource(TimeSpan.FromSeconds(30))
from uSetBridgePort in ovs.UpdateBridgePort(newBridge.BridgeName, vlanTag, vlanMode, cancelSetBridge.Token).ToAff(l => l)
from uWait in c.WaitForBridgeAdapter(newBridge.BridgeName)
from uEnable in
enableBridges.Contains(newBridge.BridgeName)
Expand Down Expand Up @@ -458,7 +479,7 @@ select from updateBridgeAdapter in !isNewCreatedBridge
? AddOperation(
() => default(RT).HostNetworkCommands.Bind(cc => cc
.EnableBridgeAdapter(newBridge.BridgeName)
.Bind(_ => c.ConfigureNATAdapter(newBridge.BridgeName, newBridge.IPAddress,
.Bind(_ => c.ConfigureAdapterIp(newBridge.BridgeName, newBridge.IPAddress,
newBridge.Network))), NetworkChangeOperation.ConfigureNatIp,
newBridge.BridgeName)
: unit
Expand Down Expand Up @@ -531,6 +552,54 @@ from networkProvider in newConfig.NetworkProviders
}
}

public Aff<RT, Unit> UpdateBridgePorts(
NetworkProvidersConfiguration newConfig,
Seq<string> createdBridges,
OVSBridgeInfo ovsBridges)
{
using (_logger.BeginScope("Method: {method}", nameof(UpdateBridgePorts)))
{

foreach (var networkProvider in newConfig.NetworkProviders
.Where(x => x.Type is NetworkProviderType.Overlay or NetworkProviderType.NatOverLay))
{
var (vlanTag, vlanMode) = GetBridgePortSettings(networkProvider.BridgeOptions);

if (createdBridges.Contains(networkProvider.BridgeName))
return SuccessAff(unit);

var (currentTag, currentVLanMode) = ovsBridges.Ports.Find(networkProvider.BridgeName)
.Map(port => (port.Tag, port.VlanMode)).IfNone((null,null));

if(currentTag.GetValueOrDefault() == vlanTag.GetValueOrDefault() && currentVLanMode == vlanMode)
return SuccessAff(unit);


AddOperation(
() => default(RT).OVS.Bind(ovs =>
{
var cancelSourceCommand = new CancellationTokenSource(TimeSpan.FromSeconds(30));

return ovs.UpdateBridgePort(networkProvider.BridgeName, vlanTag, vlanMode, cancelSourceCommand.Token)
.ToAff(l => l);
}),
_ => true,
() => default(RT).OVS.Bind(ovs =>
{
var cancelSourceCommand = new CancellationTokenSource(TimeSpan.FromSeconds(30));
return ovs
.UpdateBridgePort(networkProvider.BridgeName, currentTag, currentVLanMode, cancelSourceCommand.Token)
.ToAff(l => l);
}),
NetworkChangeOperation.UpdateBridgePort, networkProvider.BridgeName
);

}

return SuccessAff(unit);
}
}

public Aff<RT, Unit> CreateOverlayAdapterPorts(
NetworkProvidersConfiguration newConfig,
OVSBridgeInfo ovsBridges)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public string this[NetworkChangeOperation key]{
NetworkChangeOperation.RemoveNetNat => "Remove host NAT for provider {0}",
NetworkChangeOperation.RemoveAdapterPort => "Remove adapter '{0}' from bridge '{1}'",
NetworkChangeOperation.AddAdapterPort => "Add adapter '{0}' to bridge '{1}'",
NetworkChangeOperation.UpdateBridgePort => "Configure port options for bridge {0}",
NetworkChangeOperation.ConfigureNatIp => "Configure ip settings for NAT bridge '{0}'",
NetworkChangeOperation.UpdateBridgeMapping => "Update mapping of bridges to network providers",
NetworkChangeOperation.RemoveMissingBridge => "Host adapter '{0}' not found - removing bridge",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Net;
using Eryph.Core.Network;

namespace Eryph.Modules.VmHostAgent.Networks;

public readonly record struct NewBridge(string BridgeName, IPAddress IPAddress, IPNetwork Network);
public readonly record struct NewBridge(string BridgeName, IPAddress IPAddress, IPNetwork Network, NetworkProviderBridgeOptions? Options);
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ public record BridgePort : OVSTableRecord, IOVSEntityWithName
Columns = new Dictionary<string, OVSFieldMetadata>(OVSTableRecord.Columns)
{
{ "name", OVSValue<string>.Metadata() },
{ "tag", OVSValue<int>.Metadata() },
{ "vlan_mode", OVSValue<string>.Metadata() },
};

public string Name => GetValue<string>("name");
public int? Tag => GetValue<int>("tag");
public string? VlanMode => GetValue<string>("vlan_mode");

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public interface IOVSControl
EitherAsync<Error, Seq<BridgePort>> GetPorts(CancellationToken cancellationToken);
EitherAsync<Error, OVSTableRecord> GetOVSTable(CancellationToken cancellationToken);
EitherAsync<Error, Unit> UpdateBridgeMapping(string bridgeMappings, CancellationToken cancellationToken);
EitherAsync<Error, Unit> UpdateBridgePort(string bridgeName, int? tag, string? vlanMode, CancellationToken cancellationToken);

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Dbosoft.OVN;
using Dbosoft.OVN.Model;
Expand Down Expand Up @@ -44,6 +45,29 @@ from _ in UpdateRecord("open", ".",

}

public EitherAsync<Error, Unit> UpdateBridgePort(string bridgeName, int? tag, string? vlanMode, CancellationToken cancellationToken)
{
var columns = new Map<string, IOVSField>();
if(tag > 0)
columns = columns.Add("tag", new OVSValue<int>(tag.Value));
if(vlanMode != null)
columns = columns.Add("vlan_mode", new OVSValue<string>(vlanMode));

var columnsToClear = new Lst<string>();
if(tag.GetValueOrDefault() == 0)
columnsToClear = columnsToClear.Add("tag");

if (string.IsNullOrWhiteSpace(vlanMode))
columnsToClear = columnsToClear.Add("vlan_mode");

return from ovsRecord in GetOVSTable(cancellationToken)
from _ in UpdateRecord("port", bridgeName,
Map<string, IOVSField>.Empty,columns,
columnsToClear, cancellationToken)
select Unit.Default;

}

public EitherAsync<Error, Unit> AddBridge(string bridgeName, CancellationToken cancellationToken)
{
return RunCommand($" --may-exist add-br \"{bridgeName}\"", false, cancellationToken).Map(_ => Unit.Default);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using LanguageExt;
using Eryph.Core.Network;
using LanguageExt;

namespace Eryph.Modules.VmHostAgent.Networks;

public readonly record struct OVSBridgeInfo(
Lst<string> Bridges, HashMap<string, string> BridgePorts
);
Lst<string> Bridges, HashMap<string, string> BridgePorts,
HashMap<string, OVSBridgePortInfo> Ports
);

public readonly record struct OVSBridgePortInfo(string BridgeName,
string PortName, int? Tag, string? VlanMode);
Loading