Skip to content

Commit 3688d8c

Browse files
committed
update the GH workflow, make most of the structs private, wait for chip ingress readiness before registering anything
1 parent 2d24aa5 commit 3688d8c

File tree

10 files changed

+167
-92
lines changed

10 files changed

+167
-92
lines changed

.github/workflows/framework-dockercompose-tests.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
test:
77
defaults:
88
run:
9-
working-directory: framework/examples/myproject
9+
working-directory: framework/examples/chip_ingress
1010
runs-on: ubuntu-latest
1111
permissions:
1212
id-token: write
@@ -70,7 +70,8 @@ jobs:
7070
if: steps.changes.outputs.src == 'true'
7171
env:
7272
CTF_CONFIGS: ${{ matrix.test.config }}
73-
CHIP_INGRESS_IMAGE: ${{ secrets.AWS_ACCOUNT_ID_PROD }}.dkr.ecr.us-west-2.amazonaws.com/atlas-chip-ingress:qa-latest
73+
CHIP_INGRESS_IMAGE: ${{ secrets.AWS_ACCOUNT_ID_PROD }}.dkr.ecr.us-west-2.amazonaws.com/atlas-chip-ingress:da84cb72d3a160e02896247d46ab4b9806ebee2f
74+
CHIP_CONFIG_IMAGE: ${{ secrets.AWS_ACCOUNT_ID_PROD }}.dkr.ecr.us-west-2.amazonaws.com/atlas-chip-config:7b4e9ee68fd1c737dd3480b5a3ced0188f29b969
7475
CTF_LOG_LEVEL: debug
7576
run: |
7677
go test -timeout ${{ matrix.test.timeout }} -v -count ${{ matrix.test.count }} -run ${{ matrix.test.name }}

framework/components/dockercompose/chip_ingress_set/chip_config.go

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,12 @@ import (
1414
"github.com/jhump/protocompile"
1515
"google.golang.org/protobuf/reflect/protoreflect"
1616

17-
cc "github.com/smartcontractkit/atlas/chip-config/client" // TODO: can we move it to chainlink-common?
1817
"github.com/smartcontractkit/chainlink-common/pkg/chipingress/pb"
1918
)
2019

