Mount a share at the access the server gave this machine, and not at all when it does not export it here
This commit is contained in:
@@ -86,7 +86,9 @@ password: the options, fstab lines and mountinfo the tools read are redacted.
|
||||
what `watch` found in the module's state `shares`, keyed by this machine, with `wants`: each share this
|
||||
machine asks of which server and whether read-write or read-only (`[{"server", "share", "access"}]`).
|
||||
The server exports a share only to the machines that want it and that it grants, and decides the access
|
||||
itself. Every five minutes the bundle also asks the controller's `data` verb where the mesh keeps data on
|
||||
itself: `apply` mounts a share at the access the server's answer gives this machine (`to[]`), read-only
|
||||
when either the share or this machine's grant is, and not at all when the server does not export it
|
||||
here (said as failed, naming the grant that is missing). Every five minutes the bundle also asks the controller's `data` verb where the mesh keeps data on
|
||||
this machine, and leaves the paths for `apply`; a failed ask keeps the last paths. No module reads
|
||||
another's files (ADR 0260).
|
||||
|
||||
|
||||
@@ -252,7 +252,13 @@ func (a *Applier) Look(ctx context.Context) Status {
|
||||
notSet[w.Name] = ShareStatus{Name: w.Name, Kind: "nfs", Mountpoint: w.Mountpoint, State: StateFailed,
|
||||
Why: fmt.Sprintf("the sharing machine gave the path %q, which the module does not mount", e.Path)}
|
||||
default:
|
||||
pairs = append(pairs, NFSPair(w, cfg.At, e))
|
||||
mine, exported := e.ForNode(cfg.Node)
|
||||
if !exported {
|
||||
notSet[w.Name] = ShareStatus{Name: w.Name, Kind: "nfs", Mountpoint: w.Mountpoint, State: StateFailed,
|
||||
Why: "the sharing machine " + st.Server + " does not export " + w.Name + " to this machine: its grants do not name " + cfg.Node}
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, NFSPair(w, cfg.At, mine))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,3 +306,24 @@ func TestTheBundleKeepsTheLastDataWhenAnAskFails(t *testing.T) {
|
||||
t.Errorf("a failed ask keeps the last paths and says why: %+v %v %q", d, err, said)
|
||||
}
|
||||
}
|
||||
|
||||
// The server decides each node's access (finding 5): a share it exports read-write may reach this node
|
||||
// read-only, and a share it exports to other nodes only does not reach this one at all. The mount takes
|
||||
// the access the server gave this node, never the share's own.
|
||||
func TestTheServersAccessForThisNodeIsTheMountsAccess(t *testing.T) {
|
||||
library := ServerShare{Name: "library", Path: "/srv/library", Access: "rw", Live: true,
|
||||
To: []ShareClient{{Node: "ws", Address: "10.0.0.3", Access: "rw"}, {Node: "laptop", Address: "10.0.0.4", Access: "ro"}}}
|
||||
got, ok := library.ForNode("laptop")
|
||||
if !ok || got.Access != "ro" {
|
||||
t.Errorf("the laptop, granted read-only, gets %q (exported to it: %v)", got.Access, ok)
|
||||
}
|
||||
if p := NFSPair(Wanted{Name: "library", Mountpoint: "/srv/library"}, "192.0.2.10", got); !strings.Contains(p.Mount, "Options=ro,") {
|
||||
t.Errorf("a read-only grant mounted read-write:\n%s", p.Mount)
|
||||
}
|
||||
if got, ok := library.ForNode("ws"); !ok || got.Access != "rw" {
|
||||
t.Errorf("the workstation, granted read-write, gets %q (%v)", got.Access, ok)
|
||||
}
|
||||
if _, ok := library.ForNode("control"); ok {
|
||||
t.Errorf("a node the server does not export to is treated as exported to")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,29 @@ type ServerShare struct {
|
||||
GID int `json:"gid"`
|
||||
Clients string `json:"clients,omitempty"`
|
||||
Live bool `json:"live"`
|
||||
// To is each node the server exports the share to, at the access it decided for that node.
|
||||
To []ShareClient `json:"to,omitempty"`
|
||||
}
|
||||
|
||||
// ShareClient is one node a share is exported to, as the server decided it.
|
||||
type ShareClient struct {
|
||||
Node string `json:"node"`
|
||||
Address string `json:"address"`
|
||||
Access string `json:"access"`
|
||||
}
|
||||
|
||||
// ForNode is the share as the server exports it to one node: its access is the lower of the share's own
|
||||
// and the node's, and false when the server does not export it to that node at all.
|
||||
func (e ServerShare) ForNode(node string) (ServerShare, bool) {
|
||||
for _, c := range e.To {
|
||||
if c.Node == node {
|
||||
if c.Access == "ro" || e.Access == "ro" {
|
||||
e.Access = "ro"
|
||||
}
|
||||
return e, true
|
||||
}
|
||||
}
|
||||
return e, false
|
||||
}
|
||||
|
||||
func header(name, kind string) string {
|
||||
|
||||
@@ -128,7 +128,7 @@ func (f *fakeSystem) everything(except ...string) string {
|
||||
|
||||
const config = "shares=library=/srv/library downloads=/srv/downloads:ro\n" +
|
||||
"sources=games=smb://192.0.2.76/Share@/mnt/games usb=disk:1234-ABCD@/mnt/usb\n" +
|
||||
"server=homeserver\nat=192.0.2.10\naccount=operator\n" +
|
||||
"server=homeserver\nat=192.0.2.10\nnode=workstation\naccount=operator\n" +
|
||||
"secret=/var/lib/mesh/mounts/smb-credentials.secret\nstate=/var/lib/mesh/mounts\n"
|
||||
|
||||
func exportsAnswer(t *testing.T, shares ...ServerShare) string {
|
||||
@@ -140,8 +140,8 @@ func exportsAnswer(t *testing.T, shares ...ServerShare) string {
|
||||
}
|
||||
|
||||
var (
|
||||
library = ServerShare{Name: "library", Path: "/tank/media", Access: "rw", UID: 1001, GID: 2000, Live: true}
|
||||
downloads = ServerShare{Name: "downloads", Path: "/tank/downloads", Access: "rw", UID: 1001, GID: 2000, Live: true}
|
||||
library = ServerShare{Name: "library", Path: "/tank/media", Access: "rw", UID: 1001, GID: 2000, Live: true, To: []ShareClient{{Node: "workstation", Address: "10.0.0.3", Access: "rw"}}}
|
||||
downloads = ServerShare{Name: "downloads", Path: "/tank/downloads", Access: "rw", UID: 1001, GID: 2000, Live: true, To: []ShareClient{{Node: "workstation", Address: "10.0.0.3", Access: "rw"}}}
|
||||
)
|
||||
|
||||
func TestTheSettingsAreReadAndWhatCannotBeIsSaidPlainly(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user