exp.upspin.io@v0.0.0-20230625230448-5076e5b595ec/cmd/demoserver/main.go (about)

     1  // Copyright 2017 The Upspin Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Command demoserver serves an Upspin tree containing a series of boxes
     6  // (files) containing Schrödinger's cats. The cats inside the boxes are in the
     7  // superposition of dead and alive until a client does a Lookup of the box, at
     8  // which point the superposition collapses and the reality of the cat's state
     9  // is revealed.
    10  //
    11  // The purpose of this program is to demonstrate the implementation of a
    12  // combined Upspin DirServer and StoreServer that serves dynamic content.
    13  //
    14  // See also: https://en.wikipedia.org/wiki/Schrödinger's_cat
    15  package main // import "exp.upspin.io/cmd/demoserver"
    16  
    17  import (
    18  	"fmt"
    19  	"math/rand"
    20  	"net/http"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"upspin.io/access"
    27  	"upspin.io/cloud/https"
    28  	"upspin.io/config"
    29  	"upspin.io/errors"
    30  	"upspin.io/flags"
    31  	"upspin.io/log"
    32  	"upspin.io/pack"
    33  	"upspin.io/path"
    34  	"upspin.io/rpc/dirserver"
    35  	"upspin.io/rpc/storeserver"
    36  	"upspin.io/serverutil"
    37  	"upspin.io/upspin"
    38  
    39  	_ "upspin.io/key/transports"
    40  	_ "upspin.io/pack/eeintegrity"
    41  )
    42  
    43  func main() {
    44  	rand.Seed(time.Now().UnixNano())
    45  	flags.Parse(flags.Server)
    46  
    47  	addr := upspin.NetAddr(flags.NetAddr)
    48  	ep := upspin.Endpoint{
    49  		Transport: upspin.Remote,
    50  		NetAddr:   addr,
    51  	}
    52  	cfg, err := config.FromFile(flags.Config)
    53  	if err != nil {
    54  		log.Fatal(err)
    55  	}
    56  
    57  	s, err := newServer(ep, cfg)
    58  	if err != nil {
    59  		log.Fatal(err)
    60  	}
    61  
    62  	http.Handle("/api/Store/", storeserver.New(cfg, s.StoreServer(), addr))
    63  	http.Handle("/api/Dir/", dirserver.New(cfg, s.DirServer(), addr))
    64  
    65  	https.ListenAndServeFromFlags(nil)
    66  }
    67  
    68  // box represents an opened box.
    69  type box struct {
    70  	*upspin.DirEntry
    71  	data []byte
    72  }
    73  
    74  // server provides implementations of upspin.DirServer and upspin.StoreServer
    75  // (accessed by calling the respective methods) that serve a tree containing
    76  // many boxes containing Schrödinger's Cats.
    77  type server struct {
    78  	ep  upspin.Endpoint
    79  	cfg upspin.Config
    80  
    81  	accessEntry *upspin.DirEntry
    82  	accessBytes []byte
    83  
    84  	mu    sync.Mutex
    85  	open  *sync.Cond // Broadcast when a box is opened for the first time.
    86  	boxes []box
    87  }
    88  
    89  type dirServer struct {
    90  	*server
    91  }
    92  
    93  type storeServer struct {
    94  	*server
    95  }
    96  
    97  func (s *server) DirServer() upspin.DirServer {
    98  	return &dirServer{s}
    99  }
   100  
   101  func (s *server) StoreServer() upspin.StoreServer {
   102  	return &storeServer{s}
   103  }
   104  
   105  const (
   106  	accessRef  = upspin.Reference(access.AccessFile)
   107  	accessFile = "read,list:all\n"
   108  )
   109  
   110  var accessRefdata = upspin.Refdata{Reference: accessRef}
   111  
   112  func newServer(ep upspin.Endpoint, cfg upspin.Config) (*server, error) {
   113  	s := &server{
   114  		ep:  ep,
   115  		cfg: cfg,
   116  	}
   117  	s.open = sync.NewCond(&s.mu)
   118  
   119  	var err error
   120  	s.accessEntry, s.accessBytes, err = s.pack(access.AccessFile, 0, []byte(accessFile))
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	return s, nil
   126  }
   127  
   128  const packing = upspin.EEIntegrityPack
   129  
   130  func (s *server) pack(filePath string, seq int64, data []byte) (*upspin.DirEntry, []byte, error) {
   131  	name := upspin.PathName(s.cfg.UserName()) + "/" + upspin.PathName(filePath)
   132  	de := &upspin.DirEntry{
   133  		Writer:     s.cfg.UserName(),
   134  		Name:       name,
   135  		SignedName: name,
   136  		Packing:    packing,
   137  		Time:       upspin.Now(),
   138  		Sequence:   seq,
   139  	}
   140  
   141  	bp, err := pack.Lookup(packing).Pack(s.cfg, de)
   142  	if err != nil {
   143  		return nil, nil, err
   144  	}
   145  	cipher, err := bp.Pack(data)
   146  	if err != nil {
   147  		return nil, nil, err
   148  	}
   149  	bp.SetLocation(upspin.Location{
   150  		Endpoint:  s.ep,
   151  		Reference: upspin.Reference(filePath),
   152  	})
   153  	return de, cipher, bp.Close()
   154  }
   155  
   156  // These methods implement upspin.Service.
   157  
   158  func (s *server) Endpoint() upspin.Endpoint { return s.ep }
   159  func (*server) Ping() bool                  { return true }
   160  func (*server) Close()                      {}
   161  
   162  // These methods implement upspin.Dialer.
   163  
   164  func (s *storeServer) Dial(upspin.Config, upspin.Endpoint) (upspin.Service, error) { return s, nil }
   165  func (s *dirServer) Dial(upspin.Config, upspin.Endpoint) (upspin.Service, error)   { return s, nil }
   166  
   167  // These methods implement upspin.DirServer.
   168  
   169  func (s *dirServer) Lookup(name upspin.PathName) (*upspin.DirEntry, error) {
   170  	p, err := path.Parse(name)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	fp := p.FilePath()
   176  	switch fp {
   177  	case "": // Root directory.
   178  		s.mu.Lock()
   179  		total := len(s.boxes)
   180  		s.mu.Unlock()
   181  		return &upspin.DirEntry{
   182  			Name:       p.Path(),
   183  			SignedName: p.Path(),
   184  			Attr:       upspin.AttrDirectory,
   185  			Time:       upspin.Now(),
   186  			Sequence:   int64(total * 2),
   187  		}, nil
   188  	case access.AccessFile:
   189  		return s.accessEntry, nil
   190  	}
   191  
   192  	n := matchBox(fp)
   193  	if n < 0 {
   194  		return nil, errors.E(name, errors.NotExist)
   195  	}
   196  
   197  	s.mu.Lock()
   198  	defer s.mu.Unlock()
   199  
   200  	total := len(s.boxes)
   201  	if n > total {
   202  		return nil, errors.E(name, errors.NotExist)
   203  	}
   204  
   205  	if n == total {
   206  		// A new box is opened!
   207  		de, data, err := s.pack(fp, int64(total*2+1), randomState())
   208  		if err != nil {
   209  			return nil, errors.E(name, err)
   210  		}
   211  		s.boxes = append(s.boxes, box{de, data})
   212  		s.open.Broadcast()
   213  	}
   214  
   215  	return s.boxes[n].DirEntry, nil
   216  }
   217  
   218  func (s *dirServer) Glob(pattern string) ([]*upspin.DirEntry, error) {
   219  	return serverutil.Glob(pattern, s.Lookup, s.listDir)
   220  }
   221  
   222  func (s *dirServer) listDir(name upspin.PathName) ([]*upspin.DirEntry, error) {
   223  	p, err := path.Parse(name)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	if p.User() != s.cfg.UserName() || p.FilePath() != "" {
   228  		return nil, errors.E(name, errors.NotExist)
   229  	}
   230  
   231  	s.mu.Lock()
   232  	defer s.mu.Unlock()
   233  
   234  	des := []*upspin.DirEntry{
   235  		s.accessEntry,
   236  	}
   237  
   238  	// List all the opened boxes in numerical order.
   239  	for n := range s.boxes {
   240  		de := s.boxes[n].DirEntry.Copy()
   241  		de.MarkIncomplete()
   242  		des = append(des, de)
   243  	}
   244  
   245  	// The final, closed box.
   246  	des = append(des, s.closedBox(len(s.boxes)))
   247  
   248  	return des, nil
   249  }
   250  
   251  func (s *dirServer) closedBox(n int) *upspin.DirEntry {
   252  	name := upspin.PathName(s.cfg.UserName()) + "/" + upspin.PathName(fmtBox(n))
   253  	return &upspin.DirEntry{
   254  		Name:       name,
   255  		SignedName: name,
   256  		Attr:       upspin.AttrIncomplete,
   257  		Time:       upspin.Now(),
   258  		Writer:     s.cfg.UserName(),
   259  		Sequence:   int64(n * 2),
   260  	}
   261  }
   262  
   263  func (s *dirServer) Watch(name upspin.PathName, seq int64, done <-chan struct{}) (<-chan upspin.Event, error) {
   264  	p, err := path.Parse(name)
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  	if p.User() != s.cfg.UserName() {
   269  		return nil, errors.E(name, errors.NotExist)
   270  	}
   271  
   272  	fp := p.FilePath()
   273  	match := func(de *upspin.DirEntry) bool {
   274  		return fp == "" || name == de.Name
   275  
   276  	}
   277  
   278  	n := int(seq)
   279  	if n < 0 {
   280  		n = 0
   281  	}
   282  	isDone := func() bool {
   283  		select {
   284  		case <-done:
   285  			return true
   286  		default:
   287  			return false
   288  		}
   289  	}
   290  	events := make(chan upspin.Event)
   291  	go func() {
   292  		<-done
   293  		s.open.Broadcast()
   294  	}()
   295  	go func() {
   296  		defer close(events)
   297  		for {
   298  			s.mu.Lock()
   299  			if n == len(s.boxes) {
   300  				// Send the closed box.
   301  				go func(n int) {
   302  					if de := s.closedBox(n); match(de) {
   303  						events <- upspin.Event{
   304  							Entry: s.closedBox(n),
   305  						}
   306  					}
   307  				}(n)
   308  			}
   309  			for !isDone() && n >= len(s.boxes) {
   310  				s.open.Wait()
   311  			}
   312  			if isDone() {
   313  				s.mu.Unlock()
   314  				return
   315  			}
   316  			de := s.boxes[n].DirEntry
   317  			s.mu.Unlock()
   318  
   319  			// Send the next opened box.
   320  			if match(de) {
   321  				events <- upspin.Event{
   322  					Entry: de,
   323  				}
   324  			}
   325  			n++
   326  		}
   327  	}()
   328  	return events, nil
   329  }
   330  
   331  func (s *dirServer) WhichAccess(name upspin.PathName) (*upspin.DirEntry, error) {
   332  	return s.accessEntry, nil
   333  }
   334  
   335  // This method implements upspin.StoreServer.
   336  
   337  func (s *storeServer) Get(ref upspin.Reference) ([]byte, *upspin.Refdata, []upspin.Location, error) {
   338  	if ref == accessRef {
   339  		return s.accessBytes, &accessRefdata, nil, nil
   340  	}
   341  
   342  	n := matchBox(string(ref))
   343  
   344  	s.mu.Lock()
   345  	defer s.mu.Unlock()
   346  
   347  	if n < 0 || n >= len(s.boxes) {
   348  		return nil, nil, nil, errors.E(errors.NotExist, errors.Errorf("unknown reference %q", ref))
   349  	}
   350  
   351  	return s.boxes[n].data, &upspin.Refdata{Reference: ref}, nil, nil
   352  }
   353  
   354  // The DirServer and StoreServer methods below are not implemented.
   355  
   356  var errNotImplemented = errors.E(errors.Permission, "method not implemented: demoserver is read-only")
   357  
   358  func (*dirServer) Put(entry *upspin.DirEntry) (*upspin.DirEntry, error) {
   359  	return nil, errNotImplemented
   360  }
   361  
   362  func (*dirServer) Delete(name upspin.PathName) (*upspin.DirEntry, error) {
   363  	return nil, errNotImplemented
   364  }
   365  
   366  func (*storeServer) Put(data []byte) (*upspin.Refdata, error) {
   367  	return nil, errNotImplemented
   368  }
   369  
   370  func (*storeServer) Delete(ref upspin.Reference) error {
   371  	return errNotImplemented
   372  }
   373  
   374  // Utility functions.
   375  
   376  const boxName = "box"
   377  
   378  func fmtBox(n int) string {
   379  	return fmt.Sprintf("%s%d", boxName, n)
   380  }
   381  
   382  func matchBox(filePath string) int {
   383  	if !strings.HasPrefix(filePath, boxName) {
   384  		return -1
   385  	}
   386  	n := filePath[len(boxName):]
   387  	i, _ := strconv.ParseInt(n, 10, 32)
   388  	if i < 0 || fmtBox(int(i)) != filePath {
   389  		return -1
   390  	}
   391  	return int(i)
   392  }
   393  
   394  var states = [][]byte{
   395  	[]byte("A dead cat.\n"),
   396  	[]byte("A live cat.\n"),
   397  }
   398  
   399  func randomState() []byte {
   400  	return states[rand.Intn(2)]
   401  }