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  }