Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19173aeb07 |
@@ -322,6 +322,9 @@ var defaultSeats = append([]Seat{
|
||||
// node-mounts writes a mount and an automount unit per share on a machine that asks (shares.go).
|
||||
{Name: NFSServerSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0263", Serves: nfsServerVerbs()},
|
||||
{Name: MountsSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0263", Serves: mountsVerbs()},
|
||||
// A machine's SMB shares for the devices on its own network (novox/hq ADR 0341, smb.go): its holder
|
||||
// writes the SMB configuration, serves 445 alone and maps every client to the shared folder's owner.
|
||||
{Name: SMBServerSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0341", Serves: smbServerVerbs()},
|
||||
// A node's files and accounts, read (novox/hq ADR 0310, reads.go): by an account of the holder's own that holds
|
||||
// no secret, on every node, so the reads ADR 0268 §5 takes over from the login shell's execute have a tool.
|
||||
{Name: ReadsSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0310", Serves: readsVerbs()},
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
||||
delivered[s.Delivers] = s.Name
|
||||
}
|
||||
}
|
||||
// Forty-one with node-reads (novox/hq ADR 0310); forty with node-nfs-server and node-mounts (novox/hq ADR 0263); thirty-eight with node-resolver (novox/hq ADR 0247); thirty-seven with mesh-delivery (novox/hq ADR 0239); thirty-six since node-resolver-config retired
|
||||
// Forty-two with node-smb-server (novox/hq ADR 0341); forty-one with node-reads (novox/hq ADR 0310); forty with node-nfs-server and node-mounts (novox/hq ADR 0263); thirty-eight with node-resolver (novox/hq ADR 0247); thirty-seven with mesh-delivery (novox/hq ADR 0239); thirty-six since node-resolver-config retired
|
||||
// into node-uplink (novox/hq ADR 0223); thirty-seven
|
||||
// since the retired node-dns-resolver went (novox/hq ADR 0220); thirty-eight with
|
||||
// node-backup (novox/hq ADR 0214); thirty-seven with node-message-bus (novox/hq ADR 0215);
|
||||
@@ -59,8 +59,8 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
||||
// node-container-runtime (ADR 0207); nineteen with node-environment and node-login-shell (ADR 0203,
|
||||
// ADR 0204); seventeen with node-build-agent (ADR 0190). One fewer once the retired
|
||||
// mesh-build-machine row goes, when no registered manifest claims it.
|
||||
if len(Seats()) != 41 {
|
||||
t.Errorf("the mesh defines %d seats rather than 41; the set is closed, so a change here is "+
|
||||
if len(Seats()) != 42 {
|
||||
t.Errorf("the mesh defines %d seats rather than 42; the set is closed, so a change here is "+
|
||||
"a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ func nfsServerVerbs() []Verb {
|
||||
{Name: "exports", Description: "Every share this machine exports: its name, its path, read-write or " +
|
||||
"read-only, the owner every client is mapped to (uid and gid), each node it is exported to with that " +
|
||||
"node's private address and access (only the nodes the server grants it to and that ask for it), " +
|
||||
"what is granted and not asked for or asked for and not granted, and whether the kernel holds it " +
|
||||
"now. Also the exports found that are not the mesh's: a dataset's sharenfs property, a line in " +
|
||||
"/etc/exports.",
|
||||
"the subnets of this machine's local network it is exported to when the server grants it " +
|
||||
"local-network (novox/hq ADR 0341), what is granted and not asked for or asked for and not " +
|
||||
"granted, and whether the kernel holds it now. Also the exports found that are not the mesh's: a " +
|
||||
"dataset's sharenfs property, a line in /etc/exports.",
|
||||
Input: schema(map[string]string{}, nil),
|
||||
Replaces: []string{"exportfs -v", "cat /etc/exports", "zfs get sharenfs"}},
|
||||
{Name: "clients", Description: "Which machines have mounted which share now, as the NFS server " +
|
||||
@@ -55,8 +56,10 @@ func nfsServerVerbs() []Verb {
|
||||
Input: schema(map[string]string{}, nil),
|
||||
Replaces: []string{"exportfs -ra"}},
|
||||
{Name: "adopt", Description: "Take over an export a person made by hand: clear a dataset's sharenfs " +
|
||||
"property for a path the mesh's own export now serves — only when that export is live. Without " +
|
||||
"confirm, says what it would do and changes nothing.",
|
||||
"property for a path the mesh's own export now serves — only when that export is live and covers " +
|
||||
"every client the property exported to: for a property exporting to the local network, the " +
|
||||
"mesh's local-network export of the path (novox/hq ADR 0341). Without confirm, says what it " +
|
||||
"would do and changes nothing.",
|
||||
Input: schema(map[string]string{
|
||||
"path": "the shared path whose hand-made export is taken over",
|
||||
"confirm": "\"true\": change it (needs why); anything else is a dry run",
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package catalogue
|
||||
|
||||
// A machine's SMB shares, served to the devices on its own network that are not nodes (novox/hq
|
||||
// ADR 0341).
|
||||
//
|
||||
// **One holder per machine.** Two modules writing one machine's Samba configuration is the conflict a
|
||||
// seat exists to refuse, so the role is a node seat. It delivers nothing: its clients are devices that
|
||||
// are not nodes, and a node that wants the same files mounts the NFS share (ADR 0263), so no provision
|
||||
// is answered through it.
|
||||
//
|
||||
// **The data is the operator's** (ADR 0051). The holder never creates, chowns or removes anything under
|
||||
// a shared path, and every client acts as the shared folder's owner. Its verbs read, and the one that
|
||||
// takes over the configuration a person wrote by hand does it only on a person's word, keeping a dated
|
||||
// copy of what it replaces.
|
||||
|
||||
// SMBServerSeat is the role of the machine that shares folders over SMB with its local network
|
||||
// (novox/hq ADR 0341).
|
||||
const SMBServerSeat = "node-smb-server"
|
||||
|
||||
// smbServerVerbs is the contract every holder of node-smb-server serves (novox/hq ADR 0341).
|
||||
func smbServerVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "shares", Description: "Every SMB share this machine serves: its name, its path, read-write " +
|
||||
"or read-only, the owner every client acts as (uid and gid), the logins allowed and whether each " +
|
||||
"login's password was given or is kept as found, and whether the service serves it now. Also " +
|
||||
"whether the configuration is the mesh's or still the hand-written one, and each hand-written " +
|
||||
"share with whether the settings name it. A password is never shown.",
|
||||
Input: schema(map[string]string{}, nil),
|
||||
Replaces: []string{"testparm -s", "cat /etc/samba/smb.conf", "pdbedit -L"}},
|
||||
{Name: "sessions", Description: "Which devices are connected to which share now, with the login and " +
|
||||
"the protocol version each uses, as the SMB service knows them.",
|
||||
Input: schema(map[string]string{}, nil),
|
||||
Replaces: []string{"smbstatus"}},
|
||||
{Name: "test", Description: "Whether this machine serves one share now and whether its SMB service " +
|
||||
"is up and listening on 445: what a device on the local network is answered.",
|
||||
Input: schema(map[string]string{"share": "the share, by its name"}, []string{"share"}),
|
||||
Replaces: []string{"smbclient -L"}},
|
||||
{Name: "reload", Description: "Have the SMB service read its configuration again now (smbcontrol " +
|
||||
"reload-config) and answer the shares as it then serves them. The module's own process writes the " +
|
||||
"configuration; this is for after a change made outside it. Changes no shared path.",
|
||||
Input: schema(map[string]string{}, nil),
|
||||
Replaces: []string{"smbcontrol smbd reload-config", "systemctl reload smb"}},
|
||||
{Name: "adopt", Description: "Take over the SMB configuration a person wrote by hand: keep a dated " +
|
||||
"copy of it beside it and write the mesh's own in its place — only when every hand-written share " +
|
||||
"has its counterpart in the settings, so no share disappears. Without confirm, says what it would " +
|
||||
"do and changes nothing: each hand-written share beside the mesh's rendering of it.",
|
||||
Input: schema(map[string]string{
|
||||
"confirm": "\"true\": change it (needs why); anything else is a dry run",
|
||||
"why": "why, for the record",
|
||||
}, nil, "confirm"),
|
||||
Replaces: []string{"edit /etc/samba/smb.conf"}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Defends novox/hq ADR 0341: a machine's SMB shares are one node seat, node-smb-server, with its verbs
|
||||
// required of every holder, delivering nothing — its clients are devices that are not nodes.
|
||||
|
||||
func TestTheSMBServerIsANodeSeatWithItsVerbs(t *testing.T) {
|
||||
s, ok := SeatNamed(SMBServerSeat)
|
||||
if !ok {
|
||||
t.Fatalf("%s is not in the mesh's set", SMBServerSeat)
|
||||
}
|
||||
if s.Scope != ScopeNode || s.Decision != "novox/hq ADR 0341" || s.Delivers != "" || s.Replicated {
|
||||
t.Errorf("%s is %+v; a node seat under ADR 0341 that delivers nothing", SMBServerSeat, s)
|
||||
}
|
||||
var got []string
|
||||
for _, v := range s.Serves {
|
||||
got = append(got, v.Name)
|
||||
if v.Optional {
|
||||
t.Errorf("%s.%s is optional; its first holder serves it", SMBServerSeat, v.Name)
|
||||
}
|
||||
if v.Description == "" || v.Input["type"] != "object" || len(v.Replaces) == 0 {
|
||||
t.Errorf("%s.%s has no description, no object schema or says it replaces nothing", SMBServerSeat, v.Name)
|
||||
}
|
||||
}
|
||||
if strings.Join(got, " ") != "shares sessions test reload adopt" {
|
||||
t.Errorf("%s serves %v", SMBServerSeat, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Taking over the hand-written configuration is a person's act (ADR 0341 clause 7): a dry run unless the
|
||||
// string switch says "true", and it says it keeps a dated copy and refuses to drop a share.
|
||||
func TestTheSMBAdoptIsADryRunUnlessConfirmed(t *testing.T) {
|
||||
s, _ := SeatNamed(SMBServerSeat)
|
||||
for _, v := range s.Serves {
|
||||
if v.Name != "adopt" {
|
||||
continue
|
||||
}
|
||||
props, _ := v.Input["properties"].(map[string]any)
|
||||
confirm, _ := props["confirm"].(map[string]any)
|
||||
if confirm == nil || confirm["enum"] == nil || confirm["type"] != "string" {
|
||||
t.Errorf("adopt has no string confirm switch: %v", v.Input)
|
||||
}
|
||||
for _, says := range []string{"Without confirm, says what it would do and changes nothing", "dated copy", "no share disappears"} {
|
||||
if !strings.Contains(v.Description, says) {
|
||||
t.Errorf("adopt does not say %q: %s", says, v.Description)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Error("node-smb-server has no adopt")
|
||||
}
|
||||
|
||||
// A holder serving every verb holds the seat; a verb too many, one too few or the mesh's scope is refused,
|
||||
// as the catalogue's smb-server module claims it.
|
||||
func TestAHolderOfTheSMBSeatServesEveryVerbAndNothingElse(t *testing.T) {
|
||||
seat, _ := SeatNamed(SMBServerSeat)
|
||||
verbs := []string{"shares", "sessions", "test", "reload", "adopt"}
|
||||
holder := Manifest{Module: "smb-server", Version: "1",
|
||||
Claims: []Claim{{Name: SMBServerSeat, Scope: ScopeNode, Serves: verbs}}}
|
||||
if err := CanHold(holder, seat); err != nil {
|
||||
t.Errorf("smb-server serving every verb is refused: %v", err)
|
||||
}
|
||||
short := holder
|
||||
short.Claims = []Claim{{Name: SMBServerSeat, Scope: ScopeNode, Serves: verbs[:4]}}
|
||||
if err := CanHold(short, seat); err == nil || !strings.Contains(err.Error(), "adopt") {
|
||||
t.Errorf("leaving adopt out was accepted: %v", err)
|
||||
}
|
||||
mesh := holder
|
||||
mesh.Claims = []Claim{{Name: SMBServerSeat, Scope: ScopeMesh, Serves: verbs}}
|
||||
if err := CanHold(mesh, seat); err == nil {
|
||||
t.Error("claiming a node seat at the mesh's scope was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
// node-nfs-server's adopt clears sharenfs only once the mesh's export covers every client the property
|
||||
// exported to, the local network included (ADR 0341 clause 10), and exports names the local-network grant.
|
||||
func TestTheNFSVerbsSayTheLocalNetworkGrant(t *testing.T) {
|
||||
s, _ := SeatNamed(NFSServerSeat)
|
||||
for _, v := range s.Serves {
|
||||
switch v.Name {
|
||||
case "adopt":
|
||||
if !strings.Contains(v.Description, "covers every client the property exported to") {
|
||||
t.Errorf("nfs adopt does not wait for the export to cover every client: %s", v.Description)
|
||||
}
|
||||
case "exports":
|
||||
if !strings.Contains(v.Description, "local-network") {
|
||||
t.Errorf("nfs exports does not say the local-network grant: %s", v.Description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user