Globber

Wherein you use the Globber interface to create your own server namespace. This is an advanced tutorial.

Prerequisites:
All tutorials require Vanadium installation, and must run in a bash shell.
Further, please download this script then source it:

source ~/Downloads/scenario-e-setup.sh

This command wipes tutorial files and defines shell environment variables. If you start a new terminal, and just want to re-establish the environment, see instructions here.

If you would like to generate files from this tutorial without the copy/paste steps, download and source this script. Files will be created in the $V_TUT directory.

Introduction

In the suffix Part I and Part II tutorials, we introduced the concept of server namespace where multiple services on a single server can be accessed by name and can be discovered with the namespace glob command.

There are two variants of the Globber interface defined in rpc/model.go: AllGlobber, and ChildrenGlobber. AllGlobber is the most flexible option, but also the most difficult to implement correctly. It is used by specialized applications like a mount table server. ChildrenGlobber provides most of the same functionality, but is much easier to implement.

In this tutorial, you will learn how to implement complex namespaces using the ChildrenGlobber interface.

File server

A namespace is a tree structure similar to a file system. In the namespace, the nodes are service objects and the edges are their names. In a file system, the nodes are files or directories, and the edges are the file names and directory names.

Let's create a server that mirrors a local directory tree in its namespace.

To do this, we'll use two new services and a custom dispatcher.

Services

Let's create two services: one for files, one for directories.

File service

 mkdir -p $V_TUT/src/fileserver
 cat - <<EOF >$V_TUT/src/fileserver/file_service.go
package main

import (
  "errors"
  "v.io/v23/context"
  "v.io/v23/rpc"
)

type fileService struct {
  name string
}

func (s *fileService) GetContents(*context.T, rpc.ServerCall) ([]byte, error) {
  return nil, errors.New("method not implemented")
}

func (s *fileService) SetContents(*context.T, rpc.ServerCall, []byte) (error) {
  return errors.New("method not implemented")
}
EOF

Code walk

Since we're not going to send any calls to the file service in this example, we leave it unimplemented.

The directory service is the interesting part.

Directory service

 cat - <<EOF >$V_TUT/src/fileserver/dir_service.go
package main

import (
  "os"
  "v.io/v23/context"
  "v.io/v23/glob"
  "v.io/v23/naming"
  "v.io/v23/rpc"
)

type dirService struct {
  name string
}

func (s *dirService) GlobChildren__(
    _ *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
  f, err := os.Open(s.name)
  if err != nil {
    return err
  }
  defer f.Close()
  fi, err := f.Readdir(0)
  if err != nil {
    return err
  }
  sender := call.SendStream()
  for _, file := range fi {
    name := file.Name()
    if m.Match(name) {
      if err := sender.Send(naming.GlobChildrenReplyName{name}); err != nil {
        return err
      }
    }
  }
  return nil
}
EOF

Code walk

GlobChildren__ is a special method that is automatically detected by the Vanadium infrastructure. It is called when the server receives a glob request, e.g. from a client using namespace glob to inspect the server's namespace.

Service objects that can have children must implement GlobChildren__ in order to have their children appear in the server's namespace.

The directory service's implementation iterates through the files in the directory and sends the ones that match.

Since files don't have children, the file service doesn't need to implement GlobChildren__.

Let's add a custom dispatcher that uses our two services.

Dispatcher

 cat - <<EOF >$V_TUT/src/fileserver/dispatcher.go
package main

import (
  "os"
  "path/filepath"
  "strings"
  "v.io/v23/context"
  "v.io/v23/security"
)

type myDispatcher struct {
  rootDir string
}

func (d *myDispatcher) Lookup(
    _ *context.T, suffix string) (interface{}, security.Authorizer, error) {

  relPath := filepath.Join(strings.Split(suffix, "/")...)
  path := filepath.Join(d.rootDir, relPath)
  fi, err := os.Stat(path)
  switch {
  case err != nil:
    return nil, nil, err
  case fi.IsDir():
    return &dirService{path}, nil, nil
  default:
    return &fileService{path}, nil, nil
  }
}
EOF

Code walk

This dispatcher maps the suffix to a file or directory on the local file system. If it is a file, Lookup returns a fileService object. If it is a directory, it returns a dirService object.

You might have noticed that we haven't used any VDL-generated stub code. That's to keep this example as simple as possible. The main benefit of using stub code in servers is static type-checking, which means that the compiler will enforce that all the method arguments and return values have the types defined in the VDL file. Adding a VDL file is left as an exercise.

All we need now is the main function.

main

 cat - <<EOF >$V_TUT/src/fileserver/main.go
package main

import (
  "flag"
  "log"
  "v.io/v23"
  "v.io/x/ref/lib/signals"
  _ "v.io/x/ref/runtime/factories/generic"
)

var (
  name = flag.String(
    "mount-name", "", "Name for service in default mount table.")
  root = flag.String(
    "root-dir", ".", "The root directory of the file server.")
)

func main() {
  ctx, shutdown := v23.Init()
  defer shutdown()

  _, _, err := v23.WithNewDispatchingServer(ctx, *name, &myDispatcher{*root})
  if err != nil {
    log.Panic("Failure creating server: ", err)
  }
  <-signals.ShutdownOnSignals(ctx)
}
EOF
go build fileserver

Code walk

The main function creates the vanadium server that serves our custom dispatcher.

Try it

Start a mount table.

PORT_MT=23000  # Pick an unused port.
kill_tut_process TUT_PID_MT
$V_BIN/mounttabled \
    --v23.credentials $V_TUT/cred/basics \
    --v23.tcp.address :$PORT_MT &
TUT_PID_MT=$!
export V23_NAMESPACE=/localhost:$PORT_MT

Fire up the file server with its root directory set to "$V_TUT":

go install fileserver
kill_tut_process TUT_PID_SERVER
$V_TUT/bin/fileserver \
    --v23.credentials $V_TUT/cred/basics \
    --mount-name my-file-server \
    --root-dir "$V_TUT" &
TUT_PID_SERVER=$!

Let's take a look at the server's namespace.

$V_BIN/namespace \
    --v23.credentials $V_TUT/cred/basics \
    glob "my-file-server/..."

All the files under "$V_TUT" are in the server's namespace.

Exercises

  1. What would it take to modify the fortune server from The suffix - Part I to organize the fortune services by category, e.g. romance, finance, sports, etc.?

  2. Finish the implementation of the file and directory services:

    • Write a service definition in VDL.
    • Implement the missing methods.
    • Write a client for them.
    • Add your own Authorizer.

Cleanup

kill_tut_process TUT_PID_SERVER
kill_tut_process TUT_PID_MT
unset V23_NAMESPACE

Summary