From f93f0c32a823358ec7d8dc977a9a00da95cdd37a Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 23 Sep 2026 12:55:10 -0400 Subject: [PATCH] feat(datagen): expand OSType with ESXi, hypervisor-host Linux, and Unix families (PIPE-1260) Assisted-By: Claude Opus 4.8 --- docs/datagen.md | 43 ++++++ internal/config/generator_hostmetrics_test.go | 18 ++- internal/datagen/osrelease.go | 81 +++++++++++- internal/datagen/ostype.go | 51 ++++++-- internal/datagen/ostype_expansion_test.go | 122 ++++++++++++++++++ internal/datagen/ostype_test.go | 2 +- internal/datagen/services.go | 73 ++++++++++- internal/datagen/systems.go | 24 ++++ 8 files changed, 394 insertions(+), 20 deletions(-) create mode 100644 internal/datagen/ostype_expansion_test.go diff --git a/docs/datagen.md b/docs/datagen.md index 6535cb66..90e9a0b6 100644 --- a/docs/datagen.md +++ b/docs/datagen.md @@ -207,6 +207,49 @@ user := env.Users[r.Intn(len(env.Users))] system := env.Systems[r.Intn(len(env.Systems))] ``` +## OS Types (`OSType`) + +A `SystemIdentity` carries an `OSType`: the operating system of a real OS host, +as opposed to the embedded `ApplianceOS` a purpose-built appliance runs (see +below). + +### Real vs appliance boundary + +The test is **installability, not openness**. A real OS installs on supported +hardware you choose; it belongs in `OSType` even when it is closed (Windows, +macOS, and ESXi all qualify). An appliance OS ships preinstalled on dedicated +hardware and is not separately installable, so it lives in `ApplianceOS` +(NimbleOS, PAN-OS, NX-OS, and the rest). ESXi is the boundary case that makes the +rule concrete: closed like an appliance, but you install it on hardware you pick, +so it is a real OS. + +### The `OSType` set + +`ParseOSType` gates the user-facing `os:` knob against this set; each value has a +coherent release pool, an OS-appropriate `host.id`, and a bespoke server-role +service roster. + +| `OSType` | `os.type` (semconv) | Notes | +|----------|---------------------|-------| +| `linux` | `linux` | general-purpose Linux distros | +| `windows` | `windows` | | +| `macos` | `darwin` | | +| `esxi` | `esxi` | VMware ESXi, a real OS (closed, but installable) | +| `xen-dom0` | `linux` | Xen dom0 (XCP-ng / Citrix Hypervisor) | +| `nutanix-ahv` | `linux` | Nutanix AHV host | +| `openstack-kvm` | `linux` | OpenStack/KVM compute host | +| `aix` | `aix` | | +| `solaris` | `solaris` | Oracle Solaris | +| `freebsd` | `freebsd` | | +| `openbsd` | `openbsd` | | + +The three hypervisor-host Linux flavors are distinct simulate-as selectors that +carry their real distro in `os.name`/`os.version` while reporting `os.type=linux` +(they are Linux under the hood). The Unix families are already valid semconv +`os.type` values, so they pass through unchanged. Random environment population +draws Linux and Windows hosts; the expanded OSes are selected by explicit +configuration. + ## Appliance Identities Alongside the general-purpose `SystemIdentity`, datagen models purpose-built diff --git a/internal/config/generator_hostmetrics_test.go b/internal/config/generator_hostmetrics_test.go index 7106d583..649be804 100644 --- a/internal/config/generator_hostmetrics_test.go +++ b/internal/config/generator_hostmetrics_test.go @@ -73,12 +73,28 @@ func TestHostMetricsGeneratorConfig_Validate(t *testing.T) { }, }, { - name: "invalid OS", + name: "valid OS esxi", + config: HostMetricsGeneratorConfig{ + Workers: 1, + Rate: time.Second, + OS: "esxi", + }, + }, + { + name: "valid OS solaris", config: HostMetricsGeneratorConfig{ Workers: 1, Rate: time.Second, OS: "solaris", }, + }, + { + name: "invalid OS", + config: HostMetricsGeneratorConfig{ + Workers: 1, + Rate: time.Second, + OS: "plan9", + }, wantErr: true, errMsg: "unsupported OS", }, diff --git a/internal/datagen/osrelease.go b/internal/datagen/osrelease.go index d4e76296..b14f71f8 100644 --- a/internal/datagen/osrelease.go +++ b/internal/datagen/osrelease.go @@ -81,6 +81,67 @@ var ( } ) +// osRelease is one authentic release for a PIPE-1260 OS: os.name / os.version / +// os.build_id / os.description, each an internally-consistent real value. +type osRelease struct{ name, version, buildID, description string } + +// extendedReleases holds the authentic release pools for the OSType values added +// in PIPE-1260. The hypervisor-host Linux flavors carry their real distro in +// os.name and emit os.type=linux via SemconvOSType. Values are real product +// versions and build strings. +var extendedReleases = map[OSType][]osRelease{ + OSESXi: { + {"VMware ESXi", "8.0.2", "22380479", "VMware ESXi 8.0 Update 2 (build 22380479)"}, + {"VMware ESXi", "8.0.1", "21495797", "VMware ESXi 8.0 Update 1 (build 21495797)"}, + {"VMware ESXi", "8.0.0", "20513097", "VMware ESXi 8.0 GA (build 20513097)"}, + {"VMware ESXi", "7.0.3", "19193900", "VMware ESXi 7.0 Update 3c (build 19193900)"}, + }, + OSAIX: { + {"AIX", "7.3", "7300-02-02-2420", "AIX 7.3 TL2 SP2"}, + {"AIX", "7.3", "7300-01-03-2346", "AIX 7.3 TL1 SP3"}, + {"AIX", "7.2", "7200-05-07-2420", "AIX 7.2 TL5 SP7"}, + }, + OSSolaris: { + {"Oracle Solaris", "11.4", "11.4.54.0.1.135", "Oracle Solaris 11.4 SRU54"}, + {"Oracle Solaris", "11.4", "11.4.42.0.1.111", "Oracle Solaris 11.4 SRU42"}, + {"Oracle Solaris", "11.4", "11.4.60.0.1.146", "Oracle Solaris 11.4 SRU60"}, + }, + OSFreeBSD: { + {"FreeBSD", "14.1-RELEASE", "14.1-RELEASE-p6", "FreeBSD 14.1-RELEASE-p6"}, + {"FreeBSD", "14.0-RELEASE", "14.0-RELEASE-p8", "FreeBSD 14.0-RELEASE-p8"}, + {"FreeBSD", "13.3-RELEASE", "13.3-RELEASE-p5", "FreeBSD 13.3-RELEASE-p5"}, + }, + OSOpenBSD: { + {"OpenBSD", "7.5", "GENERIC.MP#82", "OpenBSD 7.5"}, + {"OpenBSD", "7.4", "GENERIC.MP#4", "OpenBSD 7.4"}, + {"OpenBSD", "7.3", "GENERIC.MP#1", "OpenBSD 7.3"}, + }, + OSXenDom0: { + {"XCP-ng", "8.3", "4.19.0+1", "XCP-ng 8.3"}, + {"XCP-ng", "8.2.1", "4.19.0+1", "XCP-ng 8.2.1 LTS"}, + }, + OSNutanixAHV: { + {"Nutanix AHV", "20230302.103003", "5.10.149-2.nutanix.el8", "Nutanix AHV 20230302.103003"}, + {"Nutanix AHV", "20220304.480", "5.10.149-1.nutanix.el7", "Nutanix AHV 20220304.480"}, + }, + OSOpenStackKVM: { + {"Red Hat Enterprise Linux", "9.4", "5.14.0-427.el9_4.x86_64", "Red Hat Enterprise Linux 9.4 (Plow)"}, + {"Ubuntu", "22.04.4", "5.15.0-105-generic", "Ubuntu 22.04.4 LTS"}, + }, +} + +// buildExtendedOSInfo projects an osRelease into an OSInfo carrying os as the +// os.type source. SemconvOSType handles the linux mapping for hypervisor hosts. +func buildExtendedOSInfo(os OSType, rel osRelease) OSInfo { + return OSInfo{ + Type: os, + Name: rel.name, + Version: rel.version, + BuildID: rel.buildID, + Description: rel.description, + } +} + // GenerateOSInfo returns a coherent OSInfo for the given OS type, drawn from the // authentic release pools. Windows picks one real UBR per selection. // Deterministic for a given (r, os). @@ -92,6 +153,9 @@ func GenerateOSInfo(r *rand.Rand, os OSType) OSInfo { case OSMacOS: return buildMacOSInfo(macReleases[r.Intn(len(macReleases))]) // #nosec G404 default: + if pool, ok := extendedReleases[os]; ok { + return buildExtendedOSInfo(os, pool[r.Intn(len(pool))]) // #nosec G404 + } return buildLinuxOSInfo(linuxReleases[r.Intn(len(linuxReleases))]) // #nosec G404 } } @@ -108,6 +172,9 @@ func osInfoForTier(r *rand.Rand, os OSType, older bool) OSInfo { case OSMacOS: return buildMacOSInfo(macReleases[pickHalfIndex(r, len(macReleases), older)]) default: + if pool, ok := extendedReleases[os]; ok { + return buildExtendedOSInfo(os, pool[pickHalfIndex(r, len(pool), older)]) + } return buildLinuxOSInfo(linuxReleases[r.Intn(len(linuxReleases))]) // #nosec G404 } } @@ -157,15 +224,23 @@ func buildLinuxOSInfo(rel linuxRelease) OSInfo { } // GenerateHostID returns an OS-appropriate host.id: a /etc/machine-id-style -// 32-char lowercase hex string on Linux, a registry MachineGuid-style GUID on -// Windows, and an uppercase IOPlatformUUID on macOS. +// 32-char lowercase hex string on Linux (and the hypervisor-host Linux flavors), +// a registry MachineGuid-style GUID on Windows, an uppercase IOPlatformUUID on +// macOS, a lowercase SMBIOS/system UUID on ESXi and the BSDs, an AIX +// uname-m-style machine id, and an 8-hex-digit hostid on Solaris. func GenerateHostID(r *rand.Rand, os OSType) string { h := randomHex(r, 16) // 32 lowercase hex chars switch os { - case OSWindows: + case OSWindows, OSESXi, OSFreeBSD, OSOpenBSD: return formatUUID(h) case OSMacOS: return strings.ToUpper(formatUUID(h)) + case OSAIX: + // uname -m form: "00" + 6-hex serial + "4C00" model suffix. + return "00" + strings.ToUpper(h[:6]) + "4C00" + case OSSolaris: + // hostid: 8 lowercase hex digits. + return h[:8] default: return h } diff --git a/internal/datagen/ostype.go b/internal/datagen/ostype.go index bd1a7d13..a027759d 100644 --- a/internal/datagen/ostype.go +++ b/internal/datagen/ostype.go @@ -20,20 +20,36 @@ import ( // internally and to the user, but a real OpenTelemetry pipeline stamps // os.type=darwin, so that is the value emitted on records. +// simulatableOSes is the set of OSType values the fake-identity path can render +// a coherent host for. ParseOSType gates the user-facing `os:` knob against it. +var simulatableOSes = map[OSType]bool{ + OSLinux: true, OSWindows: true, OSMacOS: true, + OSESXi: true, OSXenDom0: true, OSNutanixAHV: true, OSOpenStackKVM: true, + OSAIX: true, OSSolaris: true, OSFreeBSD: true, OSOpenBSD: true, +} + +// osAliases maps accepted spellings to their canonical OSType. +var osAliases = map[string]OSType{ + "darwin": OSMacOS, + "vmware": OSESXi, + "vmware-esxi": OSESXi, + "xen": OSXenDom0, + "ahv": OSNutanixAHV, + "kvm": OSOpenStackKVM, +} + // ParseOSType maps a user-supplied OS string to an OSType for the fake-identity -// path. It accepts the three simulate-able OSes, treating "darwin" as an alias -// for "macos". Unknown values return an error. +// path. It accepts every simulate-able OS plus a few aliases (e.g. "darwin" for +// macos, "vmware" for esxi). Unknown values return an error. func ParseOSType(s string) (OSType, error) { - switch strings.ToLower(strings.TrimSpace(s)) { - case "linux": - return OSLinux, nil - case "windows": - return OSWindows, nil - case "macos", "darwin": - return OSMacOS, nil - default: - return "", fmt.Errorf("datagen: unsupported OS %q (want one of: linux, windows, macos)", s) + key := strings.ToLower(strings.TrimSpace(s)) + if alias, ok := osAliases[key]; ok { + return alias, nil } + if os := OSType(key); simulatableOSes[os] { + return os, nil + } + return "", fmt.Errorf("datagen: unsupported OS %q (want one of: linux, windows, macos, esxi, xen-dom0, nutanix-ahv, openstack-kvm, aix, solaris, freebsd, openbsd)", s) } // OSTypeFromGOOS maps a runtime.GOOS value to an OSType for the real-host path. @@ -53,10 +69,17 @@ func OSTypeFromGOOS(goos string) OSType { } // SemconvOSType returns the OpenTelemetry semantic-convention os.type value for -// o, which differs from the OSType constant only for macOS (macos -> darwin). +// o. It differs from the OSType constant for macOS (macos -> darwin) and for the +// hypervisor-host Linux flavors, which are Linux under the hood (-> linux). The +// Unix families (aix, solaris, freebsd, openbsd) are already valid semconv +// os.type values, so they pass through unchanged. func (o OSType) SemconvOSType() string { - if o == OSMacOS { + switch { + case o == OSMacOS: return "darwin" + case hypervisorHostLinux[o]: + return "linux" + default: + return string(o) } - return string(o) } diff --git a/internal/datagen/ostype_expansion_test.go b/internal/datagen/ostype_expansion_test.go new file mode 100644 index 00000000..65054c37 --- /dev/null +++ b/internal/datagen/ostype_expansion_test.go @@ -0,0 +1,122 @@ +package datagen + +import ( + "math/rand" + "regexp" + "testing" +) + +// expandedOSes is the PIPE-1260 additions: ESXi, the hypervisor-host Linux +// flavors, and the Unix families. semconv is the os.type wire value each emits. +var expandedOSes = []struct { + os OSType + semconv string + name string // substring expected in os.name +}{ + {OSESXi, "esxi", "ESXi"}, + {OSXenDom0, "linux", "XCP-ng"}, + {OSNutanixAHV, "linux", "Nutanix"}, + {OSOpenStackKVM, "linux", ""}, // a real Linux distro; os.name is the distro + {OSAIX, "aix", "AIX"}, + {OSSolaris, "solaris", "Solaris"}, + {OSFreeBSD, "freebsd", "FreeBSD"}, + {OSOpenBSD, "openbsd", "OpenBSD"}, +} + +func TestParseOSType_Expanded(t *testing.T) { + for _, e := range expandedOSes { + got, err := ParseOSType(string(e.os)) + if err != nil { + t.Errorf("ParseOSType(%q): unexpected error: %v", e.os, err) + continue + } + if got != e.os { + t.Errorf("ParseOSType(%q) = %q, want %q", e.os, got, e.os) + } + } +} + +func TestSemconvOSType_Expanded(t *testing.T) { + for _, e := range expandedOSes { + if got := e.os.SemconvOSType(); got != e.semconv { + t.Errorf("%q.SemconvOSType() = %q, want %q", e.os, got, e.semconv) + } + } +} + +func TestGenerateOSInfo_Expanded(t *testing.T) { + for _, e := range expandedOSes { + info := GenerateOSInfo(rand.New(rand.NewSource(1)), e.os) // #nosec G404 + if info.Type != e.os { + t.Errorf("%s: Type = %q, want %q", e.os, info.Type, e.os) + } + if info.Name == "" || info.Version == "" || info.BuildID == "" || info.Description == "" { + t.Errorf("%s: incomplete OSInfo: %+v", e.os, info) + } + if e.name != "" && !regexp.MustCompile(regexp.QuoteMeta(e.name)).MatchString(info.Name) { + t.Errorf("%s: os.name = %q, want to contain %q", e.os, info.Name, e.name) + } + // Deterministic for a given (seed, os). + again := GenerateOSInfo(rand.New(rand.NewSource(1)), e.os) // #nosec G404 + if info != again { + t.Errorf("%s: GenerateOSInfo not deterministic: %+v vs %+v", e.os, info, again) + } + } +} + +func TestGenerateHostID_Expanded(t *testing.T) { + uuidLower := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) + linux32 := regexp.MustCompile(`^[0-9a-f]{32}$`) + aixRE := regexp.MustCompile(`^00[0-9A-F]{6}4C00$`) + solarisRE := regexp.MustCompile(`^[0-9a-f]{8}$`) + + cases := map[OSType]*regexp.Regexp{ + OSESXi: uuidLower, + OSFreeBSD: uuidLower, + OSOpenBSD: uuidLower, + OSXenDom0: linux32, + OSNutanixAHV: linux32, + OSOpenStackKVM: linux32, + OSAIX: aixRE, + OSSolaris: solarisRE, + } + for os, re := range cases { + id := GenerateHostID(rand.New(rand.NewSource(1)), os) // #nosec G404 + if !re.MatchString(id) { + t.Errorf("%s host.id = %q, want match %s", os, id, re) + } + } +} + +func TestGenerateServicesForSystem_Expanded(t *testing.T) { + contains := func(svcs []*ServiceIdentity, name string) bool { + for _, s := range svcs { + if s.Name == name { + return true + } + } + return false + } + + // Every expanded OS in a server role gets its bespoke daemon set, which is + // larger than the one-service default fallback. + for _, e := range expandedOSes { + svcs := GenerateServicesForSystem(rand.New(rand.NewSource(1)), e.os, RoleServer, "host1") // #nosec G404 + if len(svcs) < 3 { + t.Errorf("%s server services = %d, want a bespoke set (>=3), not the default fallback", e.os, len(svcs)) + } + } + + // The virtualization hosts must carry their signature virtualization daemon. + // These pools are small enough that the full set is always returned. + xen := GenerateServicesForSystem(rand.New(rand.NewSource(1)), OSXenDom0, RoleServer, "h") // #nosec G404 + if !contains(xen, "xapi") { + t.Errorf("Xen dom0 services missing xapi: %+v", xen) + } + for _, os := range []OSType{OSNutanixAHV, OSOpenStackKVM} { + svcs := GenerateServicesForSystem(rand.New(rand.NewSource(1)), os, RoleServer, "h") // #nosec G404 + if !contains(svcs, "libvirtd") { + t.Errorf("%s services missing libvirtd: %+v", os, svcs) + } + } +} diff --git a/internal/datagen/ostype_test.go b/internal/datagen/ostype_test.go index c924372f..a20ea237 100644 --- a/internal/datagen/ostype_test.go +++ b/internal/datagen/ostype_test.go @@ -13,7 +13,7 @@ func TestParseOSType(t *testing.T) { "macos": {"macos", OSMacOS, false}, "darwin alias": {"darwin", OSMacOS, false}, "upper+space": {" MacOS ", OSMacOS, false}, - "unsupported": {"freebsd", "", true}, + "unsupported": {"plan9", "", true}, "empty": {"", "", true}, } for name, c := range cases { diff --git a/internal/datagen/services.go b/internal/datagen/services.go index 02f3a359..7cc3f759 100644 --- a/internal/datagen/services.go +++ b/internal/datagen/services.go @@ -81,6 +81,75 @@ var linuxWorkstationServices = []serviceTemplate{ {"NetworkManager", "Network Manager", "/usr/sbin/NetworkManager", StartAutomatic, "root"}, } +// extendedServerServices holds the bespoke server-role daemon sets for the +// PIPE-1260 OSes. Each is the authentic daemon roster for that platform, so a +// generated host reads as a real box of its kind (ESXi hostd/vpxa, AIX SRC, +// Solaris SMF FMRIs, BSD rc daemons, the virtualization stacks). +var extendedServerServices = map[OSType][]serviceTemplate{ + OSESXi: { + {"hostd", "VMware ESXi Host Agent", "/bin/hostd", StartAutomatic, "root"}, + {"vpxa", "vCenter Agent", "/bin/vpxa", StartAutomatic, "root"}, + {"fdm", "vSphere HA Agent", "/opt/vmware/fdm/fdm", StartAutomatic, "root"}, + {"ntpd", "Network Time Protocol Daemon", "/sbin/ntpd", StartAutomatic, "root"}, + {"sfcbd-watchdog", "CIM Server", "/sbin/sfcbd-watchdog", StartAutomatic, "root"}, + {"dcui", "Direct Console User Interface", "/sbin/dcui", StartAutomatic, "root"}, + {"vsanmgmtd", "vSAN Management Daemon", "/usr/lib/vmware/vsan/bin/vsanmgmtd", StartAutomatic, "root"}, + }, + OSAIX: { + {"sshd", "OpenSSH Daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, + {"srcmstr", "System Resource Controller", "/usr/sbin/srcmstr", StartAutomatic, "root"}, + {"inetd", "Internet Super-Server", "/usr/sbin/inetd", StartAutomatic, "root"}, + {"syslogd", "System Logging Daemon", "/usr/sbin/syslogd", StartAutomatic, "root"}, + {"cron", "Clock Daemon", "/usr/sbin/cron", StartAutomatic, "root"}, + {"xntpd", "Network Time Protocol Daemon", "/usr/sbin/xntpd", StartAutomatic, "root"}, + {"qdaemon", "Queue Daemon", "/usr/sbin/qdaemon", StartAutomatic, "root"}, + }, + OSSolaris: { + {"svc:/network/ssh:default", "SSH server", "/usr/lib/ssh/sshd", StartAutomatic, "root"}, + {"svc:/system/system-log:default", "System Log", "/usr/sbin/syslogd", StartAutomatic, "root"}, + {"svc:/network/ntp:default", "Network Time Protocol", "/usr/lib/inet/ntpd", StartAutomatic, "root"}, + {"svc:/system/cron:default", "Clock Daemon", "/usr/sbin/cron", StartAutomatic, "root"}, + {"svc:/system/name-service-cache:default", "Name Service Cache Daemon", "/usr/sbin/nscd", StartAutomatic, "root"}, + {"svc:/network/smtp:sendmail", "Sendmail", "/usr/lib/sendmail", StartAutomatic, "root"}, + }, + OSFreeBSD: { + {"sshd", "OpenSSH Daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, + {"syslogd", "System Logging Daemon", "/usr/sbin/syslogd", StartAutomatic, "root"}, + {"cron", "Clock Daemon", "/usr/sbin/cron", StartAutomatic, "root"}, + {"ntpd", "Network Time Protocol Daemon", "/usr/sbin/ntpd", StartAutomatic, "root"}, + {"pf", "Packet Filter Firewall", "/sbin/pfctl", StartAutomatic, "root"}, + {"nginx", "nginx web server", "/usr/local/sbin/nginx", StartAutomatic, "www"}, + }, + OSOpenBSD: { + {"sshd", "OpenSSH Daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, + {"syslogd", "System Logging Daemon", "/usr/sbin/syslogd", StartAutomatic, "root"}, + {"cron", "Clock Daemon", "/usr/sbin/cron", StartAutomatic, "root"}, + {"ntpd", "OpenNTP Daemon", "/usr/sbin/ntpd", StartAutomatic, "_ntp"}, + {"pf", "Packet Filter Firewall", "/sbin/pfctl", StartAutomatic, "root"}, + {"httpd", "OpenBSD httpd", "/usr/sbin/httpd", StartAutomatic, "www"}, + }, + OSXenDom0: { + {"xapi", "XenAPI Toolstack", "/opt/xensource/bin/xapi", StartAutomatic, "root"}, + {"xenstored", "Xen Store Daemon", "/usr/sbin/oxenstored", StartAutomatic, "root"}, + {"xenconsoled", "Xen Console Daemon", "/usr/sbin/xenconsoled", StartAutomatic, "root"}, + {"sshd", "OpenSSH Daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, + {"ntpd", "Network Time Protocol Daemon", "/usr/sbin/ntpd", StartAutomatic, "root"}, + }, + OSNutanixAHV: { + {"libvirtd", "libvirt Virtualization Daemon", "/usr/sbin/libvirtd", StartAutomatic, "root"}, + {"frodo", "AHV I/O Path (Frodo)", "/usr/bin/frodo", StartAutomatic, "root"}, + {"sshd", "OpenSSH Daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, + {"chronyd", "NTP Client (chrony)", "/usr/sbin/chronyd", StartAutomatic, "chrony"}, + }, + OSOpenStackKVM: { + {"libvirtd", "libvirt Virtualization Daemon", "/usr/sbin/libvirtd", StartAutomatic, "root"}, + {"openstack-nova-compute", "OpenStack Nova Compute", "/usr/bin/nova-compute", StartAutomatic, "nova"}, + {"neutron-openvswitch-agent", "OpenStack Neutron Open vSwitch Agent", "/usr/bin/neutron-openvswitch-agent", StartAutomatic, "neutron"}, + {"sshd", "OpenSSH Daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, + {"chronyd", "NTP Client (chrony)", "/usr/sbin/chronyd", StartAutomatic, "chrony"}, + }, +} + // GenerateServicesForSystem returns services appropriate for the given OS and role. // // Domain controllers always receive every entry in dcServices: NTDS, DNS, @@ -104,8 +173,10 @@ func GenerateServicesForSystem(r *rand.Rand, os OSType, role SystemRole, hostnam templates = linuxServerServices case os == OSLinux && role == RoleWorkstation: templates = linuxWorkstationServices + case role == RoleServer && extendedServerServices[os] != nil: + templates = extendedServerServices[os] default: - // Router or macOS — minimal services + // Router, macOS, or a non-server role on an expanded OS — minimal services templates = []serviceTemplate{ {"sshd", "OpenSSH server daemon", "/usr/sbin/sshd", StartAutomatic, "root"}, } diff --git a/internal/datagen/systems.go b/internal/datagen/systems.go index e73e0956..08230488 100644 --- a/internal/datagen/systems.go +++ b/internal/datagen/systems.go @@ -14,8 +14,32 @@ const ( OSLinux OSType = "linux" OSWindows OSType = "windows" OSMacOS OSType = "macos" + + // ESXi is a real OS: closed, but it installs on hardware you choose, so it + // belongs here rather than in ApplianceOS. + OSESXi OSType = "esxi" + + // Hypervisor-host Linux flavors. Each renders os.type=linux (its distro is + // carried in os.name/os.version) but is a distinct simulate-as selector. + OSXenDom0 OSType = "xen-dom0" // Xen dom0 (XCP-ng / Citrix Hypervisor) + OSNutanixAHV OSType = "nutanix-ahv" // Nutanix AHV host + OSOpenStackKVM OSType = "openstack-kvm" // OpenStack/KVM compute host + + // Unix families. + OSAIX OSType = "aix" + OSSolaris OSType = "solaris" + OSFreeBSD OSType = "freebsd" + OSOpenBSD OSType = "openbsd" ) +// hypervisorHostLinux is the set of OSType values that are Linux under the hood +// (os.type=linux) but present a distinct hypervisor-host identity. +var hypervisorHostLinux = map[OSType]bool{ + OSXenDom0: true, + OSNutanixAHV: true, + OSOpenStackKVM: true, +} + // Arch represents a CPU architecture. type Arch string