2120
// code copied from: https://github.com/smartcontractkit/atlas/blob/master/chip-cli/config/config.go and https://github.com/smartcontractkit/atlas/blob/master/chip-cli/config/proto_validator.go
22-
// reason: avoid dependency on the chip-cli module in the testing framework
23-
func chipConfigClient(ctx context.Context, chipConfigOutput *ChipConfigOutput) (cc.ChipConfigClient, error) {
24-
fmt.Printf("🔌 Initiating connection to Chip Config at \033[1m%s\033[0m...\n\n", chipConfigOutput.GRPCExternalURL)
25-
26-
var clientOpts []cc.ClientOpt
27-
clientOpts = append(clientOpts, cc.WithBasicAuth(chipConfigOutput.Username, chipConfigOutput.Password))
28-
29-
client, err := cc.NewChipConfigClient(chipConfigOutput.GRPCExternalURL, clientOpts...)
30-
if err != nil {
31-
return nil, fmt.Errorf("failed to create Chip Config client: %w", err)
32-
}
33-
34-
// Check we can connect to the server
35-
_, pErr := client.Ping(ctx)
36-
if pErr != nil {
37-
return nil, fmt.Errorf("failed to connect to Chip Config: %w", pErr)
38-
}
39-
40-
fmt.Printf("🔗 Connected to Chip Config\n\n")
41-
42-
return client, nil
43-
}
44-
45-
func convertToPbSchemas(schemas map[string]*Schema, domain string) []*pb.Schema {
21+
// reason: avoid dependency on the private atlas module
22+
func convertToPbSchemas(schemas map[string]*schema, domain string) []*pb.Schema {
4623
pbSchemas := make([]*pb.Schema, len(schemas))
4724

4825
for i, schema := range slices.Collect(maps.Values(schemas)) {
@@ -84,54 +61,53 @@ func convertToPbSchemas(schemas map[string]*Schema, domain string) []*pb.Schema
8461
return pbSchemas
8562
}
8663

87-
type RegistrationConfig struct {
64+
type registrationConfig struct {
8865
Domain string `json:"domain"`
89-
Schemas []Schema `json:"schemas"`
66+
Schemas []schema `json:"schemas"`
9067
}
9168

92-
type Schema struct {
69+
type schema struct {
9370
Entity string `json:"entity"`
9471
Path string `json:"path"`
95-
References []SchemaReference `json:"references,omitempty"`
72+
References []schemaReference `json:"references,omitempty"`
9673
SchemaContent string
97-
Metadata Metadata `json:"metadata,omitempty"`
74+
Metadata metadata `json:"metadata,omitempty"`
9875
}
9976

100-
type Metadata struct {
101-
Stores map[string]Store `json:"stores"`
77+
type metadata struct {
78+
Stores map[string]store `json:"stores"`
10279
}
10380

104-
type Store struct {
81+
type store struct {
10582
Index []string `json:"index"`
10683
Partition []string `json:"partition"`
10784
}
10885

109-
type SchemaReference struct {
86+
type schemaReference struct {
11087
Name string `json:"name"`
11188
Entity string `json:"entity"`
11289
Path string `json:"path"`
11390
}
11491

115-
func parseSchemaConfig(configFilePath, schemaDir string) (*RegistrationConfig, map[string]*Schema, error) {
92+
func parseSchemaConfig(configFilePath, schemaDir string) (*registrationConfig, map[string]*schema, error) {
11693
cfg, err := readConfig(configFilePath)
11794
if err != nil {
11895
return nil, nil, err
11996
}
12097

121-
if err := ValidateEntityNames(cfg, schemaDir); err != nil {
98+
if err := validateEntityNames(cfg, schemaDir); err != nil {
12299
return nil, nil, fmt.Errorf("entity name validation failed: %w", err)
123100
}
124101

125102
// Our end goal is to generate a schema registration request to chip config
126103
// We will use a map to store the schemas by entity and path
127104
// this is because more than one schema may reference the same schema
128105
// technically, since SR is idempotent, this is not strictly necessary, as duplicate registrations are noop
129-
schemas := make(map[string]*Schema)
130-
131-
for _, schema := range cfg.Schemas {
106+
schemas := make(map[string]*schema)
132107

108+
for _, s := range cfg.Schemas {
133109
// For each of the schemas, we need to get the references schema content
134-
for _, reference := range schema.References {
110+
for _, reference := range s.References {
135111

136112
// read schema contents
137113
refSchemaContent, err := os.ReadFile(path.Join(schemaDir, reference.Path))
@@ -147,47 +123,47 @@ func parseSchemaConfig(configFilePath, schemaDir string) (*RegistrationConfig, m
147123
continue
148124
}
149125

150-
schemas[key] = &Schema{
126+
schemas[key] = &schema{
151127
Entity: reference.Entity,
152128
Path: reference.Path,
153129
SchemaContent: string(refSchemaContent),
154130
}
155131
}
156132

157133
// add the root schema to the map
158-
schemaContent, err := os.ReadFile(path.Join(schemaDir, schema.Path))
134+
schemaContent, err := os.ReadFile(path.Join(schemaDir, s.Path))
159135
if err != nil {
160136
return nil, nil, fmt.Errorf("error reading schema: %v", err)
161137
}
162138

163-
key := fmt.Sprintf("%s:%s", schema.Entity, schema.Path)
139+
key := fmt.Sprintf("%s:%s", s.Entity, s.Path)
164140
// if the schema already exists, that means it is referenced by another schema.
165141
// so we just need to add the references to the existing schema in the map
166142
if s, ok := schemas[key]; ok {
167-
s.References = append(s.References, schema.References...)
143+
s.References = append(s.References, s.References...)
168144
continue
169145
}
170146

171-
schemas[key] = &Schema{
172-
Entity: schema.Entity,
173-
Path: schema.Path,
147+
schemas[key] = &schema{
148+
Entity: s.Entity,
149+
Path: s.Path,
174150
SchemaContent: string(schemaContent),
175-
References: schema.References,
151+
References: s.References,
176152
}
177153

178154
}
179155

180156
return cfg, schemas, nil
181157
}
182158

183-
func readConfig(path string) (*RegistrationConfig, error) {
159+
func readConfig(path string) (*registrationConfig, error) {
184160
f, err := os.Open(path)
185161
if err != nil {
186162
return nil, fmt.Errorf("failed to open config file '%s': %w", path, err)
187163
}
188164
defer f.Close()
189165

190-
var cfg RegistrationConfig
166+
var cfg registrationConfig
191167

192168
dErr := json.NewDecoder(f).Decode(&cfg)
193169
if dErr != nil {
@@ -197,10 +173,10 @@ func readConfig(path string) (*RegistrationConfig, error) {
197173
return &cfg, nil
198174
}
199175

200-
// ValidateEntityNames validates that all entity names in the config match the fully qualified
176+
// validateEntityNames validates that all entity names in the config match the fully qualified
201177
// protobuf names (package.MessageName) from their corresponding proto files.
202178
// It collects all validation errors and returns them together for better user experience.
203-
func ValidateEntityNames(cfg *RegistrationConfig, schemaDir string) error {
179+
func validateEntityNames(cfg *registrationConfig, schemaDir string) error {
204180
var errors []string
205181

206182
for _, schema := range cfg.Schemas {

framework/components/dockercompose/chip_ingress_set/chip_ingress.go

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"github.com/testcontainers/testcontainers-go/modules/compose"
1818
"github.com/testcontainers/testcontainers-go/wait"
1919

20+
"github.com/smartcontractkit/chainlink-common/pkg/chipingress"
21+
"github.com/smartcontractkit/chainlink-common/pkg/chipingress/pb"
2022
"github.com/smartcontractkit/chainlink-testing-framework/framework"
2123
)
2224

@@ -236,8 +238,8 @@ func NewWithContext(ctx context.Context, in *Input) (*Output, error) {
236238

237239
output := &Output{
238240
ChipIngress: &ChipIngressOutput{
239-
GRPCInternalURL: fmt.Sprintf("http://%s:%s", DEFAULT_CHIP_INGRESS_SERVICE_NAME, DEFAULT_CHIP_INGRESS_GRPC_PORT),
240-
GRPCExternalURL: fmt.Sprintf("http://%s:%s", chipIngressExternalHost, DEFAULT_CHIP_INGRESS_GRPC_PORT),
241+
GRPCInternalURL: fmt.Sprintf("%s:%s", DEFAULT_CHIP_INGRESS_SERVICE_NAME, DEFAULT_CHIP_INGRESS_GRPC_PORT),
242+
GRPCExternalURL: fmt.Sprintf("%s:%s", chipIngressExternalHost, DEFAULT_CHIP_INGRESS_GRPC_PORT),
241243
},
242244
ChipConfig: &ChipConfigOutput{
243245
GRPCInternalURL: fmt.Sprintf("%s:%s", DEFAULT_CHIP_CONFIG_SERVICE_NAME, DEFAULT_CHIP_CONFIG_INTERNAL_PORT),
@@ -258,7 +260,12 @@ func NewWithContext(ctx context.Context, in *Input) (*Output, error) {
258260
in.UseCache = true
259261
framework.L.Info().Msg("Chip Ingress stack started")
260262

261-
return output, checkSchemaRegistryReadiness(ctx, 2*time.Minute, 300*time.Millisecond, output.RedPanda.SchemaRegistryExternalURL, 3)
263+
rpReadyErr := checkSchemaRegistryReadiness(ctx, 2*time.Minute, 300*time.Millisecond, output.RedPanda.SchemaRegistryExternalURL, 3)
264+
if rpReadyErr != nil {
265+
return nil, errors.Wrap(rpReadyErr, "redpanda not ready")
266+
}
267+
268+
return output, checkChipIngressReadiness(ctx, 2*time.Minute, 300*time.Millisecond, 3, output.ChipIngress)
262269
}
263270

264271
func composeFilePath(rawFilePath string) (string, error) {
@@ -323,9 +330,11 @@ func connectNetwork(connCtx context.Context, timeout time.Duration, dockerClient
323330
// checkSchemaRegistryReadiness verifies that the Schema Registry answers 2xx on GET /subjects
324331
// for minSuccessCount *consecutive* attempts, polling every `interval`, with an overall `timeout`.
325332
func checkSchemaRegistryReadiness(ctx context.Context, timeout, interval time.Duration, registryURL string, minSuccessCount int) error {
326-
if minSuccessCount < 1 {
327-
minSuccessCount = 1
328-
}
333+
framework.L.Info().Msg("Starting Schema Registry readiness check")
334+
defer func() {
335+
framework.L.Info().Msg("Schema Registry readiness check finished")
336+
}()
337+
329338
u, uErr := url.Parse(registryURL)
330339
if uErr != nil {
331340
return fmt.Errorf("parse registry URL: %w", uErr)
@@ -356,10 +365,7 @@ func checkSchemaRegistryReadiness(ctx context.Context, timeout, interval time.Du
356365
t := time.NewTicker(interval)
357366
defer t.Stop()
358367

359-
consecutive := 0
360-
var lastErr error
361-
362-
for {
368+
return readinessCheck(ctx, timeout, interval, minSuccessCount, func(ctx context.Context) error {
363369
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
364370
// small belt-and-suspenders to ensure no reuse even if transport changes
365371
req.Close = true
@@ -372,21 +378,99 @@ func checkSchemaRegistryReadiness(ctx context.Context, timeout, interval time.Du
372378
}
373379

374380
if err == nil && resp.StatusCode/100 == 2 {
375-
framework.L.Debug().Msgf("schema registry ready check succeeded with status %d (%d/%d)", resp.StatusCode, consecutive+1, minSuccessCount)
381+
return nil
382+
} else {
383+
if err != nil {
384+
return fmt.Errorf("GET /subjects failed: %w", err)
385+
} else {
386+
return fmt.Errorf("GET /subjects status %d", resp.StatusCode)
387+
}
388+
}
389+
})
390+
}
391+
392+
// checkChipIngressReadiness verifies that the Chip Ingress can register a schema
393+
// for minSuccessCount *consecutive* attempts, polling every `interval`, with an overall `timeout`.
394+
func checkChipIngressReadiness(ctx context.Context, timeout, interval time.Duration, minSuccessCount int, chipIngressOutput *ChipIngressOutput) error {
395+
client, cErr := chipingress.NewClient(chipIngressOutput.GRPCExternalURL)
396+
if cErr != nil {
397+
return fmt.Errorf("failed to create Chip client: %w", cErr)
398+
}
399+
400+
framework.L.Info().Msg("Starting Chip Ingress readiness check")
401+
defer func() {
402+
framework.L.Info().Msg("Chip Ingress readiness check finished")
403+
}()
404+
405+
ctx, cancel := context.WithTimeout(ctx, timeout)
406+
defer cancel()
407+
408+
t := time.NewTicker(interval)
409+
defer t.Stop()
410+
411+
// raw schema from demo_client_payload.proto with one new field
412+
schema := `
413+
syntax = "proto3";
414+
415+
option go_package = "github.com/smartcontractkit/atlas/chip-ingress/cmd/demo_client/pb";
416+
417+
package pb;
418+
419+
// Used for testing
420+
message DemoClientPayload {
421+
string id=1;
422+
string domain=2;
423+
string entity=3;
424+
int64 batch_num=4;
425+
int64 message_num=5;
426+
int64 batch_position=6;
427+
optional string new_field=7; // New field added
428+
}
429+
`
430+
431+
subject := "platform-demo"
432+
schemaObj := &pb.Schema{
433+
Subject: subject,
434+
Format: pb.SchemaType_PROTOBUF,
435+
Schema: schema,
436+
}
437+
438+
request := &pb.RegisterSchemaRequest{
439+
Schemas: []*pb.Schema{schemaObj},
440+
}
441+
442+
return readinessCheck(ctx, timeout, interval, minSuccessCount, func(ctx context.Context) error {
443+
_, err := client.RegisterSchema(ctx, request)
444+
return err
445+
})
446+
}
447+
448+
func readinessCheck(ctx context.Context, timeout, interval time.Duration, minSuccessCount int, checkFunc func(context.Context) error) error {
449+
if minSuccessCount < 1 {
450+
minSuccessCount = 1
451+
}
452+
453+
ctx, cancel := context.WithTimeout(ctx, timeout)
454+
defer cancel()
455+
456+
t := time.NewTicker(interval)
457+
defer t.Stop()
458+
459+
consecutive := 0
460+
var lastErr error
461+
462+
for {
463+
if err := checkFunc(ctx); err == nil {
464+
framework.L.Debug().Msgf("readiness check succeeded (%d/%d)", consecutive+1, minSuccessCount)
376465
consecutive++
377466
if consecutive >= minSuccessCount {
378-
framework.L.Debug().Msg("schema registry is ready")
467+
framework.L.Debug().Msg("service is ready")
379468
return nil
380469
}
381470
} else {
382471
consecutive = 0
383-
if err != nil {
384-
framework.L.Debug().Msgf("schema registry ready check failed with error %v (need %d/%d consecutive successes)", err, consecutive, minSuccessCount)
385-
lastErr = fmt.Errorf("GET /subjects failed: %w", err)
386-
} else {
387-
framework.L.Debug().Msgf("schema registry ready check failed with error %v and status code %d (need %d/%d consecutive successes)", err, resp.StatusCode, consecutive, minSuccessCount)
388-
lastErr = fmt.Errorf("GET /subjects status %d", resp.StatusCode)
389-
}
472+
framework.L.Debug().Msgf("readiness check failed with error %v (need %d/%d consecutive successes)", err, consecutive, minSuccessCount)
473+
lastErr = fmt.Errorf("readiness check failed: %w", err)
390474
}
391475

392476
select {
@@ -397,7 +481,7 @@ func checkSchemaRegistryReadiness(ctx context.Context, timeout, interval time.Du
397481
return fmt.Errorf("schema registry not ready after %s; needed %d consecutive successes (got %d): %w",
398482
timeout, minSuccessCount, consecutive, lastErr)
399483
case <-t.C:
400-
framework.L.Debug().Msg("schema registry not ready yet, retrying...")
484+
framework.L.Debug().Msg("Schema registry not ready yet, retrying...")
401485
// poll again
402486
}
403487
}

framework/components/dockercompose/chip_ingress_set/docker-compose.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ services:
3131
CSA_AUTH_USERNAME: ${CSA_AUTH_USERNAME:-}
3232
CSA_AUTH_PASSWORD: ${CSA_AUTH_PASSWORD:-}
3333
CSA_AUTH_REQUIRE_TLS: ${CSA_AUTH_REQUIRE_TLS:-false}
34+
CHIP_CONFIG_ENABLED: ${CHIP_CONFIG_ENABLED:-true}
35+
CHIP_CONFIG_REQUIRE_TLS: ${CHIP_CONFIG_REQUIRE_TLS:-false}
36+
CHIP_CONFIG_AUTH_USERNAME: ${CHIP_CONFIG_AUTH_USERNAME:-admin}
37+
CHIP_CONFIG_AUTH_PASSWORD: ${CHIP_CONFIG_AUTH_PASSWORD:-password}
38+
CHIP_CONFIG_HOST: ${CHIP_CONFIG_HOST:-chip-config}
39+
CHIP_CONFIG_PORT: ${CHIP_CONFIG_PORT:-50051}
3440
ports:
3541
- "50051:50051"
3642
- "9090:9090"
@@ -75,7 +81,7 @@ services:
7581
POSTGRES_USER: postgres
7682
POSTGRES_PASSWORD: postgres
7783
healthcheck:
78-
test: [ "CMD", "pg_isready", "-U", "${POSTGRES_USER}", "-d", "${POSTGRES_DB}", "-h", "localhost" ]
84+
test: [ "CMD", "pg_isready", "-U", "postgres", "-d", "chip_config", "-h", "localhost" ]
7985
interval: 5s
8086
timeout: 10s
8187
retries: 3

0 commit comments

Comments
 (0)