blob: b0c6cc46e513ff2efb1119be8ee23ba941194a5a [file] [log] [blame]
package impl
import (
"strings"
"v.io/core/veyron/services/mgmt/lib/acls"
"v.io/core/veyron/services/mgmt/lib/fs"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/security/access"
verror "v.io/core/veyron2/verror2"
"v.io/core/veyron2/vlog"
)
// appRepoService implements the Application repository interface.
type appRepoService struct {
// store is the storage server used for storing application
// metadata.
// All objects share the same Memstore.
store *fs.Memstore
// storeRoot is a name in the directory under which all data will be
// stored.
storeRoot string
// suffix is the name of the application object.
suffix string
}
const pkgPath = "v.io/core/veyron/services/mgmt/application/impl/"
var (
ErrInvalidSuffix = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
ErrNotFound = verror.Register(pkgPath+".NotFound", verror.NoRetry, "{1:}{2:} not found{:_}")
ErrInvalidBlessing = verror.Register(pkgPath+".InvalidBlessing", verror.NoRetry, "{1:}{2:} invalid blessing{:_}")
)
// NewApplicationService returns a new Application service implementation.
func NewApplicationService(store *fs.Memstore, storeRoot, suffix string) *appRepoService {
return &appRepoService{store: store, storeRoot: storeRoot, suffix: suffix}
}
func parse(context ipc.ServerContext, suffix string) (string, string, error) {
tokens := strings.Split(suffix, "/")
switch len(tokens) {
case 2:
return tokens[0], tokens[1], nil
case 1:
return tokens[0], "", nil
default:
return "", "", verror.Make(ErrInvalidSuffix, context.Context())
}
}
func (i *appRepoService) Match(context ipc.ServerContext, profiles []string) (application.Envelope, error) {
vlog.VI(0).Infof("%v.Match(%v)", i.suffix, profiles)
empty := application.Envelope{}
name, version, err := parse(context, i.suffix)
if err != nil {
return empty, err
}
if version == "" {
return empty, verror.Make(ErrInvalidSuffix, context.Context())
}
i.store.Lock()
defer i.store.Unlock()
for _, profile := range profiles {
path := naming.Join("/applications", name, profile, version)
entry, err := i.store.BindObject(path).Get(context)
if err != nil {
continue
}
envelope, ok := entry.Value.(application.Envelope)
if !ok {
continue
}
return envelope, nil
}
return empty, verror.Make(ErrNotFound, context.Context())
}
func (i *appRepoService) Put(context ipc.ServerContext, profiles []string, envelope application.Envelope) error {
vlog.VI(0).Infof("%v.Put(%v, %v)", i.suffix, profiles, envelope)
name, version, err := parse(context, i.suffix)
if err != nil {
return err
}
if version == "" {
return verror.Make(ErrInvalidSuffix, context.Context())
}
i.store.Lock()
defer i.store.Unlock()
// Transaction is rooted at "", so tname == tid.
tname, err := i.store.BindTransactionRoot("").CreateTransaction(context)
if err != nil {
return err
}
for _, profile := range profiles {
path := naming.Join(tname, "/applications", name, profile, version)
object := i.store.BindObject(path)
_, err := object.Put(context, envelope)
if err != nil {
return verror.Make(ErrOperationFailed, context.Context())
}
}
if err := i.store.BindTransaction(tname).Commit(context); err != nil {
return verror.Make(ErrOperationFailed, context.Context())
}
return nil
}
func (i *appRepoService) Remove(context ipc.ServerContext, profile string) error {
vlog.VI(0).Infof("%v.Remove(%v)", i.suffix, profile)
name, version, err := parse(context, i.suffix)
if err != nil {
return err
}
i.store.Lock()
defer i.store.Unlock()
// Transaction is rooted at "", so tname == tid.
tname, err := i.store.BindTransactionRoot("").CreateTransaction(context)
if err != nil {
return err
}
path := naming.Join(tname, "/applications", name, profile)
if version != "" {
path += "/" + version
}
object := i.store.BindObject(path)
found, err := object.Exists(context)
if err != nil {
return verror.Make(ErrOperationFailed, context.Context())
}
if !found {
return verror.Make(ErrNotFound, context.Context())
}
if err := object.Remove(context); err != nil {
return verror.Make(ErrOperationFailed, context.Context())
}
if err := i.store.BindTransaction(tname).Commit(context); err != nil {
return verror.Make(ErrOperationFailed, context.Context())
}
return nil
}
func (i *appRepoService) allApplications() ([]string, error) {
apps, err := i.store.BindObject("/applications").Children()
if err != nil {
return nil, err
}
return apps, nil
}
func (i *appRepoService) allAppVersions(appName string) ([]string, error) {
profiles, err := i.store.BindObject(naming.Join("/applications", appName)).Children()
if err != nil {
return nil, err
}
uniqueVersions := make(map[string]struct{})
for _, profile := range profiles {
versions, err := i.store.BindObject(naming.Join("/applications", appName, profile)).Children()
if err != nil {
return nil, err
}
for _, v := range versions {
uniqueVersions[v] = struct{}{}
}
}
versions := make([]string, len(uniqueVersions))
index := 0
for v, _ := range uniqueVersions {
versions[index] = v
index++
}
return versions, nil
}
func (i *appRepoService) GlobChildren__(ipc.ServerContext) (<-chan string, error) {
vlog.VI(0).Infof("%v.GlobChildren__()", i.suffix)
i.store.Lock()
defer i.store.Unlock()
var elems []string
if i.suffix != "" {
elems = strings.Split(i.suffix, "/")
}
var results []string
var err error
switch len(elems) {
case 0:
results, err = i.allApplications()
if err != nil {
return nil, err
}
case 1:
results, err = i.allAppVersions(elems[0])
if err != nil {
return nil, err
}
case 2:
versions, err := i.allAppVersions(elems[0])
if err != nil {
return nil, err
}
for _, v := range versions {
if v == elems[1] {
return nil, nil
}
}
return nil, verror.Make(ErrNotFound, nil)
default:
return nil, verror.Make(ErrNotFound, nil)
}
ch := make(chan string, len(results))
for _, r := range results {
ch <- r
}
close(ch)
return ch, nil
}
func (i *appRepoService) GetACL(ctx ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
i.store.Lock()
defer i.store.Unlock()
path := naming.Join("/acls", i.suffix, "data")
return getACL(i.store, path)
}
func (i *appRepoService) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
i.store.Lock()
defer i.store.Unlock()
path := naming.Join("/acls", i.suffix, "data")
return setACL(i.store, path, acl, etag)
}
// getACL fetches a TaggedACLMap out of the Memstore at the provided path.
// path is expected to already have been cleaned by naming.Join or its ilk.
func getACL(store *fs.Memstore, path string) (access.TaggedACLMap, string, error) {
entry, err := store.BindObject(path).Get(nil)
if verror.Is(err, fs.ErrNotInMemStore.ID) {
// No ACL exists
return nil, "default", verror.Make(ErrNotFound, nil)
} else if err != nil {
vlog.Errorf("getACL: internal failure in fs.Memstore")
return nil, "", err
}
acl, ok := entry.Value.(access.TaggedACLMap)
if !ok {
return nil, "", err
}
etag, err := acls.ComputeEtag(acl)
if err != nil {
return nil, "", err
}
return acl, etag, nil
}
// setACL wites a TaggedACLMap into the Memstore at the provided path.
// where path is expected to have already been cleaned by naming.Join.
func setACL(store *fs.Memstore, path string, acl access.TaggedACLMap, etag string) error {
_, oetag, err := getACL(store, path)
if verror.Is(err, ErrNotFound.ID) {
oetag = etag
} else if err != nil {
return err
}
if oetag != etag {
return verror.Make(access.BadEtag, nil, etag, oetag)
}
tname, err := store.BindTransactionRoot("").CreateTransaction(nil)
if err != nil {
return err
}
object := store.BindObject(path)
if _, err := object.Put(nil, acl); err != nil {
return err
}
if err := store.BindTransaction(tname).Commit(nil); err != nil {
return verror.Make(ErrOperationFailed, nil)
}
return nil
}