Merge pull request #6 from 30x/all-handlers
All handlers
diff --git a/apigeeSync_suite_test.go b/apigeeSync_suite_test.go
index 92c6aff..ef8a1f6 100644
--- a/apigeeSync_suite_test.go
+++ b/apigeeSync_suite_test.go
@@ -1,12 +1,389 @@
-package apidApigeeSync_test
+package apidApigeeSync
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
+ "github.com/30x/apid"
+ "github.com/30x/apid/factory"
+ "io/ioutil"
+ "net/http/httptest"
+ "os"
+ "encoding/json"
+ "net/http"
+ "github.com/apigee-labs/transicator/common"
+ "time"
+ "strconv"
)
+var (
+ tmpDir string
+ testServer *httptest.Server
+ testRouter apid.Router
+)
+
+const testScope = "bootstrap"
+
+var _ = BeforeSuite(func(done Done) {
+ var phase int
+
+ apid.Initialize(factory.DefaultServicesFactory())
+
+ config := apid.Config()
+
+ var err error
+ tmpDir, err = ioutil.TempDir("", "api_test")
+ Expect(err).NotTo(HaveOccurred())
+ config.Set("local_storage_path", tmpDir)
+
+ testRouter = apid.API().Router()
+ testServer = httptest.NewServer(testRouter)
+
+ config.Set(configProxyServerBaseURI, testServer.URL)
+ config.Set(configSnapServerBaseURI, testServer.URL)
+ config.Set(configChangeServerBaseURI, testServer.URL)
+ config.Set(configApidClusterId, "apid_config_scope_0")
+ config.Set(configName, "testhost")
+
+ config.Set(configSnapshotProtocol, "json")
+ config.Set(configApidClusterId, testScope)
+ config.Set(configConsumerKey, "XXXXXXX")
+ config.Set(configConsumerSecret, "YYYYYYY")
+
+ // mock upstream testServer
+ testRouter.HandleFunc("/accesstoken", func(w http.ResponseWriter, req *http.Request) {
+ defer GinkgoRecover()
+
+ Expect(req.Method).To(Equal("POST"))
+ Expect(req.Header.Get("Content-Type")).To(Equal("application/x-www-form-urlencoded; param=value"))
+
+ err := req.ParseForm()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(req.Form.Get("grant_type")).To(Equal("client_credentials"))
+ Expect(req.Header.Get("status")).To(Equal("ONLINE"))
+ Expect(req.Header.Get("apid_cluster_Id")).To(Equal("bootstrap"))
+ Expect(req.Header.Get("display_name")).To(Equal("testhost"))
+
+ var plugInfo []pluginDetail
+ plInfo := []byte(req.Header.Get("plugin_details"))
+ err = json.Unmarshal(plInfo, &plugInfo)
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(plugInfo[0].Name).To(Equal("apidApigeeSync"))
+ Expect(plugInfo[0].SchemaVersion).To(Equal("0.0.2"))
+
+ res := oauthTokenResp{}
+ res.AccessToken = "accesstoken"
+ body, err := json.Marshal(res)
+ Expect(err).NotTo(HaveOccurred())
+ w.Write(body)
+
+ }).Methods("POST")
+
+ testRouter.HandleFunc("/snapshots", func(w http.ResponseWriter, req *http.Request) {
+ defer GinkgoRecover()
+
+ q := req.URL.Query()
+
+ if phase == 0 {
+ phase = 1
+ Expect(q.Get("scope")).To(Equal(testScope))
+ Expect(req.Header.Get("apid_cluster_Id")).To(Equal("bootstrap"))
+
+ apidcfgItem := common.Row{}
+ apidcfgItems := []common.Row{}
+ apidcfgItemCh := common.Row{}
+ apidcfgItemsCh := []common.Row{}
+ scv := &common.ColumnVal{
+ Value: testScope,
+ Type: 1,
+ }
+ apidcfgItem["id"] = scv
+ scv = &common.ColumnVal{
+ Value: testScope,
+ Type: 1,
+ }
+ apidcfgItem["_change_selector"] = scv
+ apidcfgItems = append(apidcfgItems, apidcfgItem)
+
+ scv = &common.ColumnVal{
+ Value: "apid_config_scope_id_0",
+ Type: 1,
+ }
+ apidcfgItemCh["id"] = scv
+
+ scv = &common.ColumnVal{
+ Value: "apid_config_scope_id_0",
+ Type: 1,
+ }
+ apidcfgItemCh["_change_selector"] = scv
+
+ scv = &common.ColumnVal{
+ Value: testScope,
+ Type: 1,
+ }
+ apidcfgItemCh["apid_cluster_id"] = scv
+
+ scv = &common.ColumnVal{
+ Value: "ert452",
+ Type: 1,
+ }
+ apidcfgItemCh["scope"] = scv
+
+ {
+ scv = &common.ColumnVal{
+ Value: "att",
+ Type: 1,
+ }
+ apidcfgItemCh["org"] = scv
+
+ }
+ {
+ scv = &common.ColumnVal{
+ Value: "prod",
+ Type: 1,
+ }
+ apidcfgItemCh["env"] = scv
+ }
+
+ apidcfgItemsCh = append(apidcfgItemsCh, apidcfgItemCh)
+
+ res := &common.Snapshot{}
+ res.SnapshotInfo = "snapinfo1"
+
+ res.Tables = []common.Table{
+ {
+ Name: "edgex.apid_cluster",
+ Rows: apidcfgItems,
+ },
+ {
+ Name: "edgex.data_scope",
+ Rows: apidcfgItemsCh,
+ },
+ }
+
+ body, err := json.Marshal(res)
+ Expect(err).NotTo(HaveOccurred())
+
+ w.Write(body)
+ return
+ } else {
+ phase = 2
+ Expect(q.Get("scope")).To(Equal("ert452"))
+ res := &common.Snapshot{}
+ res.SnapshotInfo = "snapinfo1"
+
+ apidcfgItems := []common.Row{}
+ res.Tables = []common.Table{
+ {
+ Name: "kms.api_product",
+ Rows: apidcfgItems,
+ },
+ }
+
+ body, err := json.Marshal(res)
+ Expect(err).NotTo(HaveOccurred())
+
+ w.Write(body)
+ return
+ }
+
+ }).Methods("GET")
+
+ testRouter.HandleFunc("/changes", func(w http.ResponseWriter, req *http.Request) {
+ defer GinkgoRecover()
+
+ if req.URL.Query().Get("since") == "lastSeq_01" {
+ go func() {
+ block, err := strconv.Atoi(req.URL.Query().Get("block"))
+ Expect(err).NotTo(HaveOccurred())
+ time.Sleep(time.Duration(block) * time.Second)
+ w.WriteHeader(http.StatusNotModified)
+ }()
+ return
+ }
+
+ Expect(req.Header.Get("apid_cluster_Id")).To(Equal("bootstrap"))
+ q := req.URL.Query()
+ Expect(q.Get("snapshot")).To(Equal("snapinfo1"))
+ scparams := q["scope"]
+ Expect(scparams).To(ContainElement("ert452"))
+ Expect(scparams).To(ContainElement("bootstrap"))
+
+ res := &common.ChangeList{}
+
+ res.LastSequence = "lastSeq_01"
+ mpItems := common.Row{}
+
+ scv := &common.ColumnVal{
+ Value: "apid_config_scope_id_1",
+ Type: 1,
+ }
+ mpItems["id"] = scv
+
+ scv = &common.ColumnVal{
+ Value: testScope,
+ Type: 1,
+ }
+ mpItems["apid_cluster_id"] = scv
+
+ scv = &common.ColumnVal{
+ Value: "ert452",
+ Type: 1,
+ }
+ mpItems["scope"] = scv
+ {
+ scv = &common.ColumnVal{
+ Value: "att",
+ Type: 1,
+ }
+ mpItems["org"] = scv
+ }
+ {
+ scv = &common.ColumnVal{
+ Value: "prod",
+ Type: 1,
+ }
+ mpItems["env"] = scv
+ }
+
+ res.Changes = []common.Change{
+ {
+ Table: "edgex.data_scope",
+ NewRow: mpItems,
+ Operation: 1,
+ },
+ }
+ body, err := json.Marshal(res)
+ Expect(err).NotTo(HaveOccurred())
+ w.Write(body)
+
+ }).Methods("GET")
+
+ // This is actually the first test :)
+ // Tests that entire bootstrap and set of sync operations work
+ apid.Events().ListenFunc(ApigeeSyncEventSelector, func(event apid.Event) {
+ defer GinkgoRecover()
+
+ if s, ok := event.(*common.Snapshot); ok {
+
+ Expect(s.SnapshotInfo).Should(Equal("snapinfo1"))
+
+ for _, t := range s.Tables {
+ switch t.Name {
+
+ case "edgex.apid_cluster":
+ Expect(t.Rows).To(HaveLen(1))
+ r := t.Rows[0]
+ var cs, id string
+ r.Get("_change_selector", &cs)
+ r.Get("id", &id)
+
+ Expect(cs).To(Equal("bootstrap"))
+ Expect(id).To(Equal("bootstrap"))
+
+ case "edgex.data_scope":
+ Expect(t.Rows).To(HaveLen(1))
+ r := t.Rows[0]
+
+ var cs, id, clusterID, env, org, scope string
+ r.Get("_change_selector", &cs)
+ r.Get("id", &id)
+ r.Get("apid_cluster_id", &clusterID)
+ r.Get("env", &env)
+ r.Get("org", &org)
+ r.Get("scope", &scope)
+
+ Expect(id).To(Equal("apid_config_scope_id_0"))
+ Expect(cs).To(Equal("apid_config_scope_id_0"))
+ Expect(clusterID).To(Equal("bootstrap"))
+ Expect(env).To(Equal("prod"))
+ Expect(org).To(Equal("att"))
+ Expect(scope).To(Equal("ert452"))
+
+ case "kms.api_product":
+ Expect(t.Rows).To(HaveLen(0))
+
+ default:
+ Fail("invalid table: " + t.Name)
+ }
+ }
+
+ } else if cl, ok := event.(*common.ChangeList); ok {
+
+ Expect(cl.LastSequence).To(Equal("lastSeq_01"))
+ Expect(cl.Changes).To(HaveLen(1))
+
+ c := cl.Changes[0]
+ Expect(c.Table).To(Equal("edgex.data_scope"))
+ Expect(c.Operation).To(Equal(common.Insert))
+
+ Expect(c.NewRow).ToNot(BeNil())
+
+ var id, clusterID, env, org, scope string
+ c.NewRow.Get("id", &id)
+ c.NewRow.Get("apid_cluster_id", &clusterID)
+ c.NewRow.Get("env", &env)
+ c.NewRow.Get("org", &org)
+ c.NewRow.Get("scope", &scope)
+
+ Expect(id).To(Equal("apid_config_scope_id_1"))
+ Expect(clusterID).To(Equal("bootstrap"))
+ Expect(env).To(Equal("prod"))
+ Expect(org).To(Equal("att"))
+ Expect(scope).To(Equal("ert452"))
+
+ events.ListenFunc(apid.EventDeliveredSelector, func(e apid.Event) {
+ defer GinkgoRecover()
+
+ // allow other handler to execute to insert last_sequence
+ time.Sleep(50 * time.Millisecond)
+ var seq string
+ err = getDB().
+ QueryRow("SELECT last_sequence FROM APID_CLUSTER LIMIT 1;").
+ Scan(&seq)
+
+ Expect(err).NotTo(HaveOccurred())
+ Expect(seq).To(Equal("lastSeq_01"))
+
+ close(done)
+ })
+ }
+ })
+
+ apid.InitializePlugins()
+})
+
+var _ = BeforeEach(func() {
+ apid.Events().Close()
+
+ token = ""
+ downloadDataSnapshot = false
+ downloadBootSnapshot = false
+ changeFinished = false
+ lastSequence = ""
+
+ _, err := getDB().Exec("DELETE FROM APID_CLUSTER")
+ Expect(err).NotTo(HaveOccurred())
+ _, err = getDB().Exec("DELETE FROM DATA_SCOPE")
+ Expect(err).NotTo(HaveOccurred())
+
+ db, err := data.DB()
+ Expect(err).NotTo(HaveOccurred())
+ _, err = db.Exec("DELETE FROM APID")
+ Expect(err).NotTo(HaveOccurred())
+})
+
+
+var _ = AfterSuite(func() {
+ apid.Events().Close()
+ if testServer != nil {
+ testServer.Close()
+ }
+ os.RemoveAll(tmpDir)
+})
+
func TestApigeeSync(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "ApigeeSync Suite")
diff --git a/apigee_sync.go b/apigee_sync.go
index a3de787..be087ec 100644
--- a/apigee_sync.go
+++ b/apigee_sync.go
@@ -14,43 +14,44 @@
)
var token string
-var tokenActive, downloadDataSnapshot, downloadBootSnapshot, chfin bool
+var downloadDataSnapshot, downloadBootSnapshot, changeFinished bool
var lastSequence string
-var gsnapshotInfo string
-func add_headers(req *http.Request) {
- req.Header.Add("Authorization", "Bearer "+token)
- req.Header.Set("apid_instance_id", guuid)
- req.Header.Set("apid_cluster_Id", gapidConfigId)
+func addHeaders(req *http.Request) {
+ req.Header.Add("Authorization", "Bearer " + token)
+ req.Header.Set("apid_instance_id", apidInfo.InstanceID)
+ req.Header.Set("apid_cluster_Id", apidInfo.ClusterID)
req.Header.Set("updated_at_apid", time.Now().Format(time.RFC3339))
}
-func donehandler(e apid.Event) {
- if rsp, ok := e.(apid.EventDeliveryEvent); ok {
- if rsp.Description == "event complete" {
- if ev, ok := rsp.Event.(*common.Snapshot); ok {
- if downloadBootSnapshot == false {
- downloadBootSnapshot = true
- log.Debug("Updated bootstrap SnapshotInfo")
- } else {
- gsnapshotInfo = ev.SnapshotInfo
- downloadDataSnapshot = true
- log.Debug("Updated data SnapshotInfo")
- }
- } else if ev, ok := rsp.Event.(*common.ChangeList); ok {
+func postPluginDataDelivery(e apid.Event) {
+
+ if ede, ok := e.(apid.EventDeliveryEvent); ok {
+
+ if ev, ok := ede.Event.(*common.ChangeList); ok {
+ if lastSequence != ev.LastSequence {
lastSequence = ev.LastSequence
- status := persistChange(lastSequence)
- if status == false {
- log.Fatal("Unable to update Sequence in DB")
+ err := persistChange(lastSequence)
+ if err != nil {
+ log.Panic("Unable to update Sequence in DB")
}
- chfin = true
+ }
+ changeFinished = true
+
+ } else if _, ok := ede.Event.(*common.Snapshot); ok {
+ if downloadBootSnapshot == false {
+ downloadBootSnapshot = true
+ log.Debug("Updated bootstrap SnapshotInfo")
+ } else {
+ downloadDataSnapshot = true
+ log.Debug("Updated data SnapshotInfo")
}
}
}
}
/*
- * Helper function that sleeps for N seconds, if comm. with change agent
+ * Helper function that sleeps for N seconds if comm with change agent
* fails. The retry interval gradually is incremented each time it fails
* till it reaches the Polling Int time, and after which it constantly
* retries at the polling time interval
@@ -61,10 +62,13 @@
pollInterval := config.GetInt(configPollInterval)
for {
startTime := time.Second
- _ = pollChangeAgent() // todo: handle error
+ err := pollChangeAgent()
+ if err != nil {
+ log.Debugf("Error connecting to changeserver: %v", err)
+ }
endTime := time.Second
// Gradually increase retry interval, and max at some level
- if endTime-startTime <= 1 {
+ if endTime - startTime <= 1 {
if times < pollInterval {
times++
} else {
@@ -87,7 +91,7 @@
func pollChangeAgent() error {
if downloadDataSnapshot != true {
- log.Warning("Waiting for snapshot download to complete")
+ log.Warn("Waiting for snapshot download to complete")
return errors.New("Snapshot download in progress...")
}
changesUri, err := url.Parse(config.GetString(configChangeServerBaseURI))
@@ -101,10 +105,10 @@
* Check to see if we have lastSequence already saved in the DB,
* in which case, it has to be used to prevent re-reading same data
*/
- lastSequence = findapidConfigInfo("lastSequence")
+ lastSequence = findApidConfigInfo(lastSequence)
for {
log.Debug("polling...")
- if tokenActive == false {
+ if token == "" {
/* token not valid?, get a new token */
status := getBearerToken()
if status == false {
@@ -113,7 +117,7 @@
}
/* Find the scopes associated with the config id */
- scopes := findScopesforId(gapidConfigId)
+ scopes := findScopesForId(apidInfo.ClusterID)
v := url.Values{}
/* Sequence added to the query if available */
@@ -130,16 +134,16 @@
for _, scope := range scopes {
v.Add("scope", scope)
}
- v.Add("scope", gapidConfigId)
- v.Add("snapshot", gsnapshotInfo)
+ v.Add("scope", apidInfo.ClusterID)
+ v.Add("snapshot", apidInfo.LastSnapshot)
changesUri.RawQuery = v.Encode()
uri := changesUri.String()
- log.Info("Fetching changes: ", uri)
+ log.Debugf("Fetching changes: %s", uri)
/* If error, break the loop, and retry after interval */
client := &http.Client{}
req, err := http.NewRequest("GET", uri, nil)
- add_headers(req)
+ addHeaders(req)
r, err := client.Do(req)
if err != nil {
log.Errorf("change agent comm error: %s", err)
@@ -149,12 +153,11 @@
/* If the call is not Authorized, update flag */
if r.StatusCode != http.StatusOK {
if r.StatusCode == http.StatusUnauthorized {
- tokenActive = false
+ token = ""
log.Errorf("Token expired? Unauthorized request.")
}
r.Body.Close()
- log.Errorf("Get Changes request failed with Resp err: %d",
- r.StatusCode)
+ log.Errorf("Get Changes request failed with Resp err: %d", r.StatusCode)
return err
}
@@ -162,14 +165,22 @@
err = json.NewDecoder(r.Body).Decode(&resp)
r.Body.Close()
if err != nil {
- log.Errorf("JSON Response Data not parsable: [%s] ", err)
+ log.Errorf("JSON Response Data not parsable: %v", err)
return err
}
+ if lastSequence != resp.LastSequence {
+ lastSequence = resp.LastSequence
+ err := persistChange(lastSequence)
+ if err != nil {
+ log.Panic("Unable to update Sequence in DB")
+ }
+ }
+
/* If valid data present, Emit to plugins */
if len(resp.Changes) > 0 {
- chfin = false
- events.ListenFunc(apid.EventDeliveredSelector, donehandler)
+ changeFinished = false
+ events.ListenFunc(apid.EventDeliveredSelector, postPluginDataDelivery)
events.Emit(ApigeeSyncEventSelector, &resp)
/*
* The plugins should have finished what they are doing.
@@ -178,18 +189,26 @@
* (Should there be a configurable Fudge factor?) FIXME
*/
for count := 0; count < 1000; count++ {
- if chfin == false {
- log.Info("Waiting for plugins to complete...")
+ if changeFinished == false {
+ log.Debug("Waiting for plugins to complete...")
time.Sleep(time.Duration(count) * 100 * time.Millisecond)
} else {
break
}
}
- if chfin == false {
- log.Fatal("Never got ack from plugins. Investigate..")
+ if changeFinished == false {
+ log.Panic("Never got ack from plugins. Investigate.")
}
} else {
- log.Info("No Changes detected for Scopes ", scopes)
+ log.Debugf("No Changes detected for Scopes: %s", scopes)
+
+ if lastSequence != resp.LastSequence {
+ lastSequence = resp.LastSequence
+ err := persistChange(lastSequence)
+ if err != nil {
+ log.Panic("Unable to update Sequence in DB")
+ }
+ }
}
}
}
@@ -200,26 +219,27 @@
*/
func getBearerToken() bool {
- log.Info("Getting a Bearer token.")
+ log.Debug("Getting a Bearer token.")
uri, err := url.Parse(config.GetString(configProxyServerBaseURI))
if err != nil {
log.Error(err)
return false
}
uri.Path = path.Join(uri.Path, "/accesstoken")
- tokenActive = false
+
+ token = ""
form := url.Values{}
form.Set("grant_type", "client_credentials")
form.Add("client_id", config.GetString(configConsumerKey))
form.Add("client_secret", config.GetString(configConsumerSecret))
req, err := http.NewRequest("POST", uri.String(), bytes.NewBufferString(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
- req.Header.Set("display_name", ginstName)
- req.Header.Set("apid_instance_id", guuid)
- req.Header.Set("apid_cluster_Id", gapidConfigId)
+ req.Header.Set("display_name", apidInfo.InstanceName)
+ req.Header.Set("apid_instance_id", apidInfo.InstanceID)
+ req.Header.Set("apid_cluster_Id", apidInfo.ClusterID)
req.Header.Set("status", "ONLINE")
req.Header.Set("created_at_apid", time.Now().Format(time.RFC3339))
- req.Header.Set("plugin_details", gpgInfo)
+ req.Header.Set("plugin_details", apidPluginDetails)
client := &http.Client{}
resp, err := client.Do(req)
@@ -245,8 +265,7 @@
return false
}
token = oauthResp.AccessToken
- tokenActive = true
- log.Info("Got a new Bearer token.")
+ log.Debug("Got a new Bearer token.")
return true
}
@@ -267,7 +286,7 @@
func Redirect(req *http.Request, via []*http.Request) error {
req.Header.Add("Authorization", "Bearer "+token)
- req.Header.Add("org", gapidConfigId)
+ req.Header.Add("org", apidInfo.ClusterID)
return nil
}
@@ -283,28 +302,25 @@
* If there is already previous data in sqlite, don't fetch
* again from snapshot server.
*/
-func DownloadSnapshots() {
+func bootstrap() {
- /*
- * Skip Downloading snapshot, if there is already a snapshot
- * available from previous run of APID
- */
- gsnapshotInfo = findapidConfigInfo("snapshotInfo")
- if gsnapshotInfo != "" {
+ // Skip Downloading snapshot if there is already a snapshot available from previous run of APID
+ if apidInfo.LastSnapshot != "" {
+
downloadDataSnapshot = true
downloadBootSnapshot = true
- log.Infof("Starting on downloaded snapshot: %s", gsnapshotInfo)
+ log.Infof("Starting on downloaded snapshot: %s", apidInfo.LastSnapshot)
- // verify DB is accessible
- _, err := data.DBVersion(gsnapshotInfo)
+ // ensure DB version will be accessible on behalf of dependant plugins
+ _, err := data.DBVersion(apidInfo.LastSnapshot)
if err != nil {
log.Panicf("Database inaccessible: %v", err)
}
- // allow plugins to start immediately on existing database
+ // allow plugins (including this one) to start immediately on existing database
snap := &common.Snapshot{
- SnapshotInfo: gsnapshotInfo,
+ SnapshotInfo: apidInfo.LastSnapshot,
}
events.Emit(ApigeeSyncEventSelector, snap)
@@ -312,7 +328,7 @@
}
/* Phase 1 */
- DownloadSnapshot()
+ downloadSnapshot()
/*
* Give some time for all the plugins to process the Downloaded
@@ -332,20 +348,22 @@
log.Debug("Proceeding with existing Sqlite data")
} else if downloadBootSnapshot == true {
log.Debug("Proceed to download Snapshot for data scopes")
- DownloadSnapshot()
+ downloadSnapshot()
} else {
- log.Fatal("Snapshot for bootscope failed")
+ log.Panic("Snapshot for bootscope failed")
}
}
-func DownloadSnapshot() {
+func downloadSnapshot() {
+
+ log.Debugf("downloadSnapshot")
var scopes []string
/* Get the bearer token */
status := getBearerToken()
if status == false {
- log.Fatal("Unable to get Bearer token or is Invalid")
+ log.Panic("Unable to get Bearer token or is Invalid")
}
snapshotUri, err := url.Parse(config.GetString(configSnapServerBaseURI))
if err != nil {
@@ -353,12 +371,12 @@
}
if downloadBootSnapshot == false {
- scopes = append(scopes, (gapidConfigId))
+ scopes = append(scopes, apidInfo.ClusterID)
} else {
- scopes = findScopesforId(gapidConfigId)
+ scopes = findScopesForId(apidInfo.ClusterID)
}
if scopes == nil {
- log.Fatal("Scope cannot be found to download snapshot")
+ log.Panic("Scope cannot be found to download snapshot")
}
/* Frame and send the snapshot request */
snapshotUri.Path = path.Join(snapshotUri.Path, "/snapshots")
@@ -369,13 +387,13 @@
}
snapshotUri.RawQuery = v.Encode()
uri := snapshotUri.String()
- log.Info("Snapshot Download : ", uri)
+ log.Info("Snapshot Download: ", uri)
client := &http.Client{
CheckRedirect: Redirect,
}
req, err := http.NewRequest("GET", uri, nil)
- add_headers(req)
+ addHeaders(req)
/* Set the transport protocol type based on conf file input */
if config.GetString(configSnapshotProtocol) == "json" {
@@ -397,11 +415,10 @@
if err != nil {
if downloadBootSnapshot == false {
- log.Fatal("JSON Response Data not parsable: ", err)
+ log.Fatalf("JSON Response Data not parsable: %v", err)
} else {
-
/*
- * If the data set is empty, allow it to proceed, as changeserver
+ * If the data set is empty, allow it to proceed, as change server
* will feed data. Since Bootstrapping has passed, it has the
* Bootstrap config id to function.
*/
@@ -412,7 +429,7 @@
if r.StatusCode == 200 {
log.Info("Emit Snapshot response to plugins")
- events.ListenFunc(apid.EventDeliveredSelector, donehandler)
+ events.ListenFunc(apid.EventDeliveredSelector, postPluginDataDelivery)
events.Emit(ApigeeSyncEventSelector, &resp)
} else {
@@ -420,88 +437,3 @@
}
}
-
-/*
- * For the given apidConfigId, this function will retrieve all the scopes
- * associated with it
- */
-func findScopesforId(configId string) (scopes []string) {
-
- var scope string
- db, err := data.DB()
- if err != nil {
- log.Errorf("DB open Error: %s", err)
- return nil
- }
-
- rows, err := db.Query("select scope from DATA_SCOPE where apid_cluster_id = $1", configId)
- if err != nil {
- log.Errorf("Failed to query DATA_SCOPE. Err: %s", err)
- return nil
- }
- defer rows.Close()
- for rows.Next() {
- rows.Scan(&scope)
- scopes = append(scopes, scope)
- }
- return scopes
-}
-
-/*
- * Retrieve SnapshotInfo for the given apidConfigId from apid_config table
- */
-func findapidConfigInfo(qparam string) (info string) {
-
- db, err := data.DB()
- if err != nil {
- log.Errorf("DB open Error: %s", err)
- return ""
- }
- query := "select " + qparam + " from APID_CLUSTER"
- rows, err := db.Query(query)
- if err != nil {
- log.Errorf("Failed to query APID_CLUSTER. Err: %s", err)
- return ""
- }
- defer rows.Close()
- for rows.Next() {
- rows.Scan(&info)
- }
- return info
-}
-
-/*
- * Persist the last change Id each time a change has been successfully
- * processed by the plugin(s)
- */
-func persistChange(lastChange string) bool {
- db, err := data.DB()
- if err != nil {
- log.Errorf("DB open Error: %s", err)
- return false
- }
- txn, err := db.Begin()
- if err != nil {
- log.Error("Unable to create Sqlite transaction")
- return false
- }
- prep, err := txn.Prepare("UPDATE APID_CLUSTER SET lastSequence=$1;")
- if err != nil {
- log.Error("UPDATE APID_CLUSTER Failed: ", err)
- return false
- }
- defer prep.Close()
- s := txn.Stmt(prep)
- _, err = s.Exec(lastChange)
- s.Close()
- if err != nil {
- log.Error("UPDATE DATA_SCOPE Failed: ", err)
- txn.Rollback()
- return false
- } else {
- log.Info("UPDATE DATA_SCOPE Success: (", lastChange, ")")
- txn.Commit()
- return true
- }
-
-}
diff --git a/apigee_sync_test.go b/apigee_sync_test.go
index 507aa99..dea238c 100644
--- a/apigee_sync_test.go
+++ b/apigee_sync_test.go
@@ -1,314 +1,29 @@
package apidApigeeSync
import (
- "encoding/json"
- "github.com/30x/apid"
- "github.com/30x/apid/factory"
- "github.com/apigee-labs/transicator/common"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "time"
+ "github.com/30x/apid"
+ "github.com/apigee-labs/transicator/common"
)
-var _ = Describe("api", func() {
+var _ = Describe("listener", func() {
- var server *httptest.Server
- var plugInfo []pluginDetail
+ It("should bootstrap from local DB if present", func(done Done) {
- BeforeSuite(func() {
- apid.Initialize(factory.DefaultServicesFactory())
- })
+ Expect(apidInfo.LastSnapshot).NotTo(BeEmpty())
- AfterSuite(func() {
- apid.Events().Close()
- server.Close()
- })
+ apid.Events().ListenFunc(ApigeeSyncEventSelector, func(event apid.Event) {
+ defer GinkgoRecover()
- It("perform sync round-trip", func(done Done) {
- scount := 0
- phase := 0
- scope := "bootstrap"
- key := "XXXXXXX"
- secret := "YYYYYYY"
+ if s, ok := event.(*common.Snapshot); ok {
+ Expect(s.SnapshotInfo).Should(Equal(apidInfo.LastSnapshot))
+ Expect(s.Tables).To(BeNil())
- // mock upstream server
- server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-
- // first request is for a token
- if req.URL.Path == "/accesstoken" {
- Expect(req.Method).To(Equal("POST"))
- Expect(req.Header.Get("Content-Type")).To(Equal("application/x-www-form-urlencoded; param=value"))
-
- err := req.ParseForm()
- Expect(err).NotTo(HaveOccurred())
- Expect(req.Form.Get("grant_type")).To(Equal("client_credentials"))
- Expect(req.Header.Get("status")).To(Equal("ONLINE"))
- Expect(req.Header.Get("apid_cluster_Id")).To(Equal("bootstrap"))
- Expect(req.Header.Get("display_name")).To(Equal("testhost"))
- plinfo := []byte(req.Header.Get("plugin_details"))
- err = json.Unmarshal(plinfo, &plugInfo)
- Expect(err).NotTo(HaveOccurred())
- Expect(plugInfo[0].Name).To(Equal("apidApigeeSync"))
- Expect(plugInfo[0].SchemaVersion).To(Equal("0.0.2"))
-
- res := oauthTokenResp{}
- res.AccessToken = "accesstoken"
- body, err := json.Marshal(res)
- Expect(err).NotTo(HaveOccurred())
- w.Write(body)
- return
+ close(done)
}
+ })
- // next requests are for changes
- if req.URL.Path == "/snapshots" {
- Expect(req.Method).To(Equal("GET"))
- q := req.URL.Query()
-
- if phase == 0 {
- phase = 1
- Expect(q.Get("scope")).To(Equal(scope))
- Expect(req.Header.Get("apid_cluster_Id")).To(Equal("bootstrap"))
-
- apidcfgItem := common.Row{}
- apidcfgItems := []common.Row{}
- apidcfgItemCh := common.Row{}
- apidcfgItemsCh := []common.Row{}
- scv := &common.ColumnVal{
- Value: scope,
- Type: 1,
- }
- apidcfgItem["id"] = scv
- scv = &common.ColumnVal{
- Value: scope,
- Type: 1,
- }
- apidcfgItem["_change_selector"] = scv
- apidcfgItems = append(apidcfgItems, apidcfgItem)
-
- scv = &common.ColumnVal{
- Value: "apid_config_scope_id_0",
- Type: 1,
- }
- apidcfgItemCh["id"] = scv
-
- scv = &common.ColumnVal{
- Value: "apid_config_scope_id_0",
- Type: 1,
- }
- apidcfgItemCh["_change_selector"] = scv
-
- scv = &common.ColumnVal{
- Value: scope,
- Type: 1,
- }
- apidcfgItemCh["apid_cluster_id"] = scv
-
- scv = &common.ColumnVal{
- Value: "ert452",
- Type: 1,
- }
- apidcfgItemCh["scope"] = scv
-
- {
- scv = &common.ColumnVal{
- Value: "att",
- Type: 1,
- }
- apidcfgItemCh["org"] = scv
-
- }
- {
- scv = &common.ColumnVal{
- Value: "prod",
- Type: 1,
- }
- apidcfgItemCh["env"] = scv
- }
-
- apidcfgItemsCh = append(apidcfgItemsCh, apidcfgItemCh)
-
- res := &common.Snapshot{}
- res.SnapshotInfo = "snapinfo1"
-
- res.Tables = []common.Table{
- {
- Name: "edgex.apid_cluster",
- Rows: apidcfgItems,
- },
- {
- Name: "edgex.data_scope",
- Rows: apidcfgItemsCh,
- },
- }
-
- body, err := json.Marshal(res)
- Expect(err).NotTo(HaveOccurred())
- w.Write(body)
- return
- } else {
- phase = 2
- Expect(q.Get("scope")).To(Equal("ert452"))
- res := &common.Snapshot{}
- res.SnapshotInfo = "snapinfo1"
-
- apidcfgItems := []common.Row{}
- res.Tables = []common.Table{
- {
- Name: "kms.api_product",
- Rows: apidcfgItems,
- },
- }
-
- body, err := json.Marshal(res)
- Expect(err).NotTo(HaveOccurred())
- w.Write(body)
- return
- }
-
- }
- // next requests are for changes
- if req.URL.Path == "/changes" {
- Expect(req.Method).To(Equal("GET"))
- Expect(req.Header.Get("apid_cluster_Id")).To(Equal("bootstrap"))
- q := req.URL.Query()
- Expect(q.Get("snapshot")).To(Equal("snapinfo1"))
- scparams := q["scope"]
- Expect(scparams).To(ContainElement("ert452"))
- Expect(scparams).To(ContainElement("bootstrap"))
-
- res := &common.ChangeList{}
-
- res.LastSequence = "lastSeq_01"
- mpItems := common.Row{}
-
- scv := &common.ColumnVal{
- Value: "apid_config_scope_id_1",
- Type: 1,
- }
- mpItems["id"] = scv
-
- scv = &common.ColumnVal{
- Value: scope,
- Type: 1,
- }
- mpItems["apid_cluster_id"] = scv
-
- scv = &common.ColumnVal{
- Value: "ert452",
- Type: 1,
- }
- mpItems["scope"] = scv
- {
- scv = &common.ColumnVal{
- Value: "att",
- Type: 1,
- }
- mpItems["org"] = scv
- }
- {
- scv = &common.ColumnVal{
- Value: "prod",
- Type: 1,
- }
- mpItems["env"] = scv
- }
-
- res.Changes = []common.Change{
- {
- Table: "edgex.data_scope",
- NewRow: mpItems,
- Operation: 1,
- },
- }
- body, err := json.Marshal(res)
- Expect(err).NotTo(HaveOccurred())
- w.Write(body)
- return
- }
- Fail("should not reach")
- }))
-
- config = apid.Config()
- config.Set(configProxyServerBaseURI, server.URL)
- config.Set(configSnapServerBaseURI, server.URL)
- config.Set(configChangeServerBaseURI, server.URL)
- config.Set(configApidClusterId, "apid_config_scope_0")
- config.Set(configName, "testhost")
-
- config.Set(configSnapshotProtocol, "json")
- config.Set(configApidClusterId, scope)
- config.Set(configConsumerKey, key)
- config.Set(configConsumerSecret, secret)
-
- // set up temporary test database
- tmpDir, err := ioutil.TempDir("", "apigee_sync_test")
- Expect(err).NotTo(HaveOccurred())
- defer os.RemoveAll(tmpDir)
-
- config.Set("data_path", tmpDir)
-
- // start process - plugin will automatically start polling
- apid.InitializePlugins()
-
- h := &test_handler{
- "sync data",
- func(event apid.Event) {
- _, ok := event.(*common.Snapshot)
- if ok {
- if phase > 1 {
- db, err := data.DB()
- Expect(err).NotTo(HaveOccurred())
- // verify event data (post snapshot)
- err = db.QueryRow("Select count(scp.id) from data_scope as scp INNER JOIN apid_cluster as ap WHERE scp.apid_cluster_id = ap.id").Scan(&scount)
- Expect(err).NotTo(HaveOccurred())
- Expect(scount).Should(Equal(1))
- }
- } else {
- // verify event data (post change)
- // There should be 2 scopes now
- _, ok := event.(*common.ChangeList)
- if ok {
- time.Sleep(200 * time.Millisecond)
- db, err := data.DB()
- Expect(err).NotTo(HaveOccurred())
- err = db.QueryRow("Select count(scp.id) from data_scope as scp INNER JOIN apid_cluster as ap WHERE scp.apid_cluster_id = ap.id").Scan(&scount)
- Expect(err).NotTo(HaveOccurred())
- Expect(scount).Should(Equal(2))
- close(done)
- } else {
- Fail("Unexpected event")
- }
- }
-
- },
- }
-
- donehandler := func(e apid.Event) {
- if rsp, ok := e.(apid.EventDeliveryEvent); ok {
- Expect(rsp.Description).Should(Equal("event complete"))
- } else {
- Fail("Unexpected event")
- }
- }
- apid.Events().Listen(ApigeeSyncEventSelector, h)
- events.ListenFunc(apid.EventDeliveredSelector, donehandler)
-
+ bootstrap()
})
})
-
-type test_handler struct {
- description string
- f func(event apid.Event)
-}
-
-func (t *test_handler) String() string {
- return t.description
-}
-
-func (t *test_handler) Handle(event apid.Event) {
- t.f(event)
-}
diff --git a/data.go b/data.go
new file mode 100644
index 0000000..e93971b
--- /dev/null
+++ b/data.go
@@ -0,0 +1,318 @@
+package apidApigeeSync
+
+import (
+ "database/sql"
+ "github.com/30x/apid"
+ "sync"
+ "fmt"
+ "crypto/rand"
+ "errors"
+)
+
+var (
+ unsafeDB apid.DB
+ dbMux sync.RWMutex
+)
+
+type dataApidCluster struct {
+ ChangeSelector, ID, Name, OrgAppName, CreatedBy, UpdatedBy, Description string
+ Updated, Created string
+}
+
+type dataDataScope struct {
+ ChangeSelector, ID, ClusterID, Scope, Org, Env, CreatedBy, UpdatedBy string
+ Updated, Created string
+}
+
+/*
+This plugin uses 2 databases:
+1. The default DB is used for APID table.
+2. The versioned DB is used for APID_CLUSTER & DATA_SCOPE
+(Currently, the snapshot never changes, but this is future-proof)
+*/
+func initDB(db apid.DB) error {
+ _, err := db.Exec(`
+ CREATE TABLE IF NOT EXISTS APID (
+ instance_id text,
+ last_snapshot_info text,
+ PRIMARY KEY (instance_id)
+ );
+ CREATE TABLE IF NOT EXISTS APID_CLUSTER (
+ id text,
+ name text,
+ description text,
+ umbrella_org_app_name text,
+ created text,
+ created_by text,
+ updated text,
+ updated_by text,
+ _change_selector text,
+ last_sequence text,
+ PRIMARY KEY (id)
+ );
+ CREATE TABLE IF NOT EXISTS DATA_SCOPE (
+ id text,
+ apid_cluster_id text,
+ scope text,
+ org text,
+ env text,
+ created text,
+ created_by text,
+ updated text,
+ updated_by text,
+ _change_selector text,
+ PRIMARY KEY (id, apid_cluster_id)
+ );
+ `)
+ if err != nil {
+ return err
+ }
+
+ log.Debug("Database tables created.")
+ return nil
+}
+
+func getDB() apid.DB {
+ dbMux.RLock()
+ db := unsafeDB
+ dbMux.RUnlock()
+ return db
+}
+
+func setDB(db apid.DB) {
+ dbMux.Lock()
+ unsafeDB = db
+ dbMux.Unlock()
+}
+
+func insertApidCluster(dac dataApidCluster, txn *sql.Tx) error {
+
+ log.Debugf("inserting into APID_CLUSTER: %v", dac)
+
+ stmt, err := txn.Prepare(`
+ INSERT INTO APID_CLUSTER
+ (id, _change_selector, name, umbrella_org_app_name,
+ created, created_by, updated, updated_by,
+ description)
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9);
+ `)
+ if err != nil {
+ log.Errorf("prepare insert into APID_CLUSTER transaction Failed: %v", err)
+ return err
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(
+ dac.ID, dac.ChangeSelector, dac.Name, dac.OrgAppName,
+ dac.Created, dac.CreatedBy, dac.Updated, dac.UpdatedBy,
+ dac.Description)
+
+ if err != nil {
+ log.Errorf("insert APID_CLUSTER failed: %v", err)
+ }
+
+ return err
+}
+
+func insertDataScope(ds dataDataScope, txn *sql.Tx) error {
+
+ log.Debugf("insert DATA_SCOPE: %v", ds)
+
+ stmt, err := txn.Prepare(`
+ INSERT INTO DATA_SCOPE
+ (id, apid_cluster_id, scope, org,
+ env, created, created_by, updated,
+ updated_by, _change_selector)
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);
+ `)
+ if err != nil {
+ log.Errorf("insert DATA_SCOPE failed: %v", err)
+ return err
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(
+ ds.ID, ds.ClusterID, ds.Scope, ds.Org,
+ ds.Env, ds.Created, ds.CreatedBy, ds.Updated,
+ ds.UpdatedBy, ds.ChangeSelector)
+
+ if err != nil {
+ log.Errorf("insert DATA_SCOPE failed: %v", err)
+ return err
+ }
+
+ return nil
+}
+
+func deleteDataScope(ds dataDataScope, txn *sql.Tx) error {
+
+ log.Debugf("delete DATA_SCOPE: %v", ds)
+
+ stmt, err := txn.Prepare("DELETE FROM DATA_SCOPE WHERE id=$1 and apid_cluster_id=$2")
+ if err != nil {
+ log.Errorf("update DATA_SCOPE failed: %v", err)
+ return err
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(ds.ID, ds.ClusterID)
+
+ if err != nil {
+ log.Errorf("delete DATA_SCOPE failed: %v", err)
+ return err
+ }
+
+ return nil
+}
+
+/*
+ * For the given apidConfigId, this function will retrieve all the scopes
+ * associated with it
+ */
+func findScopesForId(configId string) (scopes []string) {
+
+ log.Debugf("findScopesForId: %s", configId)
+
+ var scope string
+ db := getDB()
+
+ rows, err := db.Query("select scope from DATA_SCOPE where apid_cluster_id = $1", configId)
+ if err != nil {
+ log.Errorf("Failed to query DATA_SCOPE: %v", err)
+ return
+ }
+ defer rows.Close()
+ for rows.Next() {
+ rows.Scan(&scope)
+ scopes = append(scopes, scope)
+ }
+
+ log.Debugf("scopes: %v", scopes)
+ return
+}
+
+/*
+ * Retrieve SnapshotInfo for the given apidConfigId from apid_config table
+ */
+func findApidConfigInfo(qparam string) (info string) {
+
+ log.Debugf("findApidConfigInfo: %s", qparam)
+
+ db := getDB()
+
+ rows, err := db.Query("select ? from APID_CLUSTER", qparam)
+ if err != nil {
+ log.Errorf("Failed to query APID_CLUSTER: %v", err)
+ return
+ }
+ defer rows.Close()
+ for rows.Next() {
+ rows.Scan(&info)
+ }
+
+ log.Debugf("info: %s", info)
+
+ return
+}
+
+/*
+ * Persist the last change Id each time a change has been successfully
+ * processed by the plugin(s)
+ */
+func persistChange(lastChange string) error {
+
+ log.Debugf("persistChange: %s", lastChange)
+
+ db := getDB()
+
+ stmt, err := db.Prepare("UPDATE APID_CLUSTER SET last_sequence=$1;")
+ if err != nil {
+ log.Errorf("UPDATE APID_CLUSTER Failed: %v", err)
+ return err
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(lastChange)
+ if err != nil {
+ log.Errorf("UPDATE DATA_SCOPE Failed: %v", err)
+ return err
+ }
+
+ log.Infof("UPDATE DATA_SCOPE Success: %s", lastChange)
+
+ return nil
+}
+
+func getApidInstanceInfo() (info apidInstanceInfo, err error) {
+
+ // always use default database for this
+ var db apid.DB
+ db, err = data.DB()
+ if err != nil {
+ return
+ }
+
+ err = db.QueryRow("SELECT instance_id, last_snapshot_info FROM APID LIMIT 1").
+ Scan(&info.InstanceID, &info.LastSnapshot)
+ if err != nil {
+ if err != sql.ErrNoRows {
+ log.Errorf("Unable to retrieve apidInstanceInfo: %v", err)
+ return
+ } else {
+ // first start - no row, generate a UUID and store it
+ err = nil
+ info.InstanceID = generateUUID()
+
+ db.Exec("INSERT INTO APID (instance_id) VALUES (?)", info.InstanceID)
+ }
+ }
+
+ // if name not explicitly configured, just use InstanceID
+ config.SetDefault(configName, info.InstanceID)
+ info.InstanceName = config.GetString(configName)
+
+ // not stored in DB
+ info.ClusterID = config.GetString(configApidClusterId)
+
+ return
+}
+
+func updateApidInstanceInfo() error {
+
+ // always use default database for this
+ db, err := data.DB()
+ if err != nil {
+ return err
+ }
+
+ rows, err := db.Exec(`
+ INSERT OR REPLACE
+ INTO APID (instance_id, last_snapshot_info)
+ VALUES (?, ?)`,
+ apidInfo.InstanceID, apidInfo.LastSnapshot)
+ if err != nil {
+ return err
+ }
+ n, err := rows.RowsAffected()
+ if err == nil && n == 0 {
+ err = errors.New("no rows affected")
+ }
+
+ return err
+}
+
+/*
+ * generates a random uuid (mix of timestamp & crypto random string)
+ */
+func generateUUID() string {
+
+ buff := make([]byte, 16)
+ numRead, err := rand.Read(buff)
+ if numRead != len(buff) || err != nil {
+ panic(err)
+ }
+ /* uuid v4 spec */
+ buff[6] = (buff[6] | 0x40) & 0x4F
+ buff[8] = (buff[8] | 0x80) & 0xBF
+ return fmt.Sprintf("%x-%x-%x-%x-%x", buff[0:4], buff[4:6], buff[6:8], buff[8:10], buff[10:])
+}
diff --git a/init.go b/init.go
index d143dde..931a9ec 100644
--- a/init.go
+++ b/init.go
@@ -1,7 +1,6 @@
package apidApigeeSync
import (
- "crypto/rand"
"encoding/json"
"fmt"
"github.com/30x/apid"
@@ -21,47 +20,81 @@
)
var (
- log apid.LogService
- config apid.ConfigService
- data apid.DataService
- events apid.EventsService
- gapidConfigId string
- guuid string
- ginstName string
- gpgInfo string
+ log apid.LogService
+ config apid.ConfigService
+ data apid.DataService
+ events apid.EventsService
+
+ apidInfo apidInstanceInfo
+ apidPluginDetails string
)
+type apidInstanceInfo struct {
+ InstanceID, InstanceName, ClusterID, LastSnapshot string
+}
+
type pluginDetail struct {
Name string `json:"name"`
SchemaVersion string `json:"schemaVer"`
}
-/*
- * generates a random uuid
- */
-func generate_uuid() string {
- buff := make([]byte, 16)
- numRead, err := rand.Read(buff)
- if numRead != len(buff) || err != nil {
- panic(err)
- }
- /* uuid v4 spec */
- buff[6] = (buff[6] | 0x40) & 0x4F
- buff[8] = (buff[8] | 0x80) & 0xBF
- return fmt.Sprintf("%x-%x-%x-%x-%x", buff[0:4], buff[4:6], buff[6:8], buff[8:10], buff[10:])
-}
-
func init() {
apid.RegisterPlugin(initPlugin)
}
+func initPlugin(services apid.Services) (apid.PluginData, error) {
+ log = services.Log().ForModule("apigeeSync")
+ log.Debug("start init")
+
+ config = services.Config()
+ config.SetDefault(configPollInterval, 120)
+
+ data = services.Data()
+ events = services.Events()
+
+ /* This callback function will get called, once all the plugins are
+ * initialized (not just this plugin). This is needed because,
+ * downloadSnapshots/changes etc have to begin to be processed only
+ * after all the plugins are initialized
+ */
+ events.ListenFunc(apid.SystemEventsSelector, postInitPlugins)
+
+ // check for required values
+ for _, key := range []string{configProxyServerBaseURI, configConsumerKey, configConsumerSecret,
+ configSnapServerBaseURI, configChangeServerBaseURI} {
+ if !config.IsSet(key) {
+ return pluginData, fmt.Errorf("Missing required config value: %s", key)
+ }
+ }
+
+ // set up default database
+ db, err := data.DB()
+ if err != nil {
+ log.Panicf("Unable to access DB: %v", err)
+ }
+ err = initDB(db)
+ if err != nil {
+ log.Panicf("Unable to initialize DB: %v", err)
+ }
+ setDB(db)
+
+ apidInfo, err = getApidInstanceInfo()
+ if err != nil {
+ log.Panicf("Unable to get apid instance info: %v", err)
+ }
+
+ log.Debug("end init")
+
+ return pluginData, nil
+}
+
+// Plugins have all initialized, gather their info and start the ApigeeSync downloads
func postInitPlugins(event apid.Event) {
var plinfoDetails []pluginDetail
if pie, ok := event.(apid.PluginsInitializedEvent); ok {
-
/*
* Store the plugin details in the heap. Needed during
- * Bearer token generation request
+ * Bearer token generation request.
*/
for _, plugin := range pie.Plugins {
name := plugin.Name
@@ -76,17 +109,17 @@
}
if plinfoDetails == nil {
log.Panicf("No Plugins registered!")
- } else {
- pgInfo, err := json.Marshal(plinfoDetails)
- if err != nil {
- log.Panic("Unable to masrhal plugin data", err)
- }
- gpgInfo = (string(pgInfo[:]))
}
+ pgInfo, err := json.Marshal(plinfoDetails)
+ if err != nil {
+ log.Panicf("Unable to marshal plugin data: %v", err)
+ }
+ apidPluginDetails = string(pgInfo[:])
+
log.Debug("start post plugin init")
- /* call to Download Snapshot info */
- go DownloadSnapshots()
+
+ go bootstrap()
/* Begin Looking for changes periodically */
log.Debug("starting update goroutine")
@@ -97,91 +130,3 @@
}
}
-func initPlugin(services apid.Services) (apid.PluginData, error) {
- log = services.Log().ForModule("apigeeSync")
- log.Debug("start init")
-
- config = services.Config()
- data = services.Data()
- events = services.Events()
- guuid = findapidConfigInfo("instance_id")
- if guuid == "" {
- guuid = generate_uuid()
- }
-
- /* If The Instance has no name configured, just re-use UUID */
- ginstName = config.GetString(configName)
- if ginstName == "" {
- ginstName = guuid
- }
-
- /* This callback function will get called, once all the plugins are
- * initialized (not just this plugin). This is needed because,
- * DownloadSnapshots/Changes etc have to begin to be processed only
- * after all the plugins are initialized
- */
- events.ListenFunc(apid.SystemEventsSelector, postInitPlugins)
-
- config.SetDefault(configPollInterval, 120)
- gapidConfigId = config.GetString(configApidClusterId)
- db, err := data.DB()
- if err != nil {
- log.Panic("Unable to access DB", err)
- }
-
- // check for required values
- for _, key := range []string{configProxyServerBaseURI, configConsumerKey, configConsumerSecret, configSnapServerBaseURI, configChangeServerBaseURI} {
- if !config.IsSet(key) {
- return pluginData, fmt.Errorf("Missing required config value: %s", key)
- }
- }
-
- var count int
- row := db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='apid_cluster' COLLATE NOCASE;")
- if err := row.Scan(&count); err != nil {
- log.Panic("Unable to setup database", err)
- }
- if count == 0 {
- createTables(db)
- }
-
- log.Debug("end init")
-
- return pluginData, nil
-}
-
-func createTables(db apid.DB) {
- _, err := db.Exec(`
-CREATE TABLE apid_cluster (
- id text,
- instance_id text,
- name text,
- description text,
- umbrella_org_app_name text,
- created int64,
- created_by text,
- updated int64,
- updated_by text,
- _change_selector text,
- snapshotInfo text,
- lastSequence text,
- PRIMARY KEY (id)
-);
-CREATE TABLE data_scope (
- id text,
- apid_cluster_id text,
- scope text,
- org text,
- env text,
- created int64,
- created_by text,
- updated int64,
- updated_by text,
- _change_selector text,
- PRIMARY KEY (id)
-);
-`)
- if err != nil {
- log.Panic("Unable to initialize DB", err)
- }
-}
diff --git a/listener.go b/listener.go
index c5f154a..41b7a8e 100644
--- a/listener.go
+++ b/listener.go
@@ -1,11 +1,15 @@
package apidApigeeSync
import (
- "database/sql"
"github.com/30x/apid"
"github.com/apigee-labs/transicator/common"
)
+const (
+ LISTENER_TABLE_APID_CLUSTER = "edgex.apid_cluster"
+ LISTENER_TABLE_DATA_SCOPE = "edgex.data_scope"
+)
+
type handler struct {
}
@@ -15,180 +19,150 @@
func (h *handler) Handle(e apid.Event) {
- res := true
-
- db, err := data.DB()
- if err != nil {
- panic("Unable to access Sqlite DB")
- }
-
- txn, err := db.Begin()
- if err != nil {
- log.Error("Unable to create Sqlite transaction")
- return
- }
-
- snapData, ok := e.(*common.Snapshot)
- if ok {
- res = processSnapshot(snapData, txn)
+ if changeSet, ok := e.(*common.ChangeList); ok {
+ processChangeList(changeSet)
+ } else if snapShot, ok := e.(*common.Snapshot); ok {
+ processSnapshot(snapShot)
} else {
- changeSet, ok := e.(*common.ChangeList)
- if ok {
- res = processChange(changeSet, txn)
- } else {
- log.Fatal("Received invalid event: %v", e)
- }
+ log.Errorf("Received invalid event. Ignoring. %v", e)
}
- if res == true {
- txn.Commit()
- } else {
- txn.Rollback()
- }
- return
}
-func processSnapshot(snapshot *common.Snapshot, txn *sql.Tx) bool {
+func processSnapshot(snapshot *common.Snapshot) {
- log.Debugf("Process Snapshot data")
- res := true
+ log.Debugf("Snapshot received. Switching to DB version: %s", snapshot.SnapshotInfo)
- for _, payload := range snapshot.Tables {
-
- switch payload.Name {
- case "edgex.apid_cluster":
- res = insertApidCluster(payload.Rows, txn, snapshot.SnapshotInfo)
- case "edgex.data_scope":
- res = insertDataScopes(payload.Rows, txn)
- }
- if res == false {
- log.Error("Error encountered in Downloading Snapshot for ApidApigeeSync")
- return res
- }
+ db, err := data.DBVersion(snapshot.SnapshotInfo)
+ if err != nil {
+ log.Panicf("Unable to access database: %v", err)
}
- return res
-}
-func processChange(changes *common.ChangeList, txn *sql.Tx) bool {
+ err = initDB(db)
+ if err != nil {
+ log.Panicf("Unable to initialize database: %v", err)
+ }
- log.Debugf("apigeeSyncEvent: %d changes", len(changes.Changes))
- var rows []common.Row
- res := true
+ tx, err := db.Begin()
+ if err != nil {
+ log.Panicf("Error starting transaction: %v", err)
+ }
+ defer tx.Rollback()
- for _, payload := range changes.Changes {
- rows = nil
- switch payload.Table {
- case "edgex.data_scope":
- switch payload.Operation {
- case common.Insert:
- rows = append(rows, payload.NewRow)
- res = insertDataScopes(rows, txn)
+ for _, table := range snapshot.Tables {
+
+ switch table.Name {
+ case LISTENER_TABLE_APID_CLUSTER:
+ if len(table.Rows) != 1 {
+ log.Panic("Illegal state for apid_cluster. Must be a single row.")
+ }
+ ac := makeApidClusterFromRow(table.Rows[0])
+ err := insertApidCluster(ac, tx)
+ if err != nil {
+ log.Panic("Snapshot update failed: %v", err)
+ }
+
+ case LISTENER_TABLE_DATA_SCOPE:
+ for _, row := range table.Rows {
+ ds := makeDataScopeFromRow(row)
+ err := insertDataScope(ds, tx)
+ if err != nil {
+ log.Panic("Snapshot update failed: %v", err)
+ }
}
}
- if res == false {
- log.Error("Sql Operation error. Operation rollbacked")
- return res
- }
}
- return res
-}
-/*
- * INSERT INTO APP_CREDENTIAL op
- */
-func insertApidCluster(rows []common.Row, txn *sql.Tx, snapInfo string) bool {
-
- var scope, id, name, orgAppName, createdBy, updatedBy, Description string
- var updated, created int64
-
- prep, err := txn.Prepare("INSERT INTO APID_CLUSTER (id, instance_id, _change_selector, name, umbrella_org_app_name, created, created_by, updated, updated_by, snapshotInfo)VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);")
+ err = tx.Commit()
if err != nil {
- log.Error("INSERT APID_CLUSTER Failed: ", err)
- return false
+ log.Panicf("Error committing Snapshot change: %v", err)
}
- defer prep.Close()
- for _, ele := range rows {
- ele.Get("id", &id)
- ele.Get("_change_selector", &scope)
- ele.Get("name", &name)
- ele.Get("umbrella_org_app_name", &orgAppName)
- ele.Get("created", &created)
- ele.Get("created_by", &createdBy)
- ele.Get("updated", &updated)
- ele.Get("updated_by", &updatedBy)
- ele.Get("description", &Description)
-
- s := txn.Stmt(prep)
- _, err = s.Exec(
- id,
- guuid,
- scope,
- name,
- orgAppName,
- created,
- createdBy,
- updated,
- updatedBy,
- snapInfo)
- s.Close()
- if err != nil {
- log.Error("INSERT APID_CLUSTER Failed: ", id, ", ", scope, ")", err)
- return false
- } else {
- log.Info("INSERT APID_CLUSTER Success: (", id, ", ", scope, ")")
- }
- }
- return true
-}
-
-/*
- * INSERT INTO APP_CREDENTIAL op
- */
-func insertDataScopes(rows []common.Row, txn *sql.Tx) bool {
-
- var id, scopeId, apiConfigId, scope, createdBy, updatedBy, org, env string
- var created, updated int64
-
- prep, err := txn.Prepare("INSERT INTO DATA_SCOPE (id, _change_selector, apid_cluster_id, scope, org, env, created, created_by, updated, updated_by)VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);")
+ apidInfo.LastSnapshot = snapshot.SnapshotInfo
+ err = updateApidInstanceInfo()
if err != nil {
- log.Error("INSERT DATA_SCOPE Failed: ", err)
- return false
+ log.Panicf("Unable to update instance info: %v", err)
}
- defer prep.Close()
- for _, ele := range rows {
+ setDB(db)
+ log.Debugf("Snapshot processed: %s", snapshot.SnapshotInfo)
+}
- ele.Get("id", &id)
- ele.Get("_change_selector", &scopeId)
- ele.Get("apid_cluster_id", &apiConfigId)
- ele.Get("scope", &scope)
- ele.Get("org", &org)
- ele.Get("env", &env)
- ele.Get("created", &created)
- ele.Get("created_by", &createdBy)
- ele.Get("updated", &updated)
- ele.Get("updated_by", &updatedBy)
+func processChangeList(changes *common.ChangeList) {
- s := txn.Stmt(prep)
- _, err = s.Exec(
- id,
- scopeId,
- apiConfigId,
- scope,
- org,
- env,
- created,
- createdBy,
- updated,
- updatedBy)
- s.Close()
+ tx, err := getDB().Begin()
+ if err != nil {
+ log.Panicf("Error processing ChangeList: %v", err)
+ }
+ defer tx.Rollback()
- if err != nil {
- log.Error("INSERT DATA_SCOPE Failed: ", id, ", ", scope, ")", err)
- return false
- } else {
- log.Info("INSERT DATA_SCOPE Success: (", id, ", ", scope, ")")
+ log.Debugf("apigeeSyncEvent: %d changes", len(changes.Changes))
+
+ for _, change := range changes.Changes {
+ switch change.Table {
+ case "edgex.apid_cluster":
+ switch change.Operation {
+ case common.Delete:
+ // todo: shut down apid, delete databases, scorch the earth!
+ log.Panicf("illegal operation: %s for %s", change.Operation, change.Table)
+ default:
+ log.Panicf("illegal operation: %s for %s", change.Operation, change.Table)
+ }
+ case "edgex.data_scope":
+ switch change.Operation {
+ case common.Insert:
+ ds := makeDataScopeFromRow(change.NewRow)
+ err = insertDataScope(ds, tx)
+ case common.Delete:
+ ds := makeDataScopeFromRow(change.OldRow)
+ deleteDataScope(ds, tx)
+ default:
+ // common.Update is not allowed
+ log.Panicf("illegal operation: %s for %s", change.Operation, change.Table)
+ }
+ }
+ if err != nil{
+ log.Panicf("Error processing ChangeList: %v", err)
}
}
- return true
+
+ err = tx.Commit()
+ if err != nil {
+ log.Panicf("Error processing ChangeList: %v", err)
+ }
}
+
+func makeApidClusterFromRow(row common.Row) dataApidCluster {
+
+ dac := dataApidCluster{}
+
+ row.Get("id", &dac.ID)
+ row.Get("_change_selector", &dac.ChangeSelector)
+ row.Get("name", &dac.Name)
+ row.Get("umbrella_org_app_name", &dac.OrgAppName)
+ row.Get("created", &dac.Created)
+ row.Get("created_by", &dac.CreatedBy)
+ row.Get("updated", &dac.Updated)
+ row.Get("updated_by", &dac.UpdatedBy)
+ row.Get("description", &dac.Description)
+
+ return dac
+}
+
+func makeDataScopeFromRow(row common.Row) dataDataScope {
+
+ ds := dataDataScope{}
+
+ row.Get("id", &ds.ID)
+ row.Get("_change_selector", &ds.ChangeSelector)
+ row.Get("apid_cluster_id", &ds.ClusterID)
+ row.Get("scope", &ds.Scope)
+ row.Get("org", &ds.Org)
+ row.Get("env", &ds.Env)
+ row.Get("created", &ds.Created)
+ row.Get("created_by", &ds.CreatedBy)
+ row.Get("updated", &ds.Updated)
+ row.Get("updated_by", &ds.UpdatedBy)
+
+ return ds
+}
+
diff --git a/listener_test.go b/listener_test.go
new file mode 100644
index 0000000..02d255d
--- /dev/null
+++ b/listener_test.go
@@ -0,0 +1,338 @@
+package apidApigeeSync
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+
+ "github.com/apigee-labs/transicator/common"
+)
+
+var _ = Describe("listener", func() {
+
+ handler := handler{}
+
+ Context("ApigeeSync snapshot event", func() {
+
+ It("should set DB to appropriate version", func() {
+
+ event := common.Snapshot{
+ SnapshotInfo: "test_snapshot",
+ Tables: []common.Table{},
+ }
+
+ handler.Handle(&event)
+
+ Expect(apidInfo.LastSnapshot).To(Equal(event.SnapshotInfo))
+
+ expectedDB, err := data.DBVersion(event.SnapshotInfo)
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(getDB() == expectedDB).Should(BeTrue())
+ })
+
+ It("should fail if zero apid_cluster rows", func() {
+
+ event := common.Snapshot{
+ SnapshotInfo: "test_snapshot_fail",
+ Tables: []common.Table{
+ {
+ Name: LISTENER_TABLE_APID_CLUSTER,
+ Rows: []common.Row{},
+ },
+ },
+ }
+
+ Expect(func() { handler.Handle(&event) }).To(Panic())
+ })
+
+ It("should fail if more than one apid_cluster rows", func() {
+
+ event := common.Snapshot{
+ SnapshotInfo: "test_snapshot_fail",
+ Tables: []common.Table{
+ {
+ Name: LISTENER_TABLE_APID_CLUSTER,
+ Rows: []common.Row{{}, {}},
+ },
+ },
+ }
+
+ Expect(func() { handler.Handle(&event) }).To(Panic())
+ })
+
+ It("should process a valid Snapshot", func() {
+
+ event := common.Snapshot{
+ SnapshotInfo: "test_snapshot_valid",
+ Tables: []common.Table{
+ {
+ Name: LISTENER_TABLE_APID_CLUSTER,
+ Rows: []common.Row{
+ {
+ "id": &common.ColumnVal{Value: "i"},
+ "_change_selector": &common.ColumnVal{Value: "c"},
+ "name": &common.ColumnVal{Value: "n"},
+ "umbrella_org_app_name": &common.ColumnVal{Value: "o"},
+ "created": &common.ColumnVal{Value: "c"},
+ "created_by": &common.ColumnVal{Value: "c"},
+ "updated": &common.ColumnVal{Value: "u"},
+ "updated_by": &common.ColumnVal{Value: "u"},
+ "description": &common.ColumnVal{Value: "d"},
+ },
+ },
+ },
+ {
+ Name: LISTENER_TABLE_DATA_SCOPE,
+ Rows: []common.Row{
+ {
+ "id": &common.ColumnVal{Value: "i"},
+ "_change_selector": &common.ColumnVal{Value: "c"},
+ "apid_cluster_id": &common.ColumnVal{Value: "a"},
+ "scope": &common.ColumnVal{Value: "s"},
+ "org": &common.ColumnVal{Value: "o"},
+ "env": &common.ColumnVal{Value: "e"},
+ "created": &common.ColumnVal{Value: "c"},
+ "created_by": &common.ColumnVal{Value: "c"},
+ "updated": &common.ColumnVal{Value: "u"},
+ "updated_by": &common.ColumnVal{Value: "u"},
+ },
+ },
+ },
+ },
+ }
+
+ handler.Handle(&event)
+
+ info, err := getApidInstanceInfo()
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(info.LastSnapshot).To(Equal(event.SnapshotInfo))
+
+ db := getDB()
+
+ // apid Cluster
+ var dcs []dataApidCluster
+
+ rows, err := db.Query(`
+ SELECT id, name, description, umbrella_org_app_name,
+ created, created_by, updated, updated_by,
+ _change_selector
+ FROM APID_CLUSTER`)
+ Expect(err).NotTo(HaveOccurred())
+ defer rows.Close()
+
+ c := dataApidCluster{}
+ for rows.Next() {
+ rows.Scan(&c.ID, &c.Name, &c.Description, &c.OrgAppName,
+ &c.Created, &c.CreatedBy, &c.Updated, &c.UpdatedBy,
+ &c.ChangeSelector)
+ dcs = append(dcs, c)
+ }
+
+ Expect(len(dcs)).To(Equal(1))
+ dc := dcs[0]
+
+ Expect(dc.ID).To(Equal("i"))
+ Expect(dc.Name).To(Equal("n"))
+ Expect(dc.Description).To(Equal("d"))
+ Expect(dc.OrgAppName).To(Equal("o"))
+ Expect(dc.Created).To(Equal("c"))
+ Expect(dc.CreatedBy).To(Equal("c"))
+ Expect(dc.Updated).To(Equal("u"))
+ Expect(dc.UpdatedBy).To(Equal("u"))
+ Expect(dc.ChangeSelector).To(Equal("c"))
+
+ // Data Scope
+ var dds []dataDataScope
+
+ rows, err = db.Query(`
+ SELECT id, apid_cluster_id, scope, org,
+ env, created, created_by, updated,
+ updated_by, _change_selector
+ FROM DATA_SCOPE`)
+ Expect(err).NotTo(HaveOccurred())
+ defer rows.Close()
+
+ d := dataDataScope{}
+ for rows.Next() {
+ rows.Scan(&d.ID, &d.ClusterID, &d.Scope, &d.Org,
+ &d.Env, &d.Created, &d.CreatedBy, &d.Updated,
+ &d.UpdatedBy, &d.ChangeSelector)
+ dds = append(dds, d)
+ }
+
+ Expect(len(dds)).To(Equal(1))
+ ds := dds[0]
+
+ Expect(ds.ID).To(Equal("i"))
+ Expect(ds.Org).To(Equal("o"))
+ Expect(ds.Env).To(Equal("e"))
+ Expect(ds.Scope).To(Equal("s"))
+ Expect(ds.Created).To(Equal("c"))
+ Expect(ds.CreatedBy).To(Equal("c"))
+ Expect(ds.Updated).To(Equal("u"))
+ Expect(ds.UpdatedBy).To(Equal("u"))
+ Expect(ds.ChangeSelector).To(Equal("c"))
+ })
+ })
+
+ Context("ApigeeSync change event", func() {
+
+ Context(LISTENER_TABLE_APID_CLUSTER, func() {
+
+ It("insert event should panic", func() {
+
+ event := common.ChangeList{
+ LastSequence: "test",
+ Changes: []common.Change{
+ {
+ Operation: common.Insert,
+ Table: LISTENER_TABLE_APID_CLUSTER,
+ },
+ },
+ }
+
+ Expect(func() { handler.Handle(&event) }).To(Panic())
+ })
+
+ It("update event should panic", func() {
+
+ event := common.ChangeList{
+ LastSequence: "test",
+ Changes: []common.Change{
+ {
+ Operation: common.Update,
+ Table: LISTENER_TABLE_APID_CLUSTER,
+ },
+ },
+ }
+
+ Expect(func() { handler.Handle(&event) }).To(Panic())
+ })
+
+ PIt("delete event should kill all the things!")
+ })
+
+ Context(LISTENER_TABLE_DATA_SCOPE, func() {
+
+ It("insert event should add", func() {
+ event := common.ChangeList{
+ LastSequence: "test",
+ Changes: []common.Change{
+ {
+ Operation: common.Insert,
+ Table: LISTENER_TABLE_DATA_SCOPE,
+ NewRow: common.Row{
+ "id": &common.ColumnVal{Value: "i"},
+ "_change_selector": &common.ColumnVal{Value: "c"},
+ "apid_cluster_id": &common.ColumnVal{Value: "a"},
+ "scope": &common.ColumnVal{Value: "s"},
+ "org": &common.ColumnVal{Value: "o"},
+ "env": &common.ColumnVal{Value: "e"},
+ "created": &common.ColumnVal{Value: "c"},
+ "created_by": &common.ColumnVal{Value: "c"},
+ "updated": &common.ColumnVal{Value: "u"},
+ "updated_by": &common.ColumnVal{Value: "u"},
+ },
+ },
+ },
+ }
+
+ handler.Handle(&event)
+
+ var dds []dataDataScope
+
+ rows, err := getDB().Query(`
+ SELECT id, apid_cluster_id, scope, org,
+ env, created, created_by, updated,
+ updated_by, _change_selector
+ FROM DATA_SCOPE`)
+ Expect(err).NotTo(HaveOccurred())
+ defer rows.Close()
+
+ d := dataDataScope{}
+ for rows.Next() {
+ rows.Scan(&d.ID, &d.ClusterID, &d.Scope, &d.Org,
+ &d.Env, &d.Created, &d.CreatedBy, &d.Updated,
+ &d.UpdatedBy, &d.ChangeSelector)
+ dds = append(dds, d)
+ }
+
+ Expect(len(dds)).To(Equal(1))
+ ds := dds[0]
+
+ Expect(ds.ID).To(Equal("i"))
+ Expect(ds.Org).To(Equal("o"))
+ Expect(ds.Env).To(Equal("e"))
+ Expect(ds.Scope).To(Equal("s"))
+ Expect(ds.Created).To(Equal("c"))
+ Expect(ds.CreatedBy).To(Equal("c"))
+ Expect(ds.Updated).To(Equal("u"))
+ Expect(ds.UpdatedBy).To(Equal("u"))
+ Expect(ds.ChangeSelector).To(Equal("c"))
+ })
+
+ It("delete event should delete", func() {
+ insert := common.ChangeList{
+ LastSequence: "test",
+ Changes: []common.Change{
+ {
+ Operation: common.Insert,
+ Table: LISTENER_TABLE_DATA_SCOPE,
+ NewRow: common.Row{
+ "id": &common.ColumnVal{Value: "i"},
+ "_change_selector": &common.ColumnVal{Value: "c"},
+ "apid_cluster_id": &common.ColumnVal{Value: "a"},
+ "scope": &common.ColumnVal{Value: "s"},
+ "org": &common.ColumnVal{Value: "o"},
+ "env": &common.ColumnVal{Value: "e"},
+ "created": &common.ColumnVal{Value: "c"},
+ "created_by": &common.ColumnVal{Value: "c"},
+ "updated": &common.ColumnVal{Value: "u"},
+ "updated_by": &common.ColumnVal{Value: "u"},
+ },
+ },
+ },
+ }
+
+ handler.Handle(&insert)
+
+ delete := common.ChangeList{
+ LastSequence: "test",
+ Changes: []common.Change{
+ {
+ Operation: common.Delete,
+ Table: LISTENER_TABLE_DATA_SCOPE,
+ OldRow: insert.Changes[0].NewRow,
+ },
+ },
+ }
+
+ handler.Handle(&delete)
+
+ var nRows int
+ err := getDB().QueryRow("SELECT count(id) FROM DATA_SCOPE").Scan(&nRows)
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(nRows).To(Equal(0))
+ })
+
+ It("update event should panic", func() {
+
+ event := common.ChangeList{
+ LastSequence: "test",
+ Changes: []common.Change{
+ {
+ Operation: common.Update,
+ Table: LISTENER_TABLE_DATA_SCOPE,
+ },
+ },
+ }
+
+ Expect(func() { handler.Handle(&event) }).To(Panic())
+ })
+
+ })
+
+ })
+})
diff --git a/payload.go b/payload.go
deleted file mode 100644
index 1728922..0000000
--- a/payload.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package apidApigeeSync
-
-type Payload struct {
- Email string `json:"email"`
- FirstName string `json:"firstName"`
- LastName string `json:"lastName"`
- UserName string `json:"userName"`
- Organization string `json:"organizationName"`
- Status string `json:"status"`
- CreatedAt int64 `json:"createdAt"`
- CreatedBy string `json:"createdBy"`
- LastModifiedAt int64 `json:"lastModifiedAt"`
- LastModifiedBy string `json:"lastModifiedBy"`
- AppId string `json:"appId"`
- AppFamily string `json:"appFamily"`
- ConsumerSecret string `json:"consumerSecret"`
- IssuedAt int64 `json:"issuedAt"`
- DeveloperId string `json:"developerId"`
- CallbackUrl string `json:"callbackUrl"`
- AppName string `json:"name"`
- ApiProducts []Apip `json:"apiProducts"`
- Environments []string `json:"environments"`
- Resources []string `json:"apiResources"`
- URL string `json:"url"`
- Type int `json:"type"`
- ParentId string `json:"parentId"`
- Etag string `json:"etag"`
- Customtag string `json:"customtag"`
- Manifest string `json:"manifest"`
-}
-
-type DataPayload struct {
- EntityIdentifier string `json:"entityIdentifier"`
- EntityType string `json:"entityType"`
- Operation string `json:"operation"`
- PldCont Payload `json:"entityPayload"`
-}
-
-type ChangePayload struct {
- LastMsId int64 `json:"_id"`
- Ts int64 `json:"_ts"`
- Tags []string `json:"tags"`
- Data DataPayload `json:"data"`
-}
-
-type ChangeSet struct {
- AtStart bool `json:"atStart"`
- AtEnd bool `json:"atEnd"`
- Changes []ChangePayload `json:"changes"`
-}
-
-type Apip struct {
- ApiProduct string `json:"apiproduct"`
- Status string `json:"status"`
-}