blob: 9bcec0d460de4b90fb5c0c6a16b42adb147cd82a [file] [log] [blame]
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -07001// Copyright 2015 The Vanadium 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
5package server
6
7import (
Adam Sadovsky232c3662015-06-04 15:00:09 -07008 "path"
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -07009 "sync"
10
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070011 "v.io/v23/context"
12 "v.io/v23/rpc"
13 "v.io/v23/security/access"
Adam Sadovskyf2efeb52015-08-31 14:17:49 -070014 wire "v.io/v23/services/syncbase"
15 nosqlwire "v.io/v23/services/syncbase/nosql"
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070016 "v.io/v23/verror"
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070017 "v.io/x/lib/vlog"
Adam Sadovskyf2efeb52015-08-31 14:17:49 -070018 "v.io/x/ref/services/syncbase/server/interfaces"
19 "v.io/x/ref/services/syncbase/server/nosql"
20 "v.io/x/ref/services/syncbase/server/util"
21 "v.io/x/ref/services/syncbase/store"
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070022)
23
Adam Sadovsky1c91f2a2015-06-04 22:23:51 -070024// app is a per-app singleton (i.e. not per-request) that handles App RPCs.
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070025type app struct {
26 name string
27 s *service
28 // The fields below are initialized iff this app exists.
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070029 exists bool
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070030 // Guards the fields below. Held during database Create, Delete, and
31 // SetPermissions.
32 mu sync.Mutex
Adam Sadovskybc00bd62015-05-22 12:50:03 -070033 dbs map[string]interfaces.Database
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070034}
35
36var (
37 _ wire.AppServerMethods = (*app)(nil)
Adam Sadovskybc00bd62015-05-22 12:50:03 -070038 _ interfaces.App = (*app)(nil)
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070039)
40
41////////////////////////////////////////
42// RPC methods
43
Adam Sadovsky20c70b92015-09-15 11:47:11 -070044// TODO(sadovsky): Implement Glob__ or GlobChildren__.
45
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070046// TODO(sadovsky): Require the app name to match the client's blessing name.
47// I.e. reserve names at the app level of the hierarchy.
48func (a *app) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions) error {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070049 if a.exists {
50 return verror.New(verror.ErrExist, ctx, a.name)
51 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070052 // This app does not yet exist; a is just an ephemeral handle that holds
53 // {name string, s *service}. a.s.createApp will create a new app handle and
54 // store it in a.s.apps[a.name].
55 return a.s.createApp(ctx, call, a.name, perms)
56}
57
Ali Ghassemif074df82015-09-03 15:03:22 -070058func (a *app) Destroy(ctx *context.T, call rpc.ServerCall) error {
59 return a.s.destroyApp(ctx, call, a.name)
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070060}
61
Ivan Pilat8e4a4ab2015-07-14 12:14:52 -070062func (a *app) Exists(ctx *context.T, call rpc.ServerCall) (bool, error) {
63 if !a.exists {
64 return false, nil
65 }
66 return util.ErrorToExists(util.GetWithAuth(ctx, call, a.s.st, a.stKey(), &appData{}))
67}
68
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070069func (a *app) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070070 if !a.exists {
71 return verror.New(verror.ErrNoExist, ctx, a.name)
72 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070073 return a.s.setAppPerms(ctx, call, a.name, perms, version)
74}
75
76func (a *app) GetPermissions(ctx *context.T, call rpc.ServerCall) (perms access.Permissions, version string, err error) {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070077 if !a.exists {
78 return nil, "", verror.New(verror.ErrNoExist, ctx, a.name)
79 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070080 data := &appData{}
Adam Sadovskya31a7cd2015-07-08 10:44:07 -070081 if err := util.GetWithAuth(ctx, call, a.s.st, a.stKey(), data); err != nil {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070082 return nil, "", err
83 }
84 return data.Perms, util.FormatVersion(data.Version), nil
85}
86
Adam Sadovsky20c70b92015-09-15 11:47:11 -070087func (a *app) ListDatabases(ctx *context.T, call rpc.ServerCall) ([]string, error) {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070088 if !a.exists {
Adam Sadovsky20c70b92015-09-15 11:47:11 -070089 return nil, verror.New(verror.ErrNoExist, ctx, a.name)
Adam Sadovskyd6b9a232015-06-16 14:04:45 -070090 }
Adam Sadovsky49261192015-05-19 17:39:59 -070091 // Check perms.
92 sn := a.s.st.NewSnapshot()
Adam Sadovskya31a7cd2015-07-08 10:44:07 -070093 if err := util.GetWithAuth(ctx, call, sn, a.stKey(), &appData{}); err != nil {
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -070094 sn.Abort()
Adam Sadovsky20c70b92015-09-15 11:47:11 -070095 return nil, err
Adam Sadovsky49261192015-05-19 17:39:59 -070096 }
Adam Sadovsky20c70b92015-09-15 11:47:11 -070097 return util.ListChildren(ctx, call, sn, util.JoinKeyParts(util.DbInfoPrefix, a.name))
Adam Sadovsky49261192015-05-19 17:39:59 -070098}
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -070099
100////////////////////////////////////////
Adam Sadovskybc00bd62015-05-22 12:50:03 -0700101// interfaces.App methods
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700102
Himabindu Puchaf9ec56f2015-06-02 11:34:05 -0700103func (a *app) Service() interfaces.Service {
104 return a.s
105}
106
Adam Sadovskybc00bd62015-05-22 12:50:03 -0700107func (a *app) NoSQLDatabase(ctx *context.T, call rpc.ServerCall, dbName string) (interfaces.Database, error) {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700108 if !a.exists {
109 vlog.Fatalf("app %q does not exist", a.name)
110 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700111 a.mu.Lock()
112 defer a.mu.Unlock()
113 d, ok := a.dbs[dbName]
114 if !ok {
Adam Sadovskya3fc33c2015-06-02 18:44:46 -0700115 return nil, verror.New(verror.ErrNoExist, ctx, dbName)
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700116 }
117 return d, nil
118}
119
Raja Daouda9cfe5e2015-05-28 14:38:34 -0700120func (a *app) NoSQLDatabaseNames(ctx *context.T, call rpc.ServerCall) ([]string, error) {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700121 if !a.exists {
122 vlog.Fatalf("app %q does not exist", a.name)
123 }
Adam Sadovsky8db74432015-05-29 17:37:32 -0700124 // In the future this API will likely be replaced by one that streams the
125 // database names.
Raja Daouda9cfe5e2015-05-28 14:38:34 -0700126 a.mu.Lock()
127 defer a.mu.Unlock()
128 dbNames := make([]string, 0, len(a.dbs))
129 for n := range a.dbs {
130 dbNames = append(dbNames, n)
131 }
132 return dbNames, nil
133}
134
Jatin Lodhiaf6486d42015-07-17 15:57:36 -0700135func (a *app) CreateNoSQLDatabase(ctx *context.T, call rpc.ServerCall, dbName string, perms access.Permissions, metadata *nosqlwire.SchemaMetadata) error {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700136 if !a.exists {
137 vlog.Fatalf("app %q does not exist", a.name)
138 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700139 // TODO(sadovsky): Crash if any step fails, and use WAL to ensure that if we
140 // crash, upon restart we execute any remaining steps before we start handling
141 // client requests.
142 //
143 // Steps:
144 // 1. Check appData perms, create dbInfo record.
145 // 2. Initialize database.
146 // 3. Flip dbInfo.Initialized to true. <===== CHANGE BECOMES VISIBLE
147 a.mu.Lock()
148 defer a.mu.Unlock()
149 if _, ok := a.dbs[dbName]; ok {
150 // TODO(sadovsky): Should this be ErrExistOrNoAccess, for privacy?
151 return verror.New(verror.ErrExist, ctx, dbName)
152 }
153
154 // 1. Check appData perms, create dbInfo record.
Adam Sadovskyb6a5aa32015-07-07 13:05:26 -0700155 rootDir, engine := a.rootDirForDb(dbName), a.s.opts.Engine
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700156 aData := &appData{}
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -0700157 if err := store.RunInTransaction(a.s.st, func(tx store.Transaction) error {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700158 // Check appData perms.
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -0700159 if err := util.GetWithAuth(ctx, call, tx, a.stKey(), aData); err != nil {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700160 return err
161 }
162 // Check for "database already exists".
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -0700163 if _, err := a.getDbInfo(ctx, tx, dbName); verror.ErrorID(err) != verror.ErrNoExist.ID {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700164 if err != nil {
165 return err
166 }
167 // TODO(sadovsky): Should this be ErrExistOrNoAccess, for privacy?
168 return verror.New(verror.ErrExist, ctx, dbName)
169 }
170 // Write new dbInfo.
171 info := &dbInfo{
Adam Sadovskyb6a5aa32015-07-07 13:05:26 -0700172 Name: dbName,
173 RootDir: rootDir,
174 Engine: engine,
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700175 }
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -0700176 return a.putDbInfo(ctx, tx, dbName, info)
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700177 }); err != nil {
178 return err
179 }
180
181 // 2. Initialize database.
182 if perms == nil {
183 perms = aData.Perms
184 }
Jatin Lodhiaf6486d42015-07-17 15:57:36 -0700185 d, err := nosql.NewDatabase(ctx, a, dbName, metadata, nosql.DatabaseOptions{
Adam Sadovsky232c3662015-06-04 15:00:09 -0700186 Perms: perms,
Adam Sadovskyb6a5aa32015-07-07 13:05:26 -0700187 RootDir: rootDir,
188 Engine: engine,
Adam Sadovsky232c3662015-06-04 15:00:09 -0700189 })
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700190 if err != nil {
191 return err
192 }
193
194 // 3. Flip dbInfo.Initialized to true.
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -0700195 if err := store.RunInTransaction(a.s.st, func(tx store.Transaction) error {
196 return a.updateDbInfo(ctx, tx, dbName, func(info *dbInfo) error {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700197 info.Initialized = true
198 return nil
199 })
200 }); err != nil {
201 return err
202 }
203
204 a.dbs[dbName] = d
205 return nil
206}
207
Ali Ghassemif074df82015-09-03 15:03:22 -0700208func (a *app) DestroyNoSQLDatabase(ctx *context.T, call rpc.ServerCall, dbName string) error {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700209 if !a.exists {
210 vlog.Fatalf("app %q does not exist", a.name)
211 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700212 // TODO(sadovsky): Crash if any step fails, and use WAL to ensure that if we
213 // crash, upon restart we execute any remaining steps before we start handling
214 // client requests.
215 //
216 // Steps:
217 // 1. Check databaseData perms.
218 // 2. Flip dbInfo.Deleted to true. <===== CHANGE BECOMES VISIBLE
219 // 3. Delete database.
220 // 4. Delete dbInfo record.
221 a.mu.Lock()
222 defer a.mu.Unlock()
223 d, ok := a.dbs[dbName]
224 if !ok {
Ali Ghassemif074df82015-09-03 15:03:22 -0700225 return nil // destroy is idempotent
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700226 }
227
228 // 1. Check databaseData perms.
Himabindu Puchaf9ec56f2015-06-02 11:34:05 -0700229 if err := d.CheckPermsInternal(ctx, call, d.St()); err != nil {
Adam Sadovskya3fc33c2015-06-02 18:44:46 -0700230 if verror.ErrorID(err) == verror.ErrNoExist.ID {
Ali Ghassemif074df82015-09-03 15:03:22 -0700231 return nil // destroy is idempotent
Adam Sadovskya3fc33c2015-06-02 18:44:46 -0700232 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700233 return err
234 }
235
236 // 2. Flip dbInfo.Deleted to true.
Sergey Rogulenko1068b1a2015-08-03 16:53:27 -0700237 if err := store.RunInTransaction(a.s.st, func(tx store.Transaction) error {
238 return a.updateDbInfo(ctx, tx, dbName, func(info *dbInfo) error {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700239 info.Deleted = true
240 return nil
241 })
242 }); err != nil {
243 return err
244 }
245
246 // 3. Delete database.
Sergey Rogulenkod3a738b2015-06-10 18:37:41 -0700247 if err := d.St().Close(); err != nil {
248 return err
249 }
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700250 if err := util.DestroyStore(a.s.opts.Engine, a.rootDirForDb(dbName)); err != nil {
Sergey Rogulenkod3a738b2015-06-10 18:37:41 -0700251 return err
252 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700253
254 // 4. Delete dbInfo record.
Sergey Rogulenko1b92b4f2015-06-18 23:06:11 -0700255 if err := a.delDbInfo(ctx, a.s.st, dbName); err != nil {
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700256 return err
257 }
258
259 delete(a.dbs, dbName)
260 return nil
261}
262
263func (a *app) SetDatabasePerms(ctx *context.T, call rpc.ServerCall, dbName string, perms access.Permissions, version string) error {
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700264 if !a.exists {
265 vlog.Fatalf("app %q does not exist", a.name)
266 }
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700267 a.mu.Lock()
268 defer a.mu.Unlock()
269 d, ok := a.dbs[dbName]
270 if !ok {
Adam Sadovskya3fc33c2015-06-02 18:44:46 -0700271 return verror.New(verror.ErrNoExist, ctx, dbName)
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700272 }
273 return d.SetPermsInternal(ctx, call, perms, version)
274}
275
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700276func (a *app) Name() string {
277 return a.name
278}
279
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700280////////////////////////////////////////
281// Internal helpers
282
Adam Sadovskya31a7cd2015-07-08 10:44:07 -0700283func (a *app) stKey() string {
284 return util.JoinKeyParts(util.AppPrefix, a.stKeyPart())
285}
286
Adam Sadovskyf3b7abc2015-05-04 15:33:22 -0700287func (a *app) stKeyPart() string {
288 return a.name
289}
Sergey Rogulenkod3a738b2015-06-10 18:37:41 -0700290
Adam Sadovskyd6b9a232015-06-16 14:04:45 -0700291func (a *app) rootDirForDb(dbName string) string {
Sergey Rogulenkod3a738b2015-06-10 18:37:41 -0700292 return path.Join(a.s.opts.RootDir, "apps", a.name, dbName)
293}