Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve service initialization process #1047

Merged
merged 1 commit into from
Mar 7, 2022

Conversation

FelixTing
Copy link
Member

@FelixTing FelixTing commented Feb 25, 2022

  • Start the Web Server before the trigger initialization.
  • Add retry mechanism for MqttFactory.Create()

fix: #1046

Signed-off-by: Felix Ting [email protected]

If your build fails due to your commit message not passing the build checks, please review the guidelines here: https://github.com/edgexfoundry/app-functions-sdk-go/blob/main/.github/CONTRIBUTING.md

PR Checklist

Please check if your PR fulfills the following requirements:

  • I am not introducing a breaking change (if you are, flag in conventional commit message with BREAKING CHANGE: describing the break)
  • I am not introducing a new dependency (add notes below if you are)
  • I have added unit tests for the new feature or bug fix (if not, why?)
  • I have fully tested (add details below) this the new feature or bug fix (if not, why?)
  • I have opened a PR for the related docs change (if not, why?)
    feat: Update External MQTT Trigger Docs edgex-docs#705

Testing Instructions

Download this Docker Compose file

Edit the Docker Compose file to add the custom App Service's service key to EdgeX service secretstore-setup's ADD_SECRETSTORE_TOKENS environment variable.

  secretstore-setup:
    container_name: edgex-security-secretstore-setup
    depends_on:
    - security-bootstrapper
    - vault
    environment:
      ADD_KNOWN_SECRETS: redisdb[app-rules-engine],redisdb[device-rest],redisdb[device-virtual]
      ADD_SECRETSTORE_TOKENS: 'app-external-mqtt-trigger'

Run EdgeX Foundry
docker-compose -f docker-compose.yml up -d

Download this App Service configuration
Modify [Trigger] section with the following settings:

[Trigger]
Type="external-mqtt"
  [Trigger.externalmqtt]
  Url = "tls://test.mosquitto.org:8884"
  SubscribeTopics="test/#"
  ClientId ="app-external-mqtt-trigger"
  Qos            = 0
  KeepAlive      = 10
  Retained       = false
  AutoReconnect  = true
  ConnectTimeout = "30s"
  SkipCertVerify = false
  AuthMode = "clientcert"
  SecretPath = "mqtt"

Run App Service with the configuration and EDGEX_SECURITY_SECRET_STORE=true

Verify logs contain following messages

level=INFO ts=2022-03-03T17:20:58.040262Z app=app- source=mqtt.go:76 msg="Initializing MQTT Trigger"
level=INFO ts=2022-03-03T17:20:58.040284Z app=app- source=server.go:156 msg="Starting HTTP Web Server on address localhost:59706"
level=ERROR ts=2022-03-03T17:20:58.04159Z app=app- source=mqttfactory.go:68 msg="failed to get secret data from the secret provider, error: Error found on handling secrets from underlying data-store: Received a '404' response from the secret store"
level=INFO ts=2022-03-03T17:20:58.04162Z app=app- source=mqttfactory.go:70 msg="Waiting for the secret creation API call to seed the proper credentials..."

Generate a TLS client certificate for test.mosquitto.org, refer to https://test.mosquitto.org/ssl/

Add clientcert to Secret Store using this App Service API

Verify logs contain following messages

level=ERROR ts=2022-03-03T17:47:33.693386Z app=app- source=mqttfactory.go:74 msg="invalid secret data, error: AuthModeCert selected however the key or cert PEM block was not found for secret=mqtt"
level=INFO ts=2022-03-03T17:47:33.693453Z app=app- source=mqttfactory.go:70 msg="Waiting for the secret creation API call to seed the proper credentials..."

Add clientkey to Secret Store

Verify logs contain following messages

