diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ec848c5..5a6f13c 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -2,6 +2,22 @@ name: CodeIntegration on: workflow_dispatch: + inputs: + run_docs: + description: 'generates the documentation' + type: boolean + default: true + required: true + run_windows: + description: 'runs the Windows job' + type: boolean + default: true + required: true + run_linux: + description: 'runs the Linux job' + type: boolean + default: true + required: true push: branches: [ "main", "testing-*", "issue*" ] @@ -9,7 +25,7 @@ run-name: Code Integration [${{ github.event_name }}][${{ github.head_ref || git jobs: docs: - if: true + if: ${{ inputs.run_docs == true }} runs-on: ubuntu-latest defaults: run: @@ -52,7 +68,7 @@ jobs: path: "docs/*.pdf" build-linux: - if: true + if: ${{ inputs.run_linux == true }} runs-on: ubuntu-latest env: GO_VERSION: 1.20.14 @@ -61,7 +77,7 @@ jobs: GOOS: linux CGO_ENABLED: 1 CMAKE_BUILD_PARALLEL_LEVEL: 4 - IGNORE_PACKAGES: 'tray|docker|docs|version|webgui' + IGNORE_PACKAGES: 'tray|docker|docs|version|webgui|workers$' defaults: run: shell: bash @@ -89,7 +105,7 @@ jobs: mkdir -p cover mkdir -p report mkdir -p unit - go install github.com/jstemmer/go-junit-report@v1.0.0 + go install github.com/jstemmer/go-junit-report/v2@v2.1.0 go install github.com/axw/gocov/gocov@v1.1.0 go install github.com/matm/gocov-html/cmd/gocov-html@v1.2.0 go install github.com/AlekSi/gocov-xml@v1.1.0 @@ -162,8 +178,25 @@ jobs: name: linux_coverage_report path: "report/*.html" + - name: Fail build on test error + run: | + set +e + for i in $(go list ./... | grep -E -v "${IGNORE_PACKAGES}" | sed -n -e 's|github.com\/parvit\/qpep\/||p') + do + pushd $i + if [ ! -f unit_tests.out ]; then + exit 1 + fi + cat unit_tests.out + grep -E "FAIL:" unit_tests.out + if [ $? -eq 0 ]; then + exit 1 + fi + popd + done + build-windows: - if: true + if: ${{ inputs.run_windows == true }} runs-on: windows-latest env: GO_VERSION: 1.20.14 @@ -173,7 +206,7 @@ jobs: CGO_ENABLED: 1 QPEP_CI_ENV: 1 CMAKE_BUILD_PARALLEL_LEVEL: 4 - IGNORE_PACKAGES: 'tray|docker|docs|version|webgui' + IGNORE_PACKAGES: 'tray|docker|docs|version|webgui|workers$' MINGW_BASEDIR: 'C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin' defaults: run: @@ -260,7 +293,7 @@ jobs: MKDIR unit MKDIR cover MKDIR report - go install github.com/jstemmer/go-junit-report@v1.0.0 + go install github.com/jstemmer/go-junit-report/v2@v2.1.0 go install github.com/axw/gocov/gocov@v1.1.0 go install github.com/matm/gocov-html/cmd/gocov-html@v1.2.0 go install github.com/AlekSi/gocov-xml@v1.1.0 @@ -268,15 +301,14 @@ jobs: - name: Test shell: bash run: | - set -x export WORKSPACE=$( echo "${{ github.workspace }}" | sed -e 's|\\|/|g' ) go generate github.com/parvit/qpep/windivert for i in $(go list ./... | grep -E -v "${IGNORE_PACKAGES}" | sed -n -e 's|github.com\/parvit\/qpep\/||p') do pushd $i cp -r $WORKSPACE/windivert/x64/* . - go test -v -gcflags=-l -cover -c -o qpep.$(basename $PWD).test &> NUL || true - ./qpep.$(basename $PWD).test -test.v -test.timeout 5m -test.coverprofile=$WORKSPACE/cover/$(basename $PWD).out &> unit_tests.out || true + go test -v -gcflags=-l -cover -c -o qpep.$(basename $PWD).test > unit_tests.out 2>&1 || true + ./qpep.$(basename $PWD).test -test.v -test.timeout 5m -test.coverprofile=$WORKSPACE/cover/$(basename $PWD).out >> unit_tests.out 2>&1 || true grep -E "PASS|FAIL|SKIP" unit_tests.out || true cat unit_tests.out | go-junit-report > $WORKSPACE/unit/$(basename $PWD).xml popd @@ -287,7 +319,6 @@ jobs: shell: bash if: always() run: | - set -x export WORKSPACE=$( echo "${{ github.workspace }}" | sed -e 's|\\|/|g' ) for i in $(go list ./... | grep -E -v "${IGNORE_PACKAGES}" | sed -n -e 's|github.com\/parvit\/qpep\/||p') do @@ -318,3 +349,20 @@ jobs: with: name: windows_coverage_report path: report/*.html + + - name: Fail build on test error + shell: bash + run: | + set +e + for i in $(go list ./... | grep -E -v "${IGNORE_PACKAGES}" | sed -n -e 's|github.com\/parvit\/qpep\/||p') + do + if [ ! -f unit_tests.out ]; then + exit 1 + fi + pushd $i + grep -E "FAIL:" unit_tests.out + if [ $? -eq 0 ]; then + exit 1 + fi + popd + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c27099..85ec451 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,22 +26,65 @@ jobs: defaults: run: shell: bash - + steps: - uses: actions/checkout@v4 - name: Check tag uses: mukunku/tag-exists-action@v1.0.0 id: checkTag - with: + with: tag: 'v${{ github.event.inputs.version_tag }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + - if: ${{ steps.checkTag.outputs.exists == 'true' }} name: Fail build run: exit 1 - + + docs: + if: true + runs-on: ubuntu-latest + defaults: + run: + shell: bash + + env: + GO_VERSION: 1.20.14 + PANDOC_VERSION: 3.3 + + steps: + - uses: actions/checkout@v4 + with: + clean: true + submodules: false + + - name: Install Pandoc + uses: pandoc/actions/setup@v1.0.0 + with: + version: ${{ env.PANDOC_VERSION }} + + - name: Install TeXlive + run: sudo apt-get update && sudo apt-get install texlive-full + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Generate PDF + run: | + cd docs/ + CURDATE=$(date '+%x %T') + sed -i -E -e 's|subtitle:.+|subtitle: "User Manual - version ${{ github.event.inputs.version_tag }}"|' -e "s|date:.+|date: \"$CURDATE\"|" user-manual.md + sed -i -E -e 's|page-background:\s*resources/draft.png||' user-manual.md + go generate + + - uses: actions/upload-artifact@v4 + with: + name: qpep_user_manual + path: "docs/*.pdf" + build-linux: needs: check-release runs-on: ubuntu-latest @@ -75,35 +118,36 @@ jobs: - name: Prepare run: | + sudo apt-get install -y pkg-config libgtk-3-dev libayatana-appindicator-dev go clean -cache -x mkdir -p cover mkdir -p report mkdir -p unit + go install github.com/jstemmer/go-junit-report/v2@v2.1.0 + go install github.com/axw/gocov/gocov@v1.1.0 + go install github.com/matm/gocov-html/cmd/gocov-html@v1.2.0 + go install github.com/AlekSi/gocov-xml@v1.1.0 - name: Build Backends run: | cd backend/ go generate - - name: Build Executable + - name: Build QPep run: | go build -v -o build/qpep + - name: Build QPep Tray + run: | + pushd qpep-tray + go build -o ../build/qpep-tray + popd + - uses: actions/upload-artifact@v4 with: name: qpep_linux_b${{ github.run_id }} path: build/ - - name: Prepare Tests - run: | - mkdir -p unit/ - mkdir -p cover/ - mkdir -p report/ - go install github.com/jstemmer/go-junit-report@v1.0.0 - go install github.com/axw/gocov/gocov@v1.1.0 - go install github.com/matm/gocov-html/cmd/gocov-html@v1.2.0 - go install github.com/AlekSi/gocov-xml@v1.1.0 - - name: Test if: ${{ inputs.test_release }} run: | @@ -111,16 +155,31 @@ jobs: for i in $(go list ./... | grep -E -v "${IGNORE_PACKAGES}" | sed -n -e 's|github.com\/parvit\/qpep\/||p') do pushd $i + export WORKSPACE="${{ github.workspace }}" go test -v -gcflags=-l -cover -c -o qpep.$(basename $PWD).test &> /dev/null || true - ./qpep.$(basename $PWD).test -test.v -test.timeout 5m -test.coverprofile=${{ github.workspace }}/cover/$(basename $PWD).out &> unit_tests.out || true + ./qpep.$(basename $PWD).test -test.v -test.timeout 5m -test.coverprofile=$WORKSPACE/cover/$(basename $PWD).out &> unit_tests.out || true grep -E "PASS|FAIL|SKIP" unit_tests.out || true - cat unit_tests.out | go-junit-report > ${{ github.workspace }}/unit/$(basename $PWD).xml + cat unit_tests.out | go-junit-report > $WORKSPACE/unit/$(basename $PWD).xml + popd + done + continue-on-error: true + + - name: Publish Coverage Results + if: always() + run: | + for i in $(go list ./... | grep -E -v "${IGNORE_PACKAGES}" | sed -n -e 's|github.com\/parvit\/qpep\/||p') + do + pushd $i + echo "=== Package $i ===" >> $GITHUB_STEP_SUMMARY + gocov convert ${{ github.workspace }}/cover/$(basename $PWD).out | gocov report | grep "Coverage" >> $GITHUB_STEP_SUMMARY || true + echo >> $GITHUB_STEP_SUMMARY + gocov convert ${{ github.workspace }}/cover/$(basename $PWD).out | gocov-html > ${{ github.workspace }}/report/$(basename $PWD).html || true popd done continue-on-error: true - name: Publish Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 + uses: EnricoMi/publish-unit-test-result-action/linux@v2 if: ${{ inputs.test_release }} with: check_name: "Unit Tests - Linux Platform" @@ -224,7 +283,7 @@ jobs: MKDIR unit MKDIR cover MKDIR report - go install github.com/jstemmer/go-junit-report@v1.0.0 + go install github.com/jstemmer/go-junit-report/v2@v2.1.0 go install github.com/axw/gocov/gocov@v1.1.0 go install github.com/matm/gocov-html/cmd/gocov-html@v1.2.0 go install github.com/AlekSi/gocov-xml@v1.1.0 @@ -255,79 +314,35 @@ jobs: check_name: "Unit Tests - Windows Platform" junit_files: "unit/*.xml" - docs: - if: true - runs-on: ubuntu-latest - defaults: - run: - shell: bash - - env: - GO_VERSION: 1.20.14 - PANDOC_VERSION: 3.3 - - steps: - - uses: actions/checkout@v4 - with: - clean: true - submodules: false - - - name: Install Pandoc - uses: pandoc/actions/setup@v1.0.0 - with: - version: ${{ env.PANDOC_VERSION }} - - - name: Install TeXlive - run: sudo apt-get update && sudo apt-get install texlive-full - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Generate PDF - run: | - cd docs/ - CURDATE=$(date '+%x %T') - sed -i -E -e 's|subtitle:.+|subtitle: "User Manual - version ${{ github.event.inputs.version_tag }}"|' -e "s|date:.+|date: \"$CURDATE\"|" user-manual.md - sed -i -E -e 's|page-background:\s*resources/draft.png||' user-manual.md - go generate - - - uses: actions/upload-artifact@v4 - with: - name: qpep_user_manual - path: "docs/*.pdf" - create-release-tag: needs: [build-windows, build-linux, docs] runs-on: ubuntu-latest defaults: - run: + run: shell: bash - + steps: - - name: Changelog - uses: scottbrenner/generate-changelog-action@master - id: Changelog - env: - REPO: ${{ github.repository }} - - name: Download Windows Artifact uses: actions/download-artifact@v4 with: name: qpep_windows_b${{ github.run_id }} - + - name: Download Linux Artifact uses: actions/download-artifact@v4 with: name: qpep_linux_b${{ github.run_id }} - + + - name: Download UserManual Artifact + uses: actions/download-artifact@v4 + with: + name: qpep_user_manual + - name: Prepare archives run: | cd ${{ github.workspace }} 7z a -tzip qpep_windows_b${{ github.run_id }}.zip ${{ github.workspace }}/installer.msi 7z a -tzip qpep_linux_b${{ github.run_id }}.zip ${{ github.workspace }}/qpep - + - name: Create Release id: create_release uses: actions/create-release@latest @@ -342,24 +357,34 @@ jobs: prerelease: false - name: Attach Windows Release Asset - id: upload-release-asset + id: upload-release-asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: qpep_windows_b${{ github.run_id }}.zip asset_name: qpep_windows_v${{ github.event.inputs.version_tag }}_b${{ github.run_id }}.zip asset_content_type: application/zip - + - name: Attach Linux Release Asset - id: upload-linux-asset + id: upload-linux-asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: qpep_linux_b${{ github.run_id }}.zip asset_name: qpep_linux_v${{ github.event.inputs.version_tag }}_b${{ github.run_id }}.zip asset_content_type: application/zip - + + - name: Attach UserManual Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: qpep_user_manual.zip + asset_name: qpep_user_manual_v${{ github.event.inputs.version_tag }}_b${{ github.run_id }}.zip + asset_content_type: application/zip diff --git a/shared/configuration/params_validation.go b/shared/configuration/params_validation.go index bbf3f4f..dc27acd 100644 --- a/shared/configuration/params_validation.go +++ b/shared/configuration/params_validation.go @@ -8,6 +8,12 @@ import ( "strconv" "strings" "sync" + "time" +) + +const ( + MIN_IDLE_TIMEOUT = 1 * time.Second + MAX_IDLE_TIMEOUT = 60 * time.Second ) // AssertParamNumeric panics with error ErrImpossibleValidationRequested if the min and max values @@ -136,6 +142,13 @@ func AssertParamHostsDifferent(name string, values ...string) { } } +func AssertParamValidTimeout(name string, value time.Duration) { + if value < MIN_IDLE_TIMEOUT || value > MAX_IDLE_TIMEOUT { + logger.Error("'%s' parameter not in acceptable range: %v [%v:%v]\n", name, value, MIN_IDLE_TIMEOUT, MAX_IDLE_TIMEOUT) + panic(errors.ErrConfigurationValidationFailed) + } +} + // addressRangesChecker struct organizes the logic for checking the speed limits // for a certain address / range / domain type addressRangesChecker struct { diff --git a/shared/configuration/params_validation_test.go b/shared/configuration/params_validation_test.go index 585ce1a..ff8cfa2 100644 --- a/shared/configuration/params_validation_test.go +++ b/shared/configuration/params_validation_test.go @@ -9,6 +9,7 @@ import ( "net" "os" "testing" + "time" ) func TestParamsValidation(t *testing.T) { @@ -46,6 +47,19 @@ func (s *ParamsValidationSuite) TestParamsValidation_Numeric_Invalid() { }) } +func (s *ParamsValidationSuite) TestParamsValidation_ValidTimeout() { + t := s.T() + assert.NotPanics(t, func() { + AssertParamValidTimeout("test", 10*time.Second) + }) + assert.PanicsWithValue(t, errors.ErrConfigurationValidationFailed, func() { + AssertParamValidTimeout("test", 500*time.Millisecond) + }) + assert.PanicsWithValue(t, errors.ErrConfigurationValidationFailed, func() { + AssertParamValidTimeout("test", 2*time.Minute) + }) +} + func (s *ParamsValidationSuite) TestParamsValidation_IP() { t := s.T() assert.NotPanics(t, func() { diff --git a/workers/client/client.go b/workers/client/client.go index 007c61c..fccc0d2 100644 --- a/workers/client/client.go +++ b/workers/client/client.go @@ -149,10 +149,10 @@ func initialCheckConnection() { return } - generalConfig := configuration.QPepConfig.General + configGeneral := configuration.QPepConfig.General - keepRedirectionRetries = generalConfig.MaxConnectionRetries // reset connection tries - preferProxy := generalConfig.PreferProxy + keepRedirectionRetries = configGeneral.MaxConnectionRetries // reset connection tries + preferProxy := configGeneral.PreferProxy if preferProxy { logger.Info("Proxy preference set, trying to connect...\n") @@ -166,10 +166,10 @@ func initialCheckConnection() { // failedCheckConnection method handles the logic for switching between diverter and proxy (or viceversa if PreferProxy true) // after half connection tries are failed, and stopping altogether if retries are exhausted func failedCheckConnection() bool { - generalConfig := configuration.QPepConfig.General + configGeneral := configuration.QPepConfig.General - maxRetries := generalConfig.MaxConnectionRetries - preferProxy := generalConfig.PreferProxy + maxRetries := configGeneral.MaxConnectionRetries + preferProxy := configGeneral.PreferProxy gateway.ScaleUpTimeout() @@ -244,31 +244,32 @@ func clientStatisticsUpdate(localAddr, apiAddr string, apiPort int, publicAddres // validateConfiguration method handles the checking of the configuration values provided in the configuration files // for the client mode func validateConfiguration() { - clientConfig := configuration.QPepConfig.Client - generalConfig := configuration.QPepConfig.General - protoConfig := configuration.QPepConfig.Protocol + configClient := configuration.QPepConfig.Client + configGeneral := configuration.QPepConfig.General + configProto := configuration.QPepConfig.Protocol - configuration.AssertParamIP("listen host", clientConfig.LocalListeningAddress) - configuration.AssertParamPort("listen port", clientConfig.LocalListenPort) + configuration.AssertParamIP("listen host", configClient.LocalListeningAddress) + configuration.AssertParamPort("listen port", configClient.LocalListenPort) - configuration.AssertParamNumeric("buffer size", protoConfig.BufferSize, 1, 1024) + configuration.AssertParamNumeric("buffer size", configProto.BufferSize, 1, 1024) // resolve local listening address - clientConfig.LocalListeningAddress, clientAdditional.RedirectedInterfaces = - gateway.GetDefaultLanListeningAddress(clientConfig.LocalListeningAddress, clientConfig.GatewayHost) + configClient.LocalListeningAddress, clientAdditional.RedirectedInterfaces = + gateway.GetDefaultLanListeningAddress(configClient.LocalListeningAddress, configClient.GatewayHost) // panic if configuration is inconsistent - configuration.AssertParamIP("gateway host", clientConfig.GatewayHost) - configuration.AssertParamPort("gateway port", clientConfig.GatewayPort) + configuration.AssertParamIP("gateway host", configClient.GatewayHost) + configuration.AssertParamPort("gateway port", configClient.GatewayPort) - configuration.AssertParamPort("api port", generalConfig.APIPort) + configuration.AssertParamPort("api port", configGeneral.APIPort) - configuration.AssertParamNumeric("max connection retries", generalConfig.MaxConnectionRetries, 1, 300) - configuration.AssertParamNumeric("max diverter threads", generalConfig.WinDivertThreads, 1, 32) + configuration.AssertParamNumeric("max connection retries", configGeneral.MaxConnectionRetries, 1, 300) + configuration.AssertParamNumeric("max diverter threads", configGeneral.WinDivertThreads, 1, 32) + configuration.AssertParamValidTimeout("idle timeout", configProto.IdleTimeout) - configuration.AssertParamHostsDifferent("hosts", clientConfig.GatewayHost, clientConfig.LocalListeningAddress) - configuration.AssertParamPortsDifferent("ports", clientConfig.GatewayPort, - clientConfig.LocalListenPort, generalConfig.APIPort) + configuration.AssertParamHostsDifferent("hosts", configClient.GatewayHost, configClient.LocalListeningAddress) + configuration.AssertParamPortsDifferent("ports", configClient.GatewayPort, + configClient.LocalListenPort, configGeneral.APIPort) configuration.AssertParamNumeric("auto-redirected interfaces", len(clientAdditional.RedirectedInterfaces), 0, 256) diff --git a/workers/server/server.go b/workers/server/server.go index 37eae47..a7fafc4 100644 --- a/workers/server/server.go +++ b/workers/server/server.go @@ -104,6 +104,7 @@ func performanceWatcher(ctx context.Context, cancel context.CancelFunc) { func validateConfiguration() { configSrv := configuration.QPepConfig.Server configGeneral := configuration.QPepConfig.General + configProto := configuration.QPepConfig.Protocol configBroker := configuration.QPepConfig.Analytics configuration.AssertParamIP("listen host", configSrv.LocalListeningAddress) @@ -112,6 +113,7 @@ func validateConfiguration() { configSrv.LocalListeningAddress, _ = gateway.GetDefaultLanListeningAddress(configSrv.LocalListeningAddress, "") configuration.AssertParamPort("api port", configGeneral.APIPort) + configuration.AssertParamValidTimeout("idle timeout", configProto.IdleTimeout) configuration.AssertParamPortsDifferent("ports", configSrv.LocalListenPort, configGeneral.APIPort) if !configBroker.Enabled {