-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
caab755
commit a1fe0e0
Showing
47 changed files
with
4,218 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1,605 changes: 1,605 additions & 0 deletions
1,605
integration_test/snowpipestreaming/snowpipestreaming_test.go
Large diffs are not rendered by default.
Oops, something went wrong.
11 changes: 11 additions & 0 deletions
11
integration_test/snowpipestreaming/testdata/docker-compose.rudder-snowpipe-clients.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
version: "3.9" | ||
|
||
services: | ||
rudder-snowpipe-clients: | ||
image: "hub.dev-rudder.rudderlabs.com/dockerhub-proxy/rudderstack/rudder-snowpipe-clients:develop" | ||
ports: | ||
- "9078" | ||
healthcheck: | ||
test: wget --no-verbose --tries=1 --spider http://localhost:9078/health || exit 1 | ||
interval: 1s | ||
retries: 25 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
router/batchrouter/asyncdestinationmanager/snowpipestreaming/apiadapter.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package snowpipestreaming | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/rudderlabs/rudder-go-kit/stats" | ||
|
||
backendconfig "github.com/rudderlabs/rudder-server/backend-config" | ||
"github.com/rudderlabs/rudder-server/router/batchrouter/asyncdestinationmanager/snowpipestreaming/internal/model" | ||
) | ||
|
||
type apiAdapter struct { | ||
stats struct { | ||
createChannelCount stats.Counter | ||
deleteChannelCount stats.Counter | ||
insertCount stats.Counter | ||
statusCount stats.Counter | ||
createChannelResponseTime stats.Timer | ||
deleteChannelResponseTime stats.Timer | ||
insertResponseTime stats.Timer | ||
statusResponseTime stats.Timer | ||
} | ||
|
||
api | ||
} | ||
|
||
func newApiAdapter(api api, statsFactory stats.Stats, destination *backendconfig.DestinationT) *apiAdapter { | ||
adapter := &apiAdapter{} | ||
adapter.api = api | ||
|
||
tags := stats.Tags{ | ||
"module": "batch_router", | ||
"workspaceId": destination.WorkspaceID, | ||
"destType": destination.DestinationDefinition.Name, | ||
"destinationId": destination.ID, | ||
} | ||
adapter.stats.createChannelCount = statsFactory.NewTaggedStat("snowpipestreaming_create_channel_count", stats.CountType, tags) | ||
adapter.stats.deleteChannelCount = statsFactory.NewTaggedStat("snowpipestreaming_delete_channel_count", stats.CountType, tags) | ||
adapter.stats.insertCount = statsFactory.NewTaggedStat("snowpipestreaming_insert_count", stats.CountType, tags) | ||
adapter.stats.statusCount = statsFactory.NewTaggedStat("snowpipestreaming_status_count", stats.CountType, tags) | ||
adapter.stats.createChannelResponseTime = statsFactory.NewTaggedStat("snowpipestreaming_create_channel_response_time", stats.TimerType, tags) | ||
adapter.stats.deleteChannelResponseTime = statsFactory.NewTaggedStat("snowpipestreaming_delete_channel_response_time", stats.TimerType, tags) | ||
adapter.stats.insertResponseTime = statsFactory.NewTaggedStat("snowpipestreaming_insert_response_time", stats.TimerType, tags) | ||
adapter.stats.statusResponseTime = statsFactory.NewTaggedStat("snowpipestreaming_status_response_time", stats.TimerType, tags) | ||
|
||
return adapter | ||
} | ||
|
||
func (a *apiAdapter) CreateChannel(ctx context.Context, req *model.CreateChannelRequest) (*model.ChannelResponse, error) { | ||
defer a.stats.createChannelCount.Increment() | ||
defer a.stats.createChannelResponseTime.RecordDuration()() | ||
return a.api.CreateChannel(ctx, req) | ||
} | ||
|
||
func (a *apiAdapter) DeleteChannel(ctx context.Context, channelID string, sync bool) error { | ||
defer a.stats.deleteChannelCount.Increment() | ||
defer a.stats.deleteChannelResponseTime.RecordDuration()() | ||
return a.api.DeleteChannel(ctx, channelID, sync) | ||
} | ||
|
||
func (a *apiAdapter) Insert(ctx context.Context, channelID string, insertRequest *model.InsertRequest) (*model.InsertResponse, error) { | ||
defer a.stats.insertCount.Increment() | ||
defer a.stats.insertResponseTime.RecordDuration()() | ||
return a.api.Insert(ctx, channelID, insertRequest) | ||
} | ||
|
||
func (a *apiAdapter) Status(ctx context.Context, channelID string) (*model.StatusResponse, error) { | ||
defer a.stats.statusCount.Increment() | ||
defer a.stats.statusResponseTime.RecordDuration()() | ||
return a.api.Status(ctx, channelID) | ||
} |
166 changes: 166 additions & 0 deletions
166
router/batchrouter/asyncdestinationmanager/snowpipestreaming/channel.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package snowpipestreaming | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/rudderlabs/rudder-server/router/batchrouter/asyncdestinationmanager/common" | ||
internalapi "github.com/rudderlabs/rudder-server/router/batchrouter/asyncdestinationmanager/snowpipestreaming/internal/api" | ||
"github.com/rudderlabs/rudder-server/router/batchrouter/asyncdestinationmanager/snowpipestreaming/internal/model" | ||
"github.com/rudderlabs/rudder-server/warehouse/integrations/manager" | ||
whutils "github.com/rudderlabs/rudder-server/warehouse/utils" | ||
) | ||
|
||
func (m *Manager) createChannel( | ||
ctx context.Context, | ||
asyncDest *common.AsyncDestinationStruct, | ||
destConf destConfig, | ||
tableName string, | ||
eventSchema whutils.ModelTableSchema, | ||
) (*model.ChannelResponse, error) { | ||
if response, ok := m.channelCache.Load(tableName); ok { | ||
return response.(*model.ChannelResponse), nil | ||
} | ||
|
||
req := &model.CreateChannelRequest{ | ||
RudderIdentifier: asyncDest.Destination.ID, | ||
Partition: m.config.instanceID, | ||
AccountConfig: model.AccountConfig{ | ||
Account: destConf.Account, | ||
User: destConf.User, | ||
Role: destConf.Role, | ||
PrivateKey: whutils.FormatPemContent(destConf.PrivateKey), | ||
PrivateKeyPassphrase: destConf.PrivateKeyPassphrase, | ||
}, | ||
TableConfig: model.TableConfig{ | ||
Database: destConf.Database, | ||
Schema: destConf.Namespace, | ||
Table: tableName, | ||
}, | ||
} | ||
|
||
resp, err := m.api.CreateChannel(ctx, req) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating channel: %v", err) | ||
} | ||
if resp.Success { | ||
m.channelCache.Store(tableName, resp) | ||
return resp, nil | ||
} | ||
|
||
switch resp.Code { | ||
case internalapi.ErrSchemaDoesNotExistOrNotAuthorized: | ||
resp, err = m.handleSchemaError(ctx, req, eventSchema) | ||
if err != nil { | ||
return nil, fmt.Errorf("handling schema error: %v", err) | ||
} | ||
if !resp.Success { | ||
return nil, fmt.Errorf("creating channel for schema error: %s", resp.Error) | ||
} | ||
m.channelCache.Store(tableName, resp) | ||
return resp, nil | ||
case internalapi.ErrTableDoesNotExistOrNotAuthorized: | ||
resp, err = m.handleTableError(ctx, req, eventSchema) | ||
if err != nil { | ||
return nil, fmt.Errorf("handling table error: %v", err) | ||
} | ||
if !resp.Success { | ||
return nil, fmt.Errorf("creating channel for table error: %s", resp.Error) | ||
} | ||
m.channelCache.Store(tableName, resp) | ||
return resp, nil | ||
default: | ||
return nil, fmt.Errorf("creating channel: %v", err) | ||
} | ||
} | ||
|
||
func (m *Manager) handleSchemaError( | ||
ctx context.Context, | ||
channelReq *model.CreateChannelRequest, | ||
eventSchema whutils.ModelTableSchema, | ||
) (*model.ChannelResponse, error) { | ||
m.stats.channelSchemaCreationErrorCount.Increment() | ||
|
||
snowflakeManager, err := m.createSnowflakeManager(ctx, channelReq.TableConfig.Schema) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating snowflake manager: %v", err) | ||
} | ||
defer func() { | ||
snowflakeManager.Cleanup(ctx) | ||
}() | ||
if err := snowflakeManager.CreateSchema(ctx); err != nil { | ||
return nil, fmt.Errorf("creating schema: %v", err) | ||
} | ||
if err := snowflakeManager.CreateTable(ctx, channelReq.TableConfig.Table, eventSchema); err != nil { | ||
return nil, fmt.Errorf("creating table: %v", err) | ||
} | ||
return m.api.CreateChannel(ctx, channelReq) | ||
} | ||
|
||
func (m *Manager) handleTableError( | ||
ctx context.Context, | ||
channelReq *model.CreateChannelRequest, | ||
eventSchema whutils.ModelTableSchema, | ||
) (*model.ChannelResponse, error) { | ||
m.stats.channelTableCreationErrorCount.Increment() | ||
|
||
snowflakeManager, err := m.createSnowflakeManager(ctx, channelReq.TableConfig.Schema) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating snowflake manager: %v", err) | ||
} | ||
defer func() { | ||
snowflakeManager.Cleanup(ctx) | ||
}() | ||
if err := snowflakeManager.CreateTable(ctx, channelReq.TableConfig.Table, eventSchema); err != nil { | ||
return nil, fmt.Errorf("creating table: %v", err) | ||
} | ||
return m.api.CreateChannel(ctx, channelReq) | ||
} | ||
|
||
func (m *Manager) recreateChannel( | ||
ctx context.Context, | ||
asyncDest *common.AsyncDestinationStruct, | ||
destConf destConfig, | ||
tableName string, | ||
eventSchema whutils.ModelTableSchema, | ||
existingChannelResponse *model.ChannelResponse, | ||
) (*model.ChannelResponse, error) { | ||
if err := m.deleteChannel(ctx, tableName, existingChannelResponse.ChannelID); err != nil { | ||
return nil, fmt.Errorf("deleting channel: %v", err) | ||
} | ||
|
||
channelResponse, err := m.createChannel(ctx, asyncDest, destConf, tableName, eventSchema) | ||
if err != nil { | ||
return nil, fmt.Errorf("recreating channel: %v", err) | ||
} | ||
return channelResponse, nil | ||
} | ||
|
||
func (m *Manager) deleteChannel(ctx context.Context, tableName string, channelID string) error { | ||
m.channelCache.Delete(tableName) | ||
if err := m.api.DeleteChannel(ctx, channelID, true); err != nil { | ||
return fmt.Errorf("deleting channel: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (m *Manager) createSnowflakeManager(ctx context.Context, namespace string) (manager.Manager, error) { | ||
modelWarehouse := whutils.ModelWarehouse{ | ||
WorkspaceID: m.destination.WorkspaceID, | ||
Destination: *m.destination, | ||
Namespace: namespace, | ||
Type: m.destination.DestinationDefinition.Name, | ||
Identifier: m.destination.WorkspaceID + ":" + m.destination.ID, | ||
} | ||
modelWarehouse.Destination.Config["useKeyPairAuth"] = true // Since we are currently only supporting key pair auth | ||
|
||
sf, err := manager.New(whutils.SNOWFLAKE, m.conf, m.logger, m.statsFactory) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating snowflake manager: %v", err) | ||
} | ||
err = sf.Setup(ctx, modelWarehouse, &whutils.NopUploader{}) | ||
if err != nil { | ||
return nil, fmt.Errorf("setting up snowflake manager: %v", err) | ||
} | ||
return sf, nil | ||
} |
35 changes: 35 additions & 0 deletions
35
router/batchrouter/asyncdestinationmanager/snowpipestreaming/columns.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package snowpipestreaming | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
whutils "github.com/rudderlabs/rudder-server/warehouse/utils" | ||
) | ||
|
||
func (m *Manager) addColumns(ctx context.Context, namespace, tableName string, columns []whutils.ColumnInfo) error { | ||
snowflakeManager, err := m.createSnowflakeManager(ctx, namespace) | ||
if err != nil { | ||
return fmt.Errorf("creating snowflake manager: %v", err) | ||
} | ||
defer func() { | ||
snowflakeManager.Cleanup(ctx) | ||
}() | ||
if err = snowflakeManager.AddColumns(ctx, tableName, columns); err != nil { | ||
return fmt.Errorf("adding columns: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func findNewColumns(eventSchema, snowPipeSchema whutils.ModelTableSchema) []whutils.ColumnInfo { | ||
var newColumns []whutils.ColumnInfo | ||
for column, dataType := range eventSchema { | ||
if _, exists := snowPipeSchema[column]; !exists { | ||
newColumns = append(newColumns, whutils.ColumnInfo{ | ||
Name: column, | ||
Type: dataType, | ||
}) | ||
} | ||
} | ||
return newColumns | ||
} |
Oops, something went wrong.