level=INFO ts=2022-03-03T17:52:22.404456Z app=app- source=mqtt.go:127 msg="Connecting to mqtt broker for MQTT trigger at: tls://test.mosquitto.org:8884"
level=INFO ts=2022-03-03T17:52:24.14913Z app=app- source=mqtt.go:133 msg="Connected to mqtt server for MQTT trigger"
level=INFO ts=2022-03-03T17:52:24.149168Z app=app- source=service.go:200 msg="StoreAndForward disabled. Not running retry loop."
level=INFO ts=2022-03-03T17:52:24.149183Z app=app- source=service.go:203 msg="app-external-mqtt-trigger has Started"
level=INFO ts=2022-03-03T17:52:24.841152Z app=app- source=mqtt.go:161 msg="Subscribed to topic(s) 'edgex/#' for MQTT trigger"

New Dependency Instructions (If applicable)

@codecov-commenter
Copy link

codecov-commenter commented Feb 25, 2022

Codecov Report

Merging #1047 (2dccacc) into main (18aea91) will decrease coverage by 0.82%.
The diff coverage is 15.51%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1047      +/-   ##
==========================================
- Coverage   68.23%   67.41%   -0.83%     
==========================================
  Files          35       35              
  Lines        2827     2863      +36     
==========================================
+ Hits         1929     1930       +1     
- Misses        787      822      +35     
  Partials      111      111              
Impacted Files Coverage Δ
internal/app/service.go 53.19% <0.00%> (-1.33%) ⬇️
internal/trigger/mqtt/mqtt.go 42.10% <0.00%> (-2.34%) ⬇️
pkg/transforms/mqttsecret.go 20.68% <0.00%> (ø)
pkg/secure/mqttfactory.go 42.25% <19.44%> (-16.94%) ⬇️
internal/webserver/server.go 22.58% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 18aea91...2dccacc. Read the comment docs.

@FelixTing
Copy link
Member Author

@lenny-intel @cloudxxx8 @judehung for awareness.
This draft PR is ready for review. I will add unit tests and update the related docs once the proposed code change is accepted. Thanks!

Comment on lines 138 to 149
case r := <-ch:
if r.err != nil {
factory.logger.Errorf("failed to get valid secret data, error: %s. ", r.err)
if factory.secretAddedSignal == nil || !secret.IsSecurityEnabled() {
return nil, r.err
}
factory.logger.Debug("Waiting for a SecretAdded signal...")
} else {
return r.secretData, nil
}
case <-factory.secretAddedSignal:
go f(ch)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that if more than one of the channels are ready, go randomly picks which one to receive from. Under such case, is it possible to meet race condition for this select block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. No need to use channel here.

