blob: 2679e8ff7f1c89725288f81fa51b6b87bef94ab5 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package global
import (
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/discovery"
"v.io/v23/naming"
"v.io/v23/security"
"v.io/v23/security/access"
idiscovery "v.io/x/ref/lib/discovery"
)
func (d *gdiscovery) Advertise(ctx *context.T, ad *discovery.Advertisement, visibility []security.BlessingPattern) (<-chan struct{}, error) {
if !ad.Id.IsValid() {
var err error
if ad.Id, err = discovery.NewAdId(); err != nil {
return nil, err
}
}
if err := validateAd(ad); err != nil {
return nil, idiscovery.NewErrBadAdvertisement(ctx, err)
}
if len(visibility) == 0 {
visibility = []security.BlessingPattern{security.AllPrincipals}
}
principal := v23.GetPrincipal(ctx)
self := security.DefaultBlessingPatterns(principal)
perms := access.Permissions{
string(access.Admin): access.AccessList{In: self},
string(access.Read): access.AccessList{In: visibility},
}
if !d.addAd(ad) {
return nil, idiscovery.NewErrAlreadyBeingAdvertised(ctx, ad.Id)
}
// TODO(jhahn): There is no atomic way to check and reserve the name under mounttable.
// For example, the name can be overwritten by other applications of the same owner.
// But this would be OK for now.
name, err := encodeAdToSuffix(ad, d.newAdTimestampNs())
if err != nil {
return nil, err
}
if err := d.ns.SetPermissions(ctx, name, perms, "", naming.IsLeaf(true)); err != nil {
d.removeAd(ad)
return nil, err
}
// TODO(jhahn): We're using one goroutine per advertisement, but we can do
// better like have one goroutine that takes care of all advertisements.
// But this is OK for now as an experiment.
done := make(chan struct{})
go func() {
defer close(done)
defer d.removeAd(ad)
// We need a context that is detached from the deadlines and cancellation
// of ctx since we have to unmount after ctx is canceled.
rctx, rcancel := context.WithRootCancel(ctx)
defer d.unmount(rctx, rcancel, name)
for {
d.mount(ctx, name, ad.Addresses)
select {
case <-d.clock.After(d.mountTTL):
case <-ctx.Done():
return
}
}
}()
return done, nil
}
func (d *gdiscovery) newAdTimestampNs() int64 {
now := time.Now()
timestampNs := now.UnixNano()
d.mu.Lock()
if d.adTimestampNs >= timestampNs {
timestampNs = d.adTimestampNs + 1
}
d.adTimestampNs = timestampNs
d.mu.Unlock()
return timestampNs
}
func (d *gdiscovery) addAd(ad *discovery.Advertisement) bool {
d.mu.Lock()
if _, exist := d.ads[ad.Id]; exist {
d.mu.Unlock()
return false
}
d.ads[ad.Id] = struct{}{}
d.mu.Unlock()
return true
}
func (d *gdiscovery) removeAd(ad *discovery.Advertisement) {
d.mu.Lock()
delete(d.ads, ad.Id)
d.mu.Unlock()
}
func (d *gdiscovery) mount(ctx *context.T, name string, addrs []string) {
for _, addr := range addrs {
if err := d.ns.Mount(ctx, name, addr, d.mountTTLWithSlack); err != nil {
ctx.Errorf("mount(%q, %q) failed: %v", name, addr, err)
}
}
}
func (d *gdiscovery) unmount(ctx *context.T, cancel context.CancelFunc, name string) {
if err := d.ns.Delete(ctx, name, true); err != nil {
ctx.Infof("unmount(%q) failed: %v", name, err)
}
cancel()
}