Skip to content

Commit

Permalink
[TT-1862] remove logstream (#15465)
Browse files Browse the repository at this point in the history
* remove logstream, directly dump all Docker logs to files

* fail ocr test on purpose

* fix lint, newer workflow that saves ccip logs

* update default ccip config

* do not fail OCR test, pass allowed messages to log verification, add some tolerated critical messages to CCIP tests

* fix allowed message

* add more critical logs to ignore, update chainlink-solana dep

* revert chainlink-solana deps bump

* process node logs only from the current test

* fix lints

* fix lints, bump golangci-lint version

* update run-e2e-tests commit hash to develop merge

* use tagged CTF

* add plugin-scanning log assertion to OCR2 smoke tests

* print names of nodes without expected logs

* wait in a loop for nodes to have all plugins-in-logs

* fix lints
  • Loading branch information
Tofel authored Dec 12, 2024
1 parent 2c13558 commit 8804925
Show file tree
Hide file tree
Showing 37 changed files with 288 additions and 560 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/client-compatibility-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -668,9 +668,6 @@ jobs:
E2E_TEST_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }}
E2E_TEST_PYROSCOPE_ENVIRONMENT: ci-client-compatability-${{ matrix.eth_client }}-testnet
E2E_TEST_PYROSCOPE_ENABLED: "true"
E2E_TEST_LOGGING_RUN_ID: ${{ github.run_id }}
E2E_TEST_LOG_COLLECT: ${{ vars.TEST_LOG_COLLECT }}
E2E_TEST_LOG_STREAM_LOG_TARGETS: ${{ vars.LOGSTREAM_LOG_TARGETS }}
E2E_TEST_PRIVATE_ETHEREUM_EXECUTION_LAYER: ${{ matrix.evm_node.eth_implementation || 'geth' }}
E2E_TEST_PRIVATE_ETHEREUM_ETHEREUM_VERSION: auto_fill # Auto fill the version based on the docker image
E2E_TEST_PRIVATE_ETHEREUM_CUSTOM_DOCKER_IMAGE: ${{ matrix.evm_node.docker_image }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/integration-in-memory-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
contents: read
needs: changes
if: github.event_name == 'pull_request' && ( needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true')
uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@57112554b9e5cfae79e795a8b1c36acf7e9dead7
uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
workflow_name: Run CCIP Integration Tests For PR
test_path: .github/integration-in-memory-tests.yml
Expand All @@ -95,7 +95,7 @@ jobs:
contents: read
needs: changes
if: github.event_name == 'merge_group' && ( needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true')
uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@57112554b9e5cfae79e795a8b1c36acf7e9dead7
uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
workflow_name: Run CCIP Integration Tests For Merge Queue
test_path: .github/integration-in-memory-tests.yml
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ jobs:
contents: read
needs: [build-chainlink, changes]
if: github.event_name == 'pull_request' && ( needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true')
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@27467f0073162e0ca77d33ce26f649b3d0f4c188 #[email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
workflow_name: Run Core E2E Tests For PR
chainlink_version: ${{ inputs.evm-ref || github.sha }}
Expand Down Expand Up @@ -251,7 +251,7 @@ jobs:
contents: read
needs: [build-chainlink, changes]
if: github.event_name == 'merge_group' && ( needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true')
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@27467f0073162e0ca77d33ce26f649b3d0f4c188 #[email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
workflow_name: Run Core E2E Tests For Merge Queue
chainlink_version: ${{ inputs.evm-ref || github.sha }}
Expand Down Expand Up @@ -296,7 +296,7 @@ jobs:
contents: read
needs: [build-chainlink, changes]
if: github.event_name == 'pull_request' && (needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true')
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0d4a2b2b009c87b5c366d0b97f7a8d7de2f60760
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
workflow_name: Run CCIP E2E Tests For PR
chainlink_version: ${{ inputs.evm-ref || github.sha }}
Expand Down Expand Up @@ -338,7 +338,7 @@ jobs:
contents: read
needs: [build-chainlink, changes]
if: github.event_name == 'merge_group' && (needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true')
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0d4a2b2b009c87b5c366d0b97f7a8d7de2f60760
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
workflow_name: Run CCIP E2E Tests For Merge Queue
chainlink_version: ${{ inputs.evm-ref || github.sha }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on-demand-vrfv2-performance-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
run-e2e-tests-workflow:
name: Run E2E Tests
needs: set-tests-to-run
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # [email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }}
chainlink_version: ${{ inputs.chainlink_version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on-demand-vrfv2-smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
run-e2e-tests-workflow:
name: Run E2E Tests
needs: set-tests-to-run
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # [email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }}
chainlink_version: ${{ inputs.chainlink_version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on-demand-vrfv2plus-performance-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
run-e2e-tests-workflow:
name: Run E2E Tests
needs: set-tests-to-run
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # [email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }}
chainlink_version: ${{ inputs.chainlink_version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on-demand-vrfv2plus-smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
run-e2e-tests-workflow:
name: Run E2E Tests
needs: set-tests-to-run
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # [email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }}
chainlink_version: ${{ inputs.chainlink_version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-nightly-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ on:
jobs:
call-run-e2e-tests-workflow:
name: Run E2E Tests
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # [email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
chainlink_version: ${{ inputs.chainlink_version || 'develop' }}
test_path: .github/e2e-tests.yml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-selected-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ run-name: ${{ inputs.workflow_run_name }}
jobs:
call-run-e2e-tests-workflow:
name: Run E2E Tests
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # [email protected]
uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59
with:
chainlink_version: ${{ github.event.inputs.chainlink_version }}
test_path: .github/e2e-tests.yml
Expand Down
12 changes: 3 additions & 9 deletions deployment/environment/devenv/rmn.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/smartcontractkit/chainlink-testing-framework/lib/docker"
"github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env"
"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
"github.com/smartcontractkit/chainlink-testing-framework/lib/logstream"
p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types"
)

Expand Down Expand Up @@ -51,7 +50,6 @@ func NewRage2ProxyComponent(
imageVersion string,
local ProxyLocalConfig,
shared ProxySharedConfig,
logStream *logstream.LogStream,
) (*RageProxy, error) {
rageName := fmt.Sprintf("%s-proxy-%s", name, uuid.NewString()[0:8])

Expand All @@ -71,7 +69,6 @@ func NewRage2ProxyComponent(
ContainerImage: imageName,
ContainerVersion: imageVersion,
Networks: networks,
LogStream: logStream,
},
Passphrase: DefaultAFNPassphrase,
proxyListenerPort: listenPort,
Expand Down Expand Up @@ -193,16 +190,14 @@ func NewAFN2ProxyComponent(
imageName,
imageVersion string,
shared SharedConfig,
local LocalConfig,
logStream *logstream.LogStream) (*AFN2Proxy, error) {
local LocalConfig) (*AFN2Proxy, error) {
afnName := fmt.Sprintf("%s-%s", name, uuid.NewString()[0:8])
rmn := &AFN2Proxy{
EnvComponent: test_env.EnvComponent{
ContainerName: afnName,
ContainerImage: imageName,
ContainerVersion: imageVersion,
Networks: networks,
LogStream: logStream,
},
AFNPassphrase: DefaultAFNPassphrase,
Shared: shared,
Expand Down Expand Up @@ -343,15 +338,14 @@ func NewRMNCluster(
proxyVersion string,
rmnImage string,
rmnVersion string,
logStream *logstream.LogStream,
) (*RMNCluster, error) {
rmn := &RMNCluster{
t: t,
l: l,
Nodes: make(map[string]RMNNode),
}
for name, rmnConfig := range config {
proxy, err := NewRage2ProxyComponent(networks, name, proxyImage, proxyVersion, rmnConfig.ProxyLocal, rmnConfig.ProxyShared, logStream)
proxy, err := NewRage2ProxyComponent(networks, name, proxyImage, proxyVersion, rmnConfig.ProxyLocal, rmnConfig.ProxyShared)
if err != nil {
return nil, err
}
Expand All @@ -371,7 +365,7 @@ func NewRMNCluster(
return nil, err
}
rmnConfig.Local.Networking.RageProxy = strings.TrimPrefix(fmt.Sprintf("%s:%s", proxyName, port), "/")
afn, err := NewAFN2ProxyComponent(networks, name, rmnImage, rmnVersion, rmnConfig.Shared, rmnConfig.Local, logStream)
afn, err := NewAFN2ProxyComponent(networks, name, rmnImage, rmnVersion, rmnConfig.Shared, rmnConfig.Local)
if err != nil {
return nil, err
}
Expand Down
26 changes: 0 additions & 26 deletions integration-tests/ccip-tests/testconfig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,32 +430,6 @@ Example usage:
TTL = "11h"
```

### CCIP.Env.Logging

Specifies the logging configuration for the test. Imported from [LoggingConfig](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/config/logging.go#L11) in chainlink-testing-framework.
Example usage:

```toml
[CCIP.Env.Logging]
test_log_collect = false # if set to true will save logs even if test did not fail

[CCIP.Env.Logging.LogStream]
# supported targets: file, loki, in-memory. if empty no logs will be persistet
log_targets = ["file"]
# context timeout for starting log producer and also time-frame for requesting logs
log_producer_timeout = "10s"
# number of retries before log producer gives up and stops listening to logs
log_producer_retry_limit = 10

[CCIP.Env.Logging.Loki]
tenant_id = "..."
endpoint = "https://loki...."

[CCIP.Env.Logging.Grafana]
base_url = "https://grafana..../"
dashboard_url = "/d/6vjVx-1V8/ccip-long-running-tests"
```

### CCIP.Env.Lane.LeaderLaneEnabled

Specifies whether to enable the leader lane feature. This setting is only applicable for new deployments.
Expand Down
117 changes: 0 additions & 117 deletions integration-tests/ccip-tests/testconfig/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,120 +175,6 @@ type Common struct {
func (p *Common) ReadFromEnvVar() error {
logger := logging.GetTestLogger(nil)

testLogCollect := ctfconfig.MustReadEnvVar_Boolean(ctfconfig.E2E_TEST_LOG_COLLECT_ENV)
if testLogCollect != nil {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.TestLogCollect", ctfconfig.E2E_TEST_LOG_COLLECT_ENV)
p.Logging.TestLogCollect = testLogCollect
}

loggingRunID := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOGGING_RUN_ID_ENV)
if loggingRunID != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.RunID", ctfconfig.E2E_TEST_LOGGING_RUN_ID_ENV)
p.Logging.RunId = &loggingRunID
}

logstreamLogTargets := ctfconfig.MustReadEnvVar_Strings(ctfconfig.E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV, ",")
if len(logstreamLogTargets) > 0 {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.LogStream == nil {
p.Logging.LogStream = &ctfconfig.LogStreamConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.LogStream.LogTargets", ctfconfig.E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV)
p.Logging.LogStream.LogTargets = logstreamLogTargets
}

lokiTenantID := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_TENANT_ID_ENV)
if lokiTenantID != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Loki == nil {
p.Logging.Loki = &ctfconfig.LokiConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Loki.TenantId", ctfconfig.E2E_TEST_LOKI_TENANT_ID_ENV)
p.Logging.Loki.TenantId = &lokiTenantID
}

lokiEndpoint := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_ENDPOINT_ENV)
if lokiEndpoint != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Loki == nil {
p.Logging.Loki = &ctfconfig.LokiConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Loki.Endpoint", ctfconfig.E2E_TEST_LOKI_ENDPOINT_ENV)
p.Logging.Loki.Endpoint = &lokiEndpoint
}

lokiBasicAuth := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_BASIC_AUTH_ENV)
if lokiBasicAuth != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Loki == nil {
p.Logging.Loki = &ctfconfig.LokiConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Loki.BasicAuth", ctfconfig.E2E_TEST_LOKI_BASIC_AUTH_ENV)
p.Logging.Loki.BasicAuth = &lokiBasicAuth
}

lokiBearerToken := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_BEARER_TOKEN_ENV)
if lokiBearerToken != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Loki == nil {
p.Logging.Loki = &ctfconfig.LokiConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Loki.BearerToken", ctfconfig.E2E_TEST_LOKI_BEARER_TOKEN_ENV)
p.Logging.Loki.BearerToken = &lokiBearerToken
}

grafanaBaseUrl := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_GRAFANA_BASE_URL_ENV)
if grafanaBaseUrl != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Grafana == nil {
p.Logging.Grafana = &ctfconfig.GrafanaConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Grafana.BaseUrl", ctfconfig.E2E_TEST_GRAFANA_BASE_URL_ENV)
p.Logging.Grafana.BaseUrl = &grafanaBaseUrl
}

grafanaDashboardUrl := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_GRAFANA_DASHBOARD_URL_ENV)
if grafanaDashboardUrl != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Grafana == nil {
p.Logging.Grafana = &ctfconfig.GrafanaConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Grafana.DashboardUrl", ctfconfig.E2E_TEST_GRAFANA_DASHBOARD_URL_ENV)
p.Logging.Grafana.DashboardUrl = &grafanaDashboardUrl
}

grafanaBearerToken := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_GRAFANA_BEARER_TOKEN_ENV)
if grafanaBearerToken != "" {
if p.Logging == nil {
p.Logging = &ctfconfig.LoggingConfig{}
}
if p.Logging.Grafana == nil {
p.Logging.Grafana = &ctfconfig.GrafanaConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.Grafana.BearerToken", ctfconfig.E2E_TEST_GRAFANA_BEARER_TOKEN_ENV)
p.Logging.Grafana.BearerToken = &grafanaBearerToken
}

selectedNetworks := ctfconfig.MustReadEnvVar_Strings(ctfconfig.E2E_TEST_SELECTED_NETWORK_ENV, ",")
if len(selectedNetworks) > 0 {
if p.Network == nil {
Expand Down Expand Up @@ -421,9 +307,6 @@ func (p *Common) GetSethConfig() *seth.Config {
}

func (p *Common) Validate() error {
if err := p.Logging.Validate(); err != nil {
return fmt.Errorf("error validating logging config %w", err)
}
if p.Network == nil {
return errors.New("no networks specified")
}
Expand Down
11 changes: 0 additions & 11 deletions integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,6 @@ addresses_to_fund = [
[CCIP.Env.PrivateEthereumNetworks.SIMULATED_2.EthereumChainConfig.HardForkEpochs]
Deneb = 500

[CCIP.Env.Logging]
test_log_collect = false # if set to true will save logs even if test did not fail

[CCIP.Env.Logging.LogStream]
# supported targets: file, loki, in-memory. if empty no logs will be persistet
log_targets = ["file"]
# context timeout for starting log producer and also time-frame for requesting logs
log_producer_timeout = "10s"
# number of retries before log producer gives up and stops listening to logs
log_producer_retry_limit = 10

# these values will be used to set up chainlink DON
# along with these values, the secrets needs to be specified as part of .env variables
#
Expand Down
2 changes: 0 additions & 2 deletions integration-tests/ccip-tests/testsetups/test_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,6 @@ func DeployLocalCluster(
pointer.GetString(clNode.ChainlinkImage.Image),
pointer.GetString(clNode.ChainlinkImage.Version),
toml,
env.LogStream,
test_env.WithPgDBOptions(
ctftestenv.WithPostgresImageName(clNode.DBImage),
ctftestenv.WithPostgresImageVersion(clNode.DBTag),
Expand Down Expand Up @@ -381,7 +380,6 @@ func DeployLocalCluster(
pointer.GetString(testInputs.EnvInput.NewCLCluster.Common.ChainlinkImage.Image),
pointer.GetString(testInputs.EnvInput.NewCLCluster.Common.ChainlinkImage.Version),
toml,
env.LogStream,
test_env.WithPgDBOptions(
ctftestenv.WithPostgresImageName(testInputs.EnvInput.NewCLCluster.Common.DBImage),
ctftestenv.WithPostgresImageVersion(testInputs.EnvInput.NewCLCluster.Common.DBTag),
Expand Down
Loading

0 comments on commit 8804925

Please sign in to comment.