@@ -100,12 +102,21 @@ func (trigger *Trigger) Initialize(_ *sync.WaitGroup, _ context.Context, backgro
opts.KeepAlive = brokerConfig.KeepAlive
opts.Servers = []*url.URL{brokerUrl}

var secretAddedSignal chan struct{}
var ok bool
if secret.IsSecurityEnabled() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you make this check? the secretAddedSignal is created no matter the security is enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cloudxxx8 , it is no longer created in non-secure mode. It will be nil in non secure mode.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, @FelixTing should add the check back

return secretData, nil
}
for {
factory.logger.Debug("Waiting for a SecretAdded signal...")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print the INFO level log "Waiting for the secret creation API call to seed the proper credentials..."

cloudxxx8
cloudxxx8 previously approved these changes Mar 1, 2022
Copy link
Member

@cloudxxx8 cloudxxx8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

internal/app/service.go Show resolved Hide resolved
@@ -179,6 +184,13 @@ func (svc *Service) MakeItRun() error {
// deferred is a function that needs to be called when services exits.
svc.addDeferred(deferred)

// add a deferred function for the SecretAddedSignal channel created during service initialization.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// add a deferred function for the SecretAddedSignal channel created during service initialization.
// add a deferred function to close the SecretAddedSignal channel created during service initialization.

@@ -484,6 +491,9 @@ func (svc *Service) Initialize() error {
svc.ctx.appCtx, svc.ctx.appCancelCtx = context.WithCancel(context.Background())
svc.ctx.appWg = &sync.WaitGroup{}

secretAddedSignal := make(chan struct{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a buffered channel so code pushing to the channel doesn't block. Linter will fail w/o this. Please make sure you run the linter (may need to install it).

Suggested change
secretAddedSignal := make(chan struct{})
secretAddedSignal := make(chan struct{}, 1)

@@ -176,7 +176,10 @@ func TestAddSecretRequest(t *testing.T) {
mockProvider.On("StoreSecrets", "/mqtt", map[string]string{"password": "password", "username": "username"}).Return(nil)
mockProvider.On("StoreSecrets", "/no", map[string]string{"password": "password", "username": "username"}).Return(errors.New("Invalid w/o Vault"))

target := NewController(nil, dic, uuid.NewString())
ch := make(chan struct{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ch := make(chan struct{})
ch := make(chan struct{}, 1)

Comment on lines 126 to 147
secretData, err := f()
if err != nil {
factory.logger.Error(err.Error())
} else {
return secretData, nil
}
for {
factory.logger.Info("Waiting for the secret creation API call to seed the proper credentials...")
if _, ok := <-factory.secretAddedSignal; ok {
secretData, err := f()
if err != nil {
factory.logger.Error(err.Error())
} else {
return secretData, nil
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code should be in the calling function. I.e. it goes beyond get and validate. Then the above code be the body of the func instead of in-line define func.

Comment on lines 133 to 141
factory.logger.Info("Waiting for the secret creation API call to seed the proper credentials...")
if _, ok := <-factory.secretAddedSignal; ok {
secretData, err := f()
if err != nil {
factory.logger.Error(err.Error())
} else {
return secretData, nil
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only happen when running in secure mode. Otherwise it should error out because the secret wasn't found in the InsecureSecrets section of the config. Probably shouldn't not create the secretAddedSignal channel unless running in secure mode.

@FelixTing
Copy link
Member Author

Updated per all the suggestions.

@FelixTing FelixTing requested a review from lenny-goodell March 2, 2022 07:43
lenny-goodell
lenny-goodell previously approved these changes Mar 3, 2022
Copy link
Member

@lenny-goodell lenny-goodell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, ready to take out of draft and rebase?

@FelixTing FelixTing marked this pull request as ready for review March 3, 2022 15:29
@FelixTing
Copy link
Member Author

rebased

@lenny-goodell
Copy link
Member

rebased

@FelixTing , still show This branch is out-of-date with the base branch

@FelixTing
Copy link
Member Author

rebased

@FelixTing , still show This branch is out-of-date with the base branch

Uh, have some trouble with my fork repo... will fix this ASAP

- Start the Web Server before the trigger initialization.
- Add retry mechanism for MqttFactory.Create()

Signed-off-by: Felix Ting <[email protected]>
@FelixTing
Copy link
Member Author

rebased

@FelixTing , still show This branch is out-of-date with the base branch

Uh, have some trouble with my fork repo... will fix this ASAP

Sorry about the mess. This branch is now up-to-date with the main branch.

@FelixTing
Copy link
Member Author

Added testing instructions and opened a PR for docs.

Copy link
Member

@lenny-goodell lenny-goodell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lenny-goodell lenny-goodell merged commit 6bcd8b5 into edgexfoundry:main Mar 7, 2022
FelixTing added a commit to FelixTing/app-functions-sdk-go that referenced this pull request Mar 14, 2022
FelixTing added a commit to FelixTing/app-functions-sdk-go that referenced this pull request Mar 14, 2022
FelixTing added a commit to FelixTing/app-functions-sdk-go that referenced this pull request Mar 14, 2022
lenny-goodell pushed a commit that referenced this pull request Mar 15, 2022
* revert: Improve service initialization process (#1047)

This reverts commit 6bcd8b5.

* fix: Improve service initialization process

- Start webserver before trigger initialization
- Add retry mechanism for External MQTT Trigger initialization

Signed-off-by: Felix Ting <[email protected]>
@FelixTing FelixTing deleted the issue-1046 branch March 15, 2022 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve service initialization process
5 participants