diff options
22 files changed, 306 insertions, 142 deletions
diff --git a/client/js/app/yarn.lock b/client/js/app/yarn.lock index b812a8b8e37..7341cdc6132 100644 --- a/client/js/app/yarn.lock +++ b/client/js/app/yarn.lock @@ -24,9 +24,9 @@ chalk "^2.4.2" "@babel/compat-data@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" - integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc" + integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ== "@babel/core@^7.1.0", "@babel/core@^7.12.17": version "7.22.9" @@ -70,22 +70,22 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7" - integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA== +"@babel/core@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" + integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.22.15" + "@babel/generator" "^7.23.0" "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.22.20" - "@babel/helpers" "^7.22.15" - "@babel/parser" "^7.22.16" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.2" + "@babel/parser" "^7.23.0" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.20" - "@babel/types" "^7.22.19" - convert-source-map "^1.7.0" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" @@ -188,17 +188,6 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.15" -"@babel/helper-module-transforms@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e" - integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - "@babel/helper-module-transforms@^7.22.5": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" @@ -210,6 +199,17 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.5" +"@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" @@ -262,6 +262,15 @@ "@babel/traverse" "^7.22.11" "@babel/types" "^7.22.11" +"@babel/helpers@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" + integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + "@babel/highlight@^7.22.13": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" @@ -271,7 +280,12 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + +"@babel/parser@^7.14.7", "@babel/parser@^7.22.16": version "7.22.16" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== @@ -281,11 +295,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.13.tgz#23fb17892b2be7afef94f573031c2f4b42839a2b" integrity sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw== -"@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -430,7 +439,7 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.22.11", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17", "@babel/traverse@^7.22.20", "@babel/traverse@^7.22.8": +"@babel/traverse@^7.22.11", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17", "@babel/traverse@^7.22.8", "@babel/traverse@^7.23.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== @@ -446,13 +455,13 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.19", "@babel/types@^7.22.5": - version "7.22.19" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" - integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.19" + "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" "@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.3.3": @@ -464,13 +473,13 @@ "@babel/helper-validator-identifier" "^7.22.15" to-fast-properties "^2.0.0" -"@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== +"@babel/types@^7.22.17": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -1068,7 +1077,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18": version "0.3.19" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== @@ -1076,6 +1085,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@mantine/core@^6.0.0": version "6.0.21" resolved "https://registry.yarnpkg.com/@mantine/core/-/core-6.0.21.tgz#6e3a1b8d0f6869518a644d5f5e3d55a5db7e1e51" @@ -1272,10 +1289,10 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" -"@types/babel__core@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756" - integrity sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA== +"@types/babel__core@^7.20.3": + version "7.20.3" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778" + integrity sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA== dependencies: "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" @@ -1284,24 +1301,24 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.5" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.5.tgz#281f4764bcbbbc51fdded0f25aa587b4ce14da95" - integrity sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w== + version "7.6.6" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.6.tgz#676f89f67dc8ddaae923f70ebc5f1fa800c031a8" + integrity sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.4.2" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.2.tgz#843e9f1f47c957553b0c374481dc4772921d6a6b" - integrity sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ== + version "7.4.3" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.3.tgz#db9ac539a2fe05cfe9e168b24f360701bde41f5f" + integrity sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.2.tgz#4ddf99d95cfdd946ff35d2b65c978d9c9bf2645d" - integrity sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw== + version "7.20.3" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.3.tgz#a971aa47441b28ef17884ff945d0551265a2d058" + integrity sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw== dependencies: "@babel/types" "^7.20.7" @@ -1388,14 +1405,14 @@ integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== "@vitejs/plugin-react@^4": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz#e4f56f46fd737c5d386bb1f1ade86ba275fe09bd" - integrity sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.1.1.tgz#a10254dc76778027407d01b6ddbca53b23852a72" + integrity sha512-Jie2HERK+uh27e+ORXXwEP5h0Y2lS9T2PRGbfebiHGlwzDO0dEnd2aNtOR/qjBlPb1YgxwAONeblL1xqLikLag== dependencies: - "@babel/core" "^7.22.20" + "@babel/core" "^7.23.2" "@babel/plugin-transform-react-jsx-self" "^7.22.5" "@babel/plugin-transform-react-jsx-source" "^7.22.5" - "@types/babel__core" "^7.20.2" + "@types/babel__core" "^7.20.3" react-refresh "^0.14.0" acorn-jsx@^5.3.2: @@ -1809,12 +1826,12 @@ braces@^3.0.2: fill-range "^7.0.1" browserslist@^4.21.9: - version "4.21.11" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.11.tgz#35f74a3e51adc4d193dcd76ea13858de7b8fecb8" - integrity sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ== + version "4.22.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" + integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== dependencies: - caniuse-lite "^1.0.30001538" - electron-to-chromium "^1.4.526" + caniuse-lite "^1.0.30001541" + electron-to-chromium "^1.4.535" node-releases "^2.0.13" update-browserslist-db "^1.0.13" @@ -1876,10 +1893,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001538: - version "1.0.30001539" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz#325a387ab1ed236df2c12dc6cd43a4fff9903a44" - integrity sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA== +caniuse-lite@^1.0.30001541: + version "1.0.30001559" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz#95a982440d3d314c471db68d02664fb7536c5a30" + integrity sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA== capture-exit@^2.0.0: version "2.0.0" @@ -2219,10 +2236,10 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" -electron-to-chromium@^1.4.526: - version "1.4.528" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz#7c900fd73d9d2e8bb0dab0e301f25f0f4776ef2c" - integrity sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA== +electron-to-chromium@^1.4.535: + version "1.4.574" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz#6de04d7c6e244e5ffcae76d2e2a33b02cab66781" + integrity sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg== emittery@^0.13.1: version "0.13.1" diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ConsoleUrls.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ConsoleUrls.java index 82cddb46d9a..e741fb8d203 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ConsoleUrls.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ConsoleUrls.java @@ -7,6 +7,7 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import java.net.URI; @@ -25,6 +26,8 @@ public class ConsoleUrls { this.root = root.toString().replaceFirst("/$", ""); // Remove trailing slash } + public ConsoleUrls(String hostname) { this(URI.create("https://" + hostname)); } + public String root() { return root; } @@ -40,6 +43,8 @@ public class ConsoleUrls { public String tenantBilling(TenantName t) { return "%s/tenant/%s/account/billing".formatted(root, t.value()); } + public String tenantBilling(TenantName t, Bill.Id id) { return "%s/bill/%s".formatted(tenantBilling(t), id.value()); } + public String prodApplicationOverview(TenantName tenantName, ApplicationName applicationName) { return "%s/tenant/%s/application/%s/prod/instance".formatted(root, tenantName.value(), applicationName.value()); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index 54f53d64f76..9a3ea71660b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -253,7 +253,9 @@ enum PathGroup { emailVerification("/user/v1/email/verify"), /** Path used for dataplane token */ - dataplaneToken(Matcher.tenant,"/application/v4/tenant/{tenant}/token", "/application/v4/tenant/{tenant}/token/{ignored}"); + dataplaneToken(Matcher.tenant,"/application/v4/tenant/{tenant}/token", "/application/v4/tenant/{tenant}/token/{ignored}"), + + termsOfService(Matcher.tenant, "/application/v4/tenant/{tenant}/terms-of-service"); final List<String> pathSpecs; final List<Matcher> matchers; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java index d1a8b2ef0c3..44f6386e9bc 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java @@ -191,6 +191,10 @@ enum Policy { dataplaneToken(Privilege.grant(Action.all()) .on(PathGroup.dataplaneToken) + .in(SystemName.PublicCd, SystemName.Public)), + + termsOfService(Privilege.grant(Action.create) + .on(PathGroup.termsOfService) .in(SystemName.PublicCd, SystemName.Public)); private final Set<Privilege> privileges; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java index 31c8560c908..0b5359ac826 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java @@ -69,7 +69,8 @@ public enum RoleDefinition { Policy.applicationManager, Policy.keyRevokal, Policy.billingInformationRead, - Policy.accessRequests + Policy.accessRequests, + Policy.termsOfService ), /** Headless — the application specific role identified by deployment keys for production */ diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantBilling.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantBilling.java index 6e3b26661e5..1db84240fe2 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantBilling.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantBilling.java @@ -13,17 +13,21 @@ public class TenantBilling { private final TaxId taxId; private final PurchaseOrder purchaseOrder; private final Email invoiceEmail; + private final TermsOfServiceApproval tosApproval; - public TenantBilling(TenantContact contact, TenantAddress address, TaxId taxId, PurchaseOrder purchaseOrder, Email invoiceEmail) { + public TenantBilling(TenantContact contact, TenantAddress address, TaxId taxId, PurchaseOrder purchaseOrder, + Email invoiceEmail, TermsOfServiceApproval tosApproval) { this.contact = Objects.requireNonNull(contact); this.address = Objects.requireNonNull(address); this.taxId = Objects.requireNonNull(taxId); this.purchaseOrder = Objects.requireNonNull(purchaseOrder); this.invoiceEmail = Objects.requireNonNull(invoiceEmail); + this.tosApproval = Objects.requireNonNull(tosApproval); } public static TenantBilling empty() { - return new TenantBilling(TenantContact.empty(), TenantAddress.empty(), TaxId.empty(), PurchaseOrder.empty(), Email.empty()); + return new TenantBilling(TenantContact.empty(), TenantAddress.empty(), TaxId.empty(), PurchaseOrder.empty(), + Email.empty(), TermsOfServiceApproval.empty()); } public TenantContact contact() { @@ -46,24 +50,30 @@ public class TenantBilling { return invoiceEmail; } + public TermsOfServiceApproval getToSApproval() { return tosApproval; } + public TenantBilling withContact(TenantContact updatedContact) { - return new TenantBilling(updatedContact, this.address, this.taxId, this.purchaseOrder, this.invoiceEmail); + return new TenantBilling(updatedContact, this.address, this.taxId, this.purchaseOrder, this.invoiceEmail, tosApproval); } public TenantBilling withAddress(TenantAddress updatedAddress) { - return new TenantBilling(this.contact, updatedAddress, this.taxId, this.purchaseOrder, this.invoiceEmail); + return new TenantBilling(this.contact, updatedAddress, this.taxId, this.purchaseOrder, this.invoiceEmail, tosApproval); } public TenantBilling withTaxId(TaxId updatedTaxId) { - return new TenantBilling(this.contact, this.address, updatedTaxId, this.purchaseOrder, this.invoiceEmail); + return new TenantBilling(this.contact, this.address, updatedTaxId, this.purchaseOrder, this.invoiceEmail, tosApproval); } public TenantBilling withPurchaseOrder(PurchaseOrder updatedPurchaseOrder) { - return new TenantBilling(this.contact, this.address, this.taxId, updatedPurchaseOrder, this.invoiceEmail); + return new TenantBilling(this.contact, this.address, this.taxId, updatedPurchaseOrder, this.invoiceEmail, tosApproval); } public TenantBilling withInvoiceEmail(Email updatedInvoiceEmail) { - return new TenantBilling(this.contact, this.address, this.taxId, this.purchaseOrder, updatedInvoiceEmail); + return new TenantBilling(this.contact, this.address, this.taxId, this.purchaseOrder, updatedInvoiceEmail, tosApproval); + } + + public TenantBilling withToSApproval(TermsOfServiceApproval approval) { + return new TenantBilling(contact, address, taxId, purchaseOrder, invoiceEmail, approval); } public boolean isEmpty() { @@ -75,16 +85,14 @@ public class TenantBilling { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TenantBilling that = (TenantBilling) o; - return Objects.equals(contact, that.contact) && - Objects.equals(address, that.address) && - Objects.equals(taxId, that.taxId) && - Objects.equals(purchaseOrder, that.purchaseOrder) && - Objects.equals(invoiceEmail, that.invoiceEmail); + return Objects.equals(contact, that.contact) && Objects.equals(address, that.address) + && Objects.equals(taxId, that.taxId) && Objects.equals(purchaseOrder, that.purchaseOrder) + && Objects.equals(invoiceEmail, that.invoiceEmail) && Objects.equals(tosApproval, that.tosApproval); } @Override public int hashCode() { - return Objects.hash(contact, address, taxId, purchaseOrder, invoiceEmail); + return Objects.hash(contact, address, taxId, purchaseOrder, invoiceEmail, tosApproval); } @Override @@ -92,9 +100,10 @@ public class TenantBilling { return "TenantBilling{" + "contact=" + contact + ", address=" + address + - ", taxId='" + taxId + '\'' + - ", purchaseOrder='" + purchaseOrder + '\'' + + ", taxId=" + taxId + + ", purchaseOrder=" + purchaseOrder + ", invoiceEmail=" + invoiceEmail + + ", tosApproval=" + tosApproval + '}'; } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TermsOfServiceApproval.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TermsOfServiceApproval.java new file mode 100644 index 00000000000..61fba17c473 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TermsOfServiceApproval.java @@ -0,0 +1,30 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.hosted.controller.tenant; + +import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal; + +import java.time.Instant; +import java.util.Optional; + +/** + * @author bjorncs + */ +public record TermsOfServiceApproval(Instant approvedAt, Optional<SimplePrincipal> approvedBy) { + + public TermsOfServiceApproval(Instant at, SimplePrincipal by) { this(at, Optional.of(by)); } + + public TermsOfServiceApproval(String at, String by) { + this(at.isBlank() ? Instant.EPOCH : Instant.parse(at), by.isBlank() ? Optional.empty() : Optional.of(new SimplePrincipal(by))); + } + + public TermsOfServiceApproval { + if (approvedBy.isEmpty() && !Instant.EPOCH.equals(approvedAt)) + throw new IllegalArgumentException("Missing approver"); + } + + public static TermsOfServiceApproval empty() { return new TermsOfServiceApproval(Instant.EPOCH, Optional.empty()); } + + public boolean hasApproved() { return approvedBy.isPresent(); } + public boolean isEmpty() { return approvedBy.isEmpty() && Instant.EPOCH.equals(approvedAt); } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java index 7801efe504b..961925cf620 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java @@ -36,6 +36,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantBilling; import com.yahoo.vespa.hosted.controller.tenant.TenantContact; import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; +import com.yahoo.vespa.hosted.controller.tenant.TermsOfServiceApproval; import java.net.URI; import java.security.Principal; @@ -101,6 +102,9 @@ public class TenantSerializer { private static final String taxIdCodeField = "code"; private static final String purchaseOrderField = "purchaseOrder"; private static final String invoiceEmailField = "invoiceEmail"; + private static final String tosApprovalField = "tosApproval"; + private static final String tosApprovalAtField = "at"; + private static final String tosApprovalByField = "by"; private static final String awsIdField = "awsId"; private static final String roleField = "role"; @@ -292,6 +296,7 @@ public class TenantSerializer { private TenantBilling tenantInfoBillingContactFromSlime(Inspector billingObject) { var taxIdInspector = billingObject.field(taxIdField); var taxId = switch (taxIdInspector.type()) { + // TODO(bjorncs, 2023-11-02): Remove legacy tax id format case STRING -> TaxId.legacy(taxIdInspector.asString()); case OBJECT -> { var taxIdCountry = taxIdInspector.field(taxIdCountryField).asString(); @@ -304,6 +309,13 @@ public class TenantSerializer { }; var purchaseOrder = new PurchaseOrder(billingObject.field(purchaseOrderField).asString()); var invoiceEmail = new Email(billingObject.field(invoiceEmailField).asString(), false); + var tosApprovalInspector = billingObject.field(tosApprovalField); + var tosApproval = switch (tosApprovalInspector.type()) { + case OBJECT -> new TermsOfServiceApproval(tosApprovalInspector.field(tosApprovalAtField).asString(), + tosApprovalInspector.field(tosApprovalByField).asString()); + case NIX -> TermsOfServiceApproval.empty(); + default -> throw new IllegalArgumentException(taxIdInspector.type().name()); + }; return TenantBilling.empty() .withContact(TenantContact.from( @@ -313,7 +325,8 @@ public class TenantSerializer { .withAddress(tenantInfoAddressFromSlime(billingObject.field("address"))) .withTaxId(taxId) .withPurchaseOrder(purchaseOrder) - .withInvoiceEmail(invoiceEmail); + .withInvoiceEmail(invoiceEmail) + .withToSApproval(tosApproval); } private List<TenantSecretStore> secretStoresFromSlime(Inspector secretStoresObject) { @@ -382,6 +395,11 @@ public class TenantSerializer { billingCursor.setString(purchaseOrderField, billingContact.getPurchaseOrder().value()); billingCursor.setString(invoiceEmailField, billingContact.getInvoiceEmail().getEmailAddress()); toSlime(billingContact.address(), billingCursor); + if (!billingContact.getToSApproval().isEmpty()) { + var tosApprovalCursor = billingCursor.setObject(tosApprovalField); + tosApprovalCursor.setString(tosApprovalAtField, billingContact.getToSApproval().approvedAt().toString()); + tosApprovalCursor.setString(tosApprovalByField, billingContact.getToSApproval().approvedBy().get().getName()); + } } private void toSlime(List<TenantSecretStore> tenantSecretStores, Cursor parentCursor) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 1fd8e7c8f3b..6cf38a1927c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -87,6 +87,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretSto import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.api.role.RoleDefinition; import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; +import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal; import com.yahoo.vespa.hosted.controller.application.AssignedRotation; import com.yahoo.vespa.hosted.controller.application.Change; import com.yahoo.vespa.hosted.controller.application.Deployment; @@ -135,6 +136,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantBilling; import com.yahoo.vespa.hosted.controller.tenant.TenantContact; import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; +import com.yahoo.vespa.hosted.controller.tenant.TermsOfServiceApproval; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; import com.yahoo.yolean.Exceptions; @@ -386,6 +388,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private HttpResponse handlePOST(Path path, HttpRequest request) { if (path.matches("/application/v4/tenant/{tenant}")) return createTenant(path.get("tenant"), request); + if (path.matches("/application/v4/tenant/{tenant}/terms-of-service")) return approveTermsOfService(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/key")) return addDeveloperKey(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/token/{tokenid}")) return generateToken(path.get("tenant"), path.get("tokenid"), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return createApplication(path.get("tenant"), path.get("application"), request); @@ -702,6 +705,10 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { taxIdCursor.setString("code", billingContact.getTaxId().code().value()); root.setString("purchaseOrder", billingContact.getPurchaseOrder().value()); root.setString("invoiceEmail", billingContact.getInvoiceEmail().getEmailAddress()); + var tosApprovalCursor = root.setObject("tosApproval"); + var tosApproval = billingContact.getToSApproval(); + tosApprovalCursor.setString("at", !tosApproval.isEmpty() ? tosApproval.approvedAt().toString() : ""); + tosApprovalCursor.setString("by", !tosApproval.isEmpty() ? tosApproval.approvedBy().get().getName() : ""); toSlime(billingContact.address(), root); // will create "address" on the parent } @@ -812,6 +819,10 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { billingCursor.setString("purchaseOrder", billingContact.getPurchaseOrder().value()); billingCursor.setString("invoiceEmail", billingContact.getInvoiceEmail().getEmailAddress()); toSlime(billingContact.address(), billingCursor); + var tosApprovalCursor = billingCursor.setObject("tosApproval"); + var tosApproval = billingContact.getToSApproval(); + tosApprovalCursor.setString("at", !tosApproval.isEmpty() ? tosApproval.approvedAt().toString() : ""); + tosApprovalCursor.setString("by", !tosApproval.isEmpty() ? tosApproval.approvedBy().get().getName() : ""); } private void toSlime(TenantContacts contacts, Cursor parentCursor) { @@ -1239,6 +1250,20 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { return new SlimeJsonResponse(slime); } + private HttpResponse approveTermsOfService(String tenant, HttpRequest req) { + if (controller.tenants().require(TenantName.from(tenant)).type() != Tenant.Type.cloud) + throw new IllegalArgumentException("Tenant '" + tenant + "' is not a cloud tenant"); + var approvedBy = SimplePrincipal.of(req.getJDiscRequest().getUserPrincipal()); + var approvedAt = controller.clock().instant(); + + controller.tenants().lockOrThrow(TenantName.from(tenant), LockedTenant.Cloud.class, t -> { + var updatedTenant = t.withInfo(t.get().info().withBilling(t.get().info().billingContact().withToSApproval( + new TermsOfServiceApproval(approvedAt, approvedBy)))); + controller.tenants().store(updatedTenant); + }); + return new MessageResponse("Terms of service approved by %s".formatted(approvedBy.getName())); + } + private HttpResponse addDeveloperKey(String tenantName, HttpRequest request) { if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud) throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant"); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java index f2fc43933df..493d4df90a9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java @@ -31,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantBilling; import com.yahoo.vespa.hosted.controller.tenant.TenantContact; import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; +import com.yahoo.vespa.hosted.controller.tenant.TermsOfServiceApproval; import org.junit.jupiter.api.Test; import java.net.URI; @@ -240,6 +241,7 @@ public class TenantSerializerTest { .withPurchaseOrder(new PurchaseOrder("PO42")) .withTaxId(new TaxId("NO", "no_vat", "123456789MVA")) .withInvoiceEmail(new Email("billing@mycomp.any", false)) + .withToSApproval(new TermsOfServiceApproval(Instant.ofEpochMilli(1234L), new SimplePrincipal("ceo@mycomp.any"))) ); Slime slime = new Slime(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java index f8ae7c8ea50..3c57f812a48 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java @@ -112,7 +112,11 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { "code": "" }, "purchaseOrder":"", - "invoiceEmail":"" + "invoiceEmail":"", + "tosApproval": { + "at": "", + "by": "" + } } """; var request = request("/application/v4/tenant/scoober/info/billing", GET) @@ -143,6 +147,10 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { .roles(Set.of(Role.administrator(tenantName))); tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200); + var approveToSRequest = request("/application/v4/tenant/scoober/terms-of-service", POST) + .data("{}").roles(Set.of(Role.administrator(tenantName))); + tester.assertResponse(approveToSRequest, "{\"message\":\"Terms of service approved by user@test\"}", 200); + expectedResponse = """ { "contact": { @@ -158,6 +166,10 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { }, "purchaseOrder":"PO9001", "invoiceEmail":"billing@mycomp.any", + "tosApproval": { + "at": "2020-09-13T12:26:40Z", + "by": "user@test" + }, "address": { "addressLines":"addressLines", "postalCodeOrZip":"postalCodeOrZip", @@ -245,7 +257,11 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { "code": "" }, "purchaseOrder":"", - "invoiceEmail":"" + "invoiceEmail":"", + "tosApproval": { + "at": "", + "by": "" + } }, "contacts": [ {"audiences":["tenant"],"email":"contact1@example.com","emailVerified":false} @@ -287,6 +303,10 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { "city":"city", "stateRegionProvince":"stateRegionProvince", "country":"country" + }, + "tosApproval": { + "at": "", + "by": "" } }, "contacts": [ diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java index 3f6b20ccfa4..66356d979a4 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java @@ -41,9 +41,16 @@ public class SharedHost { return resources.isEmpty() ? null : resources; } + /** Whether there are any shared hosts specifically for the given cluster type, or without a cluster type restriction. */ @JsonIgnore - public boolean isEnabled(String clusterType) { - return resources.stream().anyMatch(hr -> hr.satisfiesClusterType(clusterType)); + public boolean supportsClusterType(String clusterType) { + return resources.stream().anyMatch(resource -> resource.clusterType().map(clusterType::equalsIgnoreCase).orElse(true)); + } + + /** Whether there are any shared hosts specifically for the given cluster type. */ + @JsonIgnore + public boolean hasClusterType(String clusterType) { + return resources.stream().anyMatch(resource -> resource.clusterType().map(clusterType::equalsIgnoreCase).orElse(false)); } @JsonIgnore diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index 449e1c07bf8..dfbe41e31d7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -201,6 +201,11 @@ public class NodeRepository extends AbstractComponent { /** The number of nodes we should ensure has free capacity for node failures whenever possible */ public int spareCount() { return spareCount; } + /** Returns whether nodes must be allocated to hosts that are exclusive to the cluster type. */ + public boolean exclusiveClusterType(ClusterSpec cluster) { + return sharedHosts.value().hasClusterType(cluster.type().name()); + } + /** * Returns whether nodes are allocated exclusively in this instance given this cluster spec. * Exclusive allocation requires that the wanted node resources matches the advertised resources of the node @@ -209,7 +214,7 @@ public class NodeRepository extends AbstractComponent { public boolean exclusiveAllocation(ClusterSpec clusterSpec) { return clusterSpec.isExclusive() || ( clusterSpec.type().isContainer() && zone.system().isPublic() && !zone.environment().isTest() ) || - ( !zone().cloud().allowHostSharing() && !sharedHosts.value().isEnabled(clusterSpec.type().name())); + ( !zone().cloud().allowHostSharing() && !sharedHosts.value().supportsClusterType(clusterSpec.type().name())); } /** Whether the nodes of this cluster must be running on hosts that are specifically provisioned for the application. */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java index 3c42972ee0b..108f8d77837 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java @@ -301,6 +301,7 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { .stream() .filter(node -> node.violatesExclusivity(cluster, application, + nodeRepository().exclusiveClusterType(cluster), nodeRepository().exclusiveAllocation(cluster), false, nodeRepository().zone().cloud().allowHostSharing(), diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index e1be5b48e2d..21340baf273 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -198,6 +198,7 @@ class NodeAllocation { private NodeCandidate.ExclusivityViolation violatesExclusivity(NodeCandidate candidate) { return candidate.violatesExclusivity(cluster, application, + nodeRepository.exclusiveClusterType(cluster), nodeRepository.exclusiveAllocation(cluster), nodeRepository.exclusiveProvisioning(cluster), nodeRepository.zone().cloud().allowHostSharing(), allNodes, makeExclusive); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java index 1547a266e15..8c29b40bc26 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java @@ -595,7 +595,7 @@ public abstract class NodeCandidate implements Nodelike, Comparable<NodeCandidat } public ExclusivityViolation violatesExclusivity(ClusterSpec cluster, ApplicationId application, - boolean exclusiveAllocation, boolean exclusiveProvisioning, + boolean exclusiveClusterType, boolean exclusiveAllocation, boolean exclusiveProvisioning, boolean hostSharing, NodeList allNodes, boolean makeExclusive) { if (parentHostname().isEmpty()) return ExclusivityViolation.NONE; if (type() != NodeType.tenant) return ExclusivityViolation.NONE; @@ -614,6 +614,10 @@ public abstract class NodeCandidate implements Nodelike, Comparable<NodeCandidat if ( ! emptyOrEqual(parent.flatMap(Node::exclusiveToClusterType), cluster.type())) return ExclusivityViolation.YES; + // this cluster requires a parent that was provisioned exclusively for this cluster type + if (exclusiveClusterType && parent.flatMap(Node::exclusiveToClusterType).isEmpty() && makeExclusive) + return ExclusivityViolation.YES; + // the parent is provisioned for another application if ( ! emptyOrEqual(parent.flatMap(Node::provisionedForApplicationId), application)) return ExclusivityViolation.YES; diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp index 9a734168260..150de1c9cec 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp @@ -21,6 +21,7 @@ #include <vespa/searchsummary/config/config-juniperrc.h> #include <vespa/config/retriever/configsnapshot.hpp> #include <vespa/vespalib/util/hw_info.h> +#include <vespa/config.h> #include <thread> #include <cassert> #include <cinttypes> @@ -222,6 +223,18 @@ find_document_db_config_entry(const ProtonConfig::DocumentdbVector& document_dbs return default_document_db_config_entry; } +[[nodiscard]] bool +use_hw_memory_presized_target_num_docs([[maybe_unused]] ProtonConfig::Documentdb::Mode mode) noexcept { + // If sanitizers are enabled, mmap-allocations may be intercepted and allocated pages + // may be implicitly touched+committed. This tends to explode when testing locally, so + // fall back to configured initial num-docs if this is the case. +#ifndef VESPA_USE_SANITIZER + return (mode != ProtonConfig::Documentdb::Mode::INDEX); +#else + return false; +#endif +} + AllocConfig build_alloc_config(const vespalib::HwInfo & hwInfo, const ProtonConfig& proton_config, const vespalib::string& doc_type_name) { @@ -230,7 +243,7 @@ build_alloc_config(const vespalib::HwInfo & hwInfo, const ProtonConfig& proton_c auto& document_db_config_entry = find_document_db_config_entry(proton_config.documentdb, doc_type_name); auto& alloc_config = document_db_config_entry.allocation; - uint32_t target_numdocs = (document_db_config_entry.mode != ProtonConfig::Documentdb::Mode::INDEX) + uint32_t target_numdocs = use_hw_memory_presized_target_num_docs(document_db_config_entry.mode) ? (hwInfo.memory().sizeBytes() / (MIN_MEMORY_COST_PER_DOCUMENT * proton_config.distribution.searchablecopies)) : alloc_config.initialnumdocs; auto& distribution_config = proton_config.distribution; diff --git a/searchlib/src/tests/query/querybuilder_test.cpp b/searchlib/src/tests/query/querybuilder_test.cpp index 189e0f5f0b1..606d6a2474a 100644 --- a/searchlib/src/tests/query/querybuilder_test.cpp +++ b/searchlib/src/tests/query/querybuilder_test.cpp @@ -673,7 +673,7 @@ TEST("require that empty intermediate node can be added") { } TEST("control size of SimpleQueryStackDumpIterator") { - EXPECT_EQUAL(128u, sizeof(SimpleQueryStackDumpIterator)); + EXPECT_EQUAL(120u, sizeof(SimpleQueryStackDumpIterator)); } TEST("test query parsing error") { diff --git a/searchlib/src/vespa/searchlib/common/bitvector.cpp b/searchlib/src/vespa/searchlib/common/bitvector.cpp index c359f433d12..b79703a8e5c 100644 --- a/searchlib/src/vespa/searchlib/common/bitvector.cpp +++ b/searchlib/src/vespa/searchlib/common/bitvector.cpp @@ -49,7 +49,7 @@ BitVector::allocatePaddedAndAligned(Index start, Index end, Index capacity, cons return alloc; } -BitVector::BitVector(void * buf, Index start, Index end) : +BitVector::BitVector(void * buf, Index start, Index end) noexcept : _words(static_cast<Word *>(buf) - wordNum(start)), _startOffset(start), _sz(end), diff --git a/searchlib/src/vespa/searchlib/common/bitvector.h b/searchlib/src/vespa/searchlib/common/bitvector.h index af1722e200c..943db5f06ba 100644 --- a/searchlib/src/vespa/searchlib/common/bitvector.h +++ b/searchlib/src/vespa/searchlib/common/bitvector.h @@ -40,18 +40,18 @@ public: BitVector& operator = (const BitVector &) = delete; virtual ~BitVector() = default; bool operator == (const BitVector &right) const; - const void * getStart() const { return _words; } - void * getStart() { return _words; } + const void * getStart() const noexcept { return _words; } + void * getStart() noexcept { return _words; } Range range() const noexcept { return {getStartIndex(), size()}; } - Index size() const { return vespalib::atomic::load_ref_relaxed(_sz); } - Index sizeBytes() const { return numBytes(getActiveSize()); } + Index size() const noexcept { return vespalib::atomic::load_ref_relaxed(_sz); } + Index sizeBytes() const noexcept { return numBytes(getActiveSize()); } bool testBit(Index idx) const noexcept { return ((load(_words[wordNum(idx)]) & mask(idx)) != 0); } Index getSizeAcquire() const { return vespalib::atomic::load_ref_acquire(_sz); } - bool testBitAcquire(Index idx) const { + bool testBitAcquire(Index idx) const noexcept { auto my_word = vespalib::atomic::load_ref_acquire(_words[wordNum(idx)]); return (my_word & mask(idx)) != 0; } @@ -144,31 +144,31 @@ public: } vespalib::atomic::store_ref_release(_sz, sz); } - void set_bit_no_range_check(Index idx) { + void set_bit_no_range_check(Index idx) noexcept { store_unchecked(_words[wordNum(idx)], _words[wordNum(idx)] | mask(idx)); } - void clear_bit_no_range_check(Index idx) { + void clear_bit_no_range_check(Index idx) noexcept { store_unchecked(_words[wordNum(idx)], _words[wordNum(idx)] & ~ mask(idx)); } - void flip_bit_no_range_check(Index idx) { + void flip_bit_no_range_check(Index idx) noexcept { store_unchecked(_words[wordNum(idx)], _words[wordNum(idx)] ^ mask(idx)); } - void range_check(Index idx) const { + void range_check(Index idx) const noexcept { #if VESPA_ENABLE_BITVECTOR_RANGE_CHECK assert(!_enable_range_check || (idx >= _startOffset && idx < _sz)); #else (void) idx; #endif } - void setBit(Index idx) { + void setBit(Index idx) noexcept { range_check(idx); set_bit_no_range_check(idx); } - void clearBit(Index idx) { + void clearBit(Index idx) noexcept { range_check(idx); clear_bit_no_range_check(idx); } - void flipBit(Index idx) { + void flipBit(Index idx) noexcept { range_check(idx); flip_bit_no_range_check(idx); } @@ -283,20 +283,20 @@ public: static void consider_enable_range_check(); protected: using Alloc = vespalib::alloc::Alloc; - VESPA_DLL_LOCAL BitVector(void * buf, Index start, Index end); - BitVector(void * buf, Index sz) : BitVector(buf, 0, sz) { } - BitVector() : BitVector(nullptr, 0) { } + VESPA_DLL_LOCAL BitVector(void * buf, Index start, Index end) noexcept; + BitVector(void * buf, Index sz) noexcept : BitVector(buf, 0, sz) { } + BitVector() noexcept : BitVector(nullptr, 0) { } void init(void * buf, Index start, Index end); - void updateCount() const { _numTrueBits.store(count(), std::memory_order_relaxed); } - void setTrueBits(Index numTrueBits) { _numTrueBits.store(numTrueBits, std::memory_order_relaxed); } + void updateCount() const noexcept { _numTrueBits.store(count(), std::memory_order_relaxed); } + void setTrueBits(Index numTrueBits) noexcept { _numTrueBits.store(numTrueBits, std::memory_order_relaxed); } VESPA_DLL_LOCAL void clearIntervalNoInvalidation(Range range); - bool isValidCount() const { return isValidCount(_numTrueBits.load(std::memory_order_relaxed)); } - static bool isValidCount(Index v) { return v != invalidCount(); } - static Index numWords(Index bits) { return wordNum(bits + 1 + (WordLen - 1)); } - static Index numBytes(Index bits) { return numWords(bits) * sizeof(Word); } - size_t numWords() const { return numWords(size()); } - static size_t getAlignment() { return 0x40u; } - static size_t numActiveBytes(Index start, Index end) { return numActiveWords(start, end) * sizeof(Word); } + bool isValidCount() const noexcept { return isValidCount(_numTrueBits.load(std::memory_order_relaxed)); } + static bool isValidCount(Index v) noexcept { return v != invalidCount(); } + static Index numWords(Index bits) noexcept { return wordNum(bits + 1 + (WordLen - 1)); } + static Index numBytes(Index bits) noexcept { return numWords(bits) * sizeof(Word); } + size_t numWords() const noexcept { return numWords(size()); } + static constexpr size_t getAlignment() noexcept { return 0x40u; } + static size_t numActiveBytes(Index start, Index end) noexcept { return numActiveWords(start, end) * sizeof(Word); } static Alloc allocatePaddedAndAligned(Index sz) { return allocatePaddedAndAligned(0, sz); } @@ -308,29 +308,29 @@ protected: private: static Word load(const Word &word) noexcept { return vespalib::atomic::load_ref_relaxed(word); } VESPA_DLL_LOCAL void store(Word &word, Word value); - static void store_unchecked(Word &word, Word value) { + static void store_unchecked(Word &word, Word value) noexcept { return vespalib::atomic::store_ref_relaxed(word, value); } friend PartialBitVector; - const Word * getWordIndex(Index index) const { return static_cast<const Word *>(getStart()) + wordNum(index); } - Word * getWordIndex(Index index) { return static_cast<Word *>(getStart()) + wordNum(index); } - const Word * getActiveStart() const { return getWordIndex(getStartIndex()); } - Word * getActiveStart() { return getWordIndex(getStartIndex()); } - Index getStartWordNum() const { return wordNum(getStartIndex()); } - Index getActiveSize() const { return size() - getStartIndex(); } - size_t getActiveBytes() const { return numActiveBytes(getStartIndex(), size()); } - size_t numActiveWords() const { return numActiveWords(getStartIndex(), size()); } - static size_t numActiveWords(Index start, Index end) { + const Word * getWordIndex(Index index) const noexcept { return static_cast<const Word *>(getStart()) + wordNum(index); } + Word * getWordIndex(Index index) noexcept { return static_cast<Word *>(getStart()) + wordNum(index); } + const Word * getActiveStart() const noexcept { return getWordIndex(getStartIndex()); } + Word * getActiveStart() noexcept { return getWordIndex(getStartIndex()); } + Index getStartWordNum() const noexcept { return wordNum(getStartIndex()); } + Index getActiveSize() const noexcept { return size() - getStartIndex(); } + size_t getActiveBytes() const noexcept { return numActiveBytes(getStartIndex(), size()); } + size_t numActiveWords() const noexcept { return numActiveWords(getStartIndex(), size()); } + static size_t numActiveWords(Index start, Index end) noexcept { return (end >= start) ? (numWords(end) - wordNum(start)) : 0; } - static Index invalidCount() { return std::numeric_limits<Index>::max(); } - void setGuardBit() { set_bit_no_range_check(size()); } - void incNumBits() { + static constexpr Index invalidCount() noexcept { return std::numeric_limits<Index>::max(); } + void setGuardBit() noexcept { set_bit_no_range_check(size()); } + void incNumBits() noexcept { if ( isValidCount() ) { _numTrueBits.store(_numTrueBits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); } } - void decNumBits() { + void decNumBits() noexcept { if ( isValidCount() ) { _numTrueBits.store(_numTrueBits.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed); diff --git a/searchlib/src/vespa/searchlib/parsequery/parse.h b/searchlib/src/vespa/searchlib/parsequery/parse.h index bb0b7b88caa..7029a3d4e12 100644 --- a/searchlib/src/vespa/searchlib/parsequery/parse.h +++ b/searchlib/src/vespa/searchlib/parsequery/parse.h @@ -25,7 +25,7 @@ class ParseItem public: /** The type of the item is from this set of values. It is important that these defines match those in container-search/src/main/java/com/yahoo/prelude/query/Item.java */ - enum ItemType { + enum ItemType : uint8_t { ITEM_OR = 0, ITEM_AND = 1, ITEM_NOT = 2, diff --git a/storage/src/vespa/storage/storageserver/mergethrottler.cpp b/storage/src/vespa/storage/storageserver/mergethrottler.cpp index 82bd5ff7ace..b341c676fc9 100644 --- a/storage/src/vespa/storage/storageserver/mergethrottler.cpp +++ b/storage/src/vespa/storage/storageserver/mergethrottler.cpp @@ -68,8 +68,8 @@ MergeThrottler::Metrics::Metrics(metrics::MetricSet* owner) queueSize("queuesize", {}, "Length of merge queue", this), active_window_size("active_window_size", {}, "Number of merges active within the pending window size", this), estimated_merge_memory_usage("estimated_merge_memory_usage", {}, "An estimated upper bound of the " - "memory usage of the merges currently in the active window", this), - merge_memory_limit("merge_memory_limit", {}, "The active soft limit for memory used by merge operations on this node", this), + "memory usage (in bytes) of the merges currently in the active window", this), + merge_memory_limit("merge_memory_limit", {}, "The active soft limit (in bytes) for memory used by merge operations on this node", this), bounced_due_to_back_pressure("bounced_due_to_back_pressure", {}, "Number of merges bounced due to resource exhaustion back-pressure", this), chaining("mergechains", this), local("locallyexecutedmerges", this) |