discovery: Attribute support for global discovery.
The suffix at which advertisements are mounted in global discovery
is now approximately:
Ad.Id.String() + "," + vomEncode(Ad.Attributes) // with necessary escaping.
This is a prerequisite get global discovery working in the todos app.
Change-Id: I51d06d8b82a431a271caffed3f69d47631cd751d
diff --git a/lib/discovery/global/advertise.go b/lib/discovery/global/advertise.go
index da3c45a..312c68e 100644
--- a/lib/discovery/global/advertise.go
+++ b/lib/discovery/global/advertise.go
@@ -11,7 +11,6 @@
"v.io/v23/naming"
"v.io/v23/security"
"v.io/v23/security/access"
-
idiscovery "v.io/x/ref/lib/discovery"
)
@@ -43,7 +42,10 @@
// 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 := ad.Id.String()
+ name, err := encodeAdToSuffix(ad)
+ 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
@@ -58,8 +60,8 @@
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, _ := context.WithRootCancel(ctx)
- defer d.unmount(rctx, name)
+ rctx, rcancel := context.WithRootCancel(ctx)
+ defer d.unmount(rctx, rcancel, name)
for {
d.mount(ctx, name, ad.Addresses)
@@ -99,8 +101,9 @@
}
}
-func (d *gdiscovery) unmount(ctx *context.T, name string) {
+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()
}
diff --git a/lib/discovery/global/encoding.go b/lib/discovery/global/encoding.go
new file mode 100644
index 0000000..774f5aa
--- /dev/null
+++ b/lib/discovery/global/encoding.go
@@ -0,0 +1,57 @@
+// 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 (
+ "strings"
+
+ "v.io/v23/discovery"
+ "v.io/v23/naming"
+ "v.io/v23/vom"
+)
+
+const suffixDelim = ","
+
+// encodeAdToSuffix encodes the ad.Id and the ad.Attributes into the suffix at
+// which we mount the advertisement.
+//
+// TODO(suharshs): Currently only the id and the attributes are encoded; we may
+// want to encode the rest of the advertisement someday?
+func encodeAdToSuffix(ad *discovery.Advertisement) (string, error) {
+ b, err := vom.Encode(ad.Attributes)
+ if err != nil {
+ return "", err
+ }
+ // Escape suffixDelim to use it as our delimeter between the id and the attrs.
+ id := ad.Id.String()
+ attr := naming.Escape(string(b), suffixDelim)
+ return naming.EncodeAsNameElement(id + suffixDelim + attr), nil
+}
+
+// decodeAdFromSuffix decodes s into an advertisement.
+func decodeAdFromSuffix(in string) (*discovery.Advertisement, error) {
+ s, ok := naming.DecodeFromNameElement(in)
+ if !ok {
+ return nil, NewErrAdInvalidEncoding(nil, in)
+ }
+ parts := strings.Split(s, suffixDelim)
+ if len(parts) != 2 {
+ return nil, NewErrAdInvalidEncoding(nil, in)
+ }
+ id, attrs := parts[0], parts[1]
+ ad := &discovery.Advertisement{}
+ var err error
+ if ad.Id, err = discovery.ParseAdId(id); err != nil {
+ return nil, err
+ }
+ attrs, ok = naming.Unescape(attrs)
+ if !ok {
+ return nil, NewErrAdInvalidEncoding(nil, in)
+ }
+ if err = vom.Decode([]byte(attrs), &ad.Attributes); err != nil {
+ return nil, err
+ }
+ return ad, nil
+}
diff --git a/lib/discovery/global/encoding_test.go b/lib/discovery/global/encoding_test.go
new file mode 100644
index 0000000..cf4b0db
--- /dev/null
+++ b/lib/discovery/global/encoding_test.go
@@ -0,0 +1,34 @@
+// 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 (
+ "reflect"
+ "testing"
+
+ "v.io/v23/discovery"
+)
+
+func TestAdSuffix(t *testing.T) {
+ testCases := []discovery.Advertisement{
+ {},
+ {Id: discovery.AdId{1, 2, 3}},
+ {Attributes: discovery.Attributes{"k": "v"}},
+ {Id: discovery.AdId{1, 2, 3}, Attributes: discovery.Attributes{"k": "v"}},
+ }
+ for _, want := range testCases {
+ encAd, err := encodeAdToSuffix(&want)
+ if err != nil {
+ t.Error(err)
+ }
+ got, err := decodeAdFromSuffix(encAd)
+ if err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(*got, want) {
+ t.Errorf("got %v, want %v", *got, want)
+ }
+ }
+}
diff --git a/lib/discovery/global/errors.vdl b/lib/discovery/global/errors.vdl
index f11c648..4bd5228 100644
--- a/lib/discovery/global/errors.vdl
+++ b/lib/discovery/global/errors.vdl
@@ -5,7 +5,6 @@
package global
error (
- NoNamespace() {
- "en": " namespace not found",
- }
+ NoNamespace() {"en": "namespace not found"}
+ AdInvalidEncoding(ad string) {"en": "ad ({ad}) has invalid encoding"}
)
diff --git a/lib/discovery/global/global.vdl.go b/lib/discovery/global/global.vdl.go
index f0fbd9a..2e725ee 100644
--- a/lib/discovery/global/global.vdl.go
+++ b/lib/discovery/global/global.vdl.go
@@ -19,7 +19,8 @@
// Error definitions
var (
- ErrNoNamespace = verror.Register("v.io/x/ref/lib/discovery/global.NoNamespace", verror.NoRetry, "{1:}{2:} namespace not found")
+ ErrNoNamespace = verror.Register("v.io/x/ref/lib/discovery/global.NoNamespace", verror.NoRetry, "{1:}{2:} namespace not found")
+ ErrAdInvalidEncoding = verror.Register("v.io/x/ref/lib/discovery/global.AdInvalidEncoding", verror.NoRetry, "{1:}{2:} ad ({3}) has invalid encoding")
)
// NewErrNoNamespace returns an error with the ErrNoNamespace ID.
@@ -27,6 +28,11 @@
return verror.New(ErrNoNamespace, ctx)
}
+// NewErrAdInvalidEncoding returns an error with the ErrAdInvalidEncoding ID.
+func NewErrAdInvalidEncoding(ctx *context.T, ad string) error {
+ return verror.New(ErrAdInvalidEncoding, ctx, ad)
+}
+
var __VDLInitCalled bool
// __VDLInit performs vdl initialization. It is safe to call multiple times.
@@ -49,7 +55,8 @@
__VDLInitCalled = true
// Set error format strings.
- i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoNamespace.ID), "{1:}{2:} namespace not found")
+ i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoNamespace.ID), "{1:}{2:} namespace not found")
+ i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrAdInvalidEncoding.ID), "{1:}{2:} ad ({3}) has invalid encoding")
return struct{}{}
}
diff --git a/lib/discovery/global/global_test.go b/lib/discovery/global/global_test.go
index 2aa1d02..b889e7f 100644
--- a/lib/discovery/global/global_test.go
+++ b/lib/discovery/global/global_test.go
@@ -29,8 +29,9 @@
ads := []discovery.Advertisement{
{
- Id: discovery.AdId{1, 2, 3},
- Addresses: []string{"/h1:123/x", "/h2:123/y"},
+ Id: discovery.AdId{1, 2, 3},
+ Addresses: []string{"/h1:123/x", "/h2:123/y"},
+ Attributes: discovery.Attributes{"k": "v"},
},
{
Addresses: []string{"/h1:123/x", "/h2:123/z"},
diff --git a/lib/discovery/global/scan.go b/lib/discovery/global/scan.go
index 26dab17..f336b77 100644
--- a/lib/discovery/global/scan.go
+++ b/lib/discovery/global/scan.go
@@ -21,10 +21,6 @@
if err != nil {
return nil, err
}
- target := matcher.TargetKey()
- if len(target) == 0 {
- target = "*"
- }
updateCh := make(chan discovery.Update, 10)
go func() {
@@ -32,7 +28,7 @@
var prevFound map[discovery.AdId]*discovery.Advertisement
for {
- found, err := d.doScan(ctx, target, matcher)
+ found, err := d.doScan(ctx, matcher.TargetKey(), matcher)
if found == nil {
if err != nil {
ctx.Error(err)
@@ -53,7 +49,19 @@
}
func (d *gdiscovery) doScan(ctx *context.T, target string, matcher idiscovery.Matcher) (map[discovery.AdId]*discovery.Advertisement, error) {
- scanCh, err := d.ns.Glob(ctx, target)
+ // If the target is neither empty nor a valid AdId, we return without an error,
+ // since there will be not entries with the requested target length in the namespace.
+ if len(target) > 0 {
+ if _, err := discovery.ParseAdId(target); err != nil {
+ return nil, nil
+ }
+ }
+
+ // Now that the key is either empty or an AdId, we suffix it with "*".
+ // In the case of empty, we need to scan for everything.
+ // In the case where target is a AdId we need to scan for entries prefixed with
+ // the AdId with any encoded attributes afterwards.
+ scanCh, err := d.ns.Glob(ctx, target+"*")
if err != nil {
return nil, err
}
@@ -74,7 +82,7 @@
ctx.Error(err)
continue
}
- // Since mount operation is not atomic, we may not have addresses yet.
+ // Since mount operations are not atomic, we may not have addresses yet.
// Ignore it. It will be re-scanned in the next cycle.
if len(ad.Addresses) == 0 {
continue
@@ -108,7 +116,7 @@
func convToAd(glob naming.GlobReply) (*discovery.Advertisement, error) {
switch g := glob.(type) {
case *naming.GlobReplyEntry:
- id, err := discovery.ParseAdId(g.Value.Name)
+ ad, err := decodeAdFromSuffix(g.Value.Name)
if err != nil {
return nil, err
}
@@ -116,9 +124,10 @@
for _, server := range g.Value.Servers {
addrs = append(addrs, server.Server)
}
- // We sort the addresses to avoid false update.
+ // We sort the addresses to avoid false updates.
sort.Strings(addrs)
- return &discovery.Advertisement{Id: id, Addresses: addrs}, nil
+ ad.Addresses = addrs
+ return ad, nil
case *naming.GlobReplyError:
return nil, fmt.Errorf("glob error on %s: %v", g.Value.Name, g.Value.Error)
default:
diff --git a/lib/discovery/global/validate.go b/lib/discovery/global/validate.go
index e7e0efa..83eef8e 100644
--- a/lib/discovery/global/validate.go
+++ b/lib/discovery/global/validate.go
@@ -21,11 +21,8 @@
if len(ad.Addresses) == 0 {
return errors.New("address not provided")
}
- if len(ad.Attributes) > 0 {
- return errors.New("attributes name not supported")
- }
if len(ad.Attachments) > 0 {
- return errors.New("attachments name not supported")
+ return errors.New("attachments not supported")
}
return nil
}
diff --git a/lib/discovery/global/validate_test.go b/lib/discovery/global/validate_test.go
index 792165f..e9a4a6b 100644
--- a/lib/discovery/global/validate_test.go
+++ b/lib/discovery/global/validate_test.go
@@ -17,8 +17,9 @@
}{
{
discovery.Advertisement{
- Id: discovery.AdId{1, 2, 3},
- Addresses: []string{"/h:123/x"},
+ Id: discovery.AdId{1, 2, 3},
+ Addresses: []string{"/h:123/x"},
+ Attributes: discovery.Attributes{"k": "v"},
},
true,
},
@@ -47,16 +48,6 @@
discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
Addresses: []string{"/h:123/x"},
- Attributes: discovery.Attributes{ // Has attributes.
- "k": "v",
- },
- },
- false,
- },
- {
- discovery.Advertisement{
- Id: discovery.AdId{1, 2, 3},
- Addresses: []string{"/h:123/x"},
Attachments: discovery.Attachments{ // Has attachments.
"k": []byte{1},
},