github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/server/sync.go (about) 1 // Copyright (C) 2015 NTT Innovation Institute, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package server 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/cloudwan/gohan/db" 26 "github.com/cloudwan/gohan/db/pagination" 27 "github.com/cloudwan/gohan/db/transaction" 28 "github.com/cloudwan/gohan/extension" 29 30 "github.com/cloudwan/gohan/schema" 31 gohan_sync "github.com/cloudwan/gohan/sync" 32 "github.com/cloudwan/gohan/util" 33 ) 34 35 const ( 36 syncPath = "gohan/cluster/sync" 37 lockPath = "gohan/cluster/lock" 38 39 configPrefix = "/config/" 40 statePrefix = "/state/" 41 monitoringPrefix = "/monitoring/" 42 43 eventPollingTime = 30 * time.Second 44 eventPollingLimit = 10000 45 ) 46 47 var transactionCommited chan int 48 49 func transactionCommitInformer() chan int { 50 if transactionCommited == nil { 51 transactionCommited = make(chan int, 1) 52 } 53 return transactionCommited 54 } 55 56 //DbSyncWrapper wraps db.DB so it logs events in database on every transaction. 57 type DbSyncWrapper struct { 58 db.DB 59 } 60 61 // Begin wraps transaction object with sync 62 func (sw *DbSyncWrapper) Begin() (transaction.Transaction, error) { 63 tx, err := sw.DB.Begin() 64 if err != nil { 65 return nil, err 66 } 67 return syncTransactionWrap(tx), nil 68 } 69 70 type transactionEventLogger struct { 71 transaction.Transaction 72 eventLogged bool 73 } 74 75 func syncTransactionWrap(tx transaction.Transaction) *transactionEventLogger { 76 return &transactionEventLogger{tx, false} 77 } 78 79 func (tl *transactionEventLogger) logEvent(eventType string, resource *schema.Resource, version int64) error { 80 schemaManager := schema.GetManager() 81 eventSchema, ok := schemaManager.Schema("event") 82 if !ok { 83 return fmt.Errorf("event schema not found") 84 } 85 86 if resource.Schema().Metadata["nosync"] == true { 87 log.Debug("skipping event logging for schema: %s", resource.Schema().ID) 88 return nil 89 } 90 body, err := resource.JSONString() 91 if err != nil { 92 return fmt.Errorf("Error during event resource deserialisation: %s", err.Error()) 93 } 94 eventResource, err := schema.NewResource(eventSchema, map[string]interface{}{ 95 "type": eventType, 96 "path": resource.Path(), 97 "version": version, 98 "body": body, 99 "timestamp": int64(time.Now().Unix()), 100 }) 101 tl.eventLogged = true 102 return tl.Transaction.Create(eventResource) 103 } 104 105 func (tl *transactionEventLogger) Create(resource *schema.Resource) error { 106 err := tl.Transaction.Create(resource) 107 if err != nil { 108 return err 109 } 110 return tl.logEvent("create", resource, 1) 111 } 112 113 func (tl *transactionEventLogger) Update(resource *schema.Resource) error { 114 err := tl.Transaction.Update(resource) 115 if err != nil { 116 return err 117 } 118 if !resource.Schema().StateVersioning() { 119 return tl.logEvent("update", resource, 0) 120 } 121 state, err := tl.StateFetch(resource.Schema(), resource.ID(), nil) 122 if err != nil { 123 return err 124 } 125 return tl.logEvent("update", resource, state.ConfigVersion) 126 } 127 128 func (tl *transactionEventLogger) Delete(s *schema.Schema, resourceID interface{}) error { 129 resource, err := tl.Fetch(s, resourceID, nil) 130 if err != nil { 131 return err 132 } 133 configVersion := int64(0) 134 if resource.Schema().StateVersioning() { 135 state, err := tl.StateFetch(s, resourceID, nil) 136 if err != nil { 137 return err 138 } 139 configVersion = state.ConfigVersion + 1 140 } 141 err = tl.Transaction.Delete(s, resourceID) 142 if err != nil { 143 return err 144 } 145 return tl.logEvent("delete", resource, configVersion) 146 } 147 148 func (tl *transactionEventLogger) Commit() error { 149 err := tl.Transaction.Commit() 150 if err != nil { 151 return err 152 } 153 if !tl.eventLogged { 154 return nil 155 } 156 committed := transactionCommitInformer() 157 select { 158 case committed <- 1: 159 default: 160 } 161 return nil 162 } 163 164 func (server *Server) listEvents() ([]*schema.Resource, error) { 165 tx, err := server.db.Begin() 166 if err != nil { 167 return nil, err 168 } 169 defer tx.Close() 170 schemaManager := schema.GetManager() 171 eventSchema, _ := schemaManager.Schema("event") 172 paginator, _ := pagination.NewPaginator(eventSchema, "id", pagination.ASC, eventPollingLimit, 0) 173 resourceList, _, err := tx.List(eventSchema, nil, paginator) 174 if err != nil { 175 return nil, err 176 } 177 return resourceList, nil 178 } 179 180 func (server *Server) syncEvent(resource *schema.Resource) error { 181 schemaManager := schema.GetManager() 182 eventSchema, _ := schemaManager.Schema("event") 183 tx, err := server.db.Begin() 184 if err != nil { 185 return err 186 } 187 defer tx.Close() 188 eventType := resource.Get("type").(string) 189 path := resource.Get("path").(string) 190 path = configPrefix + path 191 body := resource.Get("body").(string) 192 version, _ := resource.Get("version").(uint64) 193 log.Debug("event %s", eventType) 194 195 if eventType == "create" || eventType == "update" { 196 log.Debug("set %s on sync", path) 197 content, err := json.Marshal(map[string]interface{}{ 198 "body": body, 199 "version": version, 200 }) 201 if err != nil { 202 log.Error(fmt.Sprintf("When marshalling sync object: %s", err)) 203 return err 204 } 205 err = server.sync.Update(path, string(content)) 206 if err != nil { 207 log.Error(fmt.Sprintf("%s on sync", err)) 208 return err 209 } 210 } else if eventType == "delete" { 211 log.Debug("delete %s", path) 212 err = server.sync.Delete(path) 213 if err != nil { 214 log.Error(fmt.Sprintf("Delete from sync failed %s", err)) 215 return err 216 } 217 } 218 log.Debug("delete event %d", resource.Get("id")) 219 id := resource.Get("id") 220 err = tx.Delete(eventSchema, id) 221 if err != nil { 222 log.Error(fmt.Sprintf("delete failed: %s", err)) 223 return err 224 } 225 226 err = tx.Commit() 227 if err != nil { 228 log.Error(fmt.Sprintf("commit failed: %s", err)) 229 return err 230 } 231 return nil 232 } 233 234 //Start sync Process 235 func startSyncProcess(server *Server) { 236 pollingTicker := time.Tick(eventPollingTime) 237 committed := transactionCommitInformer() 238 go func() { 239 recentlySynced := false 240 for server.running { 241 select { 242 case <-pollingTicker: 243 if recentlySynced { 244 recentlySynced = false 245 continue 246 } 247 case <-committed: 248 recentlySynced = true 249 } 250 server.sync.Lock(syncPath, true) 251 server.Sync() 252 } 253 server.sync.Unlock(syncPath) 254 }() 255 } 256 257 //Stop Sync Process 258 func stopSyncProcess(server *Server) { 259 server.sync.Unlock(syncPath) 260 } 261 262 //Sync to sync backend database table 263 func (server *Server) Sync() error { 264 resourceList, err := server.listEvents() 265 if err != nil { 266 return err 267 } 268 for _, resource := range resourceList { 269 err = server.syncEvent(resource) 270 if err != nil { 271 return err 272 } 273 } 274 return nil 275 } 276 277 //StateUpdate updates the state in the db based on the sync event 278 func StateUpdate(response *gohan_sync.Event, server *Server) error { 279 dataStore := server.db 280 schemaPath := "/" + strings.TrimPrefix(response.Key, statePrefix) 281 var curSchema *schema.Schema 282 manager := schema.GetManager() 283 for _, s := range manager.Schemas() { 284 if strings.HasPrefix(schemaPath, s.URL) { 285 curSchema = s 286 break 287 } 288 } 289 if curSchema == nil || !curSchema.StateVersioning() { 290 log.Debug("State update on unexpected path '%s'", schemaPath) 291 return nil 292 } 293 resourceID := strings.TrimPrefix(schemaPath, curSchema.URL+"/") 294 295 tx, err := dataStore.Begin() 296 if err != nil { 297 return err 298 } 299 defer tx.Close() 300 curResource, err := tx.Fetch(curSchema, resourceID, nil) 301 if err != nil { 302 return err 303 } 304 resourceState, err := tx.StateFetch(curSchema, resourceID, nil) 305 if err != nil { 306 return err 307 } 308 if resourceState.StateVersion == resourceState.ConfigVersion { 309 return nil 310 } 311 stateVersion, ok := response.Data["version"].(float64) 312 if !ok { 313 return fmt.Errorf("No version in state information") 314 } 315 oldStateVersion := resourceState.StateVersion 316 resourceState.StateVersion = int64(stateVersion) 317 if resourceState.StateVersion < oldStateVersion { 318 return nil 319 } 320 if newError, ok := response.Data["error"].(string); ok { 321 resourceState.Error = newError 322 } 323 if newState, ok := response.Data["state"].(string); ok { 324 resourceState.State = newState 325 } 326 327 environmentManager := extension.GetManager() 328 environment, haveEnvironment := environmentManager.GetEnvironment(curSchema.ID) 329 context := map[string]interface{}{} 330 331 if haveEnvironment { 332 serviceAuthorization, _ := server.keystoneIdentity.GetServiceAuthorization() 333 334 context["catalog"] = serviceAuthorization.Catalog() 335 context["auth_token"] = serviceAuthorization.AuthToken() 336 context["resource"] = curResource.Data() 337 context["state"] = response.Data 338 context["config_version"] = resourceState.ConfigVersion 339 context["transaction"] = tx 340 341 if err := extension.HandleEvent(context, environment, "pre_state_update_in_transaction"); err != nil { 342 return err 343 } 344 } 345 346 err = tx.StateUpdate(curResource, &resourceState) 347 if err != nil { 348 return err 349 } 350 351 if haveEnvironment { 352 if err := extension.HandleEvent(context, environment, "post_state_update_in_transaction"); err != nil { 353 return err 354 } 355 } 356 357 return tx.Commit() 358 } 359 360 //MonitoringUpdate updates the state in the db based on the sync event 361 func MonitoringUpdate(response *gohan_sync.Event, server *Server) error { 362 dataStore := server.db 363 schemaPath := "/" + strings.TrimPrefix(response.Key, monitoringPrefix) 364 var curSchema *schema.Schema 365 manager := schema.GetManager() 366 for _, s := range manager.Schemas() { 367 if strings.HasPrefix(schemaPath, s.URL) { 368 curSchema = s 369 break 370 } 371 } 372 if curSchema == nil || !curSchema.StateVersioning() { 373 log.Debug("Monitoring update on unexpected path '%s'", schemaPath) 374 return nil 375 } 376 resourceID := strings.TrimPrefix(schemaPath, curSchema.URL+"/") 377 378 tx, err := dataStore.Begin() 379 if err != nil { 380 return err 381 } 382 defer tx.Close() 383 curResource, err := tx.Fetch(curSchema, resourceID, nil) 384 if err != nil { 385 return err 386 } 387 resourceState, err := tx.StateFetch(curSchema, resourceID, nil) 388 if err != nil { 389 return err 390 } 391 if resourceState.ConfigVersion != resourceState.StateVersion { 392 return nil 393 } 394 var ok bool 395 resourceState.Monitoring, ok = response.Data["monitoring"].(string) 396 if !ok { 397 return fmt.Errorf("No monitoring in state information") 398 } 399 400 environmentManager := extension.GetManager() 401 environment, haveEnvironment := environmentManager.GetEnvironment(curSchema.ID) 402 context := map[string]interface{}{} 403 context["resource"] = curResource.Data() 404 context["monitoring"] = resourceState.Monitoring 405 context["transaction"] = tx 406 407 if haveEnvironment { 408 if err := extension.HandleEvent(context, environment, "pre_monitoring_update_in_transaction"); err != nil { 409 return err 410 } 411 } 412 413 err = tx.StateUpdate(curResource, &resourceState) 414 if err != nil { 415 return err 416 } 417 418 if haveEnvironment { 419 if err := extension.HandleEvent(context, environment, "post_monitoring_update_in_transaction"); err != nil { 420 return err 421 } 422 } 423 424 return tx.Commit() 425 } 426 427 //TODO(nati) integrate with watch process 428 func startStateUpdatingProcess(server *Server) { 429 430 stateResponseChan := make(chan *gohan_sync.Event) 431 stateStopChan := make(chan bool) 432 433 if _, err := server.sync.Fetch(statePrefix); err != nil { 434 server.sync.Update(statePrefix, "{}") 435 } 436 437 if _, err := server.sync.Fetch(statePrefix); err == nil { 438 server.sync.Update(monitoringPrefix, "{}") 439 } 440 441 go func() { 442 for server.running { 443 lockKey := lockPath + "state" 444 err := server.sync.Lock(lockKey, true) 445 if err != nil { 446 log.Warning("Can't start state watch process due to lock", err) 447 time.Sleep(5 * time.Second) 448 continue 449 } 450 defer func() { 451 server.sync.Unlock(lockKey) 452 }() 453 454 err = server.sync.Watch(statePrefix, stateResponseChan, stateStopChan) 455 if err != nil { 456 log.Error(fmt.Sprintf("sync watch error: %s", err)) 457 } 458 } 459 }() 460 go func() { 461 for server.running { 462 response := <-stateResponseChan 463 err := StateUpdate(response, server) 464 if err != nil { 465 log.Warning(fmt.Sprintf("error during state update: %s", err)) 466 } 467 } 468 stateStopChan <- true 469 }() 470 monitoringResponseChan := make(chan *gohan_sync.Event) 471 monitoringStopChan := make(chan bool) 472 go func() { 473 for server.running { 474 lockKey := lockPath + "monitoring" 475 err := server.sync.Lock(lockKey, true) 476 if err != nil { 477 log.Warning("Can't start state watch process due to lock", err) 478 time.Sleep(5 * time.Second) 479 continue 480 } 481 defer func() { 482 server.sync.Unlock(lockKey) 483 }() 484 err = server.sync.Watch(monitoringPrefix, monitoringResponseChan, monitoringStopChan) 485 if err != nil { 486 log.Error(fmt.Sprintf("sync watch error: %s", err)) 487 } 488 } 489 }() 490 go func() { 491 for server.running { 492 response := <-monitoringResponseChan 493 err := MonitoringUpdate(response, server) 494 if err != nil { 495 log.Warning(fmt.Sprintf("error during state update: %s", err)) 496 } 497 } 498 monitoringStopChan <- true 499 }() 500 } 501 502 func stopStateUpdatingProcess(server *Server) { 503 } 504 505 //Run extension on sync 506 func runExtensionOnSync(server *Server, response *gohan_sync.Event, env extension.Environment) { 507 context := map[string]interface{}{ 508 "action": response.Action, 509 "data": response.Data, 510 "key": response.Key, 511 } 512 if err := env.HandleEvent("notification", context); err != nil { 513 log.Warning(fmt.Sprintf("extension error: %s", err)) 514 return 515 } 516 return 517 } 518 519 //Sync Watch Process 520 func startSyncWatchProcess(server *Server) { 521 manager := schema.GetManager() 522 config := util.GetConfig() 523 watch := config.GetStringList("watch/keys", nil) 524 events := config.GetStringList("watch/events", nil) 525 maxWorkerCount := config.GetParam("watch/worker_count", 0).(int) 526 if watch == nil { 527 return 528 } 529 530 extensions := map[string]extension.Environment{} 531 for _, event := range events { 532 path := "sync://" + event 533 env := newEnvironment(server.db, server.keystoneIdentity) 534 err := env.LoadExtensionsForPath(manager.Extensions, path) 535 if err != nil { 536 log.Fatal(fmt.Sprintf("Extensions parsing error: %v", err)) 537 } 538 extensions[event] = env 539 } 540 responseChan := make(chan *gohan_sync.Event) 541 stopChan := make(chan bool) 542 for _, path := range watch { 543 go func(path string) { 544 for server.running { 545 lockKey := lockPath + "watch" 546 err := server.sync.Lock(lockKey, true) 547 if err != nil { 548 log.Warning("Can't start watch process due to lock", err) 549 time.Sleep(5 * time.Second) 550 continue 551 } 552 defer func() { 553 server.sync.Unlock(lockKey) 554 }() 555 err = server.sync.Watch(path, responseChan, stopChan) 556 if err != nil { 557 log.Error(fmt.Sprintf("sync watch error: %s", err)) 558 } 559 } 560 }(path) 561 } 562 //main response lisnter process 563 go func() { 564 var wg sync.WaitGroup 565 workerCount := 0 566 for server.running { 567 response := <-responseChan 568 wg.Add(1) 569 workerCount++ 570 //spawn workers up to max worker count 571 go func() { 572 defer func() { 573 workerCount-- 574 wg.Done() 575 }() 576 for _, event := range events { 577 //match extensions 578 if strings.HasPrefix(response.Key, "/"+event) { 579 env := extensions[event] 580 runExtensionOnSync(server, response, env.Clone()) 581 return 582 } 583 } 584 }() 585 // Wait if worker pool is full 586 if workerCount > maxWorkerCount { 587 wg.Wait() 588 } 589 } 590 stopChan <- true 591 }() 592 593 } 594 595 //Stop Watch Process 596 func stopSyncWatchProcess(server *Server) { 597 }