diff --git a/.github/workflows/auto_release_prep.yml b/.github/workflows/auto_release_prep.yml deleted file mode 100644 index 57a12de..0000000 --- a/.github/workflows/auto_release_prep.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Automated release prep - -on: - workflow_dispatch: - -jobs: - auto_release_prep: - uses: puppetlabs/release-engineering-repo-standards/.github/workflows/auto_release_prep.yml@v1 - secrets: inherit - with: - project-type: ruby - version-file-path: lib/vmpooler/version.rb diff --git a/.github/workflows/dependabot_merge.yml b/.github/workflows/dependabot_merge.yml deleted file mode 100644 index 75b9cea..0000000 --- a/.github/workflows/dependabot_merge.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Dependabot auto-merge - -on: pull_request - -jobs: - dependabot_merge: - uses: puppetlabs/release-engineering-repo-standards/.github/workflows/dependabot_merge.yml@v1 - secrets: inherit diff --git a/.github/workflows/ensure_label.yml b/.github/workflows/ensure_label.yml deleted file mode 100644 index 50a5fa8..0000000 --- a/.github/workflows/ensure_label.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Ensure label - -on: pull_request - -jobs: - ensure_label: - uses: puppetlabs/release-engineering-repo-standards/.github/workflows/ensure_label.yml@v1 - secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d020d40..058b2cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'puppetlabs/vmpooler' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Get Current Version - uses: actions/github-script@v7 + uses: actions/github-script@v6 id: cv with: script: | @@ -29,6 +29,37 @@ jobs: echo "version=$version" >> $GITHUB_OUTPUT echo "Found version $version from lib/vmpooler/version.rb" + - name: Generate Changelog + uses: docker://githubchangeloggenerator/github-changelog-generator:1.16.2 + with: + args: >- + --future-release ${{ steps.nv.outputs.version }} + env: + CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate Changelog + run : | + set -e + if [[ -n $(git status --porcelain) ]]; then + echo "Here is the current git status:" + git status + echo + echo "The following changes were detected:" + git --no-pager diff + echo "Uncommitted PRs found in the changelog. Please submit a release prep PR of changes after running `./update-changelog`" + exit 1 + fi + + - name: Generate Release Notes + uses: docker://githubchangeloggenerator/github-changelog-generator:1.16.2 + with: + args: >- + --since-tag ${{ steps.cv.outputs.result }} + --future-release ${{ steps.nv.outputs.version }} + --output release-notes.md + env: + CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Tag Release uses: ncipollo/release-action@v1 with: @@ -39,10 +70,10 @@ jobs: prerelease: false # This step should closely match what is used in `docker/Dockerfile` in vmpooler-deployment - - name: Install Ruby jruby-9.4.12.1 + - name: Install Ruby jruby-9.4.3.0 uses: ruby/setup-ruby@v1 with: - ruby-version: 'jruby-9.4.12.1' + ruby-version: 'jruby-9.4.3.0' - name: Build gem run: gem build *.gemspec diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ba273f5..666c602 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout repo content - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: fetch-depth: 1 - name: setup ruby @@ -22,7 +22,7 @@ jobs: - name: check lock run: '[ -f "Gemfile.lock" ] && echo "package lock file exists, skipping" || bundle lock' # install java - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v3 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index d93859a..105fc8e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -18,9 +18,9 @@ jobs: strategy: matrix: ruby-version: - - 'jruby-9.4.12.1' + - 'jruby-9.4.3.0' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -34,9 +34,9 @@ jobs: strategy: matrix: ruby-version: - - 'jruby-9.4.12.1' + - 'jruby-9.4.3.0' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.github_changelog_generator b/.github_changelog_generator index ebeb260..f5bee9c 100644 --- a/.github_changelog_generator +++ b/.github_changelog_generator @@ -1,5 +1,3 @@ project=vmpooler user=puppetlabs -exclude_labels=maintenance -github-api=https://api.github.com -release-branch=main \ No newline at end of file +exclude_labels=maintenance \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index af092e8..f09aed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,118 +1,5 @@ # Changelog -## [3.8.1](https://github.com/puppetlabs/vmpooler/tree/3.8.1) (2026-01-14) - -[Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.7.0...3.8.1) - -**Implemented enhancements:** - -- \(P4DEVOPS-9434\) Add rate limiting and input validation security enhancements [\#690](https://github.com/puppetlabs/vmpooler/pull/690) ([mahima-singh](https://github.com/mahima-singh)) -- \(P4DEVOPS-8570\) Add Phase 2 optimizations: status API caching and improved Redis pipelining [\#689](https://github.com/puppetlabs/vmpooler/pull/689) ([mahima-singh](https://github.com/mahima-singh)) -- \(P4DEVOPS-8567\) Add DLQ, auto-purge, and health checks for Redis queues [\#688](https://github.com/puppetlabs/vmpooler/pull/688) ([mahima-singh](https://github.com/mahima-singh)) -- Add retry logic for immediate clone failures [\#687](https://github.com/puppetlabs/vmpooler/pull/687) ([mahima-singh](https://github.com/mahima-singh)) - -**Fixed bugs:** - -- \(P4DEVOPS-8567\) Prevent VM allocation for already-deleted request-ids [\#688](https://github.com/puppetlabs/vmpooler/pull/688) ([mahima-singh](https://github.com/mahima-singh)) -- Prevent re-queueing requests already marked as failed [\#687](https://github.com/puppetlabs/vmpooler/pull/687) ([mahima-singh](https://github.com/mahima-singh)) - -## [3.7.0](https://github.com/puppetlabs/vmpooler/tree/3.7.0) (2025-06-04) - -[Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.6.0...3.7.0) - -**Implemented enhancements:** - -- \(P4DEVOPS-6096\) Include VMs that have been requested but not moved to pending when getting queue metrics [\#681](https://github.com/puppetlabs/vmpooler/pull/681) ([isaac-hammes](https://github.com/isaac-hammes)) -- Bump redis from 5.1.0 to 5.2.0 [\#675](https://github.com/puppetlabs/vmpooler/pull/675) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rake from 13.1.0 to 13.2.1 [\#673](https://github.com/puppetlabs/vmpooler/pull/673) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump redis from 5.0.8 to 5.1.0 [\#665](https://github.com/puppetlabs/vmpooler/pull/665) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rspec from 3.12.0 to 3.13.0 [\#664](https://github.com/puppetlabs/vmpooler/pull/664) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump opentelemetry-sdk from 1.3.1 to 1.4.0 [\#663](https://github.com/puppetlabs/vmpooler/pull/663) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump mock\_redis from 0.43.0 to 0.44.0 [\#662](https://github.com/puppetlabs/vmpooler/pull/662) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump mock\_redis from 0.41.0 to 0.43.0 [\#658](https://github.com/puppetlabs/vmpooler/pull/658) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump net-ldap from 0.18.0 to 0.19.0 [\#653](https://github.com/puppetlabs/vmpooler/pull/653) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump sinatra from 3.1.0 to 3.2.0 [\#652](https://github.com/puppetlabs/vmpooler/pull/652) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump mock\_redis from 0.40.0 to 0.41.0 [\#650](https://github.com/puppetlabs/vmpooler/pull/650) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump mock\_redis from 0.37.0 to 0.40.0 [\#643](https://github.com/puppetlabs/vmpooler/pull/643) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rake from 13.0.6 to 13.1.0 [\#638](https://github.com/puppetlabs/vmpooler/pull/638) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump thor from 1.2.2 to 1.3.0 [\#635](https://github.com/puppetlabs/vmpooler/pull/635) ([dependabot[bot]](https://github.com/apps/dependabot)) - -**Fixed bugs:** - -- Bump opentelemetry-sdk from 1.4.0 to 1.4.1 [\#672](https://github.com/puppetlabs/vmpooler/pull/672) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rack from 2.2.8.1 to 2.2.9 [\#671](https://github.com/puppetlabs/vmpooler/pull/671) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump thor from 1.3.0 to 1.3.1 [\#668](https://github.com/puppetlabs/vmpooler/pull/668) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rack from 2.2.8 to 2.2.8.1 [\#666](https://github.com/puppetlabs/vmpooler/pull/666) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump concurrent-ruby from 1.2.2 to 1.2.3 [\#660](https://github.com/puppetlabs/vmpooler/pull/660) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump puma from 6.4.1 to 6.4.2 [\#655](https://github.com/puppetlabs/vmpooler/pull/655) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump puma from 6.4.0 to 6.4.1 [\#654](https://github.com/puppetlabs/vmpooler/pull/654) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Update opentelemetry-instrumentation-http\_client requirement from = 0.22.2 to = 0.22.3 [\#646](https://github.com/puppetlabs/vmpooler/pull/646) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Update opentelemetry-instrumentation-concurrent\_ruby requirement from = 0.21.1 to = 0.21.2 [\#645](https://github.com/puppetlabs/vmpooler/pull/645) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump opentelemetry-sdk from 1.3.0 to 1.3.1 [\#642](https://github.com/puppetlabs/vmpooler/pull/642) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump prometheus-client from 4.2.1 to 4.2.2 [\#641](https://github.com/puppetlabs/vmpooler/pull/641) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump redis from 5.0.7 to 5.0.8 [\#637](https://github.com/puppetlabs/vmpooler/pull/637) ([dependabot[bot]](https://github.com/apps/dependabot)) -- \(RE-15817\) Reword fail warning and get error from redis before generating message [\#633](https://github.com/puppetlabs/vmpooler/pull/633) ([isaac-hammes](https://github.com/isaac-hammes)) - -**Merged pull requests:** - -- \(P4DEVOPS-6096\) Fix gems to prevent warnings in logs [\#685](https://github.com/puppetlabs/vmpooler/pull/685) ([isaac-hammes](https://github.com/isaac-hammes)) -- \(maint\) Revert gems to last release [\#683](https://github.com/puppetlabs/vmpooler/pull/683) ([isaac-hammes](https://github.com/isaac-hammes)) -- Bump actions/setup-java from 3 to 4 [\#648](https://github.com/puppetlabs/vmpooler/pull/648) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump actions/github-script from 6 to 7 [\#644](https://github.com/puppetlabs/vmpooler/pull/644) ([dependabot[bot]](https://github.com/apps/dependabot)) - -## [3.6.0](https://github.com/puppetlabs/vmpooler/tree/3.6.0) (2023-10-05) - -[Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.5.1...3.6.0) - -**Fixed bugs:** - -- \(maint\) Fix message for timeout notification. [\#624](https://github.com/puppetlabs/vmpooler/pull/624) ([isaac-hammes](https://github.com/isaac-hammes)) - -**Merged pull requests:** - -- Bump rubocop from 1.56.3 to 1.56.4 [\#631](https://github.com/puppetlabs/vmpooler/pull/631) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump puma from 6.3.1 to 6.4.0 [\#630](https://github.com/puppetlabs/vmpooler/pull/630) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rubocop from 1.56.2 to 1.56.3 [\#628](https://github.com/puppetlabs/vmpooler/pull/628) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump actions/checkout from 3 to 4 [\#627](https://github.com/puppetlabs/vmpooler/pull/627) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Update opentelemetry-resource\_detectors requirement from = 0.24.1 to = 0.24.2 [\#626](https://github.com/puppetlabs/vmpooler/pull/626) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rubocop from 1.56.1 to 1.56.2 [\#625](https://github.com/puppetlabs/vmpooler/pull/625) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump rubocop from 1.56.0 to 1.56.1 [\#623](https://github.com/puppetlabs/vmpooler/pull/623) ([dependabot[bot]](https://github.com/apps/dependabot)) - -## [3.5.1](https://github.com/puppetlabs/vmpooler/tree/3.5.1) (2023-08-24) - -[Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.5.0...3.5.1) - -**Fixed bugs:** - -- \(maint\) Fix bugs from redis and timeout notification updates. [\#621](https://github.com/puppetlabs/vmpooler/pull/621) ([isaac-hammes](https://github.com/isaac-hammes)) - -## [3.5.0](https://github.com/puppetlabs/vmpooler/tree/3.5.0) (2023-08-23) - -[Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.4.0...3.5.0) - -**Implemented enhancements:** - -- Improve LDAP auth [\#616](https://github.com/puppetlabs/vmpooler/issues/616) -- \(maint\) Raise error when ip address is not given to vm after clone. [\#619](https://github.com/puppetlabs/vmpooler/pull/619) ([isaac-hammes](https://github.com/isaac-hammes)) -- \(POD-8\) Add timeout\_notification config to log warning before vm is destroyed. [\#618](https://github.com/puppetlabs/vmpooler/pull/618) ([isaac-hammes](https://github.com/isaac-hammes)) -- \(RE-15565\) Add ability to use bind\_as with a service account [\#617](https://github.com/puppetlabs/vmpooler/pull/617) ([yachub](https://github.com/yachub)) - -**Merged pull requests:** - -- Bump puma from 6.3.0 to 6.3.1 [\#615](https://github.com/puppetlabs/vmpooler/pull/615) ([dependabot[bot]](https://github.com/apps/dependabot)) - -## [3.4.0](https://github.com/puppetlabs/vmpooler/tree/3.4.0) (2023-08-18) - -[Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.3.0...3.4.0) - -**Implemented enhancements:** - -- \(POD-10\) Log reason for failed VM checks. [\#611](https://github.com/puppetlabs/vmpooler/pull/611) ([isaac-hammes](https://github.com/isaac-hammes)) - -**Closed issues:** - -- Log reason connection on port 22 of a failed VM [\#609](https://github.com/puppetlabs/vmpooler/issues/609) - ## [3.3.0](https://github.com/puppetlabs/vmpooler/tree/3.3.0) (2023-08-16) [Full Changelog](https://github.com/puppetlabs/vmpooler/compare/3.2.0...3.3.0) @@ -246,7 +133,6 @@ - \(maint\) Adding a provider method tag\_vm\_user [\#469](https://github.com/puppetlabs/vmpooler/pull/469) ([sbeaulie](https://github.com/sbeaulie)) - Update testing.yml [\#468](https://github.com/puppetlabs/vmpooler/pull/468) ([sbeaulie](https://github.com/sbeaulie)) - Move vsphere specific methods out of vmpooler [\#467](https://github.com/puppetlabs/vmpooler/pull/467) ([sbeaulie](https://github.com/sbeaulie)) -- Release prep for v2.0.0 [\#465](https://github.com/puppetlabs/vmpooler/pull/465) ([genebean](https://github.com/genebean)) ## [2.0.0](https://github.com/puppetlabs/vmpooler/tree/2.0.0) (2021-12-08) @@ -255,6 +141,7 @@ **Merged pull requests:** - Use credentials file for Rubygems auth [\#466](https://github.com/puppetlabs/vmpooler/pull/466) ([genebean](https://github.com/genebean)) +- Release prep for v2.0.0 [\#465](https://github.com/puppetlabs/vmpooler/pull/465) ([genebean](https://github.com/genebean)) - Add Gem release workflow [\#464](https://github.com/puppetlabs/vmpooler/pull/464) ([genebean](https://github.com/genebean)) - Update icon in the readme to reference this repo [\#463](https://github.com/puppetlabs/vmpooler/pull/463) ([genebean](https://github.com/genebean)) - \(DIO-2769\) Move vsphere provider to its own gem [\#462](https://github.com/puppetlabs/vmpooler/pull/462) ([genebean](https://github.com/genebean)) @@ -299,17 +186,13 @@ **Merged pull requests:** - \(POOLER-176\) Add Operation Label to User Metric [\#455](https://github.com/puppetlabs/vmpooler/pull/455) ([yachub](https://github.com/yachub)) +- Update OTel gems to 0.15.0 [\#450](https://github.com/puppetlabs/vmpooler/pull/450) ([genebean](https://github.com/genebean)) +- Migrate testing to GH Actions from Travis [\#446](https://github.com/puppetlabs/vmpooler/pull/446) ([genebean](https://github.com/genebean)) ## [1.1.0-rc.1](https://github.com/puppetlabs/vmpooler/tree/1.1.0-rc.1) (2021-08-11) [Full Changelog](https://github.com/puppetlabs/vmpooler/compare/1.0.0...1.1.0-rc.1) -**Merged pull requests:** - -- \(POOLER-176\) Add Operation Label to User Metric [\#454](https://github.com/puppetlabs/vmpooler/pull/454) ([yachub](https://github.com/yachub)) -- Update OTel gems to 0.15.0 [\#450](https://github.com/puppetlabs/vmpooler/pull/450) ([genebean](https://github.com/genebean)) -- Migrate testing to GH Actions from Travis [\#446](https://github.com/puppetlabs/vmpooler/pull/446) ([genebean](https://github.com/genebean)) - ## [1.0.0](https://github.com/puppetlabs/vmpooler/tree/1.0.0) (2021-02-02) [Full Changelog](https://github.com/puppetlabs/vmpooler/compare/0.18.2...1.0.0) @@ -818,13 +701,13 @@ - Do not have a hardcoded list of VM providers [\#230](https://github.com/puppetlabs/vmpooler/issues/230) - Use a dynamic check\_pool period [\#226](https://github.com/puppetlabs/vmpooler/issues/226) - vmpooler doesn't seem to recognize ready VMs [\#218](https://github.com/puppetlabs/vmpooler/issues/218) -- `find\_vmdks` in `vsphere\_helper` should not use `vmdk\_datastore.\_connection` [\#213](https://github.com/puppetlabs/vmpooler/issues/213) -- `get\_base\_vm\_container\_from` in `vsphere\_helper` ensures the wrong connection [\#212](https://github.com/puppetlabs/vmpooler/issues/212) +- `find_vmdks` in `vsphere_helper` should not use `vmdk_datastore._connection` [\#213](https://github.com/puppetlabs/vmpooler/issues/213) +- `get_base_vm_container_from` in `vsphere_helper` ensures the wrong connection [\#212](https://github.com/puppetlabs/vmpooler/issues/212) - `close` in vsphere\_helper throws an error if a connection was never made [\#211](https://github.com/puppetlabs/vmpooler/issues/211) -- `find\_pool` in vsphere\_helper.rb has subtle errors [\#210](https://github.com/puppetlabs/vmpooler/issues/210) -- `find\_pool` in vsphere\_helper tends to throw instead of returning nil for missing pools [\#209](https://github.com/puppetlabs/vmpooler/issues/209) +- `find_pool` in vsphere\_helper.rb has subtle errors [\#210](https://github.com/puppetlabs/vmpooler/issues/210) +- `find_pool` in vsphere\_helper tends to throw instead of returning nil for missing pools [\#209](https://github.com/puppetlabs/vmpooler/issues/209) - Vsphere connections are always insecure \(Ignore cert errors\) [\#207](https://github.com/puppetlabs/vmpooler/issues/207) -- `find\_folder` in vsphere\_helper.rb has subtle errors [\#204](https://github.com/puppetlabs/vmpooler/issues/204) +- `find_folder` in vsphere\_helper.rb has subtle errors [\#204](https://github.com/puppetlabs/vmpooler/issues/204) - Should not use `abort` in vsphere\_helper [\#203](https://github.com/puppetlabs/vmpooler/issues/203) - No reason why get\_snapshot\_list is defined in vsphere\_helper [\#202](https://github.com/puppetlabs/vmpooler/issues/202) - Setting max\_tries in configuration results in vSphereHelper going into infinite loop [\#199](https://github.com/puppetlabs/vmpooler/issues/199) @@ -886,7 +769,7 @@ - \(POOLER-93\) Extend API endpoint to provide just what is needed [\#245](https://github.com/puppetlabs/vmpooler/pull/245) ([sbeaulie](https://github.com/sbeaulie)) - \(POOLER-92\) Add the alias information in the API status page for each… [\#244](https://github.com/puppetlabs/vmpooler/pull/244) ([sbeaulie](https://github.com/sbeaulie)) - \(QENG-5305\) Improve vmpooler host selection [\#242](https://github.com/puppetlabs/vmpooler/pull/242) ([mattkirby](https://github.com/mattkirby)) -- Allow user to specify a configuration file in VMPOOLER\_CONFIG\_FILE variable [\#241](https://github.com/puppetlabs/vmpooler/pull/241) ([amcdson](https://github.com/amcdson)) +- Allow user to specify a configuration file in VMPOOLER\_CONFIG\_FILE variable [\#241](https://github.com/puppetlabs/vmpooler/pull/241) ([adamdav](https://github.com/adamdav)) - Fix no implicit conversion to rational from nil [\#239](https://github.com/puppetlabs/vmpooler/pull/239) ([sbeaulie](https://github.com/sbeaulie)) - Updated Vagrant box and associated docs [\#237](https://github.com/puppetlabs/vmpooler/pull/237) ([genebean](https://github.com/genebean)) - \(GH-226\) Respond quickly to VMs being consumed [\#236](https://github.com/puppetlabs/vmpooler/pull/236) ([glennsarti](https://github.com/glennsarti)) @@ -920,7 +803,88 @@ - \(maint\) Add rubocop and allow failures in Travis CI [\#183](https://github.com/puppetlabs/vmpooler/pull/183) ([glennsarti](https://github.com/glennsarti)) - \(POOLER-73\) Update unit tests prior to refactoring [\#182](https://github.com/puppetlabs/vmpooler/pull/182) ([glennsarti](https://github.com/glennsarti)) - \(POOLER-71\) Add dummy authentication provider [\#180](https://github.com/puppetlabs/vmpooler/pull/180) ([glennsarti](https://github.com/glennsarti)) +- \(maint\) Remove Ruby 1.9.3 testing from Travis [\#178](https://github.com/puppetlabs/vmpooler/pull/178) ([glennsarti](https://github.com/glennsarti)) - \(maint\) Enhance VM Pooler developer experience [\#177](https://github.com/puppetlabs/vmpooler/pull/177) ([glennsarti](https://github.com/glennsarti)) +- \(POOLER-47\) Send clone errors up [\#175](https://github.com/puppetlabs/vmpooler/pull/175) ([mattkirby](https://github.com/mattkirby)) +- \(POOLER-48\) Clear migrations at application start time [\#174](https://github.com/puppetlabs/vmpooler/pull/174) ([mattkirby](https://github.com/mattkirby)) +- Add retry logic with a delay for vsphere connections [\#173](https://github.com/puppetlabs/vmpooler/pull/173) ([mattkirby](https://github.com/mattkirby)) +- \(POOLER-44\) Fix vmpooler.migrate reference [\#172](https://github.com/puppetlabs/vmpooler/pull/172) ([mattkirby](https://github.com/mattkirby)) +- Add `puma` as required gem [\#171](https://github.com/puppetlabs/vmpooler/pull/171) ([sschneid](https://github.com/sschneid)) +- Fix JavaScript error on nil `weekly_data` [\#170](https://github.com/puppetlabs/vmpooler/pull/170) ([sschneid](https://github.com/sschneid)) +- Containerize vmpooler [\#169](https://github.com/puppetlabs/vmpooler/pull/169) ([sschneid](https://github.com/sschneid)) +- Add vagrant-vmpooler plugin to readme [\#168](https://github.com/puppetlabs/vmpooler/pull/168) ([briancain](https://github.com/briancain)) +- Improve vmpooler scheduling logic [\#167](https://github.com/puppetlabs/vmpooler/pull/167) ([mattkirby](https://github.com/mattkirby)) +- \[QENG-4181\] Add per-pool stats to `/status` API [\#162](https://github.com/puppetlabs/vmpooler/pull/162) ([rick](https://github.com/rick)) +- Merge CI.next into Master [\#161](https://github.com/puppetlabs/vmpooler/pull/161) ([shermdog](https://github.com/shermdog)) +- \(maint\) update README.md and LICENSE to reflect rebranding [\#157](https://github.com/puppetlabs/vmpooler/pull/157) ([erosa](https://github.com/erosa)) +- Add info about vmfloaty [\#156](https://github.com/puppetlabs/vmpooler/pull/156) ([briancain](https://github.com/briancain)) +- Added IP lookup functionality for /vm/hostname [\#154](https://github.com/puppetlabs/vmpooler/pull/154) ([frozenfoxx](https://github.com/frozenfoxx)) +- Improved tests for vmpooler [\#152](https://github.com/puppetlabs/vmpooler/pull/152) ([rick](https://github.com/rick)) +- Added prefix parameter to the vmpooler configuration [\#149](https://github.com/puppetlabs/vmpooler/pull/149) ([frozenfoxx](https://github.com/frozenfoxx)) +- Update license copyright [\#148](https://github.com/puppetlabs/vmpooler/pull/148) ([sschneid](https://github.com/sschneid)) +- Allow new disks to be added to running VMs via vmpooler API [\#147](https://github.com/puppetlabs/vmpooler/pull/147) ([sschneid](https://github.com/sschneid)) +- Updated YAML config variables in create\_template\_deltas.rb [\#145](https://github.com/puppetlabs/vmpooler/pull/145) ([frozenfoxx](https://github.com/frozenfoxx)) +- \(QA-2036\) Update README for Client Utility [\#143](https://github.com/puppetlabs/vmpooler/pull/143) ([cowofevil](https://github.com/cowofevil)) +- add guestinfo.hostname to VirtualMachineConfigSpecs [\#139](https://github.com/puppetlabs/vmpooler/pull/139) ([heathseals](https://github.com/heathseals)) +- \(QENG-2807\) Allow pool 'alias' names [\#138](https://github.com/puppetlabs/vmpooler/pull/138) ([sschneid](https://github.com/sschneid)) +- \(QENG-2995\) Display associated VMs in GET /token/:token endpoint [\#137](https://github.com/puppetlabs/vmpooler/pull/137) ([sschneid](https://github.com/sschneid)) +- Update API docs to include "domain" key for get vm requests [\#136](https://github.com/puppetlabs/vmpooler/pull/136) ([briancain](https://github.com/briancain)) +- \(MAINT\) Remove Ping Check on Running VMs [\#133](https://github.com/puppetlabs/vmpooler/pull/133) ([colinPL](https://github.com/colinPL)) +- \(maint\) Move VM Only When SSH Check Succeeds [\#131](https://github.com/puppetlabs/vmpooler/pull/131) ([colinPL](https://github.com/colinPL)) +- \(QENG-2952\) Check that SSH is available [\#130](https://github.com/puppetlabs/vmpooler/pull/130) ([sschneid](https://github.com/sschneid)) +- \(maint\) Update license copyright [\#128](https://github.com/puppetlabs/vmpooler/pull/128) ([sschneid](https://github.com/sschneid)) +- \(maint\) Remove duplicate \(nested\) "ok" responses [\#127](https://github.com/puppetlabs/vmpooler/pull/127) ([sschneid](https://github.com/sschneid)) +- \(maint\) Documentation updates [\#126](https://github.com/puppetlabs/vmpooler/pull/126) ([sschneid](https://github.com/sschneid)) +- Track token use times [\#125](https://github.com/puppetlabs/vmpooler/pull/125) ([sschneid](https://github.com/sschneid)) +- Docs update [\#124](https://github.com/puppetlabs/vmpooler/pull/124) ([sschneid](https://github.com/sschneid)) +- User token list [\#123](https://github.com/puppetlabs/vmpooler/pull/123) ([sschneid](https://github.com/sschneid)) +- \(maint\) Additional utility and reporting scripts [\#122](https://github.com/puppetlabs/vmpooler/pull/122) ([sschneid](https://github.com/sschneid)) +- \(maint\) Syntax fixup [\#121](https://github.com/puppetlabs/vmpooler/pull/121) ([sschneid](https://github.com/sschneid)) +- \(MAINT\) Reduce redis Calls in API [\#120](https://github.com/puppetlabs/vmpooler/pull/120) ([colinPL](https://github.com/colinPL)) +- \(maint\) Use expect\_json helper method for determining JSON response status [\#119](https://github.com/puppetlabs/vmpooler/pull/119) ([sschneid](https://github.com/sschneid)) +- \(QENG-1304\) vmpooler should require an auth key for VM destruction [\#118](https://github.com/puppetlabs/vmpooler/pull/118) ([sschneid](https://github.com/sschneid)) +- \(QENG-2636\) Host snapshots [\#117](https://github.com/puppetlabs/vmpooler/pull/117) ([sschneid](https://github.com/sschneid)) +- \(maint\) Use dep caching and containers [\#116](https://github.com/puppetlabs/vmpooler/pull/116) ([sschneid](https://github.com/sschneid)) +- \(maint\) Include travis-ci build status in README [\#115](https://github.com/puppetlabs/vmpooler/pull/115) ([sschneid](https://github.com/sschneid)) +- Show test contexts and names [\#114](https://github.com/puppetlabs/vmpooler/pull/114) ([sschneid](https://github.com/sschneid)) +- \(QENG-2246\) Add Default Rake Task [\#113](https://github.com/puppetlabs/vmpooler/pull/113) ([colinPL](https://github.com/colinPL)) +- Log empty pools [\#112](https://github.com/puppetlabs/vmpooler/pull/112) ([sschneid](https://github.com/sschneid)) +- \(QENG-2246\) Add Travis CI [\#111](https://github.com/puppetlabs/vmpooler/pull/111) ([colinPL](https://github.com/colinPL)) +- \(QENG-2388\) Tagging restrictions [\#110](https://github.com/puppetlabs/vmpooler/pull/110) ([sschneid](https://github.com/sschneid)) +- An updated dashboard [\#109](https://github.com/puppetlabs/vmpooler/pull/109) ([sschneid](https://github.com/sschneid)) +- API summary rework [\#108](https://github.com/puppetlabs/vmpooler/pull/108) ([sschneid](https://github.com/sschneid)) +- Only filter regex matches [\#106](https://github.com/puppetlabs/vmpooler/pull/106) ([sschneid](https://github.com/sschneid)) +- \(QENG-2518\) Tag-filtering [\#105](https://github.com/puppetlabs/vmpooler/pull/105) ([sschneid](https://github.com/sschneid)) +- \(QENG-2360\) check\_running\_vm Spec Tests [\#104](https://github.com/puppetlabs/vmpooler/pull/104) ([colinPL](https://github.com/colinPL)) +- \(QENG-2056\) Create daily tag indexes, report in /summary [\#102](https://github.com/puppetlabs/vmpooler/pull/102) ([sschneid](https://github.com/sschneid)) +- Store token metadata in vmpooler\_\_vm\_\_ Redis hash [\#101](https://github.com/puppetlabs/vmpooler/pull/101) ([sschneid](https://github.com/sschneid)) +- Display VM state in GET /vm/:hostname route [\#100](https://github.com/puppetlabs/vmpooler/pull/100) ([sschneid](https://github.com/sschneid)) +- Add basic auth token functionality [\#98](https://github.com/puppetlabs/vmpooler/pull/98) ([sschneid](https://github.com/sschneid)) +- Add basic HTTP authentication and /token routes [\#97](https://github.com/puppetlabs/vmpooler/pull/97) ([sschneid](https://github.com/sschneid)) +- \(QENG-2208\) Add more helper tests [\#95](https://github.com/puppetlabs/vmpooler/pull/95) ([colinPL](https://github.com/colinPL)) +- \(QENG-2208\) Move Sinatra Helpers to own file [\#94](https://github.com/puppetlabs/vmpooler/pull/94) ([colinPL](https://github.com/colinPL)) +- Fix rspec tests broken in f9de28236b726e37977123cea9b4f3a562bfdcdb [\#93](https://github.com/puppetlabs/vmpooler/pull/93) ([sschneid](https://github.com/sschneid)) +- Redirect / to /dashboard [\#92](https://github.com/puppetlabs/vmpooler/pull/92) ([sschneid](https://github.com/sschneid)) +- Ensure 'lifetime' val returned by GET /vm/:hostname is an int [\#91](https://github.com/puppetlabs/vmpooler/pull/91) ([sschneid](https://github.com/sschneid)) +- running-to-lifetime comparison should be 'greater than or equal to' [\#90](https://github.com/puppetlabs/vmpooler/pull/90) ([sschneid](https://github.com/sschneid)) +- Auto-expire Redis metadata key via Redis EXPIRE [\#89](https://github.com/puppetlabs/vmpooler/pull/89) ([sschneid](https://github.com/sschneid)) +- \(QENG-1906\) Add specs for Dashboard and root API class [\#88](https://github.com/puppetlabs/vmpooler/pull/88) ([colinPL](https://github.com/colinPL)) +- \(maint\) Fix bad redis reference [\#87](https://github.com/puppetlabs/vmpooler/pull/87) ([colinPL](https://github.com/colinPL)) +- \(QENG-1906\) Break apart check\_pending\_vm and add spec tests [\#86](https://github.com/puppetlabs/vmpooler/pull/86) ([colinPL](https://github.com/colinPL)) +- Remove defined? when checking configuration for graphite server. [\#85](https://github.com/puppetlabs/vmpooler/pull/85) ([colinPL](https://github.com/colinPL)) +- \(QENG-1906\) Add spec tests for Janitor [\#78](https://github.com/puppetlabs/vmpooler/pull/78) ([colinPL](https://github.com/colinPL)) +- \(QENG-1906\) Refactor initialize to allow config passing [\#77](https://github.com/puppetlabs/vmpooler/pull/77) ([colinPL](https://github.com/colinPL)) +- Use 'checkout' time to calculate 'running' time [\#75](https://github.com/puppetlabs/vmpooler/pull/75) ([sschneid](https://github.com/sschneid)) +- Catch improperly-formatted data payloads [\#73](https://github.com/puppetlabs/vmpooler/pull/73) ([sschneid](https://github.com/sschneid)) +- \(QENG-1905\) Adding VM-tagging support via PUT /vm/:hostname endpoint [\#72](https://github.com/puppetlabs/vmpooler/pull/72) ([sschneid](https://github.com/sschneid)) +- \(QENG-2057\) Historic Redis VM metadata [\#71](https://github.com/puppetlabs/vmpooler/pull/71) ([sschneid](https://github.com/sschneid)) +- \(QENG-1899\) Add documentation for /summary [\#67](https://github.com/puppetlabs/vmpooler/pull/67) ([colinPL](https://github.com/colinPL)) +- Use $redis.hgetall rather than hget in a loop [\#66](https://github.com/puppetlabs/vmpooler/pull/66) ([sschneid](https://github.com/sschneid)) +- /summary per-pool metrics [\#65](https://github.com/puppetlabs/vmpooler/pull/65) ([sschneid](https://github.com/sschneid)) +- Show boot metrics in /status and /summary endpoints [\#64](https://github.com/puppetlabs/vmpooler/pull/64) ([sschneid](https://github.com/sschneid)) +- \(maint\) Fixing spacing [\#63](https://github.com/puppetlabs/vmpooler/pull/63) ([sschneid](https://github.com/sschneid)) +- Metric calc via helpers [\#62](https://github.com/puppetlabs/vmpooler/pull/62) ([sschneid](https://github.com/sschneid)) +- More granular metrics [\#61](https://github.com/puppetlabs/vmpooler/pull/61) ([sschneid](https://github.com/sschneid)) diff --git a/Gemfile b/Gemfile index 0313b80..122d6b5 100644 --- a/Gemfile +++ b/Gemfile @@ -3,11 +3,11 @@ source ENV['GEM_SOURCE'] || 'https://rubygems.org' gemspec # Evaluate Gemfile.local if it exists -if File.exist? "#{__FILE__}.local" +if File.exists? "#{__FILE__}.local" instance_eval(File.read("#{__FILE__}.local")) end # Evaluate ~/.gemfile if it exists -if File.exist?(File.join(Dir.home, '.gemfile')) +if File.exists?(File.join(Dir.home, '.gemfile')) instance_eval(File.read(File.join(Dir.home, '.gemfile'))) end diff --git a/Gemfile.lock b/Gemfile.lock index a63b584..59fe3b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - vmpooler (3.8.1) + vmpooler (3.3.0) concurrent-ruby (~> 1.1) connection_pool (~> 2.4) deep_merge (~> 1.2) @@ -9,11 +9,10 @@ PATH opentelemetry-exporter-jaeger (= 0.23.0) opentelemetry-instrumentation-concurrent_ruby (= 0.21.1) opentelemetry-instrumentation-http_client (= 0.22.2) - opentelemetry-instrumentation-rack (= 0.23.4) opentelemetry-instrumentation-redis (= 0.25.3) opentelemetry-instrumentation-sinatra (= 0.23.2) - opentelemetry-resource_detectors (= 0.24.2) - opentelemetry-sdk (~> 1.8) + opentelemetry-resource_detectors (= 0.24.1) + opentelemetry-sdk (~> 1.3, >= 1.3.0) pickup (~> 0.0.11) prometheus-client (>= 2, < 5) puma (>= 5.0.4, < 7) @@ -27,41 +26,36 @@ PATH GEM remote: https://rubygems.org/ specs: - ast (2.4.3) - base64 (0.1.2) - bindata (2.5.1) - builder (3.3.0) + ast (2.4.2) + base64 (0.1.1) + bindata (2.4.15) + builder (3.2.4) climate_control (1.2.0) coderay (1.1.3) - concurrent-ruby (1.3.5) - connection_pool (2.5.3) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) deep_merge (1.2.2) - diff-lcs (1.6.2) - docile (1.4.1) - faraday (2.13.1) - faraday-net_http (>= 2.0, < 3.5) - json - logger - faraday-net_http (3.4.0) - net-http (>= 0.5.0) - ffi (1.17.2-java) - google-cloud-env (2.2.1) - faraday (>= 1.0, < 3.a) - json (2.12.2) - json (2.12.2-java) - language_server-protocol (3.17.0.5) - logger (1.7.0) - method_source (1.1.0) + diff-lcs (1.5.0) + docile (1.4.0) + faraday (2.7.10) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.15.5-java) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + json (2.6.3) + json (2.6.3-java) + language_server-protocol (3.17.0.3) + method_source (1.0.0) mock_redis (0.37.0) - mustermann (3.0.3) + mustermann (3.0.0) ruby2_keywords (~> 0.0.1) - net-http (0.6.0) - uri - net-ldap (0.19.0) - nio4r (2.7.4) - nio4r (2.7.4-java) - opentelemetry-api (1.5.0) - opentelemetry-common (0.20.1) + net-ldap (0.18.0) + nio4r (2.5.9) + nio4r (2.5.9-java) + opentelemetry-api (1.2.1) + opentelemetry-common (0.20.0) opentelemetry-api (~> 1.0) opentelemetry-exporter-jaeger (0.23.0) opentelemetry-api (~> 1.1) @@ -69,7 +63,7 @@ GEM opentelemetry-sdk (~> 1.2) opentelemetry-semantic_conventions thrift - opentelemetry-instrumentation-base (0.22.3) + opentelemetry-instrumentation-base (0.22.2) opentelemetry-api (~> 1.0) opentelemetry-registry (~> 0.1) opentelemetry-instrumentation-concurrent_ruby (0.21.1) @@ -92,67 +86,64 @@ GEM opentelemetry-common (~> 0.20.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-rack (~> 0.21) - opentelemetry-registry (0.4.0) + opentelemetry-registry (0.3.0) opentelemetry-api (~> 1.1) - opentelemetry-resource_detectors (0.24.2) + opentelemetry-resource_detectors (0.24.1) google-cloud-env opentelemetry-sdk (~> 1.0) - opentelemetry-sdk (1.8.0) + opentelemetry-sdk (1.3.0) opentelemetry-api (~> 1.1) opentelemetry-common (~> 0.20) opentelemetry-registry (~> 0.2) opentelemetry-semantic_conventions - opentelemetry-semantic_conventions (1.11.0) + opentelemetry-semantic_conventions (1.10.0) opentelemetry-api (~> 1.0) - parallel (1.27.0) - parser (3.3.8.0) + parallel (1.23.0) + parser (3.2.2.3) ast (~> 2.4.1) racc pickup (0.0.11) - prism (1.4.0) - prometheus-client (4.2.4) - base64 - pry (0.15.2) + prometheus-client (4.2.1) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - pry (0.15.2-java) + pry (0.14.2-java) coderay (~> 1.1) method_source (~> 1.0) spoon (~> 0.0) - puma (6.6.0) + puma (6.3.0) nio4r (~> 2.0) - puma (6.6.0-java) + puma (6.3.0-java) nio4r (~> 2.0) - racc (1.8.1) - racc (1.8.1-java) - rack (2.2.17) - rack-protection (3.2.0) - base64 (>= 0.1.0) + racc (1.7.1) + racc (1.7.1-java) + rack (2.2.8) + rack-protection (3.1.0) rack (~> 2.2, >= 2.2.4) - rack-test (2.2.0) + rack-test (2.1.0) rack (>= 1.3) rainbow (3.1.1) - rake (13.3.0) - redis (5.4.0) - redis-client (>= 0.22.0) - redis-client (0.24.0) + rake (13.0.6) + redis (5.0.7) + redis-client (>= 0.9.0) + redis-client (0.15.0) connection_pool - regexp_parser (2.10.0) - rexml (3.4.1) - rspec (3.13.1) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.4) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) + regexp_parser (2.8.1) + rexml (3.2.6) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.5) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.4) - rubocop (1.56.4) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) + rubocop (1.56.0) base64 (~> 0.1.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -164,48 +155,41 @@ GEM rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.44.1) - parser (>= 3.3.7.2) - prism (~> 1.4) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.13.1) + simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - sinatra (3.2.0) + sinatra (3.1.0) mustermann (~> 3.0) rack (~> 2.2, >= 2.2.4) - rack-protection (= 3.2.0) + rack-protection (= 3.1.0) tilt (~> 2.0) spicy-proton (2.1.15) bindata (~> 2.3) spoon (0.0.6) ffi statsd-ruby (1.5.0) - thor (1.3.2) - thrift (0.22.0) - tilt (2.6.0) - unicode-display_width (2.6.0) - uri (1.0.3) + thor (1.2.2) + thrift (0.18.1) + tilt (2.2.0) + unicode-display_width (2.4.2) yarjuf (2.0.0) builder rspec (~> 3) PLATFORMS - arm64-darwin-22 - arm64-darwin-23 - arm64-darwin-25 universal-java-11 - universal-java-17 - x86_64-darwin-22 x86_64-linux DEPENDENCIES climate_control (>= 0.2.0) - mock_redis (= 0.37.0) + mock_redis (>= 0.17.0) pry rack-test (>= 0.6) rspec (>= 3.2) diff --git a/docs/configuration.md b/docs/configuration.md index 560c328..e577025 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -246,18 +246,6 @@ This can be a string providing a single DN. For multiple DNs please specify the The LDAP object-type used to designate a user object. (optional) -### LDAP\_SERVICE_ACCOUNT\_HASH - -A hash containing the following parameters for a service account to perform the -initial bind. After the initial bind, then a search query is performed using the -'base' and 'user_object', then re-binds as the returned user. - -- :user_dn: The full distinguished name (DN) of the service account used to bind. - -- :password: The password for the service account used to bind. - -(optional) - ### SITE\_NAME The name of your deployment. diff --git a/examples/vmpooler.yaml.dummy-example.aliasedpools b/examples/vmpooler.yaml.dummy-example.aliasedpools index 55bf9ff..ebece50 100644 --- a/examples/vmpooler.yaml.dummy-example.aliasedpools +++ b/examples/vmpooler.yaml.dummy-example.aliasedpools @@ -17,7 +17,6 @@ logfile: '/Users/samuel/workspace/vmpooler/vmpooler.log' task_limit: 10 timeout: 15 - timeout_notification: 5 vm_checktime: 1 vm_lifetime: 12 vm_lifetime_auth: 24 @@ -39,7 +38,6 @@ datastore: 'vmstorage' size: 5 timeout: 15 - timeout_notification: 5 ready_ttl: 1440 provider: dummy dns_plugin: 'example' @@ -50,7 +48,6 @@ datastore: 'vmstorage' size: 5 timeout: 15 - timeout_notification: 5 ready_ttl: 1440 provider: dummy dns_plugin: 'example' @@ -61,7 +58,6 @@ datastore: 'vmstorage' size: 5 timeout: 15 - timeout_notification: 5 ready_ttl: 1440 provider: dummy dns_plugin: 'example' @@ -71,7 +67,6 @@ datastore: 'vmstorage' size: 5 timeout: 15 - timeout_notification: 5 ready_ttl: 1440 provider: dummy dns_plugin: 'example' @@ -82,7 +77,6 @@ datastore: 'other-vmstorage' size: 5 timeout: 15 - timeout_notification: 5 ready_ttl: 1440 provider: dummy dns_plugin: 'example' diff --git a/lib/vmpooler.rb b/lib/vmpooler.rb index 2fcde30..985a72b 100644 --- a/lib/vmpooler.rb +++ b/lib/vmpooler.rb @@ -82,7 +82,6 @@ module Vmpooler end parsed_config[:config]['clone_target'] = ENV['CLONE_TARGET'] if ENV['CLONE_TARGET'] parsed_config[:config]['timeout'] = string_to_int(ENV['TIMEOUT']) if ENV['TIMEOUT'] - parsed_config[:config]['timeout_notification'] = string_to_int(ENV['TIMEOUT_NOTIFICATION']) if ENV['TIMEOUT_NOTIFICATION'] parsed_config[:config]['vm_lifetime_auth'] = string_to_int(ENV['VM_LIFETIME_AUTH']) if ENV['VM_LIFETIME_AUTH'] parsed_config[:config]['max_tries'] = string_to_int(ENV['MAX_TRIES']) if ENV['MAX_TRIES'] parsed_config[:config]['retry_factor'] = string_to_int(ENV['RETRY_FACTOR']) if ENV['RETRY_FACTOR'] diff --git a/lib/vmpooler/api/helpers.rb b/lib/vmpooler/api/helpers.rb index 747640d..c6351a9 100644 --- a/lib/vmpooler/api/helpers.rb +++ b/lib/vmpooler/api/helpers.rb @@ -1,13 +1,10 @@ # frozen_string_literal: true -require 'vmpooler/api/input_validator' - module Vmpooler class API module Helpers - include InputValidator def tracer @tracer ||= OpenTelemetry.tracer_provider.tracer('api', Vmpooler::VERSION) @@ -71,7 +68,7 @@ module Vmpooler end end - def authenticate_ldap(port, host, encryption_hash, user_object, base, username_str, password_str, service_account_hash = nil) + def authenticate_ldap(port, host, encryption_hash, user_object, base, username_str, password_str) tracer.in_span( "Vmpooler::API::Helpers.#{__method__}", attributes: { @@ -82,14 +79,6 @@ module Vmpooler }, kind: :client ) do - if service_account_hash - username = service_account_hash[:user_dn] - password = service_account_hash[:password] - else - username = "#{user_object}=#{username_str},#{base}" - password = password_str - end - ldap = Net::LDAP.new( :host => host, :port => port, @@ -97,22 +86,12 @@ module Vmpooler :base => base, :auth => { :method => :simple, - :username => username, - :password => password + :username => "#{user_object}=#{username_str},#{base}", + :password => password_str } ) - if service_account_hash - return true if ldap.bind_as( - :base => base, - :filter => "(#{user_object}=#{username_str})", - :password => password_str - ) - elsif ldap.bind - return true - else - return false - end + return true if ldap.bind return false end @@ -137,7 +116,6 @@ module Vmpooler :method => :start_tls, :tls_options => { :ssl_version => 'TLSv1' } } - service_account_hash = auth[:ldap]['service_account_hash'] unless ldap_base.is_a? Array ldap_base = ldap_base.split @@ -156,8 +134,7 @@ module Vmpooler search_user_obj, search_base, username_str, - password_str, - service_account_hash + password_str ) return true if result end @@ -292,7 +269,6 @@ module Vmpooler def get_queue_metrics(pools, backend) tracer.in_span("Vmpooler::API::Helpers.#{__method__}") do queue = { - requested: 0, pending: 0, cloning: 0, booting: 0, @@ -302,35 +278,15 @@ module Vmpooler total: 0 } - # Use a single pipeline to fetch all queue counts at once for better performance - results = backend.pipelined do |pipeline| - # Order matters - we'll use indices to extract values - pools.each do |pool| - pipeline.scard("vmpooler__provisioning__request#{pool['name']}") # 0..n-1 - pipeline.scard("vmpooler__provisioning__processing#{pool['name']}") # n..2n-1 - pipeline.scard("vmpooler__odcreate__task#{pool['name']}") # 2n..3n-1 - pipeline.scard("vmpooler__pending__#{pool['name']}") # 3n..4n-1 - pipeline.scard("vmpooler__ready__#{pool['name']}") # 4n..5n-1 - pipeline.scard("vmpooler__running__#{pool['name']}") # 5n..6n-1 - pipeline.scard("vmpooler__completed__#{pool['name']}") # 6n..7n-1 - end - pipeline.get('vmpooler__tasks__clone') # 7n - pipeline.get('vmpooler__tasks__ondemandclone') # 7n+1 - end + queue[:pending] = get_total_across_pools_redis_scard(pools, 'vmpooler__pending__', backend) + queue[:ready] = get_total_across_pools_redis_scard(pools, 'vmpooler__ready__', backend) + queue[:running] = get_total_across_pools_redis_scard(pools, 'vmpooler__running__', backend) + queue[:completed] = get_total_across_pools_redis_scard(pools, 'vmpooler__completed__', backend) - n = pools.length - # Safely extract results with default to empty array if slice returns nil - queue[:requested] = (results[0...n] || []).sum(&:to_i) + - (results[n...(2 * n)] || []).sum(&:to_i) + - (results[(2 * n)...(3 * n)] || []).sum(&:to_i) - queue[:pending] = (results[(3 * n)...(4 * n)] || []).sum(&:to_i) - queue[:ready] = (results[(4 * n)...(5 * n)] || []).sum(&:to_i) - queue[:running] = (results[(5 * n)...(6 * n)] || []).sum(&:to_i) - queue[:completed] = (results[(6 * n)...(7 * n)] || []).sum(&:to_i) - queue[:cloning] = (results[7 * n] || 0).to_i + (results[7 * n + 1] || 0).to_i - queue[:booting] = queue[:pending].to_i - queue[:cloning].to_i - queue[:booting] = 0 if queue[:booting] < 0 - queue[:total] = queue[:requested] + queue[:pending].to_i + queue[:ready].to_i + queue[:running].to_i + queue[:completed].to_i + queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i + backend.get('vmpooler__tasks__ondemandclone').to_i + queue[:booting] = queue[:pending].to_i - queue[:cloning].to_i + queue[:booting] = 0 if queue[:booting] < 0 + queue[:total] = queue[:pending].to_i + queue[:ready].to_i + queue[:running].to_i + queue[:completed].to_i queue end @@ -595,6 +551,18 @@ module Vmpooler end end end + + def vm_ready?(vm_name, domain = nil) + tracer.in_span("Vmpooler::API::Helpers.#{__method__}") do + begin + open_socket(vm_name, domain) + rescue StandardError => _e + return false + end + + true + end + end end end end diff --git a/lib/vmpooler/api/input_validator.rb b/lib/vmpooler/api/input_validator.rb deleted file mode 100644 index add4d6a..0000000 --- a/lib/vmpooler/api/input_validator.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -module Vmpooler - class API - # Input validation helpers to enhance security - module InputValidator - # Maximum lengths to prevent abuse - MAX_HOSTNAME_LENGTH = 253 - MAX_TAG_KEY_LENGTH = 50 - MAX_TAG_VALUE_LENGTH = 255 - MAX_REASON_LENGTH = 500 - MAX_POOL_NAME_LENGTH = 100 - MAX_TOKEN_LENGTH = 64 - - # Valid patterns - HOSTNAME_PATTERN = /\A[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)* \z/ix.freeze - POOL_NAME_PATTERN = /\A[a-zA-Z0-9_-]+\z/.freeze - TAG_KEY_PATTERN = /\A[a-zA-Z0-9_\-.]+\z/.freeze - TOKEN_PATTERN = /\A[a-zA-Z0-9\-_]+\z/.freeze - INTEGER_PATTERN = /\A\d+\z/.freeze - - class ValidationError < StandardError; end - - # Validate hostname format and length - def validate_hostname(hostname) - return error_response('Hostname is required') if hostname.nil? || hostname.empty? - return error_response('Hostname too long') if hostname.length > MAX_HOSTNAME_LENGTH - return error_response('Invalid hostname format') unless hostname.match?(HOSTNAME_PATTERN) - - true - end - - # Validate pool/template name - def validate_pool_name(pool_name) - return error_response('Pool name is required') if pool_name.nil? || pool_name.empty? - return error_response('Pool name too long') if pool_name.length > MAX_POOL_NAME_LENGTH - return error_response('Invalid pool name format') unless pool_name.match?(POOL_NAME_PATTERN) - - true - end - - # Validate tag key and value - def validate_tag(key, value) - return error_response('Tag key is required') if key.nil? || key.empty? - return error_response('Tag key too long') if key.length > MAX_TAG_KEY_LENGTH - return error_response('Invalid tag key format') unless key.match?(TAG_KEY_PATTERN) - - if value - return error_response('Tag value too long') if value.length > MAX_TAG_VALUE_LENGTH - - # Sanitize value to prevent injection attacks - sanitized_value = value.gsub(/[^\w\s\-.@:\/]/, '') - return error_response('Tag value contains invalid characters') if sanitized_value != value - end - - true - end - - # Validate token format - def validate_token_format(token) - return error_response('Token is required') if token.nil? || token.empty? - return error_response('Token too long') if token.length > MAX_TOKEN_LENGTH - return error_response('Invalid token format') unless token.match?(TOKEN_PATTERN) - - true - end - - # Validate integer parameter - def validate_integer(value, name = 'value', min: nil, max: nil) - return error_response("#{name} is required") if value.nil? - - value_str = value.to_s - return error_response("#{name} must be a valid integer") unless value_str.match?(INTEGER_PATTERN) - - int_value = value.to_i - return error_response("#{name} must be at least #{min}") if min && int_value < min - return error_response("#{name} must be at most #{max}") if max && int_value > max - - int_value - end - - # Validate VM request count - def validate_vm_count(count) - validated = validate_integer(count, 'VM count', min: 1, max: 100) - return validated if validated.is_a?(Hash) # error response - - validated - end - - # Validate disk size - def validate_disk_size(size) - validated = validate_integer(size, 'Disk size', min: 1, max: 2048) - return validated if validated.is_a?(Hash) # error response - - validated - end - - # Validate lifetime (TTL) in hours - def validate_lifetime(lifetime) - validated = validate_integer(lifetime, 'Lifetime', min: 1, max: 168) # max 1 week - return validated if validated.is_a?(Hash) # error response - - validated - end - - # Validate reason text - def validate_reason(reason) - return true if reason.nil? || reason.empty? - return error_response('Reason too long') if reason.length > MAX_REASON_LENGTH - - # Sanitize to prevent XSS/injection - sanitized = reason.gsub(/[<>"']/, '') - return error_response('Reason contains invalid characters') if sanitized != reason - - true - end - - # Sanitize JSON body to prevent injection - def sanitize_json_body(body) - return {} if body.nil? || body.empty? - - begin - parsed = JSON.parse(body) - return error_response('Request body must be a JSON object') unless parsed.is_a?(Hash) - - # Limit depth and size to prevent DoS - return error_response('Request body too complex') if json_depth(parsed) > 5 - return error_response('Request body too large') if body.length > 10_240 # 10KB max - - parsed - rescue JSON::ParserError => e - error_response("Invalid JSON: #{e.message}") - end - end - - # Check if validation result is an error - def validation_error?(result) - result.is_a?(Hash) && result['ok'] == false - end - - private - - def error_response(message) - { 'ok' => false, 'error' => message } - end - - def json_depth(obj, depth = 0) - return depth unless obj.is_a?(Hash) || obj.is_a?(Array) - return depth + 1 if obj.empty? - - if obj.is_a?(Hash) - depth + 1 + obj.values.map { |v| json_depth(v, 0) }.max - else - depth + 1 + obj.map { |v| json_depth(v, 0) }.max - end - end - end - end -end diff --git a/lib/vmpooler/api/rate_limiter.rb b/lib/vmpooler/api/rate_limiter.rb deleted file mode 100644 index 8ecfb62..0000000 --- a/lib/vmpooler/api/rate_limiter.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -module Vmpooler - class API - # Rate limiter middleware to protect against abuse - # Uses Redis to track request counts per IP and token - class RateLimiter - DEFAULT_LIMITS = { - global_per_ip: { limit: 100, period: 60 }, # 100 requests per minute per IP - authenticated: { limit: 500, period: 60 }, # 500 requests per minute with token - vm_creation: { limit: 20, period: 60 }, # 20 VM creations per minute - vm_deletion: { limit: 50, period: 60 } # 50 VM deletions per minute - }.freeze - - def initialize(app, redis, config = {}) - @app = app - @redis = redis - @config = DEFAULT_LIMITS.merge(config[:rate_limits] || {}) - @enabled = config.fetch(:rate_limiting_enabled, true) - end - - def call(env) - return @app.call(env) unless @enabled - - request = Rack::Request.new(env) - client_id = identify_client(request) - endpoint_type = classify_endpoint(request) - - # Check rate limits - return rate_limit_response(client_id, endpoint_type) if rate_limit_exceeded?(client_id, endpoint_type, request) - - # Track the request - increment_request_count(client_id, endpoint_type) - - @app.call(env) - end - - private - - def identify_client(request) - # Prioritize token-based identification for authenticated requests - token = request.env['HTTP_X_AUTH_TOKEN'] - return "token:#{token}" if token && !token.empty? - - # Fall back to IP address - ip = request.ip || request.env['REMOTE_ADDR'] || 'unknown' - "ip:#{ip}" - end - - def classify_endpoint(request) - path = request.path - method = request.request_method - - return :vm_creation if method == 'POST' && path.include?('/vm') - return :vm_deletion if method == 'DELETE' && path.include?('/vm') - return :authenticated if request.env['HTTP_X_AUTH_TOKEN'] - - :global_per_ip - end - - def rate_limit_exceeded?(client_id, endpoint_type, _request) - limit_config = @config[endpoint_type] || @config[:global_per_ip] - key = "vmpooler__ratelimit__#{endpoint_type}__#{client_id}" - - current_count = @redis.get(key).to_i - current_count >= limit_config[:limit] - rescue StandardError => e - # If Redis fails, allow the request through (fail open) - warn "Rate limiter Redis error: #{e.message}" - false - end - - def increment_request_count(client_id, endpoint_type) - limit_config = @config[endpoint_type] || @config[:global_per_ip] - key = "vmpooler__ratelimit__#{endpoint_type}__#{client_id}" - - @redis.pipelined do |pipeline| - pipeline.incr(key) - pipeline.expire(key, limit_config[:period]) - end - rescue StandardError => e - # Log error but don't fail the request - warn "Rate limiter increment error: #{e.message}" - end - - def rate_limit_response(client_id, endpoint_type) - limit_config = @config[endpoint_type] || @config[:global_per_ip] - key = "vmpooler__ratelimit__#{endpoint_type}__#{client_id}" - - begin - ttl = @redis.ttl(key) - rescue StandardError - ttl = limit_config[:period] - end - - headers = { - 'Content-Type' => 'application/json', - 'X-RateLimit-Limit' => limit_config[:limit].to_s, - 'X-RateLimit-Remaining' => '0', - 'X-RateLimit-Reset' => (Time.now.to_i + ttl).to_s, - 'Retry-After' => ttl.to_s - } - - body = JSON.pretty_generate({ - 'ok' => false, - 'error' => 'Rate limit exceeded', - 'limit' => limit_config[:limit], - 'period' => limit_config[:period], - 'retry_after' => ttl - }) - - [429, headers, [body]] - end - end - end -end diff --git a/lib/vmpooler/api/v3.rb b/lib/vmpooler/api/v3.rb index 21bc4e3..41c6480 100644 --- a/lib/vmpooler/api/v3.rb +++ b/lib/vmpooler/api/v3.rb @@ -9,20 +9,6 @@ module Vmpooler api_version = '3' api_prefix = "/api/v#{api_version}" - # Simple in-memory cache for status endpoint - # rubocop:disable Style/ClassVars - @@status_cache = {} - @@status_cache_mutex = Mutex.new - # rubocop:enable Style/ClassVars - STATUS_CACHE_TTL = 30 # seconds - - # Clear cache (useful for testing) - def self.clear_status_cache - @@status_cache_mutex.synchronize do - @@status_cache.clear - end - end - helpers do include Vmpooler::API::Helpers end @@ -297,9 +283,11 @@ module Vmpooler def update_user_metrics(operation, vmname) tracer.in_span("Vmpooler::API::V3.#{__method__}") do |span| begin - jenkins_build_url = backend.hget("vmpooler__vm__#{vmname}", 'tag:jenkins_build_url') - user = backend.hget("vmpooler__vm__#{vmname}", 'token:user') - poolname = backend.hget("vmpooler__vm__#{vmname}", 'template') + backend.multi + backend.hget("vmpooler__vm__#{vmname}", 'tag:jenkins_build_url') + backend.hget("vmpooler__vm__#{vmname}", 'token:user') + backend.hget("vmpooler__vm__#{vmname}", 'template') + jenkins_build_url, user, poolname = backend.exec poolname = poolname.gsub('.', '_') if user @@ -478,32 +466,6 @@ module Vmpooler end end - # Cache helper methods for status endpoint - def get_cached_status(cache_key) - @@status_cache_mutex.synchronize do - cached = @@status_cache[cache_key] - if cached && (Time.now - cached[:timestamp]) < STATUS_CACHE_TTL - return cached[:data] - end - - nil - end - end - - def set_cached_status(cache_key, data) - @@status_cache_mutex.synchronize do - @@status_cache[cache_key] = { - data: data, - timestamp: Time.now - } - # Cleanup old cache entries (keep only last 10 unique view combinations) - if @@status_cache.size > 10 - oldest = @@status_cache.min_by { |_k, v| v[:timestamp] } - @@status_cache.delete(oldest[0]) - end - end - end - def sync_pool_templates tracer.in_span("Vmpooler::API::V3.#{__method__}") do pool_index = pool_index(pools) @@ -686,13 +648,6 @@ module Vmpooler get "#{api_prefix}/status/?" do content_type :json - # Create cache key based on view parameters - cache_key = params[:view] ? "status_#{params[:view]}" : "status_all" - - # Try to get cached response - cached_response = get_cached_status(cache_key) - return cached_response if cached_response - if params[:view] views = params[:view].split(",") end @@ -753,12 +708,7 @@ module Vmpooler result[:status][:uptime] = (Time.now - Vmpooler::API.settings.config[:uptime]).round(1) if Vmpooler::API.settings.config[:uptime] - response = JSON.pretty_generate(Hash[result.sort_by { |k, _v| k }]) - - # Cache the response - set_cached_status(cache_key, response) - - response + JSON.pretty_generate(Hash[result.sort_by { |k, _v| k }]) end # request statistics for specific pools by passing parameter 'pool' @@ -1137,29 +1087,9 @@ module Vmpooler result = { 'ok' => false } metrics.increment('http_requests_vm_total.post.vm.checkout') - # Validate and sanitize JSON body - payload = sanitize_json_body(request.body.read) - if validation_error?(payload) - status 400 - return JSON.pretty_generate(payload) - end + payload = JSON.parse(request.body.read) - # Validate each template and count - payload.each do |template, count| - validation = validate_pool_name(template) - if validation_error?(validation) - status 400 - return JSON.pretty_generate(validation) - end - - validated_count = validate_vm_count(count) - if validation_error?(validated_count) - status 400 - return JSON.pretty_generate(validated_count) - end - end - - if payload && !payload.empty? + if payload invalid = invalid_templates(payload) if invalid.empty? result = atomically_allocate_vms(payload) @@ -1278,7 +1208,6 @@ module Vmpooler result = { 'ok' => false } metrics.increment('http_requests_vm_total.get.vm.template') - # Template can contain multiple pools separated by +, so validate after parsing payload = extract_templates_from_query_params(params[:template]) if payload @@ -1308,13 +1237,6 @@ module Vmpooler status 404 result['ok'] = false - # Validate hostname - validation = validate_hostname(params[:hostname]) - if validation_error?(validation) - status 400 - return JSON.pretty_generate(validation) - end - params[:hostname] = hostname_shorten(params[:hostname]) rdata = backend.hgetall("vmpooler__vm__#{params[:hostname]}") @@ -1453,13 +1375,6 @@ module Vmpooler status 404 result['ok'] = false - # Validate hostname - validation = validate_hostname(params[:hostname]) - if validation_error?(validation) - status 400 - return JSON.pretty_generate(validation) - end - params[:hostname] = hostname_shorten(params[:hostname]) rdata = backend.hgetall("vmpooler__vm__#{params[:hostname]}") @@ -1490,21 +1405,16 @@ module Vmpooler failure = [] - # Validate hostname - validation = validate_hostname(params[:hostname]) - if validation_error?(validation) - status 400 - return JSON.pretty_generate(validation) - end - params[:hostname] = hostname_shorten(params[:hostname]) if backend.exists?("vmpooler__vm__#{params[:hostname]}") - # Validate and sanitize JSON body - jdata = sanitize_json_body(request.body.read) - if validation_error?(jdata) - status 400 - return JSON.pretty_generate(jdata) + begin + jdata = JSON.parse(request.body.read) + rescue StandardError => e + span = OpenTelemetry::Trace.current_span + span.record_exception(e) + span.status = OpenTelemetry::Trace::Status.error(e.to_s) + halt 400, JSON.pretty_generate(result) end # Validate data payload @@ -1513,13 +1423,6 @@ module Vmpooler when 'lifetime' need_token! if Vmpooler::API.settings.config[:auth] - # Validate lifetime is a positive integer - lifetime_int = arg.to_i - if lifetime_int <= 0 - failure.push("Lifetime must be a positive integer (got #{arg})") - next - end - # in hours, defaults to one week max_lifetime_upper_limit = config['max_lifetime_upper_limit'] if max_lifetime_upper_limit @@ -1529,17 +1432,13 @@ module Vmpooler end end - when 'tags' - failure.push("You provided tags (#{arg}) as something other than a hash.") unless arg.is_a?(Hash) - - # Validate each tag key and value - arg.each do |key, value| - tag_validation = validate_tag(key, value) - if validation_error?(tag_validation) - failure.push(tag_validation['error']) - end + # validate lifetime is within boundaries + unless arg.to_i > 0 + failure.push("You provided a lifetime (#{arg}) but you must provide a positive number.") end + when 'tags' + failure.push("You provided tags (#{arg}) as something other than a hash.") unless arg.is_a?(Hash) failure.push("You provided unsuppored tags (#{arg}).") if config['allowed_tags'] && !(arg.keys - config['allowed_tags']).empty? else failure.push("Unknown argument #{arg}.") @@ -1581,23 +1480,9 @@ module Vmpooler status 404 result = { 'ok' => false } - # Validate hostname - validation = validate_hostname(params[:hostname]) - if validation_error?(validation) - status 400 - return JSON.pretty_generate(validation) - end - - # Validate disk size - validated_size = validate_disk_size(params[:size]) - if validation_error?(validated_size) - status 400 - return JSON.pretty_generate(validated_size) - end - params[:hostname] = hostname_shorten(params[:hostname]) - if backend.exists?("vmpooler__vm__#{params[:hostname]}") + if ((params[:size].to_i > 0 )and (backend.exists?("vmpooler__vm__#{params[:hostname]}"))) result[params[:hostname]] = {} result[params[:hostname]]['disk'] = "+#{params[:size]}gb" diff --git a/lib/vmpooler/metrics/promstats.rb b/lib/vmpooler/metrics/promstats.rb index d0e1ab9..f24f9b9 100644 --- a/lib/vmpooler/metrics/promstats.rb +++ b/lib/vmpooler/metrics/promstats.rb @@ -329,30 +329,6 @@ module Vmpooler buckets: REDIS_CONNECT_BUCKETS, docstring: 'vmpooler redis connection wait time', param_labels: %i[type provider] - }, - vmpooler_health: { - mtype: M_GAUGE, - torun: %i[manager], - docstring: 'vmpooler health check metrics', - param_labels: %i[metric_path] - }, - vmpooler_purge: { - mtype: M_GAUGE, - torun: %i[manager], - docstring: 'vmpooler purge metrics', - param_labels: %i[metric_path] - }, - vmpooler_destroy: { - mtype: M_GAUGE, - torun: %i[manager], - docstring: 'vmpooler destroy metrics', - param_labels: %i[poolname] - }, - vmpooler_clone: { - mtype: M_GAUGE, - torun: %i[manager], - docstring: 'vmpooler clone metrics', - param_labels: %i[poolname] } } end diff --git a/lib/vmpooler/pool_manager.rb b/lib/vmpooler/pool_manager.rb index 9c6def6..3bc020b 100644 --- a/lib/vmpooler/pool_manager.rb +++ b/lib/vmpooler/pool_manager.rb @@ -82,31 +82,31 @@ module Vmpooler end # Check the state of a VM - def check_pending_vm(vm, pool, timeout, timeout_notification, provider) + def check_pending_vm(vm, pool, timeout, provider) Thread.new do begin - _check_pending_vm(vm, pool, timeout, timeout_notification, provider) + _check_pending_vm(vm, pool, timeout, provider) rescue StandardError => e $logger.log('s', "[!] [#{pool}] '#{vm}' #{timeout} #{provider} errored while checking a pending vm : #{e}") @redis.with_metrics do |redis| - fail_pending_vm(vm, pool, timeout, timeout_notification, redis) + fail_pending_vm(vm, pool, timeout, redis) end raise end end end - def _check_pending_vm(vm, pool, timeout, timeout_notification, provider) + def _check_pending_vm(vm, pool, timeout, provider) mutex = vm_mutex(vm) return if mutex.locked? mutex.synchronize do @redis.with_metrics do |redis| request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id') - if provider.vm_ready?(pool, vm, redis) + if provider.vm_ready?(pool, vm) move_pending_vm_to_ready(vm, pool, redis, request_id) else - fail_pending_vm(vm, pool, timeout, timeout_notification, redis) + fail_pending_vm(vm, pool, timeout, redis) end end end @@ -122,121 +122,34 @@ module Vmpooler $logger.log('d', "[!] [#{pool}] '#{vm}' no longer exists. Removing from pending.") end - def fail_pending_vm(vm, pool, timeout, timeout_notification, redis, exists: true) + def fail_pending_vm(vm, pool, timeout, redis, exists: true) clone_stamp = redis.hget("vmpooler__vm__#{vm}", 'clone') + time_since_clone = (Time.now - Time.parse(clone_stamp)) / 60 - - already_timed_out = time_since_clone > timeout - timing_out_soon = time_since_clone > timeout_notification && !redis.hget("vmpooler__vm__#{vm}", 'timeout_notification') - - return true if !already_timed_out && !timing_out_soon - - if already_timed_out - unless exists + if time_since_clone > timeout + if exists + request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id') + pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias') if request_id + redis.smove("vmpooler__pending__#{pool}", "vmpooler__completed__#{pool}", vm) + if request_id + ondemandrequest_hash = redis.hgetall("vmpooler__odrequest__#{request_id}") + if ondemandrequest_hash && ondemandrequest_hash['status'] != 'failed' && ondemandrequest_hash['status'] != 'deleted' + # will retry a VM that did not come up as vm_ready? only if it has not been market failed or deleted + redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool}:1:#{request_id}") + end + end + $metrics.increment("errors.markedasfailed.#{pool}") + $logger.log('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes") + else remove_nonexistent_vm(vm, pool, redis) - return true end - open_socket_error = handle_timed_out_vm(vm, pool, redis) end - - redis.hset("vmpooler__vm__#{vm}", 'timeout_notification', 1) if timing_out_soon - - nonexist_warning = if already_timed_out - "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes with error: #{open_socket_error}" - elsif timing_out_soon - time_remaining = timeout - timeout_notification - open_socket_error = redis.hget("vmpooler__vm__#{vm}", 'open_socket_error') - "[!] [#{pool}] '#{vm}' impending failure in #{time_remaining} minutes with error: #{open_socket_error}" - else - "[!] [#{pool}] '#{vm}' This error is wholly unexpected" - end - $logger.log('d', nonexist_warning) true rescue StandardError => e $logger.log('d', "Fail pending VM failed with an error: #{e}") false end - def handle_timed_out_vm(vm, pool, redis) - request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id') - pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias') if request_id - open_socket_error = redis.hget("vmpooler__vm__#{vm}", 'open_socket_error') - retry_count = redis.hget("vmpooler__odrequest__#{request_id}", 'retry_count').to_i if request_id - - # Move to DLQ before moving to completed queue - move_to_dlq(vm, pool, 'pending', 'Timeout', - open_socket_error || 'VM timed out during pending phase', - redis, request_id: request_id, pool_alias: pool_alias, retry_count: retry_count) - - clone_error = redis.hget("vmpooler__vm__#{vm}", 'clone_error') - clone_error_class = redis.hget("vmpooler__vm__#{vm}", 'clone_error_class') - redis.smove("vmpooler__pending__#{pool}", "vmpooler__completed__#{pool}", vm) - - if request_id - ondemandrequest_hash = redis.hgetall("vmpooler__odrequest__#{request_id}") - if ondemandrequest_hash && ondemandrequest_hash['status'] != 'failed' && ondemandrequest_hash['status'] != 'deleted' - # Check retry count and max retry limit before retrying - retry_count = (redis.hget("vmpooler__odrequest__#{request_id}", 'retry_count') || '0').to_i - max_retries = $config[:config]['max_vm_retries'] || 3 - - $logger.log('s', "[!] [#{pool}] '#{vm}' checking retry logic: error='#{clone_error}', error_class='#{clone_error_class}', retry_count=#{retry_count}, max_retries=#{max_retries}") - - # Determine if error is likely permanent (configuration issues) - permanent_error = permanent_error?(clone_error, clone_error_class) - $logger.log('s', "[!] [#{pool}] '#{vm}' permanent_error check result: #{permanent_error}") - - if retry_count < max_retries && !permanent_error - # Increment retry count and retry VM creation - redis.hset("vmpooler__odrequest__#{request_id}", 'retry_count', retry_count + 1) - redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool}:1:#{request_id}") - $logger.log('s', "[!] [#{pool}] '#{vm}' failed, retrying (attempt #{retry_count + 1}/#{max_retries})") - else - # Max retries exceeded or permanent error, mark request as permanently failed - failure_reason = if permanent_error - "Configuration error: #{clone_error}" - else - 'Max retry attempts exceeded' - end - redis.hset("vmpooler__odrequest__#{request_id}", 'status', 'failed') - redis.hset("vmpooler__odrequest__#{request_id}", 'failure_reason', failure_reason) - $logger.log('s', "[!] [#{pool}] '#{vm}' permanently failed: #{failure_reason}") - $metrics.increment("vmpooler_errors.permanently_failed.#{pool}") - end - end - end - $metrics.increment("vmpooler_errors.markedasfailed.#{pool}") - open_socket_error || clone_error - end - - # Determine if an error is likely permanent (configuration issue) vs transient - def permanent_error?(error_message, error_class) - return false if error_message.nil? || error_class.nil? - - permanent_error_patterns = [ - /template.*not found/i, - /template.*does not exist/i, - /invalid.*path/i, - /folder.*not found/i, - /datastore.*not found/i, - /resource pool.*not found/i, - /permission.*denied/i, - /authentication.*failed/i, - /invalid.*credentials/i, - /configuration.*error/i - ] - - permanent_error_classes = [ - 'ArgumentError', - 'NoMethodError', - 'NameError' - ] - - # Check error message patterns - permanent_error_patterns.any? { |pattern| error_message.match?(pattern) } || - # Check error class types - permanent_error_classes.include?(error_class) - end - def move_pending_vm_to_ready(vm, pool, redis, request_id = nil) clone_time = redis.hget("vmpooler__vm__#{vm}", 'clone') finish = format('%