Add tenant-scoped PrismNET routing, security-group, port, and service-IP APIs plus a deployer reconciler and Nix module that apply declarative tenant network state. Teach PlasmaVMC to realize PrismNET NICs as a concrete local worker dataplane with Linux bridges, dnsmasq-backed DHCP, tap devices, richer network metadata, stable managed-volume IDs, and file:// image imports. Expand the VM cluster validation around the new path, including the guest webapp demo, restart and cross-node migration checks, IAM listener reservation hardening, and a flake workspace-source-root audit so Nix builds keep path dependencies complete.
373 lines
9.4 KiB
Nix
373 lines
9.4 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.plasmacloud.tenantNetworking;
|
|
jsonFormat = pkgs.formats.json {};
|
|
|
|
serviceIpPoolType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Service IP pool name";
|
|
};
|
|
|
|
cidr_block = mkOption {
|
|
type = types.str;
|
|
description = "Service IP pool CIDR";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Service IP pool description";
|
|
};
|
|
|
|
pool_type = mkOption {
|
|
type = types.nullOr (types.enum [ "cluster_ip" "load_balancer" "node_port" ]);
|
|
default = null;
|
|
description = "Service IP pool type";
|
|
};
|
|
};
|
|
};
|
|
|
|
portType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Port name";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Port description";
|
|
};
|
|
|
|
ip_address = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Requested fixed IP address";
|
|
};
|
|
|
|
security_groups = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = "Security group names attached to the port";
|
|
};
|
|
|
|
admin_state_up = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Administrative state for the port";
|
|
};
|
|
};
|
|
};
|
|
|
|
subnetType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Subnet name";
|
|
};
|
|
|
|
cidr_block = mkOption {
|
|
type = types.str;
|
|
description = "Subnet CIDR";
|
|
};
|
|
|
|
gateway_ip = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Gateway IP";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Subnet description";
|
|
};
|
|
|
|
dhcp_enabled = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Enable DHCP for the subnet";
|
|
};
|
|
|
|
ports = mkOption {
|
|
type = types.listOf portType;
|
|
default = [];
|
|
description = "Ports within the subnet";
|
|
};
|
|
};
|
|
};
|
|
|
|
routerType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Router name";
|
|
};
|
|
|
|
gateway_cidr = mkOption {
|
|
type = types.str;
|
|
description = "Gateway interface CIDR attached to the VPC";
|
|
};
|
|
|
|
mac_address = mkOption {
|
|
type = types.str;
|
|
description = "Router interface MAC address";
|
|
};
|
|
|
|
external_ip = mkOption {
|
|
type = types.str;
|
|
description = "SNAT external IPv4 address";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Router description";
|
|
};
|
|
};
|
|
};
|
|
|
|
vpcType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "VPC name";
|
|
};
|
|
|
|
cidr_block = mkOption {
|
|
type = types.str;
|
|
description = "VPC CIDR";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "VPC description";
|
|
};
|
|
|
|
router = mkOption {
|
|
type = types.nullOr routerType;
|
|
default = null;
|
|
description = "Optional tenant edge router for the VPC";
|
|
};
|
|
|
|
subnets = mkOption {
|
|
type = types.listOf subnetType;
|
|
default = [];
|
|
description = "Subnets within the VPC";
|
|
};
|
|
};
|
|
};
|
|
|
|
securityGroupRuleType = types.submodule {
|
|
options = {
|
|
direction = mkOption {
|
|
type = types.enum [ "ingress" "egress" ];
|
|
description = "Rule direction";
|
|
};
|
|
|
|
protocol = mkOption {
|
|
type = types.nullOr (types.enum [ "any" "tcp" "udp" "icmp" "icmpv6" ]);
|
|
default = null;
|
|
description = "IP protocol";
|
|
};
|
|
|
|
port_range_min = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Minimum port in range";
|
|
};
|
|
|
|
port_range_max = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Maximum port in range";
|
|
};
|
|
|
|
remote_cidr = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Remote CIDR";
|
|
};
|
|
|
|
remote_group = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Remote security group name";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Rule description";
|
|
};
|
|
};
|
|
};
|
|
|
|
securityGroupType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Security group name";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Security group description";
|
|
};
|
|
|
|
rules = mkOption {
|
|
type = types.listOf securityGroupRuleType;
|
|
default = [];
|
|
description = "Security group rules";
|
|
};
|
|
};
|
|
};
|
|
|
|
tenantType = types.submodule {
|
|
options = {
|
|
org_id = mkOption {
|
|
type = types.str;
|
|
description = "Tenant organization ID";
|
|
};
|
|
|
|
project_id = mkOption {
|
|
type = types.str;
|
|
description = "Tenant project ID";
|
|
};
|
|
|
|
security_groups = mkOption {
|
|
type = types.listOf securityGroupType;
|
|
default = [];
|
|
description = "Tenant-scoped security groups";
|
|
};
|
|
|
|
service_ip_pools = mkOption {
|
|
type = types.listOf serviceIpPoolType;
|
|
default = [];
|
|
description = "Tenant-scoped Service IP pools";
|
|
};
|
|
|
|
vpcs = mkOption {
|
|
type = types.listOf vpcType;
|
|
default = [];
|
|
description = "Tenant-scoped VPCs and their nested resources";
|
|
};
|
|
};
|
|
};
|
|
|
|
configFile = jsonFormat.generate "plasmacloud-tenant-networking.json" {
|
|
inherit (cfg) tenants;
|
|
};
|
|
configPath = cfg.configPath;
|
|
configRelative = removePrefix "/etc/" configPath;
|
|
|
|
in {
|
|
options.plasmacloud.tenantNetworking = {
|
|
enable = mkEnableOption "tenant-scoped PrismNET declarations";
|
|
|
|
endpoint = mkOption {
|
|
type = types.str;
|
|
default = "http://127.0.0.1:50081";
|
|
description = "PrismNET gRPC endpoint";
|
|
};
|
|
|
|
iamEndpoint = mkOption {
|
|
type = types.str;
|
|
default = "http://127.0.0.1:50080";
|
|
description = "IAM gRPC endpoint used to mint tenant-scoped controller tokens";
|
|
};
|
|
|
|
controllerPrincipalId = mkOption {
|
|
type = types.str;
|
|
default = "plasmacloud-reconciler";
|
|
description = "Service account used by the reconciler when applying tenant declarations";
|
|
};
|
|
|
|
tenants = mkOption {
|
|
type = types.listOf tenantType;
|
|
default = [];
|
|
description = "Tenant-scoped network declarations. This is separate from platform networking under plasmacloud.network.";
|
|
};
|
|
|
|
configPath = mkOption {
|
|
type = types.str;
|
|
default = "/etc/plasmacloud/tenant-networking.json";
|
|
description = "Path for rendered tenant networking config";
|
|
};
|
|
|
|
applyOnBoot = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Apply declarations at boot";
|
|
};
|
|
|
|
applyOnChange = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Apply declarations when the config file changes";
|
|
};
|
|
|
|
prune = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Delete tenant network resources not declared for managed tenants";
|
|
};
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.plasmacloud-reconciler or (throw "plasmacloud-reconciler package not found");
|
|
description = "Reconciler package for tenant networking declarations";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = hasPrefix "/etc/" configPath;
|
|
message = "plasmacloud.tenantNetworking.configPath must be under /etc";
|
|
}
|
|
];
|
|
|
|
environment.etc."${configRelative}".source = configFile;
|
|
|
|
systemd.services.plasmacloud-tenant-networking-apply = {
|
|
description = "Apply PlasmaCloud tenant networking declarations";
|
|
after =
|
|
[ "network-online.target" ]
|
|
++ optional config.services.prismnet.enable "prismnet.service"
|
|
++ optional config.services.iam.enable "iam.service";
|
|
wants =
|
|
[ "network-online.target" ]
|
|
++ optional config.services.prismnet.enable "prismnet.service"
|
|
++ optional config.services.iam.enable "iam.service";
|
|
wantedBy = optional cfg.applyOnBoot "multi-user.target";
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
ExecStart =
|
|
"${cfg.package}/bin/plasmacloud-reconciler tenant-network"
|
|
+ " --config ${configPath}"
|
|
+ " --endpoint ${cfg.endpoint}"
|
|
+ " --iam-endpoint ${cfg.iamEndpoint}"
|
|
+ " --controller-principal-id ${cfg.controllerPrincipalId}"
|
|
+ optionalString cfg.prune " --prune";
|
|
};
|
|
};
|
|
|
|
systemd.paths.plasmacloud-tenant-networking-apply = mkIf cfg.applyOnChange {
|
|
wantedBy = [ "multi-user.target" ];
|
|
pathConfig = {
|
|
PathChanged = configPath;
|
|
};
|
|
};
|
|
};
|
|
}
|