diff options
104 files changed, 9556 insertions, 1150 deletions
diff --git a/client/go/go.mod b/client/go/go.mod index a122508b1b9..e5c205e51f4 100644 --- a/client/go/go.mod +++ b/client/go/go.mod @@ -1,13 +1,13 @@ module github.com/vespa-engine/vespa/client/go -go 1.19 +go 1.20 require ( github.com/alessio/shellescape v1.4.2 github.com/briandowns/spinner v1.23.0 github.com/fatih/color v1.16.0 - // This is the most recent version compatible with Go 1.19. Upgrade when we upgrade our Go version - github.com/go-json-experiment/json v0.0.0-20230216065249-540f01442424 + // This is the most recent version compatible with Go 1.20. Upgrade when we upgrade our Go version + github.com/go-json-experiment/json v0.0.0-20230324203220-04923b7a9528 github.com/klauspost/compress v1.17.3 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 @@ -30,6 +30,7 @@ require ( github.com/kr/pretty v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/term v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect diff --git a/client/go/go.sum b/client/go/go.sum index 92e96c4e813..e94515d46c1 100644 --- a/client/go/go.sum +++ b/client/go/go.sum @@ -1,40 +1,21 @@ -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= -github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/go-json-experiment/json v0.0.0-20230216065249-540f01442424 h1:I1EK0t+BDH+kvlozNqrvzKqsWeM2QUKxXH0iW2fjDDw= -github.com/go-json-experiment/json v0.0.0-20230216065249-540f01442424/go.mod h1:I+I5/LT2lLP0eZsBNaVDrOrYASx9h7o7mRHmy+535/A= +github.com/go-json-experiment/json v0.0.0-20230324203220-04923b7a9528 h1:hmpF6G+rHcypt8J6jhBH/rDUx+04Th/L61Y8uCKFb7Q= +github.com/go-json-experiment/json v0.0.0-20230324203220-04923b7a9528/go.mod h1:AHV+bpNGVGD0DCHMBhhTYtT7yeBYD9Yk92XAjB7vOgo= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= -github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -45,10 +26,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= @@ -57,74 +34,30 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/zalando/go-keyring v0.2.2 h1:f0xmpYiSrHtSNAVgwip93Cg8tuF45HJM6rHq/A5RI/4= -github.com/zalando/go-keyring v0.2.2/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go index 8806a21c9fc..dd605237b5f 100644 --- a/client/go/internal/cli/cmd/deploy.go +++ b/client/go/internal/cli/cmd/deploy.go @@ -60,7 +60,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, return err } timeout := time.Duration(waitSecs) * time.Second - opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target, Timeout: timeout} + opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target} if versionArg != "" { version, err := version.Parse(versionArg) if err != nil { @@ -162,7 +162,7 @@ func newActivateCmd(cli *CLI) *cobra.Command { if _, err := waiter.DeployService(target); err != nil { return err } - opts := vespa.DeploymentOptions{Target: target, Timeout: timeout} + opts := vespa.DeploymentOptions{Target: target} err = vespa.Activate(sessionID, opts) if err != nil { return err diff --git a/client/go/internal/cli/cmd/fetch.go b/client/go/internal/cli/cmd/fetch.go new file mode 100644 index 00000000000..b2e7d11ba7b --- /dev/null +++ b/client/go/internal/cli/cmd/fetch.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/vespa-engine/vespa/client/go/internal/vespa" +) + +func newFetchCmd(cli *CLI) *cobra.Command { + cmd := &cobra.Command{ + Use: "fetch [path]", + Short: "Download a deployed application package", + Long: `Download a deployed application package. + +This command can be used to download an already deployed Vespa application +package. The package is written as a ZIP file to the given path, or current +directory if no path is given. +`, + Example: `$ vespa fetch +$ vespa fetch mydir/ +$ vespa fetch -t cloud mycloudapp.zip +`, + Args: cobra.MaximumNArgs(1), + DisableAutoGenTag: true, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + target, err := cli.target(targetOptions{}) + if err != nil { + return err + } + path := "." + if len(args) > 0 { + path = args[0] + } + dstPath := "" + if err := cli.spinner(cli.Stderr, "Downloading application package...", func() error { + dstPath, err = vespa.Fetch(vespa.DeploymentOptions{Target: target}, path) + return err + }); err != nil { + return err + } + cli.printSuccess("Application package written to ", dstPath) + return nil + }, + } + return cmd +} diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go index 4d1a7cf6f89..004bdc038fe 100644 --- a/client/go/internal/cli/cmd/root.go +++ b/client/go/internal/cli/cmd/root.go @@ -282,6 +282,7 @@ func (c *CLI) configureCommands() { rootCmd.AddCommand(newVersionCmd(c)) // version rootCmd.AddCommand(newVisitCmd(c)) // visit rootCmd.AddCommand(newFeedCmd(c)) // feed + rootCmd.AddCommand(newFetchCmd(c)) // fetch } func (c *CLI) bindWaitFlag(cmd *cobra.Command, defaultSecs int, value *int) { diff --git a/client/go/internal/cli/cmd/visit.go b/client/go/internal/cli/cmd/visit.go index bb226701e0a..2ca01764deb 100644 --- a/client/go/internal/cli/cmd/visit.go +++ b/client/go/internal/cli/cmd/visit.go @@ -90,8 +90,8 @@ func newVisitCmd(cli *CLI) *cobra.Command { ) cmd := &cobra.Command{ Use: "visit", - Short: "Fetch and print all documents from Vespa", - Long: `Fetch and print all documents from Vespa. + Short: "Retrieve and print all documents from Vespa", + Long: `Retrieve and print all documents from Vespa. By default prints each document received on its own line (JSONL format). `, diff --git a/client/go/internal/util/spinner.go b/client/go/internal/util/spinner.go index 880375f961b..323a5fffe12 100644 --- a/client/go/internal/util/spinner.go +++ b/client/go/internal/util/spinner.go @@ -14,6 +14,9 @@ import ( // displayed after message. func Spinner(w io.Writer, message string, fn func() error) error { s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(w)) + // Cursor is hidden by default. Hiding cursor requires Stop() to be called to restore cursor (i.e. if the process is + // interrupted), however we don't want to bother with a signal handler just for this + s.HideCursor = false if err := s.Color("blue", "bold"); err != nil { return err } diff --git a/client/go/internal/util/tuning.go b/client/go/internal/util/tuning.go index b36c0a431da..cca314247ab 100644 --- a/client/go/internal/util/tuning.go +++ b/client/go/internal/util/tuning.go @@ -15,19 +15,29 @@ import ( func OptionallyReduceTimerFrequency() { if os.Getenv(envvars.VESPA_TIMER_HZ) == "" { backticks := BackTicksIgnoreStderr - out, _ := backticks.Run("uname", "-r") - if strings.Contains(out, "linuxkit") { - if os.Getenv(envvars.VESPA_TIMER_HZ) != "100" { - trace.Trace( - "Running docker on macos.", - "Reducing base frequency from 1000hz to 100hz due to high cost of sampling time.", - "This will reduce timeout accuracy.") + uname, _ := backticks.Run("uname", "-r") + if strings.Contains(uname, "linuxkit") { + setTimerHZ("Docker on macOS detected.", "100") + } else { + virt, _ := backticks.Run("systemd-detect-virt", "--vm") + if strings.TrimSpace(virt) == "qemu" { + setTimerHZ("QEMU virtualization detected.", "100") } - os.Setenv(envvars.VESPA_TIMER_HZ, "100") } } } +func setTimerHZ(description, timerHZ string) { + if os.Getenv(envvars.VESPA_TIMER_HZ) == timerHZ { + return + } + trace.Trace( + description, + "Reducing base frequency from 1000hz to "+timerHZ+"hz due to high cost of sampling time.", + "This will reduce timeout accuracy.") + os.Setenv(envvars.VESPA_TIMER_HZ, timerHZ) +} + func TuneResourceLimits() { var numfiles uint64 = 262144 var numprocs uint64 = 409600 diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go index b39c51916e7..4684e313291 100644 --- a/client/go/internal/vespa/deploy.go +++ b/client/go/internal/vespa/deploy.go @@ -12,6 +12,7 @@ import ( "mime/multipart" "net/http" "net/url" + "os" "path/filepath" "strconv" "strings" @@ -47,7 +48,6 @@ type Deployment struct { type DeploymentOptions struct { Target Target ApplicationPackage ApplicationPackage - Timeout time.Duration Version version.Version } @@ -119,6 +119,127 @@ func ZoneFromString(s string) (ZoneID, error) { return ZoneID{Environment: parts[0], Region: parts[1]}, nil } +func Fetch(deployment DeploymentOptions, path string) (string, error) { + if util.IsDirectory(path) { + path = filepath.Join(path, "application.zip") + } + if util.PathExists(path) { + return "", fmt.Errorf("%s already exists", path) + } + if deployment.Target.IsCloud() { + return path, fetchFromController(deployment, path) + } + return path, fetchFromConfigServer(deployment, path) +} + +func deployServiceGet(url string, deployment DeploymentOptions, w io.Writer) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + response, err := deployServiceDo(req, 0, deployment) + if err != nil { + return err + } + defer response.Body.Close() + _, err = io.Copy(w, response.Body) + return err +} + +func fetchFromController(deployment DeploymentOptions, path string) error { + var ( + pkgURL *url.URL + err error + ) + switch deployment.Target.Deployment().Zone.Environment { + case "dev", "perf": + pkgURL, err = deployment.url(fmt.Sprintf("/application/v4/tenant/%s/application/%s/instance/%s/job/%s/package", + deployment.Target.Deployment().Application.Tenant, + deployment.Target.Deployment().Application.Application, + deployment.Target.Deployment().Application.Instance, + deployment.Target.Deployment().Zone.Environment+"-"+deployment.Target.Deployment().Zone.Region, + )) + default: + pkgURL, err = deployment.url(fmt.Sprintf("/application/v4/tenant/%s/application/%s/package", + deployment.Target.Deployment().Application.Tenant, + deployment.Target.Deployment().Application.Application), + ) + } + if err != nil { + return err + } + tmpFile, err := os.CreateTemp("", "vespa") + if err != nil { + return err + } + defer os.Remove(tmpFile.Name()) + if err := deployServiceGet(pkgURL.String(), deployment, tmpFile); err != nil { + return err + } + if err := tmpFile.Close(); err != nil { + return err + } + return os.Rename(tmpFile.Name(), path) +} + +func fetchFromConfigServer(deployment DeploymentOptions, path string) error { + tmpDir, err := os.MkdirTemp("", "vespa") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + u, err := deployment.url("/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content") + if err != nil { + return err + } + dir := filepath.Join(tmpDir, "application") + if err := fetchFilesFromConfigServer(deployment, u, dir); err != nil { + return err + } + zipFile := filepath.Join(tmpDir, "application.zip") + if err := zipDir(dir, zipFile); err != nil { + return err + } + return os.Rename(zipFile, path) +} + +func fetchFilesFromConfigServer(deployment DeploymentOptions, contentURL *url.URL, path string) error { + var data bytes.Buffer + if err := deployServiceGet(contentURL.String(), deployment, &data); err != nil { + return err + } + var fileURLs []string + if err := json.Unmarshal(data.Bytes(), &fileURLs); err != nil { + return err + } + for _, fu := range fileURLs { + u, err := url.Parse(fu) + if err != nil { + return err + } + entryName := filepath.Join(path, filepath.Base(u.Path)) + if strings.HasSuffix(u.Path, "/") { + if err := fetchFilesFromConfigServer(deployment, u, entryName); err != nil { + return err + } + } else { + if err := os.MkdirAll(filepath.Dir(entryName), 0755); err != nil { + return err + } + f, err := os.Create(entryName) + if err != nil { + return err + } + if err := deployServiceGet(fu, deployment, f); err != nil { + f.Close() + return err + } + f.Close() + } + } + return nil +} + // Prepare deployment and return the session ID func Prepare(deployment DeploymentOptions) (PrepareResult, error) { if deployment.Target.IsCloud() { diff --git a/client/go/internal/vespa/deploy_test.go b/client/go/internal/vespa/deploy_test.go index 9dfdc47d8e6..09129d3027a 100644 --- a/client/go/internal/vespa/deploy_test.go +++ b/client/go/internal/vespa/deploy_test.go @@ -2,6 +2,7 @@ package vespa import ( + "archive/zip" "io" "mime" "mime/multipart" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vespa-engine/vespa/client/go/internal/mock" + "github.com/vespa-engine/vespa/client/go/internal/util" "github.com/vespa-engine/vespa/client/go/internal/version" ) @@ -196,6 +198,70 @@ func TestDeactivateCloud(t *testing.T) { assert.Equal(t, "https://api-ctl.vespa-cloud.com:4443/application/v4/tenant/t1/application/a1/instance/i1/environment/dev/region/us-north-1", req.URL.String()) } +func TestFetch(t *testing.T) { + httpClient := mock.HTTPClient{} + target := LocalTarget(&httpClient, TLSOptions{}, 0) + opts := DeploymentOptions{Target: target} + httpClient.NextResponse(mock.HTTPResponse{ + URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content", + Status: 200, + Body: []byte(`[ +"/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/", +"/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/services.xml" +]`), + }) + httpClient.NextResponse(mock.HTTPResponse{ + URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/", + Status: 200, + Body: []byte(`[ +"/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/music.sd" +]`), + }) + httpClient.NextResponse(mock.HTTPResponse{ + URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/music.sd", + Status: 200, + Body: []byte(`music.sd contents`), + }) + httpClient.NextResponse(mock.HTTPResponse{ + URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/services.xml", + Status: 200, + Body: []byte(`services.xml contents`), + }) + dir := t.TempDir() + dst, err := Fetch(opts, dir) + require.Nil(t, err) + assert.True(t, util.PathExists(dst)) + + f, err := os.Open(dst) + require.Nil(t, err) + defer f.Close() + zr, err := zip.NewReader(f, 1000) + require.Nil(t, err) + schema, err := zr.Open("schemas/music.sd") + require.Nil(t, err) + data, err := io.ReadAll(schema) + require.Nil(t, err) + assert.Equal(t, `music.sd contents`, string(data)) +} + +func TestFetchCloud(t *testing.T) { + httpClient := mock.HTTPClient{} + target, _ := createCloudTarget(t, io.Discard) + cloudTarget, ok := target.(*cloudTarget) + require.True(t, ok) + cloudTarget.httpClient = &httpClient + opts := DeploymentOptions{Target: target} + httpClient.NextResponse(mock.HTTPResponse{ + URI: "/application/v4/tenant/t1/application/a1/instance/i1/job/dev-us-north-1/package", + Status: 200, + Body: []byte(`application zip`), + }) + dir := t.TempDir() + dst, err := Fetch(opts, dir) + require.Nil(t, err) + assert.True(t, util.PathExists(dst)) +} + type pkgFixture struct { expectedPath string expectedTestPath string diff --git a/client/js/app/yarn.lock b/client/js/app/yarn.lock index 3f4d43813d9..e3d408e242e 100644 --- a/client/js/app/yarn.lock +++ b/client/js/app/yarn.lock @@ -599,115 +599,115 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== -"@esbuild/android-arm64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz#276c5f99604054d3dbb733577e09adae944baa90" - integrity sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ== - -"@esbuild/android-arm@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.5.tgz#4a3cbf14758166abaae8ba9c01a80e68342a4eec" - integrity sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA== - -"@esbuild/android-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.5.tgz#21a3d11cd4613d2d3c5ccb9e746c254eb9265b0a" - integrity sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA== - -"@esbuild/darwin-arm64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz#714cb839f467d6a67b151ee8255886498e2b9bf6" - integrity sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw== - -"@esbuild/darwin-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz#2c553e97a6d2b4ae76a884e35e6cbab85a990bbf" - integrity sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA== - -"@esbuild/freebsd-arm64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz#d554f556718adb31917a0da24277bf84b6ee87f3" - integrity sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ== - -"@esbuild/freebsd-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz#288f7358a3bb15d99e73c65c9adaa3dabb497432" - integrity sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ== - -"@esbuild/linux-arm64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz#95933ae86325c93cb6b5e8333d22120ecfdc901b" - integrity sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA== - -"@esbuild/linux-arm@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz#0acef93aa3e0579e46d33b666627bddb06636664" - integrity sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ== - -"@esbuild/linux-ia32@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz#b6e5c9e80b42131cbd6b1ddaa48c92835f1ed67f" - integrity sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ== - -"@esbuild/linux-loong64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz#e5f0cf95a180158b01ff5f417da796a1c09dfbea" - integrity sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw== - -"@esbuild/linux-mips64el@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz#ae36fb86c7d5f641f3a0c8472e83dcb6ea36a408" - integrity sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg== - -"@esbuild/linux-ppc64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz#7960cb1666f0340ddd9eef7b26dcea3835d472d0" - integrity sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q== - -"@esbuild/linux-riscv64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz#32207df26af60a3a9feea1783fc21b9817bade19" - integrity sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag== - -"@esbuild/linux-s390x@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz#b38d5681db89a3723862dfa792812397b1510a7d" - integrity sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw== - -"@esbuild/linux-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz#46feba2ad041a241379d150f415b472fe3885075" - integrity sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A== - -"@esbuild/netbsd-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz#3b5c1fb068f26bfc681d31f682adf1bea4ef0702" - integrity sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g== - -"@esbuild/openbsd-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz#ca6830316ca68056c5c88a875f103ad3235e00db" - integrity sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA== - -"@esbuild/sunos-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz#9efc4eb9539a7be7d5a05ada52ee43cda0d8e2dd" - integrity sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg== - -"@esbuild/win32-arm64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz#29f8184afa7a02a956ebda4ed638099f4b8ff198" - integrity sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg== - -"@esbuild/win32-ia32@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz#f3de07afb292ecad651ae4bb8727789de2d95b05" - integrity sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw== - -"@esbuild/win32-x64@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz#faad84c41ba12e3a0acb52571df9bff37bee75f6" - integrity sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw== +"@esbuild/android-arm64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz#646156aea43e8e6723de6e94a4ac07c5aed41be1" + integrity sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w== + +"@esbuild/android-arm@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.7.tgz#0827b49aed813c33ea18ee257c1728cdc4a01030" + integrity sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww== + +"@esbuild/android-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.7.tgz#fa294ed5214d88219d519e0ab1bbb0253a89b864" + integrity sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw== + +"@esbuild/darwin-arm64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz#e24d2ed545749ff251eabe8bce11fefa688892d3" + integrity sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw== + +"@esbuild/darwin-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz#02d1f8a572874c90d8f55dde8a859e5145bd06f6" + integrity sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ== + +"@esbuild/freebsd-arm64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz#bc6a69b9a7915da278f0a5ebaec069c813982c22" + integrity sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ== + +"@esbuild/freebsd-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz#ec3708488625d70e565968ceea1355e7c8613865" + integrity sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA== + +"@esbuild/linux-arm64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz#8e04b66c306858f92d4f90f8222775270755e88a" + integrity sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g== + +"@esbuild/linux-arm@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz#12d5b65e089029ee1fe4c591b60969c9b1a85355" + integrity sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww== + +"@esbuild/linux-ia32@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz#01eabc2a3ad9039e115db650268e4f48f910dbe2" + integrity sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g== + +"@esbuild/linux-loong64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz#70681113632970e6a5766607bbdb98aa18cf4d5f" + integrity sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw== + +"@esbuild/linux-mips64el@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz#f63c022a71a3d70c482d1943a27cb8997021e230" + integrity sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w== + +"@esbuild/linux-ppc64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz#614eafd08b0c50212f287b948b3c08d6e60f221f" + integrity sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ== + +"@esbuild/linux-riscv64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz#31d3b63f92f65968268a8e61ba59872538e80e88" + integrity sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw== + +"@esbuild/linux-s390x@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz#be94974e0caa0783ae05f9477fd7170b9ac29cb0" + integrity sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg== + +"@esbuild/linux-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz#84e8018a913dd4ecee954623e395984aef3d0007" + integrity sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ== + +"@esbuild/netbsd-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz#98898ba8800374c9df9bb182ca4f69fcecaf4411" + integrity sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ== + +"@esbuild/openbsd-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz#46dc4eda2adb51f16361b1ad10e9b3f4938c4573" + integrity sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ== + +"@esbuild/sunos-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz#1650d40dd88412ecc11490119cd23cbaf661a591" + integrity sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw== + +"@esbuild/win32-arm64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz#e61de6c4eb204d83fd912f3ae6812cc8c7d32d25" + integrity sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw== + +"@esbuild/win32-ia32@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz#3d9c159d42c67e37a433e44ef8217c661cb6f6d0" + integrity sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A== + +"@esbuild/win32-x64@0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz#02c4446f802706098d8e6ee70cf2b7aba96ded0b" + integrity sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1269,70 +1269,70 @@ dependencies: "@babel/runtime" "^7.13.10" -"@remix-run/router@1.12.0": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.12.0.tgz#e89b64b6fa97a8a5b740a4c38c2904b80f1f229a" - integrity sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg== - -"@rollup/rollup-android-arm-eabi@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.4.1.tgz#f276b0fa322270aa42d1f56c982db6ef8d6a4393" - integrity sha512-Ss4suS/sd+6xLRu+MLCkED2mUrAyqHmmvZB+zpzZ9Znn9S8wCkTQCJaQ8P8aHofnvG5L16u9MVnJjCqioPErwQ== - -"@rollup/rollup-android-arm64@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.4.1.tgz#f0492f00d18e1067785f8e820e137c00528c5e62" - integrity sha512-sRSkGTvGsARwWd7TzC8LKRf8FiPn7257vd/edzmvG4RIr9x68KBN0/Ek48CkuUJ5Pj/Dp9vKWv6PEupjKWjTYA== - -"@rollup/rollup-darwin-arm64@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.4.1.tgz#40443db7f4559171d797581e0618ec1a4c8dcee9" - integrity sha512-nz0AiGrrXyaWpsmBXUGOBiRDU0wyfSXbFuF98pPvIO8O6auQsPG6riWsfQqmCCC5FNd8zKQ4JhgugRNAkBJ8mQ== - -"@rollup/rollup-darwin-x64@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.4.1.tgz#2868f37a9f9c2c22c091b6209f6ce7454437edf9" - integrity sha512-Ogqvf4/Ve/faMaiPRvzsJEqajbqs00LO+8vtrPBVvLgdw4wBg6ZDXdkDAZO+4MLnrc8mhGV6VJAzYScZdPLtJg== - -"@rollup/rollup-linux-arm-gnueabihf@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.4.1.tgz#d78d7ad358d24058166ab5599de3dcb5ab951add" - integrity sha512-9zc2tqlr6HfO+hx9+wktUlWTRdje7Ub15iJqKcqg5uJZ+iKqmd2CMxlgPpXi7+bU7bjfDIuvCvnGk7wewFEhCg== - -"@rollup/rollup-linux-arm64-gnu@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.4.1.tgz#5d07588b40a04f5b6fbd9e0169c8dc32c1c2ed21" - integrity sha512-phLb1fN3rq2o1j1v+nKxXUTSJnAhzhU0hLrl7Qzb0fLpwkGMHDem+o6d+ZI8+/BlTXfMU4kVWGvy6g9k/B8L6Q== - -"@rollup/rollup-linux-arm64-musl@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.4.1.tgz#d452e88a02755f449f6e98d4ce424d655ef42cfe" - integrity sha512-M2sDtw4tf57VPSjbTAN/lz1doWUqO2CbQuX3L9K6GWIR5uw9j+ROKCvvUNBY8WUbMxwaoc8mH9HmmBKsLht7+w== - -"@rollup/rollup-linux-x64-gnu@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.4.1.tgz#e8e8e87ab098784383a5ced4aa4bbfa7b2c92a4e" - integrity sha512-mHIlRLX+hx+30cD6c4BaBOsSqdnCE4ok7/KDvjHYAHoSuveoMMxIisZFvcLhUnyZcPBXDGZTuBoalcuh43UfQQ== - -"@rollup/rollup-linux-x64-musl@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.4.1.tgz#3e5da42626672e2d620ed12746158b0cf6143b23" - integrity sha512-tB+RZuDi3zxFx7vDrjTNGVLu2KNyzYv+UY8jz7e4TMEoAj7iEt8Qk6xVu6mo3pgjnsHj6jnq3uuRsHp97DLwOA== - -"@rollup/rollup-win32-arm64-msvc@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.4.1.tgz#0f0d0c6b75c53643fab8238c76889a95bca3b9cc" - integrity sha512-Hdn39PzOQowK/HZzYpCuZdJC91PE6EaGbTe2VCA9oq2u18evkisQfws0Smh9QQGNNRa/T7MOuGNQoLeXhhE3PQ== - -"@rollup/rollup-win32-ia32-msvc@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.4.1.tgz#8bb9e8fbf0fdf96fe3bebcee23f5cfdbbd9a4a0a" - integrity sha512-tLpKb1Elm9fM8c5w3nl4N1eLTP4bCqTYw9tqUBxX8/hsxqHO3dxc2qPbZ9PNkdK4tg4iLEYn0pOUnVByRd2CbA== - -"@rollup/rollup-win32-x64-msvc@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.4.1.tgz#8311b77e6cce322865ba12ada8c3779369610d18" - integrity sha512-eAhItDX9yQtZVM3yvXS/VR3qPqcnXvnLyx1pLXl4JzyNMBNO3KC986t/iAg2zcMzpAp9JSvxB5VZGnBiNoA98w== +"@remix-run/router@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.0.tgz#7e29c4ee85176d9c08cb0f4456bff74d092c5065" + integrity sha512-5dMOnVnefRsl4uRnAdoWjtVTdh8e6aZqgM4puy9nmEADH72ck+uXwzpJLEKE9Q6F8ZljNewLgmTfkxUrBdv4WA== + +"@rollup/rollup-android-arm-eabi@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.0.tgz#5984f98288150a2c34928de023bbd122d61ce754" + integrity sha512-OINaBGY+Wc++U0rdr7BLuFClxcoWaVW3vQYqmQq6B3bqQ/2olkaoz+K8+af/Mmka/C2yN5j+L9scBkv4BtKsDA== + +"@rollup/rollup-android-arm64@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.5.0.tgz#8456a8c623cca4042ae4bf2ce03d875a02433191" + integrity sha512-UdMf1pOQc4ZmUA/NTmKhgJTBimbSKnhPS2zJqucqFyBRFPnPDtwA8MzrGNTjDeQbIAWfpJVAlxejw+/lQyBK/w== + +"@rollup/rollup-darwin-arm64@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.5.0.tgz#76be6832eee21dabc28f84f9f54fbfcc66615992" + integrity sha512-L0/CA5p/idVKI+c9PcAPGorH6CwXn6+J0Ys7Gg1axCbTPgI8MeMlhA6fLM9fK+ssFhqogMHFC8HDvZuetOii7w== + +"@rollup/rollup-darwin-x64@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.5.0.tgz#66bd162a3fea48cb1cef50cedccfbeee5685b444" + integrity sha512-QZCbVqU26mNlLn8zi/XDDquNmvcr4ON5FYAHQQsyhrHx8q+sQi/6xduoznYXwk/KmKIXG5dLfR0CvY+NAWpFYQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.5.0.tgz#a0e6b2a1d67a4ba0c2a61985175f65c05abc5f73" + integrity sha512-VpSQ+xm93AeV33QbYslgf44wc5eJGYfYitlQzAi3OObu9iwrGXEnmu5S3ilkqE3Pr/FkgOiJKV/2p0ewf4Hrtg== + +"@rollup/rollup-linux-arm64-gnu@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.5.0.tgz#5434b844a47ba4e35602ee312de9f39b38b1777b" + integrity sha512-OrEyIfpxSsMal44JpEVx9AEcGpdBQG1ZuWISAanaQTSMeStBW+oHWwOkoqR54bw3x8heP8gBOyoJiGg+fLY8qQ== + +"@rollup/rollup-linux-arm64-musl@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.5.0.tgz#149cab95107821fe4ae46d5f2c0658c5b0e56b9c" + integrity sha512-1H7wBbQuE6igQdxMSTjtFfD+DGAudcYWhp106z/9zBA8OQhsJRnemO4XGavdzHpGhRtRxbgmUGdO3YQgrWf2RA== + +"@rollup/rollup-linux-x64-gnu@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.5.0.tgz#6929bf3013e9d599605953ea1bc51f35376bfff7" + integrity sha512-FVyFI13tXw5aE65sZdBpNjPVIi4Q5mARnL/39UIkxvSgRAIqCo5sCpCELk0JtXHGee2owZz5aNLbWNfBHzr71Q== + +"@rollup/rollup-linux-x64-musl@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.5.0.tgz#a17f5decabf05b74aad684de56cf43a72a289a0b" + integrity sha512-eBPYl2sLpH/o8qbSz6vPwWlDyThnQjJfcDOGFbNjmjb44XKC1F5dQfakOsADRVrXCNzM6ZsSIPDG5dc6HHLNFg== + +"@rollup/rollup-win32-arm64-msvc@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.5.0.tgz#f145f10c33aa187a11fd60933465be46667e6e42" + integrity sha512-xaOHIfLOZypoQ5U2I6rEaugS4IYtTgP030xzvrBf5js7p9WI9wik07iHmsKaej8Z83ZDxN5GyypfoyKV5O5TJA== + +"@rollup/rollup-win32-ia32-msvc@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.5.0.tgz#798614b191f9ce1dc58079d1dfbc234c71df9e0e" + integrity sha512-Al6quztQUrHwcOoU2TuFblUQ5L+/AmPBXFR6dUvyo4nRj2yQRK0WIUaGMF/uwKulvRcXkpHe3k9A8Vf93VDktA== + +"@rollup/rollup-win32-x64-msvc@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.5.0.tgz#748970e066839e33ed8c935061e370c4ab050517" + integrity sha512-8kdW+brNhI/NzJ4fxDufuJUjepzINqJKLGHuxyAtpPG9bMbn8P5mtaCcbOm0EzLJ+atg+kF9dwg8jpclkVqx5w== "@sinclair/typebox@^0.27.8": version "0.27.8" @@ -2435,32 +2435,32 @@ esbuild-jest@^0: babel-jest "^26.6.3" esbuild@^0.19.3: - version "0.19.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.5.tgz#53a0e19dfbf61ba6c827d51a80813cf071239a8c" - integrity sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ== + version "0.19.7" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.7.tgz#b9a7235097b81278dcf090e2532ed13c95a2ee84" + integrity sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ== optionalDependencies: - "@esbuild/android-arm" "0.19.5" - "@esbuild/android-arm64" "0.19.5" - "@esbuild/android-x64" "0.19.5" - "@esbuild/darwin-arm64" "0.19.5" - "@esbuild/darwin-x64" "0.19.5" - "@esbuild/freebsd-arm64" "0.19.5" - "@esbuild/freebsd-x64" "0.19.5" - "@esbuild/linux-arm" "0.19.5" - "@esbuild/linux-arm64" "0.19.5" - "@esbuild/linux-ia32" "0.19.5" - "@esbuild/linux-loong64" "0.19.5" - "@esbuild/linux-mips64el" "0.19.5" - "@esbuild/linux-ppc64" "0.19.5" - "@esbuild/linux-riscv64" "0.19.5" - "@esbuild/linux-s390x" "0.19.5" - "@esbuild/linux-x64" "0.19.5" - "@esbuild/netbsd-x64" "0.19.5" - "@esbuild/openbsd-x64" "0.19.5" - "@esbuild/sunos-x64" "0.19.5" - "@esbuild/win32-arm64" "0.19.5" - "@esbuild/win32-ia32" "0.19.5" - "@esbuild/win32-x64" "0.19.5" + "@esbuild/android-arm" "0.19.7" + "@esbuild/android-arm64" "0.19.7" + "@esbuild/android-x64" "0.19.7" + "@esbuild/darwin-arm64" "0.19.7" + "@esbuild/darwin-x64" "0.19.7" + "@esbuild/freebsd-arm64" "0.19.7" + "@esbuild/freebsd-x64" "0.19.7" + "@esbuild/linux-arm" "0.19.7" + "@esbuild/linux-arm64" "0.19.7" + "@esbuild/linux-ia32" "0.19.7" + "@esbuild/linux-loong64" "0.19.7" + "@esbuild/linux-mips64el" "0.19.7" + "@esbuild/linux-ppc64" "0.19.7" + "@esbuild/linux-riscv64" "0.19.7" + "@esbuild/linux-s390x" "0.19.7" + "@esbuild/linux-x64" "0.19.7" + "@esbuild/netbsd-x64" "0.19.7" + "@esbuild/openbsd-x64" "0.19.7" + "@esbuild/sunos-x64" "0.19.7" + "@esbuild/win32-arm64" "0.19.7" + "@esbuild/win32-ia32" "0.19.7" + "@esbuild/win32-x64" "0.19.7" escalade@^3.1.1: version "3.1.1" @@ -4356,9 +4356,9 @@ multimatch@^4.0.0: minimatch "^3.0.4" nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== nanomatch@^1.2.9: version "1.2.13" @@ -4808,19 +4808,19 @@ react-refresh@^0.14.0: integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== react-router-dom@^6: - version "6.19.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.19.0.tgz#ee807e36ae7dd954db7a3f770e38b7cc026c66a8" - integrity sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ== + version "6.20.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.0.tgz#7b9527a1e29c7fb90736a5f89d54ca01f40e264b" + integrity sha512-CbcKjEyiSVpA6UtCHOIYLUYn/UJfwzp55va4yEfpk7JBN3GPqWfHrdLkAvNCcpXr8QoihcDMuk0dzWZxtlB/mQ== dependencies: - "@remix-run/router" "1.12.0" - react-router "6.19.0" + "@remix-run/router" "1.13.0" + react-router "6.20.0" -react-router@6.19.0: - version "6.19.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.19.0.tgz#6d5062fa33495859daca98d86292ab03b037a9ca" - integrity sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw== +react-router@6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.20.0.tgz#4275a3567ecc55f7703073158048db10096bb539" + integrity sha512-pVvzsSsgUxxtuNfTHC4IxjATs10UaAtvLGVSA1tbUE4GDaOSU1Esu2xF5nWLz7KPiMuW8BJWuPFdlGYJ7/rW0w== dependencies: - "@remix-run/router" "1.12.0" + "@remix-run/router" "1.13.0" react-textarea-autosize@8.3.4: version "8.3.4" @@ -4997,22 +4997,22 @@ rimraf@^3.0.2: glob "^7.1.3" rollup@^4.2.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.4.1.tgz#2f85169f23d13dabb3d9b846d753965757353820" - integrity sha512-idZzrUpWSblPJX66i+GzrpjKE3vbYrlWirUHteoAbjKReZwa0cohAErOYA5efoMmNCdvG9yrJS+w9Kl6csaH4w== + version "4.5.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.5.0.tgz#358ee6947fe0e4c8bacdae6896539cade3107655" + integrity sha512-41xsWhzxqjMDASCxH5ibw1mXk+3c4TNI2UjKbLxe6iEzrSQnqOzmmK8/3mufCPbzHNJ2e04Fc1ddI35hHy+8zg== optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.4.1" - "@rollup/rollup-android-arm64" "4.4.1" - "@rollup/rollup-darwin-arm64" "4.4.1" - "@rollup/rollup-darwin-x64" "4.4.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.4.1" - "@rollup/rollup-linux-arm64-gnu" "4.4.1" - "@rollup/rollup-linux-arm64-musl" "4.4.1" - "@rollup/rollup-linux-x64-gnu" "4.4.1" - "@rollup/rollup-linux-x64-musl" "4.4.1" - "@rollup/rollup-win32-arm64-msvc" "4.4.1" - "@rollup/rollup-win32-ia32-msvc" "4.4.1" - "@rollup/rollup-win32-x64-msvc" "4.4.1" + "@rollup/rollup-android-arm-eabi" "4.5.0" + "@rollup/rollup-android-arm64" "4.5.0" + "@rollup/rollup-darwin-arm64" "4.5.0" + "@rollup/rollup-darwin-x64" "4.5.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.5.0" + "@rollup/rollup-linux-arm64-gnu" "4.5.0" + "@rollup/rollup-linux-arm64-musl" "4.5.0" + "@rollup/rollup-linux-x64-gnu" "4.5.0" + "@rollup/rollup-linux-x64-musl" "4.5.0" + "@rollup/rollup-win32-arm64-msvc" "4.5.0" + "@rollup/rollup-win32-ia32-msvc" "4.5.0" + "@rollup/rollup-win32-x64-msvc" "4.5.0" fsevents "~2.3.2" rsvp@^4.8.4: @@ -5645,9 +5645,9 @@ v8-to-istanbul@^9.0.1: convert-source-map "^1.6.0" vite@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.0.tgz#3bfb65acda2a97127e4fa240156664a1f234ce08" - integrity sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw== + version "5.0.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.2.tgz#3c94627dace83b9bf04b64eaf618038e30fb95c0" + integrity sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g== dependencies: esbuild "^0.19.3" postcss "^8.4.31" diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java index 6e4c14bdeac..36b6d0fe07a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java @@ -25,6 +25,7 @@ import com.yahoo.vespa.model.search.SearchCluster; import com.yahoo.vespa.model.search.StreamingSearchCluster; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -72,8 +73,15 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> } private static Collection<String> getSchemasWithGlobalPhase(DeployState state) { - return state.rankProfileRegistry().all().stream() - .filter(rp -> rp.getGlobalPhase() != null).map(rp -> rp.schema().getName()).collect(Collectors.toSet()); + var res = new HashSet<String>(); + for (var schema : state.getSchemas()) { + for (var rp : state.rankProfileRegistry().rankProfilesOf(schema)) { + if (rp.getGlobalPhase() != null) { + res.add(schema.getName()); + } + } + } + return res; } public void connectSearchClusters(Map<String, SearchCluster> searchClusters) { diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java index 3fb577a5ff3..f7f3f97f3ac 100644 --- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java +++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java @@ -136,10 +136,8 @@ public class StreamingSearcher extends VespaBackEndSearcher { initializeMissingQueryFields(query); if (documentSelectionQueryParameterCount(query) != 1) { - return new Result(query, ErrorMessage.createIllegalQuery("Streaming search needs one and " + - "only one of these query parameters to be set: " + - "streaming.userid, streaming.groupname, or " + - "streaming.selection")); + return new Result(query, ErrorMessage.createIllegalQuery("Streaming search requires either " + + "streaming.groupname or streaming.selection")); } if (query.getTrace().isTraceable(4)) query.trace("Routing to search cluster " + getSearchClusterName() + " and document type " + documentType, 4); diff --git a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java index dd5e1c71b16..2a246739100 100644 --- a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java +++ b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java @@ -244,7 +244,7 @@ public class StreamingSearcherTestCase { // Magic query values are used to trigger specific behaviors from mock visitor. checkError(searcher, "/?query=noselection", - "Illegal query", "Streaming search needs one and only one"); + "Illegal query", "Streaming search requires either"); checkError(searcher, "/?streaming.userid=1&query=parseexception", "Invalid query parameter", "Failed to parse document selection string"); checkError(searcher, "/?streaming.userid=1&query=tokenizeexception", diff --git a/dependency-versions/pom.xml b/dependency-versions/pom.xml index c4edf840c7c..ca5d5cabee6 100644 --- a/dependency-versions/pom.xml +++ b/dependency-versions/pom.xml @@ -86,12 +86,12 @@ <commons-digester.vespa.version>3.2</commons-digester.vespa.version> <commons-exec.vespa.version>1.3</commons-exec.vespa.version> <commons-io.vespa.version>2.15.0</commons-io.vespa.version> - <commons-lang3.vespa.version>3.13.0</commons-lang3.vespa.version> + <commons-lang3.vespa.version>3.14.0</commons-lang3.vespa.version> <commons.math3.vespa.version>3.6.1</commons.math3.vespa.version> <commons-compress.vespa.version>1.25.0</commons-compress.vespa.version> <commons-cli.vespa.version>1.6.0</commons-cli.vespa.version> <curator.vespa.version>5.5.0</curator.vespa.version> - <dropwizard.metrics.vespa.version>4.2.22</dropwizard.metrics.vespa.version> + <dropwizard.metrics.vespa.version>4.1.12.1</dropwizard.metrics.vespa.version> <!-- ZK 3.9.1 requires this --> <eclipse-collections.vespa.version>11.1.0</eclipse-collections.vespa.version> <felix.vespa.version>7.0.5</felix.vespa.version> <felix.log.vespa.version>1.3.0</felix.log.vespa.version> @@ -120,7 +120,7 @@ <mojo-executor.vespa.version>2.4.0</mojo-executor.vespa.version> <netty.vespa.version>4.1.101.Final</netty.vespa.version> <netty-tcnative.vespa.version>2.0.62.Final</netty-tcnative.vespa.version> - <onnxruntime.vespa.version>1.16.2</onnxruntime.vespa.version> + <onnxruntime.vespa.version>1.16.3</onnxruntime.vespa.version> <opennlp.vespa.version>2.3.0</opennlp.vespa.version> <opentest4j.vespa.version>1.3.0</opentest4j.vespa.version> <org.json.vespa.version>20231013</org.json.vespa.version> diff --git a/dist/vespa-engine.repo b/dist/vespa-engine.repo index d884b404533..29c408ca66f 100644 --- a/dist/vespa-engine.repo +++ b/dist/vespa-engine.repo @@ -12,7 +12,7 @@ enabled=1 [copr:copr.fedorainfracloud.org:group_vespa:vespa] name=Copr repo for vespa owned by @vespa -baseurl=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/centos-stream-8-$basearch/ +baseurl=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/epel-8-$basearch/ type=rpm-md skip_if_unavailable=True gpgcheck=1 diff --git a/dist/vespa.spec b/dist/vespa.spec index 6cc694004e9..99e020a0bf9 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -42,7 +42,7 @@ License: Commercial URL: http://vespa.ai Source0: vespa-%{version}.tar.gz -BuildRequires: vespa-build-dependencies = 1.2.2 +BuildRequires: vespa-build-dependencies >= 1.2.3 Requires: %{name}-base = %{version}-%{release} Requires: %{name}-base-libs = %{version}-%{release} @@ -156,7 +156,7 @@ Requires: openssl-libs Requires: vespa-lz4 >= 1.9.4-1 Requires: vespa-libzstd >= 1.5.4-1 %if 0%{?el8} -Requires: vespa-openblas >= 0.3.21 +Requires: vespa-openblas >= 0.3.25 %else Requires: openblas-serial %endif @@ -196,7 +196,7 @@ Requires: vespa-protobuf = 3.21.12 Requires: protobuf Requires: llvm-libs %endif -Requires: vespa-onnxruntime = 1.16.2 +Requires: vespa-onnxruntime = 1.16.3 %description libs diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java index 31d79d34c94..d511570881b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java @@ -445,7 +445,7 @@ public class CuratorDb { } public Optional<LoadBalancer> readLoadBalancer(LoadBalancerId id) { - return read(loadBalancerPath(id), bytes -> LoadBalancerSerializer.fromJson(id, bytes)); + return read(loadBalancerPath(id), LoadBalancerSerializer::fromJson); } public void writeLoadBalancer(LoadBalancer loadBalancer, LoadBalancer.State fromState) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java index 99cc0d1e601..c4a2e4cb549 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java @@ -111,7 +111,7 @@ public class LoadBalancerSerializer { } } - public static LoadBalancer fromJson(LoadBalancerId id, byte[] data) { + public static LoadBalancer fromJson(byte[] data) { Cursor object = SlimeUtils.jsonToSlime(data).get(); Set<Real> reals = new LinkedHashSet<>(); @@ -128,8 +128,7 @@ public class LoadBalancerSerializer { Set<String> networks = new LinkedHashSet<>(); object.field(networksField).traverse((ArrayTraverser) (i, network) -> networks.add(network.asString())); - // TODO jonmv: remove fallback after data is re-written. - String idSeed = SlimeUtils.optionalString(object.field(idSeedField)).orElse(id.application().tenant().value() + id.application().application().value() + id.application().instance().value() + id.cluster().value()); + String idSeed = object.field(idSeedField).asString(); Optional<DomainName> hostname = SlimeUtils.optionalString(object.field(hostnameField)).map(DomainName::of); Optional<String> ip4Address = SlimeUtils.optionalString(object.field(lbIpAddressField)); Optional<String> ip6Address = SlimeUtils.optionalString(object.field(lbIp6AddressField)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java index 9ec63933921..60341661fc0 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java @@ -58,7 +58,7 @@ public class LoadBalancerSerializerTest { LoadBalancer.State.active, now); - var serialized = LoadBalancerSerializer.fromJson(loadBalancer.id(), LoadBalancerSerializer.toJson(loadBalancer)); + var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer)); assertEquals(loadBalancer.id(), serialized.id()); assertEquals(loadBalancer.idSeed(), serialized.idSeed()); assertEquals(loadBalancer.instance().get().hostname(), serialized.instance().get().hostname()); @@ -89,7 +89,7 @@ public class LoadBalancerSerializerTest { LoadBalancer.State.active, now); - var serialized = LoadBalancerSerializer.fromJson(loadBalancer.id(), LoadBalancerSerializer.toJson(loadBalancer)); + var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer)); assertEquals(loadBalancer.id(), serialized.id()); assertEquals(loadBalancer.idSeed(), serialized.idSeed()); assertEquals(loadBalancer.instance().get().hostname(), serialized.instance().get().hostname()); @@ -112,7 +112,7 @@ public class LoadBalancerSerializerTest { var now = Instant.now(); var loadBalancer = new LoadBalancer(loadBalancerId, "seed", Optional.empty(), LoadBalancer.State.reserved, now); - var serialized = LoadBalancerSerializer.fromJson(loadBalancerId, LoadBalancerSerializer.toJson(loadBalancer)); + var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer)); assertEquals(loadBalancer.id(), serialized.id()); assertEquals(loadBalancer.idSeed(), serialized.idSeed()); assertEquals(loadBalancer.instance(), serialized.instance()); diff --git a/parent/pom.xml b/parent/pom.xml index c6ae9cdc7e3..545db6ba1f1 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -697,6 +697,12 @@ <groupId>io.netty</groupId> <artifactId>netty-transport-native-epoll</artifactId> <version>${netty.vespa.version}</version> + <classifier>linux-x86_64</classifier> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-transport-native-epoll</artifactId> + <version>${netty.vespa.version}</version> </dependency> <dependency> <groupId>io.netty</groupId> @@ -704,6 +710,11 @@ <version>${netty-tcnative.vespa.version}</version> </dependency> <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-tcnative-boringssl-static</artifactId> + <version>${netty-tcnative.vespa.version}</version> + </dependency> + <dependency> <groupId>io.prometheus</groupId> <artifactId>simpleclient</artifactId> <version>${prometheus.client.vespa.version}</version> diff --git a/screwdriver/release-rpms.sh b/screwdriver/release-rpms.sh index b3834572a4a..76ab74e9a5c 100755 --- a/screwdriver/release-rpms.sh +++ b/screwdriver/release-rpms.sh @@ -12,10 +12,10 @@ fi readonly VESPA_RELEASE="$1" readonly VESPA_REF="$2" -VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) +VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) echo "Latest x86_64 RPM on Copr: $VESPA_RPM_X86_64" -VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) +VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) echo "Latest aarch64 RPM on Copr: $VESPA_RPM_AARCH64" if [[ "$VESPA_RELEASE" == "$VESPA_RPM_X86_64" ]] && [[ "$VESPA_RELEASE" == "$VESPA_RPM_AARCH64" ]]; then @@ -35,11 +35,11 @@ cd vespa dist/release-vespa-rpm.sh $VESPA_RELEASE $VESPA_REF while [[ "$VESPA_RELEASE" != "$VESPA_RPM_X86_64" ]] || [[ "$VESPA_RELEASE" != "$VESPA_RPM_AARCH64" ]] ; do - dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-x86_64 --repoid=vespa metadata - VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) + dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-x86_64 --repoid=vespa metadata + VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) echo "RPM x86_64: $VESPA_RPM_X86_64" - dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-aarch64 --repoid=vespa metadata - VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) + dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-aarch64 --repoid=vespa metadata + VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1) echo "RPM aarch64: $VESPA_RPM_AARCH64" sleep 150 done diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp index 0393bdc2ee8..95e4eac437c 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp @@ -222,7 +222,7 @@ public: setEstimate(HitEstimate(_activeLids.size(), false)); } - bool isWhiteList() const override { return true; } + bool isWhiteList() const noexcept final { return true; } SearchIterator::UP createFilterSearch(bool strict, FilterConstraint) const override { if (_all_lids_active) { @@ -231,7 +231,7 @@ public: return create_search_helper(strict); } - ~WhiteListBlueprint() { + ~WhiteListBlueprint() override { for (auto matchData : _matchDataVector) { delete matchData; } diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.cpp b/searchcore/src/vespa/searchcore/proton/matching/query.cpp index 071e914b405..f55ba77cec8 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/query.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/query.cpp @@ -143,7 +143,7 @@ void exchange_location_nodes(const string &location_str, IntermediateBlueprint * asRankOrAndNot(Blueprint * blueprint) { return ((blueprint->isAndNot() || blueprint->isRank())) - ? static_cast<IntermediateBlueprint *>(blueprint) + ? blueprint->asIntermediate() : nullptr; } diff --git a/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp b/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp index fcae5794e9e..af26a47fba3 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp @@ -25,15 +25,13 @@ namespace { RangeLimitMetaInfo locateFirst(uint32_t field_id, const Blueprint & blueprint) { - if (blueprint.isIntermediate()) { - const auto & intermediate = static_cast<const IntermediateBlueprint &>(blueprint); - if (intermediate.isAndNot()) { - return locateFirst(field_id, intermediate.getChild(0)); - } else if (intermediate.isRank()) { - return locateFirst(field_id, intermediate.getChild(0)); - } else if (intermediate.isAnd()) { - for (size_t i(0); i < intermediate.childCnt(); i++) { - RangeLimitMetaInfo childMeta = locateFirst(field_id, intermediate.getChild(i)); + const auto * intermediate = blueprint.asIntermediate(); + if (intermediate) { + if (intermediate->isAndNot() || intermediate->isRank()) { + return locateFirst(field_id, intermediate->getChild(0)); + } else if (intermediate->isAnd()) { + for (size_t i(0); i < intermediate->childCnt(); i++) { + RangeLimitMetaInfo childMeta = locateFirst(field_id, intermediate->getChild(i)); if (childMeta.valid()) { return childMeta; } @@ -42,9 +40,9 @@ locateFirst(uint32_t field_id, const Blueprint & blueprint) { } else { const Blueprint::State & state = blueprint.getState(); if (state.isTermLike() && (state.numFields() == 1) && (state.field(0).getFieldId() == field_id)) { - const LeafBlueprint &leaf = static_cast<const LeafBlueprint &>(blueprint); + const LeafBlueprint * leaf = blueprint.asLeaf(); vespalib::string from, too; - if (leaf.getRange(from, too)) { + if (leaf->getRange(from, too)) { return {from, too, state.estimate().estHits}; } } diff --git a/searchlib/src/main/javacc/RankingExpressionParser.jj b/searchlib/src/main/javacc/RankingExpressionParser.jj index 42f8f846199..591f0eb8b37 100755 --- a/searchlib/src/main/javacc/RankingExpressionParser.jj +++ b/searchlib/src/main/javacc/RankingExpressionParser.jj @@ -5,6 +5,7 @@ * @author bratseth */ options { + UNICODE_INPUT = true; CACHE_TOKENS = true; DEBUG_PARSER = false; USER_TOKEN_MANAGER = false; diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java index 9626059a42e..0088e3eb9de 100644 --- a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java +++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java @@ -220,6 +220,7 @@ public class EvaluationTestCase { @Test public void testTensorEvaluation() { EvaluationTester tester = new EvaluationTester(); + tester.assertEvaluates("{}", "tensor0", "{}"); // tensor map @@ -227,6 +228,9 @@ public class EvaluationTestCase { "map(tensor0, f(x) (log10(x)))", "{ {d1:0}:10, {d1:1}:100, {d1:2}:1000 }"); tester.assertEvaluates("{ {d1:0}:4, {d1:1}:9, {d1:2 }:16 }", "map(tensor0, f(x) (x * x))", "{ {d1:0}:2, {d1:1}:3, {d1:2}:4 }"); + // Unicode key + tester.assertEvaluates("tensor<int8>(drink{}):{Martini\uD83C\uDF78:30.0}", + "tensor<int8>(drink{}): {\"Martini\uD83C\uDF78\": 30 }"); // -- tensor map shorthands tester.assertEvaluates("{ {d1:0}:0, {d1:1}:1, {d1:2 }:0 }", "tensor0 == 3", "{ {d1:0}:2, {d1:1}:3, {d1:2}:4 }"); diff --git a/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp b/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp index 62628d61d49..3d3ff469546 100644 --- a/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp +++ b/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp @@ -449,7 +449,7 @@ PostingListAttributeTest::checkPostingList(const VectorType & vec, const std::ve { const typename VectorType::EnumStore & enumStore = vec.getEnumStore(); auto& dict = enumStore.get_dictionary(); - const typename VectorType::PostingList & postingList = vec.getPostingList(); + const auto& posting_store = vec.get_posting_store(); for (size_t i = 0; i < values.size(); ++i) { const uint32_t docBegin = range.getBegin(i); @@ -457,10 +457,9 @@ PostingListAttributeTest::checkPostingList(const VectorType & vec, const std::ve auto find_result = dict.find_posting_list(enumStore.make_comparator(values[i]), dict.get_frozen_root()); ASSERT_TRUE(find_result.first.valid()); - bool has_bitvector = VectorType::PostingList::isBitVector(postingList.getTypeId(find_result.second)); + bool has_bitvector = VectorType::PostingStore::isBitVector(posting_store.getTypeId(find_result.second)); - typename VectorType::PostingList::Iterator postings; - postings = postingList.begin(find_result.second); + auto postings = posting_store.begin(find_result.second); uint32_t numHits(0); bool has_btree = postings.valid(); if (postings.valid()) { @@ -472,12 +471,12 @@ PostingListAttributeTest::checkPostingList(const VectorType & vec, const std::ve EXPECT_EQ(doc, docEnd); } else { EXPECT_TRUE(has_bitvector && vec.getIsFilter()); - numHits = postingList.getBitVectorEntry(find_result.second)->_bv->reader().countTrueBits(); + numHits = posting_store.getBitVectorEntry(find_result.second)->_bv->reader().countTrueBits(); } if (has_bitvector) { uint32_t doc = docBegin; uint32_t bv_num_hits = 0; - auto& bv = postingList.getBitVectorEntry(find_result.second)->_bv->reader(); + auto& bv = posting_store.getBitVectorEntry(find_result.second)->_bv->reader(); for (auto lid = bv.getFirstTrueBit(); lid < bv.size(); lid = bv.getNextTrueBit(lid + 1)) { EXPECT_EQ(doc++, lid); ++bv_num_hits; @@ -701,12 +700,11 @@ PostingListAttributeTest::checkPostingList(AttributeType & vec, ValueType value, { const typename AttributeType::EnumStore & enumStore = vec.getEnumStore(); auto& dict = enumStore.get_dictionary(); - const typename AttributeType::PostingList & postingList = vec.getPostingList(); + const auto& posting_store = vec.get_posting_store(); auto find_result = dict.find_posting_list(vec.getEnumStore().make_comparator(value), dict.get_frozen_root()); ASSERT_TRUE(find_result.first.valid()); - typename AttributeType::PostingList::Iterator postings; - postings = postingList.begin(find_result.second); + auto postings = posting_store.begin(find_result.second); DocSet::iterator docBegin = expected.begin(); DocSet::iterator docEnd = expected.end(); diff --git a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp index 343a5c8e38b..c701d5ac19f 100644 --- a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp +++ b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp @@ -233,6 +233,7 @@ private: void testRangeSearch(const AttributePtr & ptr, uint32_t numDocs, std::vector<ValueType> values); void testRangeSearch(); void testRangeSearchLimited(); + void testRangeSearchLimitedHugeDictionary(); // test case insensitive search @@ -504,7 +505,7 @@ SearchContextTest::checkResultSet(const ResultSet & rs, const DocSet & expected, ASSERT_TRUE(array != nullptr); uint32_t i = 0; for (auto iter = expected.begin(); iter != expected.end(); ++iter, ++i) { - EXPECT_TRUE(array[i].getDocId() == *iter); + EXPECT_EQUAL(array[i].getDocId(), *iter); } } } @@ -1176,6 +1177,42 @@ SearchContextTest::testRangeSearch(const AttributePtr & ptr, uint32_t numDocs, s } } +DocSet +createDocs(uint32_t from, int32_t count) { + DocSet docs; + if (count >= 0) { + for (int32_t i(0); i < count; i++) { + docs.put(from + i); + } + } else { + for (int32_t i(0); i > count; i--) { + docs.put(from + i); + } + } + return docs; +} + +void +SearchContextTest::testRangeSearchLimitedHugeDictionary() { + Config cfg(BasicType::INT32, CollectionType::SINGLE); + cfg.setFastSearch(true); + std::vector<int32_t> v; + v.reserve(2000); + for (size_t i(0); i < v.capacity(); i++) { + v.push_back(i); + } + auto ptr = AttributeBuilder("limited-int32", cfg).fill(v).get(); + auto& vec = dynamic_cast<IntegerAttribute &>(*ptr); + + performRangeSearch(vec, "[1;9;1200]", createDocs(2, 9)); + performRangeSearch(vec, "[1;1109;1200]", createDocs(2, 1109)); + performRangeSearch(vec, "[1;3009;1200]", createDocs(2, 1200)); + + performRangeSearch(vec, "[1;9;-1200]", createDocs(2, 9)); + performRangeSearch(vec, "[1;1109;-1200]", createDocs(2, 1109)); + performRangeSearch(vec, "[1;3009;-1200]", createDocs(2000, -1200)); +} + void SearchContextTest::testRangeSearchLimited() { @@ -1980,6 +2017,7 @@ SearchContextTest::Main() testSearchIterator(); testRangeSearch(); testRangeSearchLimited(); + testRangeSearchLimitedHugeDictionary(); testCaseInsensitiveSearch(); testRegexSearch(); testPrefixSearch(); diff --git a/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp b/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp index 51e22dbcf2c..9cbfa49cf26 100644 --- a/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp +++ b/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp @@ -473,395 +473,169 @@ TEST("test SourceBlender Blueprint") { // createSearch tested by iterator unit test } -TEST("test SourceBlender below AND optimization") { - auto selector_1 = std::make_unique<InvalidSelector>(); // the one - auto selector_2 = std::make_unique<InvalidSelector>(); // not the one - //------------------------------------------------------------------------- - auto *top = new AndBlueprint(); - Blueprint::UP top_bp(top); - top->addChild(ap(MyLeafSpec(2).create())); - top->addChild(ap(MyLeafSpec(1).create())); - top->addChild(ap(MyLeafSpec(3).create())); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - top->addChild(ap(blender)); +std::unique_ptr<IntermediateBlueprint> +addLeafs(std::unique_ptr<IntermediateBlueprint> parent, std::initializer_list<std::pair<uint32_t, uint32_t>> list) { + for (const auto & leaf : list) { + parent->addChild(ap(MyLeafSpec(leaf.first).create()->setSourceId(leaf.second))); } - //------------------------------------------------------------------------- - auto *expect = new AndBlueprint(); - Blueprint::UP expect_bp(expect); + return parent; +} + +std::unique_ptr<IntermediateBlueprint> +addLeafs(uint32_t sourceId, std::unique_ptr<IntermediateBlueprint> parent, std::initializer_list<std::pair<uint32_t, uint32_t>> list) { + parent->setSourceId(sourceId); + return addLeafs(std::move(parent), list); +} + +struct SourceBlenderTestFixture { + InvalidSelector selector_1; // the one + InvalidSelector selector_2; // not the one + void addChildrenForSBTest(IntermediateBlueprint & parent); +}; + +void SourceBlenderTestFixture::addChildrenForSBTest(IntermediateBlueprint & parent) { + parent.addChild(ap(MyLeafSpec(2).create())); + parent.addChild(ap(MyLeafSpec(1).create())); + parent.addChild(ap(MyLeafSpec(3).create())); + parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_1), {{200, 2}, {100, 1}, {300, 3}})); + parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_1), {{20, 2}, {10, 1}, {30, 3}})); + parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_2), {{10, 1}, {20, 2}})); + parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_1), {{2000, 2}, {1000, 1}})); +} + +TEST_F("test SourceBlender below AND optimization", SourceBlenderTestFixture) { + auto top = std::make_unique<AndBlueprint>(); + f.addChildrenForSBTest(*top); + + auto expect = std::make_unique<AndBlueprint>(); expect->addChild(ap(MyLeafSpec(1).create())); expect->addChild(ap(MyLeafSpec(2).create())); expect->addChild(ap(MyLeafSpec(3).create())); - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - expect->addChild(ap(blender)); - } - { - auto *blender(new SourceBlenderBlueprint(*selector_1)); - { - auto *sub_and = new AndBlueprint(); - sub_and->setSourceId(3); - sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new AndBlueprint(); - sub_and->setSourceId(2); - sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new AndBlueprint(); - sub_and->setSourceId(1); - sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - blender->addChild(ap(sub_and)); - } - expect->addChild(ap(blender)); - } + expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}})); + + auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1); + blender->addChild(addLeafs(3, std::make_unique<AndBlueprint>(), {{30, 3}, {300, 3}})); + blender->addChild(addLeafs(2, std::make_unique<AndBlueprint>(), {{20, 2}, {200, 2}, {2000, 2}})); + blender->addChild(addLeafs(1, std::make_unique<AndBlueprint>(), {{10, 1}, {100, 1}, {1000, 1}})); + expect->addChild(std::move(blender)); + //------------------------------------------------------------------------- - EXPECT_NOT_EQUAL(expect_bp->asString(), top_bp->asString()); - top_bp = Blueprint::optimize(std::move(top_bp)); - EXPECT_EQUAL(expect_bp->asString(), top_bp->asString()); - expect_bp = Blueprint::optimize(std::move(expect_bp)); + EXPECT_NOT_EQUAL(expect->asString(), top->asString()); + auto top_bp = Blueprint::optimize(std::move(top)); + EXPECT_EQUAL(expect->asString(), top_bp->asString()); + auto expect_bp = Blueprint::optimize(std::move(expect)); EXPECT_EQUAL(expect_bp->asString(), top_bp->asString()); } -TEST("test SourceBlender below OR optimization") { - auto selector_1 = std::make_unique<InvalidSelector>(); // the one - auto selector_2 = std::make_unique<InvalidSelector>(); // not the one +TEST_F("test SourceBlender below OR optimization", SourceBlenderTestFixture) { + auto top = std::make_unique<OrBlueprint>(); + f.addChildrenForSBTest(*top); //------------------------------------------------------------------------- - auto *top = new OrBlueprint(); - Blueprint::UP top_up(top); - top->addChild(ap(MyLeafSpec(2).create())); - top->addChild(ap(MyLeafSpec(1).create())); - top->addChild(ap(MyLeafSpec(3).create())); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - top->addChild(ap(blender)); - } - //------------------------------------------------------------------------- - auto *expect = new OrBlueprint(); - Blueprint::UP expect_up(expect); - { - auto *blender(new SourceBlenderBlueprint(*selector_1)); - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(3); - sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(2); - sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(1); - sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(sub_and)); - } - expect->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - expect->addChild(ap(blender)); - } + auto expect = std::make_unique<OrBlueprint>(); + auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1); + blender->addChild(addLeafs(3, std::make_unique<OrBlueprint>(), {{300, 3}, {30, 3}})); + blender->addChild(addLeafs(2, std::make_unique<OrBlueprint>(), {{2000, 2}, {200, 2}, {20, 2}})); + blender->addChild(addLeafs(1, std::make_unique<OrBlueprint>(), {{1000, 1}, {100, 1}, {10, 1}})); + expect->addChild(std::move(blender)); + expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}})); expect->addChild(ap(MyLeafSpec(3).create())); expect->addChild(ap(MyLeafSpec(2).create())); expect->addChild(ap(MyLeafSpec(1).create())); //------------------------------------------------------------------------- - EXPECT_NOT_EQUAL(expect_up->asString(), top_up->asString()); - top_up = Blueprint::optimize(std::move(top_up)); - EXPECT_EQUAL(expect_up->asString(), top_up->asString()); - expect_up = Blueprint::optimize(std::move(expect_up)); - EXPECT_EQUAL(expect_up->asString(), top_up->asString()); + EXPECT_NOT_EQUAL(expect->asString(), top->asString()); + auto top_bp = Blueprint::optimize(std::move(top)); + EXPECT_EQUAL(expect->asString(), top_bp->asString()); + auto expect_bp = Blueprint::optimize(std::move(expect)); + EXPECT_EQUAL(expect_bp->asString(), top_bp->asString()); } -TEST("test SourceBlender below AND_NOT optimization") { - auto selector_1 = std::make_unique<InvalidSelector>(); // the one - auto selector_2 = std::make_unique<InvalidSelector>(); // not the one - //------------------------------------------------------------------------- - auto *top = new AndNotBlueprint(); - Blueprint::UP top_up(top); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1))); - top->addChild(ap(blender)); - } - top->addChild(ap(MyLeafSpec(2).create())); - top->addChild(ap(MyLeafSpec(1).create())); - top->addChild(ap(MyLeafSpec(3).create())); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - top->addChild(ap(blender)); - } +TEST_F("test SourceBlender below AND_NOT optimization", SourceBlenderTestFixture) { + auto top = std::make_unique<AndNotBlueprint>(); + top->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}})); + f.addChildrenForSBTest(*top); + //------------------------------------------------------------------------- - auto *expect = new AndNotBlueprint(); - Blueprint::UP expect_up(expect); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1))); - expect->addChild(ap(blender)); - } - { - auto *blender(new SourceBlenderBlueprint(*selector_1)); - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(3); - sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(2); - sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(1); - sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(sub_and)); - } - expect->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - expect->addChild(ap(blender)); - } + auto expect = std::make_unique<AndNotBlueprint>(); + expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}})); + auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1); + blender->addChild(addLeafs(3, std::make_unique<OrBlueprint>(), {{300, 3}, {30, 3}})); + blender->addChild(addLeafs(2, std::make_unique<OrBlueprint>(), {{2000, 2}, {200, 2}, {20, 2}})); + blender->addChild(addLeafs(1, std::make_unique<OrBlueprint>(), {{1000, 1}, {100, 1}, {10, 1}})); + expect->addChild(std::move(blender)); + expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}})); expect->addChild(ap(MyLeafSpec(3).create())); expect->addChild(ap(MyLeafSpec(2).create())); expect->addChild(ap(MyLeafSpec(1).create())); + //------------------------------------------------------------------------- - EXPECT_NOT_EQUAL(expect_up->asString(), top_up->asString()); - top_up = Blueprint::optimize(std::move(top_up)); - EXPECT_EQUAL(expect_up->asString(), top_up->asString()); - expect_up = Blueprint::optimize(std::move(expect_up)); + EXPECT_NOT_EQUAL(expect->asString(), top->asString()); + auto top_up = Blueprint::optimize(std::move(top)); + EXPECT_EQUAL(expect->asString(), top_up->asString()); + auto expect_up = Blueprint::optimize(std::move(expect)); EXPECT_EQUAL(expect_up->asString(), top_up->asString()); } -TEST("test SourceBlender below RANK optimization") { - auto selector_1 = std::make_unique<InvalidSelector>(); // the one - auto selector_2 = std::make_unique<InvalidSelector>(); // not the one - //------------------------------------------------------------------------- - auto *top = new RankBlueprint(); - Blueprint::UP top_up(top); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1))); - top->addChild(ap(blender)); - } - top->addChild(ap(MyLeafSpec(2).create())); - top->addChild(ap(MyLeafSpec(1).create())); - top->addChild(ap(MyLeafSpec(3).create())); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - top->addChild(ap(blender)); - } - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - top->addChild(ap(blender)); - } +TEST_F("test SourceBlender below RANK optimization", SourceBlenderTestFixture) { + auto top = std::make_unique<RankBlueprint>(); + top->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}})); + f.addChildrenForSBTest(*top); + //------------------------------------------------------------------------- - auto *expect = new RankBlueprint(); - Blueprint::UP expect_up(expect); - { - auto *blender = new SourceBlenderBlueprint(*selector_1); - blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1))); - expect->addChild(ap(blender)); - } + auto expect = std::make_unique<RankBlueprint>(); + expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}})); expect->addChild(ap(MyLeafSpec(2).create())); expect->addChild(ap(MyLeafSpec(1).create())); expect->addChild(ap(MyLeafSpec(3).create())); - { - auto *blender = new SourceBlenderBlueprint(*selector_2); - blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - expect->addChild(ap(blender)); - } - { - auto *blender(new SourceBlenderBlueprint(*selector_1)); - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(3); - sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3))); - sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(2); - sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2))); - sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2))); - blender->addChild(ap(sub_and)); - } - { - auto *sub_and = new OrBlueprint(); - sub_and->setSourceId(1); - sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1))); - sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1))); - blender->addChild(ap(sub_and)); - } - expect->addChild(ap(blender)); - } + expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}})); + auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1); + blender->addChild(addLeafs(3, std::make_unique<OrBlueprint>(), {{300, 3}, {30, 3}})); + blender->addChild(addLeafs(2, std::make_unique<OrBlueprint>(), {{2000, 2}, {200, 2}, {20, 2}})); + blender->addChild(addLeafs(1, std::make_unique<OrBlueprint>(), {{1000, 1}, {100, 1}, {10, 1}})); + expect->addChild(std::move(blender)); + //------------------------------------------------------------------------- - EXPECT_NOT_EQUAL(expect_up->asString(), top_up->asString()); - top_up = Blueprint::optimize(std::move(top_up)); - EXPECT_EQUAL(expect_up->asString(), top_up->asString()); - expect_up = Blueprint::optimize(std::move(expect_up)); + EXPECT_NOT_EQUAL(expect->asString(), top->asString()); + auto top_up = Blueprint::optimize(std::move(top)); + EXPECT_EQUAL(expect->asString(), top_up->asString()); + auto expect_up = Blueprint::optimize(std::move(expect)); EXPECT_EQUAL(expect_up->asString(), top_up->asString()); } TEST("test empty root node optimization and safeness") { //------------------------------------------------------------------------- // tests leaf node elimination - Blueprint::UP top1_up(ap(MyLeafSpec(0, true).create())); + Blueprint::UP top1(ap(MyLeafSpec(0, true).create())); //------------------------------------------------------------------------- // tests intermediate node elimination - Blueprint::UP top2_up(ap((new AndBlueprint())-> - addChild(ap(MyLeafSpec(0, true).create())). - addChild(ap(MyLeafSpec(10).create())). - addChild(ap(MyLeafSpec(20).create())))); + Blueprint::UP top2(ap((new AndBlueprint())-> + addChild(ap(MyLeafSpec(0, true).create())). + addChild(ap(MyLeafSpec(10).create())). + addChild(ap(MyLeafSpec(20).create())))); //------------------------------------------------------------------------- // tests safety of empty AND_NOT child removal - Blueprint::UP top3_up(ap((new AndNotBlueprint())-> - addChild(ap(MyLeafSpec(0, true).create())). - addChild(ap(MyLeafSpec(10).create())). - addChild(ap(MyLeafSpec(20).create())))); + Blueprint::UP top3(ap((new AndNotBlueprint())-> + addChild(ap(MyLeafSpec(0, true).create())). + addChild(ap(MyLeafSpec(10).create())). + addChild(ap(MyLeafSpec(20).create())))); //------------------------------------------------------------------------- // tests safety of empty RANK child removal - Blueprint::UP top4_up(ap((new RankBlueprint())-> - addChild(ap(MyLeafSpec(0, true).create())). - addChild(ap(MyLeafSpec(10).create())). - addChild(ap(MyLeafSpec(20).create())))); + Blueprint::UP top4(ap((new RankBlueprint())-> + addChild(ap(MyLeafSpec(0, true).create())). + addChild(ap(MyLeafSpec(10).create())). + addChild(ap(MyLeafSpec(20).create())))); //------------------------------------------------------------------------- // tests safety of empty OR child removal - Blueprint::UP top5_up(ap((new OrBlueprint())-> - addChild(ap(MyLeafSpec(0, true).create())). - addChild(ap(MyLeafSpec(0, true).create())). - addChild(ap(MyLeafSpec(0, true).create())))); + Blueprint::UP top5(ap((new OrBlueprint())-> + addChild(ap(MyLeafSpec(0, true).create())). + addChild(ap(MyLeafSpec(0, true).create())). + addChild(ap(MyLeafSpec(0, true).create())))); //------------------------------------------------------------------------- auto expect_up = std::make_unique<EmptyBlueprint>(); - //------------------------------------------------------------------------- - top1_up = Blueprint::optimize(std::move(top1_up)); - top2_up = Blueprint::optimize(std::move(top2_up)); - top3_up = Blueprint::optimize(std::move(top3_up)); - top4_up = Blueprint::optimize(std::move(top4_up)); - top5_up = Blueprint::optimize(std::move(top5_up)); - EXPECT_EQUAL(expect_up->asString(), top1_up->asString()); - EXPECT_EQUAL(expect_up->asString(), top2_up->asString()); - EXPECT_EQUAL(expect_up->asString(), top3_up->asString()); - EXPECT_EQUAL(expect_up->asString(), top4_up->asString()); - EXPECT_EQUAL(expect_up->asString(), top5_up->asString()); + EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top1))->asString()); + EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top2))->asString()); + EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top3))->asString()); + EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top4))->asString()); + EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top5))->asString()); } TEST("and with one empty child is optimized away") { diff --git a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt index 1d47b4d02ff..6ec78daecd1 100644 --- a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt @@ -6,6 +6,7 @@ vespa_add_library(searchlib_attribute OBJECT attribute.cpp attribute_blueprint_factory.cpp attribute_header.cpp + attribute_object_visitor.cpp attribute_operation.cpp attribute_read_guard.cpp attribute_weighted_set_blueprint.cpp @@ -38,27 +39,27 @@ vespa_add_library(searchlib_attribute OBJECT defines.cpp dfa_fuzzy_matcher.cpp dfa_string_comparator.cpp + direct_weighted_set_blueprint.cpp distance_metric_utils.cpp diversity.cpp dociditerator.cpp document_weight_or_filter_search.cpp - searchcontextelementiterator.cpp empty_search_context.cpp + enum_store_compaction_spec.cpp + enum_store_dictionary.cpp + enum_store_loaders.cpp enumattribute.cpp enumattributesaver.cpp enumcomparator.cpp + enumerated_multi_value_read_view.cpp enumhintsearchcontext.cpp enummodifier.cpp - enum_store_compaction_spec.cpp - enum_store_dictionary.cpp - enum_store_loaders.cpp enumstore.cpp - enumerated_multi_value_read_view.cpp - extendableattributes.cpp extendable_numeric_array_multi_value_read_view.cpp extendable_numeric_weighted_set_multi_value_read_view.cpp extendable_string_array_multi_value_read_view.cpp extendable_string_weighted_set_multi_value_read_view.cpp + extendableattributes.cpp fixedsourceselector.cpp flagattribute.cpp floatbase.cpp @@ -82,8 +83,8 @@ vespa_add_library(searchlib_attribute OBJECT multi_numeric_enum_search_context.cpp multi_numeric_flag_search_context.cpp multi_numeric_search_context.cpp - multi_string_enum_search_context.cpp multi_string_enum_hint_search_context.cpp + multi_string_enum_search_context.cpp multi_value_mapping.cpp multi_value_mapping_base.cpp multienumattribute.cpp @@ -98,11 +99,11 @@ vespa_add_library(searchlib_attribute OBJECT multivalueattributesaver.cpp multivalueattributesaverutils.cpp not_implemented_attribute.cpp - numericbase.cpp numeric_matcher.cpp numeric_range_matcher.cpp numeric_search_context.cpp numeric_sort_blob_writer.cpp + numericbase.cpp posting_list_merger.cpp postingchange.cpp postinglistattribute.cpp @@ -121,6 +122,17 @@ vespa_add_library(searchlib_attribute OBJECT reference_mappings.cpp save_utils.cpp search_context.cpp + searchcontextelementiterator.cpp + single_enum_search_context.cpp + single_numeric_enum_search_context.cpp + single_numeric_search_context.cpp + single_raw_attribute.cpp + single_raw_attribute_loader.cpp + single_raw_attribute_saver.cpp + single_raw_ext_attribute.cpp + single_small_numeric_search_context.cpp + single_string_enum_hint_search_context.cpp + single_string_enum_search_context.cpp singleboolattribute.cpp singleenumattribute.cpp singleenumattributesaver.cpp @@ -131,22 +143,12 @@ vespa_add_library(searchlib_attribute OBJECT singlesmallnumericattribute.cpp singlestringattribute.cpp singlestringpostattribute.cpp - single_enum_search_context.cpp - single_numeric_enum_search_context.cpp - single_numeric_search_context.cpp - single_raw_attribute.cpp - single_raw_attribute_loader.cpp - single_raw_attribute_saver.cpp - single_raw_ext_attribute.cpp - single_small_numeric_search_context.cpp - single_string_enum_search_context.cpp - single_string_enum_hint_search_context.cpp sourceselector.cpp - stringbase.cpp string_matcher.cpp string_search_context.cpp string_search_helper.cpp string_sort_blob_writer.cpp + stringbase.cpp valuemodifier.cpp DEPENDS ) diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp index cf8cbe3177f..50c79ce4108 100644 --- a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp +++ b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp @@ -1,12 +1,14 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "attribute_blueprint_factory.h" +#include "attribute_blueprint_params.h" +#include "attribute_object_visitor.h" #include "attribute_weighted_set_blueprint.h" +#include "direct_weighted_set_blueprint.h" +#include "document_weight_or_filter_search.h" #include "i_document_weight_attribute.h" #include "iterator_pack.h" #include "predicate_attribute.h" -#include "attribute_blueprint_params.h" -#include "document_weight_or_filter_search.h" #include <vespa/eval/eval/value.h> #include <vespa/searchlib/common/location.h> #include <vespa/searchlib/common/locationiterators.h> @@ -160,7 +162,7 @@ public: void visitMembers(vespalib::ObjectVisitor &visitor) const override; - const attribute::ISearchContext *get_attribute_search_context() const override { + const attribute::ISearchContext *get_attribute_search_context() const noexcept final { return _search_context.get(); } bool getRange(vespalib::string &from, vespalib::string &to) const override; @@ -191,29 +193,7 @@ AttributeFieldBlueprint::AttributeFieldBlueprint(FieldSpecBase field, const IAtt } } -vespalib::string -get_type(const IAttributeVector& attr) -{ - auto coll_type = CollectionType(attr.getCollectionType()); - auto basic_type = BasicType(attr.getBasicType()); - if (coll_type.type() == CollectionType::SINGLE) { - return basic_type.asString(); - } - std::ostringstream oss; - oss << coll_type.asString() << "<" << basic_type.asString() << ">"; - return oss.str(); -} -void -visit_attribute(vespalib::ObjectVisitor& visitor, const IAttributeVector& attr) -{ - visitor.openStruct("attribute", "IAttributeVector"); - visitor.visitString("name", attr.getName()); - visitor.visitString("type", get_type(attr)); - visitor.visitBool("fast_search", attr.getIsFastSearch()); - visitor.visitBool("filter", attr.getIsFilter()); - visitor.closeStruct(); -} void AttributeFieldBlueprint::visitMembers(vespalib::ObjectVisitor &visitor) const @@ -408,101 +388,9 @@ private: //----------------------------------------------------------------------------- -template <typename SearchType> -class DirectWeightedSetBlueprint : public ComplexLeafBlueprint -{ -private: - std::vector<int32_t> _weights; - std::vector<IDocumentWeightAttribute::LookupResult> _terms; - const IAttributeVector &_iattr; - const IDocumentWeightAttribute &_attr; - vespalib::datastore::EntryRef _dictionary_snapshot; -public: - DirectWeightedSetBlueprint(const FieldSpec &field, const IAttributeVector &iattr, const IDocumentWeightAttribute &attr, size_t size_hint) - : ComplexLeafBlueprint(field), - _weights(), - _terms(), - _iattr(iattr), - _attr(attr), - _dictionary_snapshot(_attr.get_dictionary_snapshot()) - { - set_allow_termwise_eval(true); - _weights.reserve(size_hint); - _terms.reserve(size_hint); - } - ~DirectWeightedSetBlueprint() override; - void addTerm(const IDocumentWeightAttribute::LookupKey & key, int32_t weight, HitEstimate & estimate) { - IDocumentWeightAttribute::LookupResult result = _attr.lookup(key, _dictionary_snapshot); - HitEstimate childEst(result.posting_size, (result.posting_size == 0)); - if (!childEst.empty) { - if (estimate.empty) { - estimate = childEst; - } else { - estimate.estHits += childEst.estHits; - } - _weights.push_back(weight); - _terms.push_back(result); - } - } - void complete(HitEstimate estimate) { - setEstimate(estimate); - } - SearchIterator::UP createLeafSearch(const TermFieldMatchDataArray &tfmda, bool) const override; - - std::unique_ptr<SearchIterator> createFilterSearch(bool strict, FilterConstraint constraint) const override; - std::unique_ptr<queryeval::MatchingElementsSearch> create_matching_elements_search(const MatchingElementsFields &fields) const override { - if (fields.has_field(_iattr.getName())) { - return queryeval::MatchingElementsSearch::create(_iattr, _dictionary_snapshot, vespalib::ConstArrayRef<IDocumentWeightAttribute::LookupResult>(_terms)); - } else { - return {}; - } - } - void visitMembers(vespalib::ObjectVisitor& visitor) const override { - LeafBlueprint::visitMembers(visitor); - visit_attribute(visitor, _iattr); - } -}; - -template <typename SearchType> -SearchIterator::UP -DirectWeightedSetBlueprint<SearchType>::createLeafSearch(const TermFieldMatchDataArray &tfmda, bool) const -{ - assert(tfmda.size() == 1); - assert(getState().numFields() == 1); - if (_terms.empty()) { - return std::make_unique<queryeval::EmptySearch>(); - } - std::vector<DocumentWeightIterator> iterators; - const size_t numChildren = _terms.size(); - iterators.reserve(numChildren); - for (const IDocumentWeightAttribute::LookupResult &r : _terms) { - _attr.create(r.posting_idx, iterators); - } - bool field_is_filter = getState().fields()[0].isFilter(); - if (field_is_filter && tfmda[0]->isNotNeeded()) { - return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators)); - } - return SearchType::create(*tfmda[0], field_is_filter, _weights, std::move(iterators)); -} - - -template <typename SearchType> -DirectWeightedSetBlueprint<SearchType>::~DirectWeightedSetBlueprint() = default; - -template <typename SearchType> -std::unique_ptr<SearchIterator> -DirectWeightedSetBlueprint<SearchType>::createFilterSearch(bool, FilterConstraint) const -{ - std::vector<DocumentWeightIterator> iterators; - iterators.reserve(_terms.size()); - for (const IDocumentWeightAttribute::LookupResult &r : _terms) { - _attr.create(r.posting_idx, iterators); - } - return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators)); -} //----------------------------------------------------------------------------- @@ -798,7 +686,7 @@ public: setResult(std::move(ws)); } else { if (_dwa != nullptr) { - auto *bp = new DirectWeightedSetBlueprint<queryeval::WeightedSetTermSearch>(_field, _attr, *_dwa, n.getNumTerms()); + auto *bp = new attribute::DirectWeightedSetBlueprint<queryeval::WeightedSetTermSearch>(_field, _attr, *_dwa, n.getNumTerms()); createDirectWeightedSet(bp, n); } else { auto *bp = new WeightedSetTermBlueprint(_field); @@ -809,7 +697,7 @@ public: void visit(query::DotProduct &n) override { if (_dwa != nullptr) { - auto *bp = new DirectWeightedSetBlueprint<queryeval::DotProductSearch>(_field, _attr, *_dwa, n.getNumTerms()); + auto *bp = new attribute::DirectWeightedSetBlueprint<queryeval::DotProductSearch>(_field, _attr, *_dwa, n.getNumTerms()); createDirectWeightedSet(bp, n); } else { auto *bp = new DotProductBlueprint(_field); diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp new file mode 100644 index 00000000000..39f39212d5c --- /dev/null +++ b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp @@ -0,0 +1,38 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "attribute_object_visitor.h" +#include <vespa/searchcommon/attribute/iattributevector.h> +#include <vespa/vespalib/objects/objectvisitor.h> +#include <sstream> + +namespace search::attribute { + +namespace { + +vespalib::string +get_type(const IAttributeVector& attr) +{ + auto coll_type = CollectionType(attr.getCollectionType()); + auto basic_type = BasicType(attr.getBasicType()); + if (coll_type.type() == CollectionType::SINGLE) { + return basic_type.asString(); + } + std::ostringstream oss; + oss << coll_type.asString() << "<" << basic_type.asString() << ">"; + return oss.str(); +} + +} + +void +visit_attribute(vespalib::ObjectVisitor& visitor, const IAttributeVector& attr) +{ + visitor.openStruct("attribute", "IAttributeVector"); + visitor.visitString("name", attr.getName()); + visitor.visitString("type", get_type(attr)); + visitor.visitBool("fast_search", attr.getIsFastSearch()); + visitor.visitBool("filter", attr.getIsFilter()); + visitor.closeStruct(); +} + +} diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h new file mode 100644 index 00000000000..29c7e1556b6 --- /dev/null +++ b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h @@ -0,0 +1,16 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +namespace vespalib { class ObjectVisitor; } + +namespace search::attribute { + +class IAttributeVector; + +/** + * Function used to visit the basic properties of an IAttributeVector. + */ +void visit_attribute(vespalib::ObjectVisitor& visitor, const IAttributeVector& attr); + +} diff --git a/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp new file mode 100644 index 00000000000..01b683f3b6d --- /dev/null +++ b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp @@ -0,0 +1,14 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "direct_weighted_set_blueprint.h" +#include "direct_weighted_set_blueprint.hpp" +#include <vespa/searchlib/queryeval/dot_product_search.h> +#include <vespa/searchlib/queryeval/weighted_set_term_search.h> + +namespace search::attribute { + +template class DirectWeightedSetBlueprint<queryeval::WeightedSetTermSearch>; +template class DirectWeightedSetBlueprint<queryeval::DotProductSearch>; + +} + diff --git a/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h new file mode 100644 index 00000000000..e50c7688ac7 --- /dev/null +++ b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h @@ -0,0 +1,72 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "attribute_object_visitor.h" +#include "i_document_weight_attribute.h" +#include <vespa/searchcommon/attribute/iattributevector.h> +#include <vespa/searchlib/common/matching_elements_fields.h> +#include <vespa/searchlib/fef/termfieldmatchdataarray.h> +#include <vespa/searchlib/queryeval/blueprint.h> +#include <vespa/searchlib/queryeval/field_spec.h> +#include <vespa/searchlib/queryeval/matching_elements_search.h> + +namespace search::queryeval { class SearchIterator; } + +namespace search::attribute { + +/** + * Blueprint used for WeightedSetTerm or DotProduct over a multi-value attribute + * which supports the IDocumentWeightAttribute interface. + * + * This allows access to low-level posting lists, which speeds up query execution. + */ +template <typename SearchType> +class DirectWeightedSetBlueprint : public queryeval::ComplexLeafBlueprint +{ +private: + std::vector<int32_t> _weights; + std::vector<IDocumentWeightAttribute::LookupResult> _terms; + const IAttributeVector &_iattr; + const IDocumentWeightAttribute &_attr; + vespalib::datastore::EntryRef _dictionary_snapshot; + +public: + DirectWeightedSetBlueprint(const queryeval::FieldSpec &field, const IAttributeVector &iattr, const IDocumentWeightAttribute &attr, size_t size_hint); + ~DirectWeightedSetBlueprint() override; + + void addTerm(const IDocumentWeightAttribute::LookupKey & key, int32_t weight, HitEstimate & estimate) { + IDocumentWeightAttribute::LookupResult result = _attr.lookup(key, _dictionary_snapshot); + HitEstimate childEst(result.posting_size, (result.posting_size == 0)); + if (!childEst.empty) { + if (estimate.empty) { + estimate = childEst; + } else { + estimate.estHits += childEst.estHits; + } + _weights.push_back(weight); + _terms.push_back(result); + } + } + void complete(HitEstimate estimate) { + setEstimate(estimate); + } + + std::unique_ptr<queryeval::SearchIterator> createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool) const override; + + std::unique_ptr<queryeval::SearchIterator> createFilterSearch(bool strict, FilterConstraint constraint) const override; + std::unique_ptr<queryeval::MatchingElementsSearch> create_matching_elements_search(const MatchingElementsFields &fields) const override { + if (fields.has_field(_iattr.getName())) { + return queryeval::MatchingElementsSearch::create(_iattr, _dictionary_snapshot, vespalib::ConstArrayRef<IDocumentWeightAttribute::LookupResult>(_terms)); + } else { + return {}; + } + } + void visitMembers(vespalib::ObjectVisitor& visitor) const override { + LeafBlueprint::visitMembers(visitor); + visit_attribute(visitor, _iattr); + } +}; + +} + diff --git a/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp new file mode 100644 index 00000000000..bf6410c347c --- /dev/null +++ b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp @@ -0,0 +1,67 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "direct_weighted_set_blueprint.h" +#include "document_weight_or_filter_search.h" +#include <vespa/searchlib/fef/termfieldmatchdata.h> +#include <vespa/searchlib/queryeval/emptysearch.h> +#include <memory> + +namespace search::attribute { + +template <typename SearchType> +DirectWeightedSetBlueprint<SearchType>::DirectWeightedSetBlueprint(const queryeval::FieldSpec &field, + const IAttributeVector &iattr, + const IDocumentWeightAttribute &attr, + size_t size_hint) + : ComplexLeafBlueprint(field), + _weights(), + _terms(), + _iattr(iattr), + _attr(attr), + _dictionary_snapshot(_attr.get_dictionary_snapshot()) +{ + set_allow_termwise_eval(true); + _weights.reserve(size_hint); + _terms.reserve(size_hint); +} + +template <typename SearchType> +DirectWeightedSetBlueprint<SearchType>::~DirectWeightedSetBlueprint() = default; + +template <typename SearchType> +std::unique_ptr<queryeval::SearchIterator> +DirectWeightedSetBlueprint<SearchType>::createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool) const +{ + assert(tfmda.size() == 1); + assert(getState().numFields() == 1); + if (_terms.empty()) { + return std::make_unique<queryeval::EmptySearch>(); + } + std::vector<DocumentWeightIterator> iterators; + const size_t numChildren = _terms.size(); + iterators.reserve(numChildren); + for (const IDocumentWeightAttribute::LookupResult &r : _terms) { + _attr.create(r.posting_idx, iterators); + } + bool field_is_filter = getState().fields()[0].isFilter(); + if (field_is_filter && tfmda[0]->isNotNeeded()) { + return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators)); + } + return SearchType::create(*tfmda[0], field_is_filter, _weights, std::move(iterators)); +} + +template <typename SearchType> +std::unique_ptr<queryeval::SearchIterator> +DirectWeightedSetBlueprint<SearchType>::createFilterSearch(bool, FilterConstraint) const +{ + std::vector<DocumentWeightIterator> iterators; + iterators.reserve(_terms.size()); + for (const IDocumentWeightAttribute::LookupResult &r : _terms) { + _attr.create(r.posting_idx, iterators); + } + return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators)); +} + +} diff --git a/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp b/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp index 44413b42921..3dda6017e0a 100644 --- a/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp +++ b/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp @@ -2,6 +2,7 @@ #include "iterator_pack.h" #include <vespa/searchlib/common/bitvector.h> +#include <limits> namespace search { @@ -10,7 +11,7 @@ AttributeIteratorPack::~AttributeIteratorPack() = default; AttributeIteratorPack::AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children) : _children(std::move(children)) { - assert(_children.size() < 0x10000); + assert(_children.size() <= std::numeric_limits<ref_t>::max()); } std::unique_ptr<BitVector> diff --git a/searchlib/src/vespa/searchlib/attribute/iterator_pack.h b/searchlib/src/vespa/searchlib/attribute/iterator_pack.h index 388dce3aeb7..04df7c9b772 100644 --- a/searchlib/src/vespa/searchlib/attribute/iterator_pack.h +++ b/searchlib/src/vespa/searchlib/attribute/iterator_pack.h @@ -15,6 +15,7 @@ private: std::vector<DocumentWeightIterator> _children; public: + using ref_t = uint16_t; AttributeIteratorPack() noexcept : _children() {} AttributeIteratorPack(AttributeIteratorPack &&rhs) noexcept = default; AttributeIteratorPack &operator=(AttributeIteratorPack &&rhs) noexcept = default; @@ -22,11 +23,11 @@ public: explicit AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children); ~AttributeIteratorPack(); - uint32_t get_docid(uint16_t ref) const { + uint32_t get_docid(ref_t ref) const { return _children[ref].valid() ? _children[ref].getKey() : endDocId; } - uint32_t seek(uint16_t ref, uint32_t docid) { + uint32_t seek(ref_t ref, uint32_t docid) { _children[ref].linearSeek(docid); if (__builtin_expect(_children[ref].valid(), true)) { return _children[ref].getKey(); @@ -34,14 +35,14 @@ public: return endDocId; } - int32_t get_weight(uint16_t ref, uint32_t) { + int32_t get_weight(ref_t ref, uint32_t) { return _children[ref].getData(); } std::unique_ptr<BitVector> get_hits(uint32_t begin_id, uint32_t end_id); void or_hits_into(BitVector &result, uint32_t begin_id); - uint16_t size() const noexcept { return _children.size(); } + ref_t size() const noexcept { return _children.size(); } void initRange(uint32_t begin, uint32_t end) { (void) end; for (auto &child: _children) { @@ -49,7 +50,7 @@ public: } } private: - uint32_t next(uint16_t ref) { + uint32_t next(ref_t ref) { ++_children[ref]; return get_docid(ref); } diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h index 09388d6d44c..2d0b8cbd733 100644 --- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h +++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h @@ -61,13 +61,13 @@ private: using DocIndices = typename MultiValueNumericEnumAttribute<B, M>::DocIndices; using FrozenDictionary = typename Dictionary::FrozenView; using Posting = typename PostingParent::Posting; - using PostingList = typename PostingParent::PostingList; + using PostingStore = typename PostingParent::PostingStore; using PostingMap = typename PostingParent::PostingMap; using QueryTermSimpleUP = AttributeVector::QueryTermSimpleUP; using WeightedIndex = typename MultiValueNumericEnumAttribute<B, M>::WeightedIndex; using generation_t = typename MultiValueNumericEnumAttribute<B, M>::generation_t; - using PostingParent::_postingList; + using PostingParent::_posting_store; using PostingParent::clearAllPostings; using PostingParent::handle_load_posting_lists; using PostingParent::handle_load_posting_lists_and_update_enum_store; diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp index 38e464f207a..53c183a6987 100644 --- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp +++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp @@ -21,7 +21,7 @@ void MultiValueNumericPostingAttribute<B, M>::mergeMemoryStats(vespalib::MemoryUsage & total) { auto& compaction_strategy = this->getConfig().getCompactionStrategy(); - total.merge(this->getPostingList().update_stat(compaction_strategy)); + total.merge(this->get_posting_store().update_stat(compaction_strategy)); } template <typename B, typename M> @@ -60,16 +60,16 @@ void MultiValueNumericPostingAttribute<B, M>::reclaim_memory(generation_t oldest_used_gen) { MultiValueNumericEnumAttribute<B, M>::reclaim_memory(oldest_used_gen); - _postingList.reclaim_memory(oldest_used_gen); + _posting_store.reclaim_memory(oldest_used_gen); } template <typename B, typename M> void MultiValueNumericPostingAttribute<B, M>::before_inc_generation(generation_t current_gen) { - _postingList.freeze(); + _posting_store.freeze(); MultiValueNumericEnumAttribute<B, M>::before_inc_generation(current_gen); - _postingList.assign_generation(current_gen); + _posting_store.assign_generation(current_gen); } template <typename B, typename M> @@ -106,9 +106,9 @@ MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::lookup( if (find_result.first.valid()) { auto pidx = find_result.second; if (pidx.valid()) { - const PostingList &plist = self.getPostingList(); - auto minmax = plist.getAggregated(pidx); - return LookupResult(pidx, plist.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first); + const auto& store = self.get_posting_store(); + auto minmax = store.getAggregated(pidx); + return LookupResult(pidx, store.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first); } } return LookupResult(); @@ -127,7 +127,7 @@ void MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx, std::vector<DocumentWeightIterator> &dst) const { assert(idx.valid()); - self.getPostingList().beginFrozen(idx, dst); + self.get_posting_store().beginFrozen(idx, dst); } template <typename B, typename M> @@ -135,21 +135,21 @@ DocumentWeightIterator MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx) const { assert(idx.valid()); - return self.getPostingList().beginFrozen(idx); + return self.get_posting_store().beginFrozen(idx); } template <typename B, typename M> std::unique_ptr<queryeval::SearchIterator> MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::make_bitvector_iterator(vespalib::datastore::EntryRef idx, uint32_t doc_id_limit, fef::TermFieldMatchData &match_data, bool strict) const { - return self.getPostingList().make_bitvector_iterator(idx, doc_id_limit, match_data, strict); + return self.get_posting_store().make_bitvector_iterator(idx, doc_id_limit, match_data, strict); } template <typename B, typename M> bool MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::has_weight_iterator(vespalib::datastore::EntryRef idx) const noexcept { - return self.getPostingList().has_btree(idx); + return self.get_posting_store().has_btree(idx); } template <typename B, typename M> diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h index 9ecfa93e5ec..67f4f25ac5b 100644 --- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h +++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h @@ -59,7 +59,7 @@ private: using WeightedIndex = typename MultiValueStringAttributeT<B, T>::WeightedIndex; using generation_t = typename MultiValueStringAttributeT<B, T>::generation_t; - using PostingParent::_postingList; + using PostingParent::_posting_store; using PostingParent::clearAllPostings; using PostingParent::handle_load_posting_lists; using PostingParent::handle_load_posting_lists_and_update_enum_store; @@ -70,9 +70,9 @@ private: void applyValueChanges(const DocIndices& docIndices, EnumStoreBatchUpdater& updater) override ; public: - using PostingParent::getPostingList; + using PostingParent::get_posting_store; using Dictionary = EnumPostingTree; - using PostingList = typename PostingParent::PostingList; + using PostingStore = typename PostingParent::PostingStore; MultiValueStringPostingAttributeT(const vespalib::string & name, const AttributeVector::Config & c); MultiValueStringPostingAttributeT(const vespalib::string & name); diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp index 7c162d32c1f..5d4f140b96c 100644 --- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp +++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp @@ -69,7 +69,7 @@ void MultiValueStringPostingAttributeT<B, T>::mergeMemoryStats(vespalib::MemoryUsage &total) { auto& compaction_strategy = this->getConfig().getCompactionStrategy(); - total.merge(this->_postingList.update_stat(compaction_strategy)); + total.merge(this->_posting_store.update_stat(compaction_strategy)); } template <typename B, typename T> @@ -77,16 +77,16 @@ void MultiValueStringPostingAttributeT<B, T>::reclaim_memory(generation_t oldest_used_gen) { MultiValueStringAttributeT<B, T>::reclaim_memory(oldest_used_gen); - _postingList.reclaim_memory(oldest_used_gen); + _posting_store.reclaim_memory(oldest_used_gen); } template <typename B, typename T> void MultiValueStringPostingAttributeT<B, T>::before_inc_generation(generation_t current_gen) { - _postingList.freeze(); + _posting_store.freeze(); MultiValueStringAttributeT<B, T>::before_inc_generation(current_gen); - _postingList.assign_generation(current_gen); + _posting_store.assign_generation(current_gen); } @@ -126,9 +126,9 @@ MultiValueStringPostingAttributeT<B, T>::DocumentWeightAttributeAdapter::lookup( if (find_result.first.valid()) { auto pidx = find_result.second; if (pidx.valid()) { - const PostingList &plist = self.getPostingList(); - auto minmax = plist.getAggregated(pidx); - return LookupResult(pidx, plist.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first); + const auto& store = self.get_posting_store(); + auto minmax = store.getAggregated(pidx); + return LookupResult(pidx, store.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first); } } return LookupResult(); @@ -147,7 +147,7 @@ void MultiValueStringPostingAttributeT<B, T>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx, std::vector<DocumentWeightIterator> &dst) const { assert(idx.valid()); - self.getPostingList().beginFrozen(idx, dst); + self.get_posting_store().beginFrozen(idx, dst); } template <typename B, typename M> @@ -155,21 +155,21 @@ DocumentWeightIterator MultiValueStringPostingAttributeT<B, M>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx) const { assert(idx.valid()); - return self.getPostingList().beginFrozen(idx); + return self.get_posting_store().beginFrozen(idx); } template <typename B, typename M> bool MultiValueStringPostingAttributeT<B, M>::DocumentWeightAttributeAdapter::has_weight_iterator(vespalib::datastore::EntryRef idx) const noexcept { - return self.getPostingList().has_btree(idx); + return self.get_posting_store().has_btree(idx); } template <typename B, typename M> std::unique_ptr<queryeval::SearchIterator> MultiValueStringPostingAttributeT<B, M>::DocumentWeightAttributeAdapter::make_bitvector_iterator(vespalib::datastore::EntryRef idx, uint32_t doc_id_limit, fef::TermFieldMatchData &match_data, bool strict) const { - return self.getPostingList().make_bitvector_iterator(idx, doc_id_limit, match_data, strict); + return self.get_posting_store().make_bitvector_iterator(idx, doc_id_limit, match_data, strict); } template <typename B, typename T> diff --git a/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h b/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h index 8f350f34c35..bc792b68a88 100644 --- a/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h +++ b/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h @@ -5,18 +5,17 @@ namespace search::attribute { /* - * Class used to traverse a posting list and call the functor for each - * lid. + * Class used to traverse a posting list and call the functor for each lid. */ -template <typename PostingList> +template <typename PostingStore> class PostingListTraverser { using EntryRef = vespalib::datastore::EntryRef; - const PostingList &_postingList; + const PostingStore &_posting_store; EntryRef _pidx; public: - PostingListTraverser(const PostingList &postingList, EntryRef pidx) - : _postingList(postingList), + PostingListTraverser(const PostingStore &posting_store, EntryRef pidx) + : _posting_store(posting_store), _pidx(pidx) { } @@ -25,13 +24,13 @@ public: template <typename Func> void foreach(Func func) const { - _postingList.foreach_frozen(_pidx, func); + _posting_store.foreach_frozen(_pidx, func); } template <typename Func> void foreach_key(Func func) const { - _postingList.foreach_frozen_key(_pidx, func); + _posting_store.foreach_frozen_key(_pidx, func); } }; diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp index 4e88fb96c7e..d3f9c3f5d82 100644 --- a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp +++ b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp @@ -15,8 +15,8 @@ PostingListAttributeBase<P>:: PostingListAttributeBase(AttributeVector &attr, IEnumStore &enumStore) : attribute::IPostingListAttributeBase(), - _postingList(enumStore.get_dictionary(), attr.getStatus(), - attr.getConfig()), + _posting_store(enumStore.get_dictionary(), attr.getStatus(), + attr.getConfig()), _attr(attr), _dictionary(enumStore.get_dictionary()) { } @@ -28,11 +28,11 @@ template <typename P> void PostingListAttributeBase<P>::clearAllPostings() { - _postingList.clearBuilder(); + _posting_store.clearBuilder(); _attr.incGeneration(); // Force freeze auto clearer = [this](EntryRef posting_idx) { - _postingList.clear(posting_idx); + _posting_store.clear(posting_idx); }; _dictionary.clear_all_posting_lists(clearer); _attr.incGeneration(); // Force freeze @@ -69,7 +69,7 @@ PostingListAttributeBase<P>::handle_load_posting_lists_and_update_enum_store(enu if (loader.is_folded_change(enum_indexes[posting_enum], enum_indexes[preve])) { postings.removeDups(); newIndex = EntryRef(); - _postingList.apply(newIndex, + _posting_store.apply(newIndex, postings._additions.data(), postings._additions.data() + postings._additions.size(), @@ -91,7 +91,7 @@ PostingListAttributeBase<P>::handle_load_posting_lists_and_update_enum_store(enu loader.set_ref_count(enum_indexes[preve], refCount); postings.removeDups(); newIndex = EntryRef(); - _postingList.apply(newIndex, + _posting_store.apply(newIndex, postings._additions.data(), postings._additions.data() + postings._additions.size(), postings._removals.data(), @@ -112,7 +112,7 @@ PostingListAttributeBase<P>::updatePostings(PostingMap &changePost, change.removeDups(); auto updater= [this, &change](EntryRef posting_idx) -> EntryRef { - _postingList.apply(posting_idx, + _posting_store.apply(posting_idx, change._additions.data(), change._additions.data() + change._additions.size(), change._removals.data(), @@ -135,7 +135,7 @@ PostingListAttributeBase<P>::forwardedOnAddDoc(DocId doc, if (doc >= wantCapacity) { wantCapacity = doc + 1; } - return _postingList.resizeBitVectors(wantSize, wantCapacity); + return _posting_store.resizeBitVectors(wantSize, wantCapacity); } template <typename P> @@ -155,7 +155,7 @@ clearPostings(attribute::IAttributeVector::EnumHandle eidx, EntryRef er(eidx); auto updater = [this, &postings](EntryRef posting_idx) -> EntryRef { - _postingList.apply(posting_idx, + _posting_store.apply(posting_idx, postings._additions.data(), postings._additions.data() + postings._additions.size(), postings._removals.data(), @@ -169,28 +169,28 @@ template <typename P> void PostingListAttributeBase<P>::forwardedShrinkLidSpace(uint32_t newSize) { - (void) _postingList.resizeBitVectors(newSize, newSize); + (void) _posting_store.resizeBitVectors(newSize, newSize); } template <typename P> attribute::PostingStoreMemoryUsage PostingListAttributeBase<P>::getMemoryUsage() const { - return _postingList.getMemoryUsage(); + return _posting_store.getMemoryUsage(); } template <typename P> bool PostingListAttributeBase<P>::consider_compact_worst_btree_nodes(const CompactionStrategy& compaction_strategy) { - return _postingList.consider_compact_worst_btree_nodes(compaction_strategy); + return _posting_store.consider_compact_worst_btree_nodes(compaction_strategy); } template <typename P> bool PostingListAttributeBase<P>::consider_compact_worst_buffers(const CompactionStrategy& compaction_strategy) { - return _postingList.consider_compact_worst_buffers(compaction_strategy); + return _posting_store.consider_compact_worst_buffers(compaction_strategy); } template <typename P, typename LoadedVector, typename LoadedValueType, @@ -219,7 +219,7 @@ handle_load_posting_lists(LoadedVector& loaded) EntryRef newIndex; PostingChange<P> postings; uint32_t docIdLimit = _attr.getNumDocs(); - _postingList.resizeBitVectors(docIdLimit, docIdLimit); + _posting_store.resizeBitVectors(docIdLimit, docIdLimit); if ( ! loaded.empty() ) { vespalib::Array<typename LoadedVector::Type> similarValues; auto value = loaded.read(); @@ -237,7 +237,7 @@ handle_load_posting_lists(LoadedVector& loaded) } else { postings.removeDups(); newIndex = EntryRef(); - _postingList.apply(newIndex, + _posting_store.apply(newIndex, postings._additions.data(), postings._additions.data() + postings._additions.size(), @@ -259,7 +259,7 @@ handle_load_posting_lists(LoadedVector& loaded) } postings.removeDups(); newIndex = EntryRef(); - _postingList.apply(newIndex, + _posting_store.apply(newIndex, postings._additions.data(), postings._additions.data() + postings._additions.size(), diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h index 3987d661d26..e2be0d69434 100644 --- a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h +++ b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h @@ -43,10 +43,10 @@ protected: using DocId = AttributeVector::DocId; using EntryRef = vespalib::datastore::EntryRef; using EnumIndex = IEnumStore::Index; - using PostingList = typename AggregationTraits::PostingList; + using PostingStore = typename AggregationTraits::PostingStoreType; using PostingMap = std::map<EnumPostingPair, PostingChange<P> >; - PostingList _postingList; + PostingStore _posting_store; AttributeVector &_attr; IEnumStoreDictionary& _dictionary; @@ -57,8 +57,8 @@ protected: void updatePostings(PostingMap &changePost, const vespalib::datastore::EntryComparator &cmp); void clearAllPostings(); - void disableFreeLists() { _postingList.disableFreeLists(); } - void disable_entry_hold_list() { _postingList.disable_entry_hold_list(); } + void disableFreeLists() { _posting_store.disableFreeLists(); } + void disable_entry_hold_list() { _posting_store.disable_entry_hold_list(); } void handle_load_posting_lists_and_update_enum_store(enumstore::EnumeratedPostingsLoader& loader); bool forwardedOnAddDoc(DocId doc, size_t wantSize, size_t wantCapacity); @@ -71,8 +71,8 @@ protected: bool consider_compact_worst_buffers(const CompactionStrategy& compaction_strategy) override; public: - const PostingList & getPostingList() const { return _postingList; } - PostingList & getPostingList() { return _postingList; } + const PostingStore & get_posting_store() const { return _posting_store; } + PostingStore & get_posting_store() { return _posting_store; } }; template <typename P, typename LoadedVector, typename LoadedValueType, @@ -86,14 +86,14 @@ public: using EnumIndex = IEnumStore::Index; using EnumStore = EnumStoreType; using ComparatorType = typename EnumStore::ComparatorType; - using PostingList = typename Parent::PostingList; + using PostingStore = typename Parent::PostingStore; using PostingMap = typename Parent::PostingMap; using Parent::clearAllPostings; using Parent::updatePostings; using Parent::handle_load_posting_lists_and_update_enum_store; using Parent::clearPostings; - using Parent::_postingList; + using Parent::_posting_store; using Parent::_attr; using Parent::_dictionary; diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h index f45f8f2245e..f5683546eea 100644 --- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h +++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h @@ -34,6 +34,7 @@ protected: using FrozenDictionary = Dictionary::FrozenView; using EntryRef = vespalib::datastore::EntryRef; using EnumIndex = IEnumStore::Index; + static constexpr uint32_t max_posting_lists_to_count = 1000; const IEnumStoreDictionary& _dictionary; const ISearchContext& _baseSearchCtx; @@ -82,20 +83,20 @@ class PostingListSearchContextT : public PostingListSearchContext protected: using DataType = DataT; using Traits = PostingListTraits<DataType>; - using PostingList = typename Traits::PostingList; + using PostingStore = typename Traits::PostingStoreType; using Posting = typename Traits::Posting; using AtomicEntryRef = vespalib::datastore::AtomicEntryRef; using EntryRef = vespalib::datastore::EntryRef; - using FrozenView = typename PostingList::BTreeType::FrozenView; + using FrozenView = typename PostingStore::BTreeType::FrozenView; - const PostingList &_postingList; + const PostingStore& _posting_store; /* * Synthetic posting lists for range search, in array or bitvector form */ PostingListMerger<DataT> _merger; PostingListSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues, - bool hasWeight, const PostingList &postingList, + bool hasWeight, const PostingStore& posting_store, bool useBitVector, const ISearchContext &baseSearchCtx); ~PostingListSearchContextT() override; @@ -113,7 +114,7 @@ protected: unsigned int singleHits() const; unsigned int approximateHits() const override; - void applyRangeLimit(int rangeLimit); + void applyRangeLimit(long rangeLimit); }; @@ -128,11 +129,11 @@ protected: using Dictionary = typename Parent::Dictionary; using DictionaryConstIterator = Dictionary::ConstIterator; using EntryRef = vespalib::datastore::EntryRef; - using PostingList = typename Parent::PostingList; + using PostingStore = typename Parent::PostingStore; using Parent::_docIdLimit; using Parent::_lowerDictItr; using Parent::_merger; - using Parent::_postingList; + using Parent::_posting_store; using Parent::_uniqueValues; using Parent::_upperDictItr; using Parent::singleHits; @@ -142,7 +143,7 @@ protected: mutable std::vector<EntryRef> _posting_indexes; PostingListFoldedSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues, - bool hasWeight, const PostingList &postingList, + bool hasWeight, const PostingStore& posting_store, bool useBitVector, const ISearchContext &baseSearchCtx); ~PostingListFoldedSearchContextT() override; @@ -242,7 +243,7 @@ PostingSearchContext(BaseSC&& base_sc, bool useBitVector, const AttrT &toBeSearc toBeSearched.getCommittedDocIdLimit(), toBeSearched.getStatus().getNumValues(), toBeSearched.hasWeightedSetType(), - toBeSearched.getPostingList(), + toBeSearched.get_posting_store(), useBitVector, *this), _toBeSearched(toBeSearched), @@ -451,14 +452,14 @@ NumericPostingSearchContext<BaseSC, AttrT, DataT>::calc_estimated_hits_in_range( { size_t exact_sum = 0; size_t estimated_sum = 0; - constexpr uint32_t max_posting_lists_to_count = 1000; + auto it = this->_lowerDictItr; - for (uint32_t count = 0; (it != this->_upperDictItr) && (count < max_posting_lists_to_count); ++it, ++count) { - exact_sum += this->_postingList.frozenSize(it.getData().load_acquire()); + for (uint32_t count = 0; (it != this->_upperDictItr) && (count < this->max_posting_lists_to_count); ++it, ++count) { + exact_sum += this->_posting_store.frozenSize(it.getData().load_acquire()); } if (it != this->_upperDictItr) { uint32_t remaining_posting_lists = this->_upperDictItr - it; - float hits_per_posting_list = static_cast<float>(exact_sum) / static_cast<float>(max_posting_lists_to_count); + float hits_per_posting_list = static_cast<float>(exact_sum) / static_cast<float>(this->max_posting_lists_to_count); estimated_sum = remaining_posting_lists * hits_per_posting_list; } return exact_sum + estimated_sum; diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp index cc12b1f7825..27ef06565a6 100644 --- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp +++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp @@ -21,9 +21,9 @@ namespace search::attribute { template <typename DataT> PostingListSearchContextT<DataT>:: PostingListSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues, bool hasWeight, - const PostingList &postingList, bool useBitVector, const ISearchContext &searchContext) + const PostingStore& posting_store, bool useBitVector, const ISearchContext &searchContext) : PostingListSearchContext(dictionary, dictionary.get_has_btree_dictionary(), docIdLimit, numValues, hasWeight, useBitVector, searchContext), - _postingList(postingList), + _posting_store(posting_store), _merger(docIdLimit) { } @@ -39,16 +39,16 @@ PostingListSearchContextT<DataT>::lookupSingle() PostingListSearchContext::lookupSingle(); if (!_pidx.valid()) return; - uint32_t typeId = _postingList.getTypeId(_pidx); - if (!_postingList.isSmallArray(typeId)) { - if (_postingList.isBitVector(typeId)) { - const BitVectorEntry *bve = _postingList.getBitVectorEntry(_pidx); + uint32_t typeId = _posting_store.getTypeId(_pidx); + if (!_posting_store.isSmallArray(typeId)) { + if (_posting_store.isBitVector(typeId)) { + const BitVectorEntry *bve = _posting_store.getBitVectorEntry(_pidx); const GrowableBitVector *bv = bve->_bv.get(); _bv = &bv->reader(); _pidx = bve->_tree; } if (_pidx.valid()) { - auto frozenView = _postingList.getTreeEntry(_pidx)->getFrozenView(_postingList.getAllocator()); + auto frozenView = _posting_store.getTreeEntry(_pidx)->getFrozenView(_posting_store.getAllocator()); _frozenRoot = frozenView.getRoot(); if (!_frozenRoot.valid()) { _pidx = vespalib::datastore::EntryRef(); @@ -62,7 +62,7 @@ void PostingListSearchContextT<DataT>::fillArray() { for (auto it(_lowerDictItr); it != _upperDictItr; ++it) { - _merger.addToArray(PostingListTraverser<PostingList>(_postingList, + _merger.addToArray(PostingListTraverser<PostingStore>(_posting_store, it.getData().load_acquire())); } _merger.merge(); @@ -73,8 +73,8 @@ void PostingListSearchContextT<DataT>::fillBitVector() { for (auto it(_lowerDictItr); it != _upperDictItr; ++it) { - _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList, - it.getData().load_acquire())); + _merger.addToBitVector(PostingListTraverser<PostingStore>(_posting_store, + it.getData().load_acquire())); } } @@ -134,10 +134,10 @@ PostingListSearchContextT<DataT>::diversify(bool forward, size_t wanted_hits, co if (!_merger.merge_done()) { _merger.reserveArray(128, wanted_hits); if (_uniqueValues == 1u && !_lowerDictItr.valid() && _pidx.valid()) { - diversity::diversify_single(_pidx, _postingList, wanted_hits, diversity_attr, + diversity::diversify_single(_pidx, _posting_store, wanted_hits, diversity_attr, max_per_group, cutoff_groups, cutoff_strict, _merger.getWritableArray(), _merger.getWritableStartPos()); } else { - diversity::diversify(forward, _lowerDictItr, _upperDictItr, _postingList, wanted_hits, diversity_attr, + diversity::diversify(forward, _lowerDictItr, _upperDictItr, _posting_store, wanted_hits, diversity_attr, max_per_group, cutoff_groups, cutoff_strict, _merger.getWritableArray(), _merger.getWritableStartPos()); } _merger.merge(); @@ -160,7 +160,7 @@ createPostingIterator(fef::TermFieldMatchData *matchData, bool strict) DocIt postings; vespalib::ConstArrayRef<Posting> array = _merger.getArray(); postings.set(&array[0], &array[array.size()]); - if (_postingList.isFilter()) { + if (_posting_store.isFilter()) { return std::make_unique<FilterAttributePostingListIteratorT<DocIt>>(_baseSearchCtx, matchData, postings); } else { return std::make_unique<AttributePostingListIteratorT<DocIt>>(_baseSearchCtx, _hasWeight, matchData, postings); @@ -180,24 +180,23 @@ createPostingIterator(fef::TermFieldMatchData *matchData, bool strict) if (!_pidx.valid()) { return std::make_unique<EmptySearch>(); } - const PostingList &postingList = _postingList; if (!_frozenRoot.valid()) { - uint32_t clusterSize = _postingList.getClusterSize(_pidx); + uint32_t clusterSize = _posting_store.getClusterSize(_pidx); assert(clusterSize != 0); using DocIt = DocIdMinMaxIterator<Posting>; DocIt postings; - const Posting *array = postingList.getKeyDataEntry(_pidx, clusterSize); + const Posting *array = _posting_store.getKeyDataEntry(_pidx, clusterSize); postings.set(array, array + clusterSize); - if (postingList.isFilter()) { + if (_posting_store.isFilter()) { return std::make_unique<FilterAttributePostingListIteratorT<DocIt>>(_baseSearchCtx, matchData, postings); } else { return std::make_unique<AttributePostingListIteratorT<DocIt>>(_baseSearchCtx, _hasWeight, matchData, postings); } } - typename PostingList::BTreeType::FrozenView frozen(_frozenRoot, postingList.getAllocator()); + typename PostingStore::BTreeType::FrozenView frozen(_frozenRoot, _posting_store.getAllocator()); - using DocIt = typename PostingList::ConstIterator; - if (_postingList.isFilter()) { + using DocIt = typename PostingStore::ConstIterator; + if (_posting_store.isFilter()) { return std::make_unique<FilterAttributePostingListIteratorT<DocIt>>(_baseSearchCtx, matchData, frozen.getRoot(), frozen.getAllocator()); } else { return std::make_unique<AttributePostingListIteratorT<DocIt>> (_baseSearchCtx, _hasWeight, matchData, frozen.getRoot(), frozen.getAllocator()); @@ -220,9 +219,9 @@ PostingListSearchContextT<DataT>::singleHits() const return 0u; } if (!_frozenRoot.valid()) { - return _postingList.getClusterSize(_pidx); + return _posting_store.getClusterSize(_pidx); } - typename PostingList::BTreeType::FrozenView frozenView(_frozenRoot, _postingList.getAllocator()); + typename PostingStore::BTreeType::FrozenView frozenView(_frozenRoot, _posting_store.getAllocator()); return frozenView.size(); } @@ -244,34 +243,55 @@ PostingListSearchContextT<DataT>::approximateHits() const template <typename DataT> void -PostingListSearchContextT<DataT>::applyRangeLimit(int rangeLimit) +PostingListSearchContextT<DataT>::applyRangeLimit(long rangeLimit) { + long n = 0; + size_t count = 0; if (rangeLimit > 0) { DictionaryConstIterator middle = _lowerDictItr; - for (int n(0); (n < rangeLimit) && (middle != _upperDictItr); ++middle) { - n += _postingList.frozenSize(middle.getData().load_acquire()); + for (; (n < rangeLimit) && (count < max_posting_lists_to_count) && (middle != _upperDictItr); ++middle, count++) { + n += _posting_store.frozenSize(middle.getData().load_acquire()); + } + if (middle == _upperDictItr) { + // All there is + } else if (n >= rangeLimit) { + _upperDictItr = middle; + } else { + size_t offset = ((rangeLimit - n) * count)/n; + middle += offset; + if (middle.valid() && ((_upperDictItr - middle) > 0)) { + _upperDictItr = middle; + } } - _upperDictItr = middle; - _uniqueValues = _upperDictItr - _lowerDictItr; } else if ((rangeLimit < 0) && (_lowerDictItr != _upperDictItr)) { rangeLimit = -rangeLimit; DictionaryConstIterator middle = _upperDictItr; - for (int n(0); (n < rangeLimit) && (middle != _lowerDictItr); ) { + for (; (n < rangeLimit) && (count < max_posting_lists_to_count) && (middle != _lowerDictItr); count++) { --middle; - n += _postingList.frozenSize(middle.getData().load_acquire()); + n += _posting_store.frozenSize(middle.getData().load_acquire()); + } + if (middle == _lowerDictItr) { + // All there is + } else if (n >= rangeLimit) { + _lowerDictItr = middle; + } else { + size_t offset = ((rangeLimit - n) * count)/n; + middle -= offset; + if (middle.valid() && ((middle - _lowerDictItr) > 0)) { + _lowerDictItr = middle; + } } - _lowerDictItr = middle; - _uniqueValues = _upperDictItr - _lowerDictItr; } + _uniqueValues = std::abs(_upperDictItr - _lowerDictItr); } template <typename DataT> PostingListFoldedSearchContextT<DataT>:: PostingListFoldedSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues, - bool hasWeight, const PostingList &postingList, + bool hasWeight, const PostingStore& posting_store, bool useBitVector, const ISearchContext &searchContext) - : Parent(dictionary, docIdLimit, numValues, hasWeight, postingList, useBitVector, searchContext), + : Parent(dictionary, docIdLimit, numValues, hasWeight, posting_store, useBitVector, searchContext), _resume_scan_itr(), _posting_indexes() { @@ -290,7 +310,7 @@ PostingListFoldedSearchContextT<DataT>::calc_estimated_hits_in_range() const if (use_dictionary_entry(it)) { auto pidx = it.getData().load_acquire(); if (pidx.valid()) { - sum += _postingList.frozenSize(pidx); + sum += _posting_store.frozenSize(pidx); if (!overflow) { if (_posting_indexes.size() < MAX_POSTING_INDEXES_SIZE) { _posting_indexes.emplace_back(pidx); @@ -312,9 +332,9 @@ void PostingListFoldedSearchContextT<DataT>::fill_array_or_bitvector_helper(EntryRef pidx) { if constexpr (fill_array) { - _merger.addToArray(PostingListTraverser<PostingList>(_postingList, pidx)); + _merger.addToArray(PostingListTraverser<PostingStore>(_posting_store, pidx)); } else { - _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList, pidx)); + _merger.addToBitVector(PostingListTraverser<PostingStore>(_posting_store, pidx)); } } diff --git a/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h b/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h index 928ecc7aaf1..9a1432a6d77 100644 --- a/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h +++ b/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h @@ -19,7 +19,7 @@ public: using AggrCalcType = vespalib::btree::NoAggrCalc; using const_iterator = vespalib::btree::BTreeConstIterator<uint32_t, vespalib::btree::BTreeNoLeafData, AggregatedType, std::less<uint32_t>, BTreeTraits >; using PostingStoreBase = vespalib::btree::BTreeStore<uint32_t, vespalib::btree::BTreeNoLeafData, AggregatedType, std::less<uint32_t>, BTreeTraits, AggrCalcType> ; - using PostingList = PostingStore<vespalib::btree::BTreeNoLeafData>; + using PostingStoreType = PostingStore<vespalib::btree::BTreeNoLeafData>; using Posting = PostingStoreBase::KeyDataType; }; @@ -33,7 +33,7 @@ public: using AggrCalcType = vespalib::btree::MinMaxAggrCalc; using const_iterator = vespalib::btree::BTreeConstIterator<uint32_t, int32_t, AggregatedType, std::less<uint32_t>, BTreeTraits >; using PostingStoreBase = vespalib::btree::BTreeStore<uint32_t, int32_t, AggregatedType, std::less<uint32_t>, BTreeTraits, AggrCalcType>; - using PostingList = PostingStore<int32_t>; + using PostingStoreType = PostingStore<int32_t>; using Posting = PostingStoreBase::KeyDataType; }; diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h index 8a187014add..fd055206a86 100644 --- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h +++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h @@ -49,7 +49,7 @@ private: using ValueModifier = typename B::BaseClass::ValueModifier; using generation_t = typename SingleValueNumericEnumAttribute<B>::generation_t; - using PostingParent::_postingList; + using PostingParent::_posting_store; using PostingParent::clearAllPostings; using PostingParent::handle_load_posting_lists; using PostingParent::handle_load_posting_lists_and_update_enum_store; diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp index 1aee447760d..6e9c6a73337 100644 --- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp +++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp @@ -37,7 +37,7 @@ void SingleValueNumericPostingAttribute<B>::mergeMemoryStats(vespalib::MemoryUsage & total) { auto& compaction_strategy = this->getConfig().getCompactionStrategy(); - total.merge(this->_postingList.update_stat(compaction_strategy)); + total.merge(this->_posting_store.update_stat(compaction_strategy)); } template <typename B> @@ -124,16 +124,16 @@ void SingleValueNumericPostingAttribute<B>::reclaim_memory(generation_t oldest_used_gen) { SingleValueNumericEnumAttribute<B>::reclaim_memory(oldest_used_gen); - _postingList.reclaim_memory(oldest_used_gen); + _posting_store.reclaim_memory(oldest_used_gen); } template <typename B> void SingleValueNumericPostingAttribute<B>::before_inc_generation(generation_t current_gen) { - _postingList.freeze(); + _posting_store.freeze(); SingleValueNumericEnumAttribute<B>::before_inc_generation(current_gen); - _postingList.assign_generation(current_gen); + _posting_store.assign_generation(current_gen); } template <typename B> diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h index 8a204a2d46b..543cfdd90ec 100644 --- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h +++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h @@ -43,15 +43,15 @@ private: using ValueModifier = typename SingleValueStringAttributeT<B>::ValueModifier; using generation_t = typename SingleValueStringAttributeT<B>::generation_t; - using PostingParent::_postingList; + using PostingParent::_posting_store; using PostingParent::clearAllPostings; using PostingParent::handle_load_posting_lists; using PostingParent::handle_load_posting_lists_and_update_enum_store; using PostingParent::forwardedOnAddDoc; public: - using PostingList = typename PostingParent::PostingList; + using PostingStore = typename PostingParent::PostingStore; using Dictionary = EnumPostingTree; - using PostingParent::getPostingList; + using PostingParent::get_posting_store; private: void freezeEnumDictionary() override; diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp index 72eae570efc..85b0c095d76 100644 --- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp +++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp @@ -43,7 +43,7 @@ void SingleValueStringPostingAttributeT<B>::mergeMemoryStats(vespalib::MemoryUsage & total) { auto& compaction_strategy = this->getConfig().getCompactionStrategy(); - total.merge(this->_postingList.update_stat(compaction_strategy)); + total.merge(this->_posting_store.update_stat(compaction_strategy)); } template <typename B> @@ -125,16 +125,16 @@ void SingleValueStringPostingAttributeT<B>::reclaim_memory(generation_t oldest_used_gen) { SingleValueStringAttributeT<B>::reclaim_memory(oldest_used_gen); - _postingList.reclaim_memory(oldest_used_gen); + _posting_store.reclaim_memory(oldest_used_gen); } template <typename B> void SingleValueStringPostingAttributeT<B>::before_inc_generation(generation_t current_gen) { - _postingList.freeze(); + _posting_store.freeze(); SingleValueStringAttributeT<B>::before_inc_generation(current_gen); - _postingList.assign_generation(current_gen); + _posting_store.assign_generation(current_gen); } template <typename B> diff --git a/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt b/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt index a4ec4666f47..baec790101d 100644 --- a/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt @@ -2,6 +2,7 @@ vespa_add_library(searchlib_query_tree OBJECT SOURCES const_bool_nodes.cpp + integer_term_vector.cpp intermediate.cpp intermediatenodes.cpp location.cpp @@ -10,6 +11,7 @@ vespa_add_library(searchlib_query_tree OBJECT simplequery.cpp stackdumpcreator.cpp stackdumpquerycreator.cpp + string_term_vector.cpp term.cpp termnodes.cpp DEPENDS diff --git a/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp new file mode 100644 index 00000000000..904f1945849 --- /dev/null +++ b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp @@ -0,0 +1,65 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "integer_term_vector.h" +#include <cassert> +#include <charconv> + +namespace search::query { + +IntegerTermVector::IntegerTermVector(uint32_t sz) + : _terms() +{ + _terms.reserve(sz); +} + +IntegerTermVector::~IntegerTermVector() = default; + +void +IntegerTermVector::addTerm(vespalib::stringref, Weight) +{ + // Will/should never happen + assert(false); +} + +void +IntegerTermVector::addTerm(int64_t, Weight) +{ + // Will/should never happen + assert(false); +} + +void +IntegerTermVector::addTerm(int64_t term) +{ + _terms.emplace_back(term); +} + +TermVector::StringAndWeight +IntegerTermVector::getAsString(uint32_t index) const +{ + const auto & v = _terms[index]; + auto res = std::to_chars(_scratchPad, _scratchPad + sizeof(_scratchPad) - 1, v, 10); + res.ptr[0] = '\0'; + return {vespalib::stringref(_scratchPad, res.ptr - _scratchPad), Weight(1)}; +} + +TermVector::IntegerAndWeight +IntegerTermVector::getAsInteger(uint32_t index) const +{ + return {_terms[index], Weight(1)}; +} + +Weight +IntegerTermVector::getWeight(uint32_t) const +{ + return Weight(1); + +} + +uint32_t +IntegerTermVector::size() const +{ + return _terms.size(); +} + +} diff --git a/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h new file mode 100644 index 00000000000..65742c308f0 --- /dev/null +++ b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h @@ -0,0 +1,29 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "term_vector.h" +#include <vector> + +namespace search::query { + +/* + * Class for integer terms owned by a MultiTerm term node. + * Weights are not stored, all terms have weight 1. + */ +class IntegerTermVector : public TermVector { + std::vector<int64_t> _terms; + mutable char _scratchPad[24]; +public: + explicit IntegerTermVector(uint32_t sz); + ~IntegerTermVector() override; + void addTerm(vespalib::stringref, Weight) override; + void addTerm(int64_t term, Weight weight) override; + void addTerm(int64_t term); + [[nodiscard]] StringAndWeight getAsString(uint32_t index) const override; + [[nodiscard]] IntegerAndWeight getAsInteger(uint32_t index) const override; + [[nodiscard]] Weight getWeight(uint32_t index) const override; + [[nodiscard]] uint32_t size() const override; +}; + +} diff --git a/searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp new file mode 100644 index 00000000000..560a0088c78 --- /dev/null +++ b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp @@ -0,0 +1,66 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "string_term_vector.h" +#include <cassert> +#include <charconv> + +namespace search::query { + +StringTermVector::StringTermVector(uint32_t sz) + : _terms() +{ + _terms.reserve(sz); +} + +StringTermVector::~StringTermVector() = default; + +void +StringTermVector::addTerm(vespalib::stringref, Weight) +{ + // Will/should never happen + assert(false); +} + +void +StringTermVector::addTerm(int64_t, Weight) +{ + // Will/should never happen + assert(false); +} + +void +StringTermVector::addTerm(vespalib::stringref term) +{ + _terms.emplace_back(term); +} + +TermVector::StringAndWeight +StringTermVector::getAsString(uint32_t index) const +{ + const auto & v = _terms[index]; + return {v, Weight(1)}; +} + + +TermVector::IntegerAndWeight +StringTermVector::getAsInteger(uint32_t index) const +{ + const auto & v = _terms[index]; + int64_t value(0); + std::from_chars(v.c_str(), v.c_str() + v.size(), value); + return {value, Weight(1)}; +} + +Weight +StringTermVector::getWeight(uint32_t) const +{ + return Weight(1); +} + +uint32_t +StringTermVector::size() const +{ + return _terms.size(); +} + +} diff --git a/searchlib/src/vespa/searchlib/query/tree/string_term_vector.h b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.h new file mode 100644 index 00000000000..e4202cd950a --- /dev/null +++ b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.h @@ -0,0 +1,28 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "term_vector.h" +#include <vector> + +namespace search::query { + +/* + * Class for string terms owned by a MultiTerm term node. + * Weights are not stored, all terms have weight 1. + */ +class StringTermVector : public TermVector { + std::vector<vespalib::string> _terms; +public: + explicit StringTermVector(uint32_t sz); + ~StringTermVector() override; + void addTerm(vespalib::stringref term, Weight weight) override; + void addTerm(int64_t term, Weight weight) override; + void addTerm(vespalib::stringref term); + [[nodiscard]] StringAndWeight getAsString(uint32_t index) const override; + [[nodiscard]] IntegerAndWeight getAsInteger(uint32_t index) const override; + [[nodiscard]] Weight getWeight(uint32_t index) const override; + [[nodiscard]] uint32_t size() const override; +}; + +} diff --git a/searchlib/src/vespa/searchlib/query/tree/term_vector.h b/searchlib/src/vespa/searchlib/query/tree/term_vector.h new file mode 100644 index 00000000000..5bb1d9fbb0b --- /dev/null +++ b/searchlib/src/vespa/searchlib/query/tree/term_vector.h @@ -0,0 +1,27 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/searchlib/query/weight.h> +#include <vespa/vespalib/stllike/string.h> +#include <utility> + +namespace search::query { + +/* + * Interface class for terms owned by a MultiTerm query node. + */ +class TermVector { +public: + using StringAndWeight = std::pair<vespalib::stringref, Weight>; + using IntegerAndWeight = std::pair<int64_t, Weight>; + virtual ~TermVector() = default; + virtual void addTerm(vespalib::stringref term, Weight weight) = 0; + virtual void addTerm(int64_t term, Weight weight) = 0; + [[nodiscard]] virtual StringAndWeight getAsString(uint32_t index) const = 0; + [[nodiscard]] virtual IntegerAndWeight getAsInteger(uint32_t index) const = 0; + [[nodiscard]] virtual Weight getWeight(uint32_t index) const = 0; + [[nodiscard]] virtual uint32_t size() const = 0; +}; + +} diff --git a/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp b/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp index 38b635d453a..a8fbe81a222 100644 --- a/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp +++ b/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp @@ -29,10 +29,10 @@ FuzzyTerm::~FuzzyTerm() = default; namespace { -class StringTermVector final : public MultiTerm::TermVector { +class WeightedStringTermVector final : public TermVector { public: - explicit StringTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); } - ~StringTermVector() override; + explicit WeightedStringTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); } + ~WeightedStringTermVector() override; void addTerm(stringref term, Weight weight) override { _terms.emplace_back(term, weight); } @@ -59,9 +59,9 @@ private: std::vector<std::pair<vespalib::string, Weight>> _terms; }; -class IntegerTermVector final : public MultiTerm::TermVector { +class WeightedIntegerTermVector final : public TermVector { public: - explicit IntegerTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); } + explicit WeightedIntegerTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); } void addTerm(stringref, Weight) override { // Will/should never happen assert(false); @@ -87,7 +87,7 @@ private: mutable char _scratchPad[24]; }; -StringTermVector::~StringTermVector() = default; +WeightedStringTermVector::~WeightedStringTermVector() = default; } @@ -99,10 +99,10 @@ MultiTerm::MultiTerm(uint32_t num_terms) MultiTerm::~MultiTerm() = default; -std::unique_ptr<MultiTerm::TermVector> +std::unique_ptr<TermVector> MultiTerm::downgrade() { // Downgrade all number to string. This should really not happen - auto new_terms = std::make_unique<StringTermVector>(_num_terms); + auto new_terms = std::make_unique<WeightedStringTermVector>(_num_terms); for (uint32_t i(0), m(_terms->size()); i < m; i++) { auto v = _terms->getAsString(i); new_terms->addTerm(v.first, v.second); @@ -113,7 +113,7 @@ MultiTerm::downgrade() { void MultiTerm::addTerm(vespalib::stringref term, Weight weight) { if ( ! _terms) { - _terms = std::make_unique<StringTermVector>(_num_terms); + _terms = std::make_unique<WeightedStringTermVector>(_num_terms); _type = Type::STRING; } if (_type == Type::INTEGER) { @@ -126,7 +126,7 @@ MultiTerm::addTerm(vespalib::stringref term, Weight weight) { void MultiTerm::addTerm(int64_t term, Weight weight) { if ( ! _terms) { - _terms = std::make_unique<IntegerTermVector>(_num_terms); + _terms = std::make_unique<WeightedIntegerTermVector>(_num_terms); _type = Type::INTEGER; } _terms->addTerm(term, weight); diff --git a/searchlib/src/vespa/searchlib/query/tree/termnodes.h b/searchlib/src/vespa/searchlib/query/tree/termnodes.h index 2da184e8c0a..5db11bf6ba9 100644 --- a/searchlib/src/vespa/searchlib/query/tree/termnodes.h +++ b/searchlib/src/vespa/searchlib/query/tree/termnodes.h @@ -7,6 +7,7 @@ #include "querynodemixin.h" #include "range.h" #include "term.h" +#include "term_vector.h" #include "const_bool_nodes.h" namespace search::query { @@ -177,19 +178,8 @@ public: class MultiTerm : public Node { public: enum class Type {STRING, INTEGER, UNKNOWN}; - using StringAndWeight = std::pair<vespalib::stringref, Weight>; - using IntegerAndWeight = std::pair<int64_t, Weight>; - struct TermVector { - using StringAndWeight = MultiTerm::StringAndWeight; - using IntegerAndWeight = MultiTerm::IntegerAndWeight; - virtual ~TermVector() = default; - virtual void addTerm(vespalib::stringref term, Weight weight) = 0; - virtual void addTerm(int64_t term, Weight weight) = 0; - virtual StringAndWeight getAsString(uint32_t index) const = 0; - virtual IntegerAndWeight getAsInteger(uint32_t index) const = 0; - virtual Weight getWeight(uint32_t index) const = 0; - virtual uint32_t size() const = 0; - }; + using StringAndWeight = TermVector::StringAndWeight; + using IntegerAndWeight = TermVector::IntegerAndWeight; ~MultiTerm() override; void addTerm(vespalib::stringref term, Weight weight); void addTerm(int64_t term, Weight weight); diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp index 5eaa2dc40ab..30aee5e0e83 100644 --- a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp @@ -644,21 +644,23 @@ IntermediateBlueprint::freeze() namespace { bool -areAnyParentsEquiv(const Blueprint * node) -{ +areAnyParentsEquiv(const Blueprint * node) { return (node != nullptr) && (node->isEquiv() || areAnyParentsEquiv(node->getParent())); } bool -canBlueprintSkipUnpack(const Blueprint & bp, const fef::MatchData & md) -{ +emptyUnpackInfo(const IntermediateBlueprint * intermediate, const fef::MatchData & md) { + return intermediate != nullptr && intermediate->calculateUnpackInfo(md).empty(); +} + +bool +canBlueprintSkipUnpack(const Blueprint & bp, const fef::MatchData & md) { if (bp.always_needs_unpack()) { return false; } - return (bp.isWhiteList() || - (bp.getState().numFields() != 0) || - (bp.isIntermediate() && - static_cast<const IntermediateBlueprint &>(bp).calculateUnpackInfo(md).empty())); + return bp.isWhiteList() || + (bp.getState().numFields() != 0) || + emptyUnpackInfo(bp.asIntermediate(), md); } } diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.h b/searchlib/src/vespa/searchlib/queryeval/blueprint.h index cd0e8f2af40..81d225661d0 100644 --- a/searchlib/src/vespa/searchlib/queryeval/blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.h @@ -27,6 +27,11 @@ class SearchIterator; class ExecuteInfo; class MatchingElementsSearch; class LeafBlueprint; +class IntermediateBlueprint; +class SourceBlenderBlueprint; +class AndBlueprint; +class AndNotBlueprint; +class OrBlueprint; /** * A Blueprint is an intermediate representation of a search. More @@ -251,16 +256,19 @@ public: vespalib::slime::Cursor & asSlime(const vespalib::slime::Inserter & cursor) const; virtual vespalib::string getClassName() const; virtual void visitMembers(vespalib::ObjectVisitor &visitor) const; - virtual bool isEquiv() const { return false; } - virtual bool isWhiteList() const { return false; } - virtual bool isIntermediate() const { return false; } - virtual LeafBlueprint * asLeaf() noexcept { return nullptr; } - virtual bool isAnd() const { return false; } - virtual bool isAndNot() const { return false; } - virtual bool isOr() const { return false; } - virtual bool isSourceBlender() const { return false; } - virtual bool isRank() const { return false; } - virtual const attribute::ISearchContext *get_attribute_search_context() const { return nullptr; } + virtual bool isEquiv() const noexcept { return false; } + virtual bool isWhiteList() const noexcept { return false; } + virtual IntermediateBlueprint * asIntermediate() noexcept { return nullptr; } + const IntermediateBlueprint * asIntermediate() const noexcept { return const_cast<Blueprint *>(this)->asIntermediate(); } + virtual const LeafBlueprint * asLeaf() const noexcept { return nullptr; } + virtual AndBlueprint * asAnd() noexcept { return nullptr; } + bool isAnd() const noexcept { return const_cast<Blueprint *>(this)->asAnd() != nullptr; } + virtual AndNotBlueprint * asAndNot() noexcept { return nullptr; } + bool isAndNot() const noexcept { return const_cast<Blueprint *>(this)->asAndNot() != nullptr; } + virtual OrBlueprint * asOr() noexcept { return nullptr; } + virtual SourceBlenderBlueprint * asSourceBlender() noexcept { return nullptr; } + virtual bool isRank() const noexcept { return false; } + virtual const attribute::ISearchContext *get_attribute_search_context() const noexcept { return nullptr; } // For document summaries with matched-elements-only set. virtual std::unique_ptr<MatchingElementsSearch> create_matching_elements_search(const MatchingElementsFields &fields) const; @@ -354,7 +362,7 @@ public: void freeze() final; UnpackInfo calculateUnpackInfo(const fef::MatchData & md) const; - bool isIntermediate() const override { return true; } + IntermediateBlueprint * asIntermediate() noexcept final { return this; } }; @@ -400,7 +408,7 @@ public: void fetchPostings(const ExecuteInfo &execInfo) override; void freeze() final; SearchIteratorUP createSearch(fef::MatchData &md, bool strict) const override; - LeafBlueprint * asLeaf() noexcept final { return this; } + const LeafBlueprint * asLeaf() const noexcept final { return this; } virtual bool getRange(vespalib::string & from, vespalib::string & to) const; virtual SearchIteratorUP createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool strict) const = 0; diff --git a/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h index bc4c68a9e24..df1ea13105a 100644 --- a/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h @@ -27,7 +27,7 @@ public: void visitMembers(vespalib::ObjectVisitor &visitor) const override; void fetchPostings(const ExecuteInfo &execInfo) override; - bool isEquiv() const override { return true; } + bool isEquiv() const noexcept final { return true; } }; } diff --git a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp index c0439df1c1b..b315965b5f4 100644 --- a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp @@ -34,13 +34,13 @@ size_t lookup_create_source(std::vector<std::unique_ptr<CombineType> > &sources, template <typename CombineType> void optimize_source_blenders(IntermediateBlueprint &self, size_t begin_idx) { std::vector<size_t> source_blenders; - SourceBlenderBlueprint *reference = nullptr; + const SourceBlenderBlueprint * reference = nullptr; for (size_t i = begin_idx; i < self.childCnt(); ++i) { - if (self.getChild(i).isSourceBlender()) { - auto *child = static_cast<SourceBlenderBlueprint *>(&self.getChild(i)); - if (reference == nullptr || reference->isCompatibleWith(*child)) { + const SourceBlenderBlueprint * sbChild = self.getChild(i).asSourceBlender(); + if (sbChild) { + if (reference == nullptr || reference->isCompatibleWith(*sbChild)) { source_blenders.push_back(i); - reference = child; + reference = sbChild; } } } @@ -50,16 +50,14 @@ void optimize_source_blenders(IntermediateBlueprint &self, size_t begin_idx) { while (!source_blenders.empty()) { blender_up = self.removeChild(source_blenders.back()); source_blenders.pop_back(); - assert(blender_up->isSourceBlender()); - auto *blender = static_cast<SourceBlenderBlueprint *>(blender_up.get()); + SourceBlenderBlueprint * blender = blender_up->asSourceBlender(); while (blender->childCnt() > 0) { Blueprint::UP child_up = blender->removeChild(blender->childCnt() - 1); size_t source_idx = lookup_create_source(sources, child_up->getSourceId(), self.get_docid_limit()); sources[source_idx]->addChild(std::move(child_up)); } } - assert(blender_up->isSourceBlender()); - auto *top = static_cast<SourceBlenderBlueprint *>(blender_up.get()); + SourceBlenderBlueprint * top = blender_up->asSourceBlender(); while (!sources.empty()) { top->addChild(std::move(sources.back())); sources.pop_back(); @@ -109,8 +107,8 @@ AndNotBlueprint::optimize_self(OptimizePass pass) return; } if (pass == OptimizePass::FIRST) { - if (getChild(0).isAndNot()) { - auto *child = static_cast<AndNotBlueprint *>(&getChild(0)); + AndNotBlueprint * child = getChild(0).asAndNot(); + if (child != nullptr) { while (child->childCnt() > 1) { addChild(child->removeChild(1)); } @@ -197,8 +195,8 @@ AndBlueprint::optimize_self(OptimizePass pass) { if (pass == OptimizePass::FIRST) { for (size_t i = 0; i < childCnt(); ++i) { - if (getChild(i).isAnd()) { - auto *child = static_cast<AndBlueprint *>(&getChild(i)); + AndBlueprint * child = getChild(i).asAnd(); + if (child != nullptr) { while (child->childCnt() > 0) { addChild(child->removeChild(0)); } @@ -299,8 +297,8 @@ OrBlueprint::optimize_self(OptimizePass pass) { if (pass == OptimizePass::FIRST) { for (size_t i = 0; (childCnt() > 1) && (i < childCnt()); ++i) { - if (getChild(i).isOr()) { - auto *child = static_cast<OrBlueprint *>(&getChild(i)); + OrBlueprint * child = getChild(i).asOr(); + if (child != nullptr) { while (child->childCnt() > 0) { addChild(child->removeChild(0)); } diff --git a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h index 75dc47272af..6d8082b60f6 100644 --- a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h +++ b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h @@ -18,7 +18,7 @@ public: HitEstimate combine(const std::vector<HitEstimate> &data) const override; FieldSpecBaseList exposeFields() const override; void optimize_self(OptimizePass pass) override; - bool isAndNot() const override { return true; } + AndNotBlueprint * asAndNot() noexcept final { return this; } Blueprint::UP get_replacement() override; void sort(Children &children) const override; bool inheritStrict(size_t i) const override; @@ -44,7 +44,7 @@ public: HitEstimate combine(const std::vector<HitEstimate> &data) const override; FieldSpecBaseList exposeFields() const override; void optimize_self(OptimizePass pass) override; - bool isAnd() const override { return true; } + AndBlueprint * asAnd() noexcept final { return this; } Blueprint::UP get_replacement() override; void sort(Children &children) const override; bool inheritStrict(size_t i) const override; @@ -68,7 +68,7 @@ public: HitEstimate combine(const std::vector<HitEstimate> &data) const override; FieldSpecBaseList exposeFields() const override; void optimize_self(OptimizePass pass) override; - bool isOr() const override { return true; } + OrBlueprint * asOr() noexcept final { return this; } Blueprint::UP get_replacement() override; void sort(Children &children) const override; bool inheritStrict(size_t i) const override; @@ -166,7 +166,7 @@ public: Blueprint::UP get_replacement() override; void sort(Children &children) const override; bool inheritStrict(size_t i) const override; - bool isRank() const override { return true; } + bool isRank() const noexcept final { return true; } SearchIterator::UP createIntermediateSearch(MultiSearch::Children subSearches, bool strict, fef::MatchData &md) const override; @@ -199,7 +199,7 @@ public: /** check if this blueprint has the same source selector as the other */ bool isCompatibleWith(const SourceBlenderBlueprint &other) const; - bool isSourceBlender() const override { return true; } + SourceBlenderBlueprint * asSourceBlender() noexcept final { return this; } uint8_t calculate_cost_tier() const override; }; diff --git a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp index 8c56a6694dc..fdea54424de 100644 --- a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp @@ -4,6 +4,7 @@ #include "termwise_helper.h" #include <vespa/searchlib/fef/matchdata.h> #include <cassert> +#include <limits> namespace search::queryeval { @@ -28,7 +29,7 @@ SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &child _children.emplace_back(child); } assert((_children.size() == _childMatch.size()) || _childMatch.empty()); - assert(_children.size() < 0x10000); + assert(_children.size() <= std::numeric_limits<ref_t>::max()); } SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &children, MatchDataUP md) diff --git a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h index ce0c47f0882..0a1b140f28a 100644 --- a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h +++ b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h @@ -18,6 +18,7 @@ private: MatchDataUP _md; public: + using ref_t = uint16_t; SearchIteratorPack(); ~SearchIteratorPack(); SearchIteratorPack(SearchIteratorPack &&rhs) noexcept; @@ -31,25 +32,25 @@ public: // TODO: use MultiSearch::Children to pass ownership SearchIteratorPack(const std::vector<SearchIterator*> &children, MatchDataUP md); - uint32_t get_docid(uint16_t ref) const { + uint32_t get_docid(ref_t ref) const { return _children[ref]->getDocId(); } - uint32_t seek(uint16_t ref, uint32_t docid) { + uint32_t seek(ref_t ref, uint32_t docid) { _children[ref]->seek(docid); return _children[ref]->getDocId(); } - int32_t get_weight(uint16_t ref, uint32_t docid) { + int32_t get_weight(ref_t ref, uint32_t docid) { _children[ref]->doUnpack(docid); return _childMatch[ref]->getWeight(); } - void unpack(uint16_t ref, uint32_t docid) { + void unpack(ref_t ref, uint32_t docid) { _children[ref]->doUnpack(docid); } - uint16_t size() const { return _children.size(); } + ref_t size() const { return _children.size(); } void initRange(uint32_t begin, uint32_t end) { for (auto & child: _children) { child->initRange(begin, end); diff --git a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h index 5710d2c2106..a95ca0efc72 100644 --- a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h +++ b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h @@ -83,7 +83,7 @@ public: return *this; } - const attribute::ISearchContext *get_attribute_search_context() const override { + const attribute::ISearchContext *get_attribute_search_context() const noexcept final { return _ctx.get(); } diff --git a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h index cc6331375cf..6a988e67149 100644 --- a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h @@ -26,7 +26,7 @@ public: ~SameElementBlueprint() override; // no match data - bool isWhiteList() const override { return true; } + bool isWhiteList() const noexcept final { return true; } // used by create visitor FieldSpec getNextChildField(const vespalib::string &field_name, uint32_t field_id); diff --git a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp index 431f386fa86..0bbdf89bab7 100644 --- a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp @@ -138,8 +138,8 @@ public: bool allTermsHaveMatch(const SimplePhraseSearch::Children &terms, const vector<uint32_t> &eval_order, uint32_t doc_id) { - for (uint32_t i = 0; i < terms.size(); ++i) { - if (!terms[eval_order[i]]->seek(doc_id)) { + for (unsigned int order : eval_order) { + if (!terms[order]->seek(doc_id)) { return false; } } @@ -147,13 +147,18 @@ allTermsHaveMatch(const SimplePhraseSearch::Children &terms, const vector<uint32 } } // namespace -void +inline void SimplePhraseSearch::phraseSeek(uint32_t doc_id) { if (allTermsHaveMatch(getChildren(), _eval_order, doc_id)) { - AndSearch::doUnpack(doc_id); - if (PhraseMatcher(_childMatch, _eval_order, _iterators).hasMatch()) { - setDocId(doc_id); - } + matchPhrase(doc_id); + } +} + +void +SimplePhraseSearch::matchPhrase(uint32_t doc_id) { + AndSearch::doUnpack(doc_id); + if (PhraseMatcher(_childMatch, _eval_order, _iterators).hasMatch()) { + setDocId(doc_id); } } @@ -180,25 +185,30 @@ void SimplePhraseSearch::doSeek(uint32_t doc_id) { phraseSeek(doc_id); if (_strict) { - uint32_t next_candidate = doc_id; - while (getDocId() < doc_id || getDocId() == beginId()) { - getChildren()[0]->seek(next_candidate + 1); - next_candidate = getChildren()[0]->getDocId(); - if (isAtEnd(next_candidate)) { - setAtEnd(); - return; - } - // child must behave as strict. - assert(next_candidate > doc_id && next_candidate != beginId()); + doStrictSeek(doc_id); + } +} - phraseSeek(next_candidate); +void +SimplePhraseSearch::doStrictSeek(uint32_t doc_id) { + uint32_t next_candidate = doc_id; + while (getDocId() < doc_id || getDocId() == beginId()) { + getChildren()[0]->seek(next_candidate + 1); + next_candidate = getChildren()[0]->getDocId(); + if (isAtEnd(next_candidate)) { + setAtEnd(); + return; } + // child must behave as strict. + assert(next_candidate > doc_id && next_candidate != beginId()); + + phraseSeek(next_candidate); } } void SimplePhraseSearch::doUnpack(uint32_t doc_id) { - // All children has already been unpacked before this call is made. + // All children have already been unpacked before this call is made. _tmd.reset(doc_id); PhraseMatcher(_childMatch, _eval_order, _iterators).fillPositions(_tmd); diff --git a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h index 00e75f44844..8ee49183f51 100644 --- a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h +++ b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h @@ -27,7 +27,9 @@ class SimplePhraseSearch : public AndSearch // Reuse this vector instead of allocating a new one when needed. std::vector<It> _iterators; - void phraseSeek(uint32_t doc_id); + VESPA_DLL_LOCAL void phraseSeek(uint32_t doc_id); + VESPA_DLL_LOCAL void matchPhrase(uint32_t doc_id) __attribute__((noinline)); + VESPA_DLL_LOCAL void doStrictSeek(uint32_t doc_id) __attribute__((noinline)); public: /** * Takes ownership of the contents of children. diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp index 2b1ae90e452..e2e9516badf 100644 --- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp @@ -97,7 +97,7 @@ WeightedSetTermBlueprint::createLeafSearch(const fef::TermFieldMatchDataArray &t { assert(tfmda.size() == 1); if ((_terms.size() == 1) && tfmda[0]->isNotNeeded()) { - if (LeafBlueprint * leaf = _terms[0]->asLeaf(); leaf != nullptr) { + if (const LeafBlueprint * leaf = _terms[0]->asLeaf(); leaf != nullptr) { // Always returnin a strict iterator independently of what was required, // as that is what we do with all the children when there are more. return leaf->createLeafSearch(tfmda, true); diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp index cc37433a696..32ae321e031 100644 --- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp @@ -18,7 +18,7 @@ template <typename HEAP, typename IteratorPack> class WeightedSetTermSearchImpl : public WeightedSetTermSearch { private: - using ref_t = uint32_t; + using ref_t = IteratorPack::ref_t; struct CmpDocId { const uint32_t *termPos; diff --git a/searchlib/src/vespa/searchlib/test/attribute_builder.cpp b/searchlib/src/vespa/searchlib/test/attribute_builder.cpp index 9a88d44c72e..d6a8b0f4fdf 100644 --- a/searchlib/src/vespa/searchlib/test/attribute_builder.cpp +++ b/searchlib/src/vespa/searchlib/test/attribute_builder.cpp @@ -32,14 +32,27 @@ add_docs(AttributeVector& attr, size_t num_docs) attr.addDocs(num_docs); } + template <typename AttrType, typename ValueType> void -fill_helper(AttributeVector& attr, std::initializer_list<ValueType> values) +fill_helper(AttributeVector& attr, std::span<ValueType> values) { add_docs(attr, values.size()); auto& real = dynamic_cast<AttrType&>(attr); uint32_t docid = 1; - for (auto value : values) { + for (const auto& value : values) { + real.update(docid++, value); + } + attr.commit(true); +} + +template <typename AttrType, typename ValueType> +void +fill_helper(AttributeVector& attr, std::initializer_list<ValueType> values) { + add_docs(attr, values.size()); + auto& real = dynamic_cast<AttrType&>(attr); + uint32_t docid = 1; + for (const auto& value : values) { real.update(docid++, value); } attr.commit(true); @@ -54,7 +67,7 @@ fill_array_helper(AttributeVector& attr, std::initializer_list<std::initializer_ auto& real = dynamic_cast<AttrType&>(attr); uint32_t docid = 1; for (auto value : values) { - for (auto elem : value) { + for (const auto& elem : value) { real.append(docid, elem, 1); } ++docid; @@ -71,7 +84,7 @@ fill_wset_helper(AttributeVector& attr, std::initializer_list<std::initializer_l auto& real = dynamic_cast<AttrType&>(attr); uint32_t docid = 1; for (auto value : values) { - for (auto elem : value) { + for (const auto& elem : value) { real.append(docid, elem.first, elem.second); } ++docid; @@ -89,6 +102,13 @@ AttributeBuilder::docs(size_t num_docs) } AttributeBuilder& +AttributeBuilder::fill(std::span<int32_t> values) +{ + fill_helper<IntegerAttribute, int32_t>(_attr, values); + return *this; +} + +AttributeBuilder& AttributeBuilder::fill(std::initializer_list<int32_t> values) { fill_helper<IntegerAttribute, int32_t>(_attr, values); diff --git a/searchlib/src/vespa/searchlib/test/attribute_builder.h b/searchlib/src/vespa/searchlib/test/attribute_builder.h index 473003bafc9..cdbed838327 100644 --- a/searchlib/src/vespa/searchlib/test/attribute_builder.h +++ b/searchlib/src/vespa/searchlib/test/attribute_builder.h @@ -8,6 +8,7 @@ #include <memory> #include <utility> #include <vector> +#include <span> namespace search { class AttributeVector; } namespace search::attribute { class Config; } @@ -38,6 +39,7 @@ public: AttributeBuilder& docs(size_t num_docs); // Fill functions for integer attributes + AttributeBuilder& fill(std::span<int32_t> values); AttributeBuilder& fill(std::initializer_list<int32_t> values); AttributeBuilder& fill(std::initializer_list<int64_t> values); AttributeBuilder& fill_array(std::initializer_list<IntList> values); diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt index 1ca890eb2a8..525c77f7df6 100644 --- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt +++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt @@ -58,10 +58,17 @@ io.netty:netty-codec:${netty.vespa.version} io.netty:netty-common:${netty.vespa.version} io.netty:netty-handler:${netty.vespa.version} io.netty:netty-resolver:${netty.vespa.version} +io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version} +io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:linux-aarch_64 +io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:linux-x86_64 +io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:osx-aarch_64 +io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:osx-x86_64 +io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:windows-x86_64 io.netty:netty-tcnative-classes:${netty-tcnative.vespa.version} io.netty:netty-tcnative:${netty-tcnative.vespa.version} io.netty:netty-transport-classes-epoll:${netty.vespa.version} io.netty:netty-transport-native-epoll:${netty.vespa.version} +io.netty:netty-transport-native-epoll:${netty.vespa.version}:linux-x86_64 io.netty:netty-transport-native-unix-common:${netty.vespa.version} io.netty:netty-transport:${netty.vespa.version} io.prometheus:simpleclient:${prometheus.client.vespa.version} @@ -122,8 +129,10 @@ org.apache.velocity:velocity-engine-core:${velocity.vespa.version} org.apache.yetus:audience-annotations:0.12.0 org.apache.zookeeper:zookeeper-jute:${zookeeper.client.vespa.version} org.apache.zookeeper:zookeeper-jute:3.8.1 +org.apache.zookeeper:zookeeper-jute:3.9.1 org.apache.zookeeper:zookeeper:${zookeeper.client.vespa.version} org.apache.zookeeper:zookeeper:3.8.1 +org.apache.zookeeper:zookeeper:3.9.1 org.apiguardian:apiguardian-api:${apiguardian.vespa.version} org.assertj:assertj-core:${assertj.vespa.version} org.bouncycastle:bcpkix-jdk18on:${bouncycastle.vespa.version} diff --git a/vespajlib/src/main/java/com/yahoo/collections/Comparables.java b/vespajlib/src/main/java/com/yahoo/collections/Comparables.java index 2e6c8fc82b3..f76509cca2c 100644 --- a/vespajlib/src/main/java/com/yahoo/collections/Comparables.java +++ b/vespajlib/src/main/java/com/yahoo/collections/Comparables.java @@ -18,7 +18,7 @@ public class Comparables { } /** - * Returns the least element, or {@code second} if they are equal according to + * Returns the greatest element, or {@code second} if they are equal according to * {@link Comparable#compareTo(Object) compareTo}. */ public static <T extends Comparable<? super T>> T max(T first, T second) { diff --git a/vespalib/src/tests/btree/btree_test.cpp b/vespalib/src/tests/btree/btree_test.cpp index 21838676906..30e3df7dd14 100644 --- a/vespalib/src/tests/btree/btree_test.cpp +++ b/vespalib/src/tests/btree/btree_test.cpp @@ -232,6 +232,7 @@ protected: void requireThatUpperBoundWorksT(); void requireThatIteratorDistanceWorks(int numEntries); void test_step_forward(int num_entries); + void test_step_backward(int num_entries); }; template <typename LeafNodeType> @@ -1476,8 +1477,12 @@ BTreeTest::requireThatIteratorDistanceWorks(int numEntries) iitbs.binarySeek(i); ++it; } - iitlsp.linearSeekPast(i); - iitbsp.binarySeekPast(i); + if (iitlsp.valid()) { + iitlsp.linearSeekPast(i); + } + if (iitbsp.valid()) { + iitbsp.binarySeekPast(i); + } Iterator iitlsp2 = iitls; Iterator iitbsp2 = iitbs; Iterator iitnr = i < numEntries ? iitn : tree.begin(); @@ -1548,8 +1553,35 @@ BTreeTest::test_step_forward(int num_entries) } } +void +BTreeTest::test_step_backward(int num_entries) +{ + GenerationHandler g; + MyTree tree; + for (int i = 0; i < num_entries; ++i) { + tree.insert(i, toStr(i)); + } + auto it = tree.begin(); + for (int i = 0; i <= num_entries; ++i) { + auto iit = tree.lowerBound(i); + auto iit2 = iit; + iit2 -= i; + EXPECT_TRUE(iit2.identical(it)); + iit2 = iit; + iit2 -= (1000000 + i); + EXPECT_TRUE(iit2.identical(it)); + for (int j = 0; j <= i; ++j) { + auto jit = tree.lowerBound(j); + auto iit3 = iit; + iit3 -= (i - j); + EXPECT_TRUE(iit3.identical(jit)); + } + } +} + TEST_F(BTreeTest, require_that_iterator_distance_works) { + requireThatIteratorDistanceWorks(0); requireThatIteratorDistanceWorks(1); requireThatIteratorDistanceWorks(3); requireThatIteratorDistanceWorks(8); @@ -1560,6 +1592,7 @@ TEST_F(BTreeTest, require_that_iterator_distance_works) TEST_F(BTreeTest, require_that_step_forward_works) { + test_step_forward(0); test_step_forward(1); test_step_forward(3); test_step_forward(8); @@ -1568,6 +1601,17 @@ TEST_F(BTreeTest, require_that_step_forward_works) test_step_forward(400); } +TEST_F(BTreeTest, require_that_step_backward_works) +{ + test_step_backward(0); + test_step_backward(1); + test_step_backward(3); + test_step_backward(8); + test_step_backward(20); + test_step_backward(100); + test_step_backward(400); +} + TEST_F(BTreeTest, require_that_foreach_key_works) { using Tree = BTree<int, int, btree::NoAggregated, MyComp, MyTraits>; diff --git a/vespalib/src/tests/util/bfloat16/CMakeLists.txt b/vespalib/src/tests/util/bfloat16/CMakeLists.txt index 6a6ed9b1997..72d6c8720d6 100644 --- a/vespalib/src/tests/util/bfloat16/CMakeLists.txt +++ b/vespalib/src/tests/util/bfloat16/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -if(EXISTS /opt/vespa-deps/include/onnxruntime/core/framework/endian.h) +if(EXISTS /opt/vespa-deps/include/onnxruntime/onnxruntime_cxx_api.h) vespa_add_executable(vespalib_bfloat16_test_app TEST SOURCES bfloat16_test.cpp DEPENDS vespalib + onnxruntime GTest::GTest ) vespa_add_test(NAME vespalib_bfloat16_test_app COMMAND vespalib_bfloat16_test_app) diff --git a/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp b/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp index b0d93dde7d3..196c5f98c9e 100644 --- a/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp +++ b/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp @@ -83,13 +83,15 @@ TEST(BFloat16Test, constants_check) { EXPECT_EQ(try_half_epsilon.to_float(), 1.0f); EXPECT_LT(big, std::numeric_limits<float>::max()); + EXPECT_GT(big, 0.5 * std::numeric_limits<float>::max()); EXPECT_GT(low, std::numeric_limits<float>::lowest()); + EXPECT_EQ(low, -big); - printf("bfloat16 epsilon: %.10g (float has %.20g)\n", eps, std::numeric_limits<float>::epsilon()); - printf("bfloat16 norm_min: %.20g (float has %.20g)\n", n_min, std::numeric_limits<float>::min()); - printf("bfloat16 denorm_min: %.20g (float has %.20g)\n", d_min, std::numeric_limits<float>::denorm_min()); - printf("bfloat16 max: %.20g (float has %.20g)\n", big, std::numeric_limits<float>::max()); - printf("bfloat16 lowest: %.20g (float has %.20g)\n", low, std::numeric_limits<float>::lowest()); + printf("bfloat16 epsilon: %a (float has %a)\n", eps, std::numeric_limits<float>::epsilon()); + printf("bfloat16 norm_min: %a (float has %a)\n", n_min, std::numeric_limits<float>::min()); + printf("bfloat16 denorm_min: %a (float has %a)\n", d_min, std::numeric_limits<float>::denorm_min()); + printf("bfloat16 max: %a (float has %a)\n", big, std::numeric_limits<float>::max()); + printf("bfloat16 lowest: %a (float has %a)\n", low, std::numeric_limits<float>::lowest()); } TEST(BFloat16Test, traits_check) { @@ -165,52 +167,19 @@ TEST(BFloat16Test, check_special_values) { EXPECT_EQ(memcmp(&f_snan, &f_from_b_snan, sizeof(float)), 0); } -#include <onnxruntime/core/framework/endian.h> - -// extract from onnx-internal header file: -namespace onnxruntime { - -//BFloat16 -struct BFloat16 { - uint16_t val{0}; - explicit BFloat16() = default; - explicit BFloat16(uint16_t v) : val(v) {} - explicit BFloat16(float v) { - if (endian::native == endian::little) { - std::memcpy(&val, reinterpret_cast<char*>(&v) + sizeof(uint16_t), sizeof(uint16_t)); - } else { - std::memcpy(&val, &v, sizeof(uint16_t)); - } - } - - float ToFloat() const { - float result; - char* const first = reinterpret_cast<char*>(&result); - char* const second = first + sizeof(uint16_t); - if (endian::native == endian::little) { - std::memset(first, 0, sizeof(uint16_t)); - std::memcpy(second, &val, sizeof(uint16_t)); - } else { - std::memcpy(first, &val, sizeof(uint16_t)); - std::memset(second, 0, sizeof(uint16_t)); - } - return result; - } -}; - -} // namespace onnxruntime +#include <onnxruntime/onnxruntime_cxx_api.h> TEST(OnnxBFloat16Test, has_same_encoding) { - EXPECT_EQ(sizeof(vespalib::BFloat16), sizeof(onnxruntime::BFloat16)); + EXPECT_EQ(sizeof(vespalib::BFloat16), sizeof(Ort::BFloat16_t)); EXPECT_EQ(sizeof(vespalib::BFloat16), sizeof(uint16_t)); - EXPECT_EQ(sizeof(onnxruntime::BFloat16), sizeof(uint16_t)); + EXPECT_EQ(sizeof(Ort::BFloat16_t), sizeof(uint16_t)); vespalib::BFloat16 our_value; uint32_t ok_count = 0; uint32_t nan_count = 0; for (uint32_t i = 0; i < (1u << 16u); ++i) { uint16_t bits = i; our_value.assign_bits(bits); - onnxruntime::BFloat16 their_value(bits); + Ort::BFloat16_t their_value = Ort::BFloat16_t::FromBits(bits); if (our_value.get_bits() != bits) { printf("bad bits %04x -> %04x (vespalib)\n", bits, our_value.get_bits()); printf("onnx converts -> %04x\n", their_value.val); @@ -226,7 +195,7 @@ TEST(OnnxBFloat16Test, has_same_encoding) { EXPECT_EQ(our_value.get_bits(), their_value.val); if (our_value.get_bits() != their_value.val) { printf("vespalib bits %04x != %04x onnx bits\n", our_value.get_bits(), their_value.val); - printf("corresponds to floats %g and %g\n", our_value.to_float(), their_value.ToFloat()); + printf("corresponds to floats %a and %a\n", our_value.to_float(), their_value.ToFloat()); continue; } float our_float = our_value.to_float(); @@ -237,13 +206,13 @@ TEST(OnnxBFloat16Test, has_same_encoding) { continue; } if (our_float != their_float) { - printf("bits %04x as float differs: vespalib %g != %g onnx\n", bits, our_value.to_float(), their_value.ToFloat()); + printf("bits %04x as float differs: vespalib %a != %a onnx\n", bits, our_value.to_float(), their_value.ToFloat()); } else { ++ok_count; } EXPECT_EQ(our_float, their_float); vespalib::BFloat16 our_back(our_float); - onnxruntime::BFloat16 their_back(their_float); + Ort::BFloat16_t their_back(their_float); EXPECT_EQ(our_back.get_bits(), their_back.val); } printf("normal floats behave equally OK in both vespalib and onnx: %d (0x%04x)\n", ok_count, ok_count); diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.h b/vespalib/src/vespa/vespalib/btree/btreeiterator.h index 9519951f9e2..cd63499a5ed 100644 --- a/vespalib/src/vespa/vespalib/btree/btreeiterator.h +++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.h @@ -232,11 +232,18 @@ protected: return *this; } + void set_subtree_position(const InternalNodeType* node, uint32_t level, uint32_t idx, size_t position); + /* * Step iterator forwards the given number of steps. */ void step_forward(size_t steps); + /* + * Step iterator backwards the given number of steps. + */ + void step_backward(size_t steps); + ~BTreeIteratorBase(); BTreeIteratorBase(const BTreeIteratorBase &other); BTreeIteratorBase &operator=(const BTreeIteratorBase &other); @@ -507,6 +514,7 @@ protected: using ParentType::_compatLeafNode; using ParentType::clearPath; using ParentType::setupEmpty; + using ParentType::step_backward; using ParentType::step_forward; public: using ParentType::end; @@ -570,6 +578,14 @@ public: step_forward(steps); return *this; } + + /* + * Step iterator backward the given number of steps. + */ + BTreeConstIterator & operator-=(size_t steps) { + step_backward(steps); + return *this; + } /** * Position iterator at first position with a key that is greater * than or equal to the key argument. The iterator must be set up @@ -712,6 +728,7 @@ public: using ParentType::_leafRoot; using ParentType::_compatLeafNode; using ParentType::end; + using ParentType::step_backward; using ParentType::step_forward; using EntryRef = datastore::EntryRef; @@ -746,6 +763,11 @@ public: return *this; } + BTreeIterator & operator-=(size_t steps) { + step_backward(steps); + return *this; + } + NodeAllocatorType & getAllocator() const { return const_cast<NodeAllocatorType &>(*_allocator); } diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp b/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp index 3119d05cfd9..b9afce54f6b 100644 --- a/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp +++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp @@ -530,6 +530,38 @@ template <typename KeyT, typename DataT, typename AggrT, uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE> void BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>:: +set_subtree_position(const InternalNodeType* node, uint32_t level, uint32_t idx, size_t position) +{ + /* + * Walk down subtree adjusting iterator for new partial position. + */ + _path[level].setIdx(idx); + size_t remaining_steps = position; + while (level > 0) { + --level; + node = _allocator->mapInternalRef(node->getChild(idx)); + assert(remaining_steps < node->validLeaves()); + idx = 0; + while (idx < node->validSlots()) { + auto valid_leaves = _allocator->validLeaves(node->getChild(idx)); + if (remaining_steps < valid_leaves) { + break; + } + remaining_steps -= valid_leaves; + ++idx; + } + assert(idx < node->validSlots()); + _path[level].setNodeAndIdx(node, idx); + } + auto lnode = _allocator->mapLeafRef(node->getChild(idx)); + assert(remaining_steps < lnode->validSlots()); + _leaf.setNodeAndIdx(lnode, remaining_steps); +} + +template <typename KeyT, typename DataT, typename AggrT, + uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE> +void +BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>:: step_forward(size_t steps) { auto lnode = _leaf.getNode(); @@ -557,10 +589,7 @@ step_forward(size_t steps) node = _path[level].getNode(); idx = _path[level].getIdx() + 1; while (idx < node->validSlots()) { - auto ref = node->getChild(idx); - auto valid_leaves = (level != 0) ? - _allocator->mapInternalRef(ref)->validLeaves() : - _allocator->mapLeafRef(ref)->validLeaves(); + auto valid_leaves = _allocator->validLeaves(node->getChild(idx)); if (remaining_steps < valid_leaves) { break; } @@ -577,32 +606,61 @@ step_forward(size_t steps) } } } + set_subtree_position(node, level, idx, remaining_steps); +} + +template <typename KeyT, typename DataT, typename AggrT, + uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE> +void +BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>:: +step_backward(size_t steps) +{ + int64_t remaining_steps = steps; + if (remaining_steps == 0) { + return; + } + if (_leaf.getNode() == nullptr) { + rbegin(); + if (_leaf.getNode() == nullptr) { + return; + } + --remaining_steps; + } + auto idx = _leaf.getIdx(); + if (idx >= remaining_steps) { + _leaf.setIdx(idx - remaining_steps); + return; + } + if (_pathSize == 0) { + _leaf.setIdx(0); + return; + } + remaining_steps -= idx; + uint32_t level = 0; + uint32_t levels = _pathSize; + const InternalNodeType* node; /* - * Walk down subtree adjusting iterator for new position. + * Find intermediate node representing subtree containing old and new + * position. */ - _path[level].setIdx(idx); - while (level > 0) { - --level; - node = _allocator->mapInternalRef(node->getChild(idx)); - assert(remaining_steps < node->validLeaves()); - idx = 0; - while (idx < node->validSlots()) { - auto ref = node->getChild(idx); - auto valid_leaves = (level != 0) ? - _allocator->mapInternalRef(ref)->validLeaves() : - _allocator->mapLeafRef(ref)->validLeaves(); - if (remaining_steps < valid_leaves) { - break; + for (;;) { + node = _path[level].getNode(); + idx = _path[level].getIdx(); + while (idx > 0 && remaining_steps > 0) { + --idx; + remaining_steps -= _allocator->validLeaves(node->getChild(idx)); + } + if (remaining_steps <= 0) { + break; + } else { + ++level; + if (level == levels) { + begin(); + return; } - remaining_steps -= valid_leaves; - ++idx; } - assert(idx < node->validSlots()); - _path[level].setNodeAndIdx(node, idx); } - lnode = _allocator->mapLeafRef(node->getChild(idx)); - assert(remaining_steps < lnode->validSlots()); - _leaf.setNodeAndIdx(lnode, remaining_steps); + set_subtree_position(node, level, idx, -remaining_steps); } template <typename KeyT, typename DataT, typename AggrT, typename CompareT, diff --git a/zookeeper-server/CMakeLists.txt b/zookeeper-server/CMakeLists.txt index d410469e225..d598747e75f 100644 --- a/zookeeper-server/CMakeLists.txt +++ b/zookeeper-server/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(zookeeper-server-common) add_subdirectory(zookeeper-server) add_subdirectory(zookeeper-server-3.8.1) +add_subdirectory(zookeeper-server-3.9.1) diff --git a/zookeeper-server/pom.xml b/zookeeper-server/pom.xml index f0ed16e67d4..f78e997f246 100644 --- a/zookeeper-server/pom.xml +++ b/zookeeper-server/pom.xml @@ -15,6 +15,7 @@ <module>zookeeper-server-common</module> <module>zookeeper-server</module> <module>zookeeper-server-3.8.1</module> + <module>zookeeper-server-3.9.1</module> </modules> <dependencies> <dependency> diff --git a/zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt b/zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt new file mode 100644 index 00000000000..295693f22d7 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt @@ -0,0 +1,2 @@ +# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +install_jar(zookeeper-server-3.9.1-jar-with-dependencies.jar) diff --git a/zookeeper-server/zookeeper-server-3.9.1/pom.xml b/zookeeper-server/zookeeper-server-3.9.1/pom.xml new file mode 100644 index 00000000000..77aec63a781 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/pom.xml @@ -0,0 +1,104 @@ +<?xml version="1.0"?> +<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>zookeeper-server-parent</artifactId> + <version>8-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + <artifactId>zookeeper-server-3.9.1</artifactId> + <packaging>container-plugin</packaging> + <version>8-SNAPSHOT</version> + <properties> + <zookeeper.version>3.9.1</zookeeper.version> + </properties> + <dependencies> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>zookeeper-server-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>zookeeper-client-common</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <!-- Don't use ZK version from zookeeper-client-common --> + <groupId>org.apache.zookeeper</groupId> + <artifactId>zookeeper</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.apache.zookeeper</groupId> + <artifactId>zookeeper</artifactId> + <version>${zookeeper.version}</version> + <exclusions> + <!-- + Container provides wiring for all common log libraries + Duplicate embedding results in various warnings being printed to stderr + --> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + </exclusion> + <exclusion> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- snappy-java and metrics-core are included here + to be able to work with ZooKeeper 3.7.0 due to + class loading issues --> + <dependency> + <groupId>io.dropwizard.metrics</groupId> + <artifactId>metrics-core</artifactId> + <scope>compile</scope> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.xerial.snappy</groupId> + <artifactId>snappy-java</artifactId> + <scope>compile</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <compilerArgs> + <arg>-Xlint:all</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + </plugin> + <plugin> + <groupId>com.yahoo.vespa</groupId> + <artifactId>bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <importPackage>com.sun.management</importPackage> + <bundleSymbolicName>zookeeper-server</bundleSymbolicName> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java new file mode 100644 index 00000000000..d986f02d89a --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java @@ -0,0 +1,43 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.zookeeper; + +import com.yahoo.cloud.config.ZookeeperServerConfig; +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.annotation.Inject; +import java.nio.file.Path; + +/** + * + * Server used for starting config server, needed to be able to have different behavior for hosted and + * self-hosted Vespa (controlled by zookeeperServerConfig.dynamicReconfiguration). + * + * @author Harald Musum + */ +public class ConfigServerZooKeeperServer extends AbstractComponent implements VespaZooKeeperServer { + + private final VespaZooKeeperServer zooKeeperServer; + + @Inject + public ConfigServerZooKeeperServer(ZookeeperServerConfig zookeeperServerConfig) { + this.zooKeeperServer = zookeeperServerConfig.dynamicReconfiguration() + ? new ReconfigurableVespaZooKeeperServer(new Reconfigurer(new VespaZooKeeperAdminImpl()), zookeeperServerConfig) + : new VespaZooKeeperServerImpl(zookeeperServerConfig); + } + + @Override + public void deconstruct() { zooKeeperServer.shutdown(); } + + @Override + public void shutdown() { + zooKeeperServer.shutdown(); + } + + @Override + public void start(Path configFilePath) { + zooKeeperServer.start(configFilePath); + } + + @Override + public boolean reconfigurable() { return zooKeeperServer.reconfigurable(); } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java new file mode 100644 index 00000000000..1b469beb1b8 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java @@ -0,0 +1,47 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.zookeeper; + +import ai.vespa.validation.Validation; +import com.yahoo.cloud.config.ZookeeperServerConfig; +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.annotation.Inject; +import java.nio.file.Path; +import java.time.Duration; + +/** + * Starts or reconfigures zookeeper cluster. + * The QuorumPeer conditionally created here is owned by the Reconfigurer; + * when it already has a peer, that peer is used here in case start or shutdown is required. + * Guarantees that server is up by writing a node to ZooKeeper successfully before + * returning from constructor. + * + * @author hmusum + */ +public class ReconfigurableVespaZooKeeperServer extends AbstractComponent implements VespaZooKeeperServer { + + private QuorumPeer peer; + + @Inject + public ReconfigurableVespaZooKeeperServer(Reconfigurer reconfigurer, ZookeeperServerConfig zookeeperServerConfig) { + Validation.require(zookeeperServerConfig.dynamicReconfiguration(), + zookeeperServerConfig.dynamicReconfiguration(), + "dynamicReconfiguration must be true"); + peer = reconfigurer.startOrReconfigure(zookeeperServerConfig, this, () -> peer = new VespaQuorumPeer()); + } + + @Override + public void shutdown() { + peer.shutdown(Duration.ofMinutes(1)); + } + + @Override + public void start(Path configFilePath) { + peer.start(configFilePath); + } + + @Override + public boolean reconfigurable() { + return true; + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java new file mode 100644 index 00000000000..3c8a373f121 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java @@ -0,0 +1,41 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.zookeeper; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.common.X509Exception; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.auth.AuthenticationProvider; +import org.apache.zookeeper.server.auth.X509AuthenticationProvider; + +import java.security.cert.X509Certificate; +import java.util.logging.Logger; + +/** + * A {@link AuthenticationProvider} to be used in combination with Vespa mTLS + * + * @author bjorncs + */ +public class VespaMtlsAuthenticationProvider extends X509AuthenticationProvider { + + private static final Logger log = Logger.getLogger(VespaMtlsAuthenticationProvider.class.getName()); + + public VespaMtlsAuthenticationProvider() throws X509Exception { super(null, null);} + + @Override + public KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte[] authData) { + // Vespa's mTLS peer authorization rules are performed by the underlying trust manager implementation. + // The client is authorized once the SSL handshake has completed. + X509Certificate[] certificateChain = (X509Certificate[]) cnxn.getClientCertificateChain(); + if (certificateChain == null || certificateChain.length == 0) { + log.warning("Client not authenticated - should not be possible with clientAuth=NEED"); + return KeeperException.Code.AUTHFAILED; + } + X509Certificate certificate = certificateChain[0]; + cnxn.addAuthInfo(new Id(getScheme(), certificate.getSubjectX500Principal().getName())); + return KeeperException.Code.OK; + } + + @Override public String getScheme() { return "x509"; } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java new file mode 100644 index 00000000000..dd5ac4e252b --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java @@ -0,0 +1,60 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.zookeeper; + +import com.yahoo.protect.Process; +import org.apache.zookeeper.server.admin.AdminServer; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; +import org.apache.zookeeper.server.quorum.QuorumPeerMain; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Starts or stops a ZooKeeper server. Extends QuorumPeerMain to be able to call initializeAndRun() and wraps + * exceptions so that it can be used by code that does not depend on ZooKeeper. + * + * @author hmusum + */ +class VespaQuorumPeer extends QuorumPeerMain implements QuorumPeer { + + private static final Logger log = java.util.logging.Logger.getLogger(VespaQuorumPeer.class.getName()); + + @Override + public void start(Path path) { + initializeAndRun(new String[]{ path.toFile().getAbsolutePath()}); + } + + @Override + public void shutdown(Duration timeout) { + if (quorumPeer != null) { + log.log(Level.FINE, "Shutting down ZooKeeper server"); + try { + quorumPeer.shutdown(); + quorumPeer.join(timeout.toMillis()); // Wait for shutdown to complete + if (quorumPeer.isAlive()) + throw new IllegalStateException("Peer still alive after " + timeout); + } catch (RuntimeException | InterruptedException e) { + // If shutdown fails, we have no other option than forcing the JVM to stop and letting it be restarted. + // + // When a VespaZooKeeperServer component receives a new config, the container will try to start a new + // server with the new config, this will fail until the old server is deconstructed. If the old server + // fails to deconstruct/shutdown, the new one will never start and if that happens forcing a restart is + // the better option. + Process.logAndDie("Failed to shut down ZooKeeper server properly, forcing shutdown", e); + } + } + } + + @Override + protected void initializeAndRun(String[] args) { + try { + super.initializeAndRun(args); + } catch (QuorumPeerConfig.ConfigException | IOException | AdminServer.AdminServerException e) { + throw new RuntimeException("Exception when initializing or running ZooKeeper server", e); + } + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java new file mode 100644 index 00000000000..1f15c758583 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java @@ -0,0 +1,93 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.zookeeper; + +import com.yahoo.cloud.config.ZookeeperServerConfig; +import com.yahoo.net.HostName; +import com.yahoo.vespa.zookeeper.client.ZkClientConfigBuilder; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.data.ACL; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author hmusum + */ +@SuppressWarnings("unused") // Created by injection +public class VespaZooKeeperAdminImpl implements VespaZooKeeperAdmin { + + private static final Logger log = java.util.logging.Logger.getLogger(VespaZooKeeperAdminImpl.class.getName()); + + @Override + public void reconfigure(String connectionSpec, String servers) throws ReconfigException { + try (ZooKeeperAdmin zooKeeperAdmin = createAdmin(connectionSpec)) { + long fromConfig = -1; + // Using string parameters because the List variant of reconfigure fails to join empty lists (observed on 3.5.6, fixed in 3.7.0). + log.log(Level.INFO, "Applying ZooKeeper config: " + servers); + byte[] appliedConfig = zooKeeperAdmin.reconfigure(null, null, servers, fromConfig, null); + log.log(Level.INFO, "Applied ZooKeeper config: " + new String(appliedConfig, StandardCharsets.UTF_8)); + + // Verify by issuing a write operation; this is only accepted once new quorum is obtained. + List<ACL> acl = ZooDefs.Ids.OPEN_ACL_UNSAFE; + String node = zooKeeperAdmin.create("/reconfigure-dummy-node", new byte[0], acl, CreateMode.EPHEMERAL_SEQUENTIAL); + zooKeeperAdmin.delete(node, -1); + + log.log(Level.INFO, "Verified ZooKeeper config: " + new String(appliedConfig, StandardCharsets.UTF_8)); + } + catch ( KeeperException.ReconfigInProgress + | KeeperException.ConnectionLossException + | KeeperException.NewConfigNoQuorum e) { + throw new ReconfigException(e); + } + catch (KeeperException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + private ZooKeeperAdmin createAdmin(String connectionSpec) { + return uncheck(() -> new ZooKeeperAdmin(connectionSpec, (int) sessionTimeout().toMillis(), + (event) -> log.log(Level.FINE, event.toString()), new ZkClientConfigBuilder().toConfig())); + } + + /** Creates a node in zookeeper, with hostname as part of node name, this ensures that server is up and working before returning */ + void createDummyNode(ZookeeperServerConfig zookeeperServerConfig) { + int sleepTime = 2_000; + try (ZooKeeperAdmin zooKeeperAdmin = createAdmin(localConnectionSpec(zookeeperServerConfig))) { + Instant end = Instant.now().plus(Duration.ofMinutes(5)); + Exception exception = null; + do { + try { + zooKeeperAdmin.create("/dummy-node-" + HostName.getLocalhost(), new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + return; + } catch (KeeperException e) { + if (e instanceof KeeperException.NodeExistsException) { + try { + zooKeeperAdmin.setData("/dummy-node-" + HostName.getLocalhost(), new byte[0], -1); + return; + } catch (KeeperException ex) { + log.log(Level.FINE, e.getMessage()); + Thread.sleep(sleepTime); + continue; + } + } + log.log(Level.FINE, e.getMessage()); + exception = e; + Thread.sleep(sleepTime); + } + } while (Instant.now().isBefore(end)); + throw new RuntimeException("Unable to create dummy node: ", exception); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} + diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java new file mode 100644 index 00000000000..4a7f85d6985 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java @@ -0,0 +1,54 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.zookeeper; + +import ai.vespa.validation.Validation; +import com.yahoo.cloud.config.ZookeeperServerConfig; +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.annotation.Inject; +import java.nio.file.Path; +import java.time.Duration; + +/** + * ZooKeeper server. Guarantees that the server is up by writing a node to ZooKeeper successfully before + * returning from constructor. + * + * @author Ulf Lilleengen + * @author Harald Musum + */ +public class VespaZooKeeperServerImpl extends AbstractComponent implements VespaZooKeeperServer { + + private final VespaQuorumPeer peer; + private final ZooKeeperRunner runner; + + @Inject + public VespaZooKeeperServerImpl(ZookeeperServerConfig zookeeperServerConfig) { + Validation.require(! zookeeperServerConfig.dynamicReconfiguration(), + ! zookeeperServerConfig.dynamicReconfiguration(), + "dynamicReconfiguration must be false"); + this.peer = new VespaQuorumPeer(); + this.runner = new ZooKeeperRunner(zookeeperServerConfig, this); + new VespaZooKeeperAdminImpl().createDummyNode(zookeeperServerConfig); + } + + @Override + public void deconstruct() { + runner.shutdown(); + super.deconstruct(); + } + + @Override + public void shutdown() { + peer.shutdown(Duration.ofMinutes(1)); + } + + @Override + public void start(Path configFilePath) { + peer.start(configFilePath); + } + + @Override + public boolean reconfigurable() { + return false; + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java new file mode 100644 index 00000000000..baa69f12968 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java @@ -0,0 +1,94 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.common; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +/** + * This class contains common utilities for netstuff. Like printing IPv6 literals correctly + */ +public class NetUtils { + + // Note: Changed from original to use hostname from InetSocketAddress if there exists one + public static String formatInetAddr(InetSocketAddress addr) { + String hostName = addr.getHostName(); + if (hostName != null) { + return String.format("%s:%s", hostName, addr.getPort()); + } + + InetAddress ia = addr.getAddress(); + + if (ia == null) { + return String.format("%s:%s", addr.getHostString(), addr.getPort()); + } + if (ia instanceof Inet6Address) { + return String.format("[%s]:%s", ia.getHostAddress(), addr.getPort()); + } else { + return String.format("%s:%s", ia.getHostAddress(), addr.getPort()); + } + } + + /** + * Separates host and port from given host port string if host port string is enclosed + * within square bracket. + * + * @param hostPort host port string + * @return String[]{host, port} if host port string is host:port + * or String[] {host, port:port} if host port string is host:port:port + * or String[] {host} if host port string is host + * or String[]{} if not a ipv6 host port string. + */ + public static String[] getIPV6HostAndPort(String hostPort) { + if (hostPort.startsWith("[")) { + int i = hostPort.lastIndexOf(']'); + if (i < 0) { + throw new IllegalArgumentException( + hostPort + " starts with '[' but has no matching ']'"); + } + String host = hostPort.substring(1, i); + if (host.isEmpty()) { + throw new IllegalArgumentException(host + " is empty."); + } + if (hostPort.length() > i + 1) { + return getHostPort(hostPort, i, host); + } + return new String[] { host }; + } else { + //Not an IPV6 host port string + return new String[] {}; + } + } + + private static String[] getHostPort(String hostPort, int indexOfClosingBracket, String host) { + // [127::1]:2181 , check separator : exits + if (hostPort.charAt(indexOfClosingBracket + 1) != ':') { + throw new IllegalArgumentException(hostPort + " does not have : after ]"); + } + // [127::1]: scenario + if (indexOfClosingBracket + 2 == hostPort.length()) { + throw new IllegalArgumentException(hostPort + " doesn't have a port after colon."); + } + //do not include + String port = hostPort.substring(indexOfClosingBracket + 2); + return new String[] { host, port }; + } +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java new file mode 100644 index 00000000000..cf7f4c44015 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server; + +import java.io.Flushable; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.apache.zookeeper.common.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This RequestProcessor logs requests to disk. It batches the requests to do + * the io efficiently. The request is not passed to the next RequestProcessor + * until its log has been synced to disk. + * + * SyncRequestProcessor is used in 3 different cases + * 1. Leader - Sync request to disk and forward it to AckRequestProcessor which + * send ack back to itself. + * 2. Follower - Sync request to disk and forward request to + * SendAckRequestProcessor which send the packets to leader. + * SendAckRequestProcessor is flushable which allow us to force + * push packets to leader. + * 3. Observer - Sync committed request to disk (received as INFORM packet). + * It never send ack back to the leader, so the nextProcessor will + * be null. This change the semantic of txnlog on the observer + * since it only contains committed txns. + */ +public class SyncRequestProcessor extends ZooKeeperCriticalThread implements RequestProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(SyncRequestProcessor.class); + + private static final Request REQUEST_OF_DEATH = Request.requestOfDeath; + + private static class FlushRequest extends Request { + private final CountDownLatch latch = new CountDownLatch(1); + public FlushRequest() { + super(null, 0, 0, 0, null, null); + } + } + + private static final Request TURN_FORWARDING_DELAY_ON_REQUEST = new Request(null, 0, 0, 0, null, null); + private static final Request TURN_FORWARDING_DELAY_OFF_REQUEST = new Request(null, 0, 0, 0, null, null); + + private static class DelayingProcessor implements RequestProcessor, Flushable { + private final RequestProcessor next; + private Queue<Request> delayed = null; + private DelayingProcessor(RequestProcessor next) { + this.next = next; + } + @Override + public void flush() throws IOException { + if (delayed == null && next instanceof Flushable) { + ((Flushable) next).flush(); + } + } + @Override + public void processRequest(Request request) throws RequestProcessorException { + if (delayed == null) { + next.processRequest(request); + } else { + delayed.add(request); + } + } + @Override + public void shutdown() { + next.shutdown(); + } + private void startDelaying() { + if (delayed == null) { + delayed = new ArrayDeque<>(); + } + } + private void flushAndStopDelaying() throws RequestProcessorException { + if (delayed != null) { + for (Request request : delayed) { + next.processRequest(request); + } + delayed = null; + } + } + } + + /** The number of log entries to log before starting a snapshot */ + private static int snapCount = ZooKeeperServer.getSnapCount(); + + /** + * The total size of log entries before starting a snapshot + */ + private static long snapSizeInBytes = ZooKeeperServer.getSnapSizeInBytes(); + + /** + * Random numbers used to vary snapshot timing + */ + private int randRoll; + private long randSize; + + private final BlockingQueue<Request> queuedRequests = new LinkedBlockingQueue<>(); + + private final Semaphore snapThreadMutex = new Semaphore(1); + + private final ZooKeeperServer zks; + + private final DelayingProcessor nextProcessor; + + /** + * Transactions that have been written and are waiting to be flushed to + * disk. Basically this is the list of SyncItems whose callbacks will be + * invoked after flush returns successfully. + */ + private final Queue<Request> toFlush; + private long lastFlushTime; + + public SyncRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) { + super("SyncThread:" + zks.getServerId(), zks.getZooKeeperServerListener()); + this.zks = zks; + this.nextProcessor = nextProcessor == null ? null : new DelayingProcessor(nextProcessor); + this.toFlush = new ArrayDeque<>(zks.getMaxBatchSize()); + } + + /** + * used by tests to check for changing + * snapcounts + * @param count + */ + public static void setSnapCount(int count) { + snapCount = count; + } + + /** + * used by tests to get the snapcount + * @return the snapcount + */ + public static int getSnapCount() { + return snapCount; + } + + private long getRemainingDelay() { + long flushDelay = zks.getFlushDelay(); + long duration = Time.currentElapsedTime() - lastFlushTime; + if (duration < flushDelay) { + return flushDelay - duration; + } + return 0; + } + + /** If both flushDelay and maxMaxBatchSize are set (bigger than 0), flush + * whenever either condition is hit. If only one or the other is + * set, flush only when the relevant condition is hit. + */ + private boolean shouldFlush() { + long flushDelay = zks.getFlushDelay(); + long maxBatchSize = zks.getMaxBatchSize(); + if ((flushDelay > 0) && (getRemainingDelay() == 0)) { + return true; + } + return (maxBatchSize > 0) && (toFlush.size() >= maxBatchSize); + } + + /** + * used by tests to check for changing + * snapcounts + * @param size + */ + public static void setSnapSizeInBytes(long size) { + snapSizeInBytes = size; + } + + private boolean shouldSnapshot() { + int logCount = zks.getZKDatabase().getTxnCount(); + long logSize = zks.getZKDatabase().getTxnSize(); + return (logCount > (snapCount / 2 + randRoll)) + || (snapSizeInBytes > 0 && logSize > (snapSizeInBytes / 2 + randSize)); + } + + private void resetSnapshotStats() { + randRoll = ThreadLocalRandom.current().nextInt(snapCount / 2); + randSize = Math.abs(ThreadLocalRandom.current().nextLong() % (snapSizeInBytes / 2)); + } + + @Override + public void run() { + try { + // we do this in an attempt to ensure that not all of the servers + // in the ensemble take a snapshot at the same time + resetSnapshotStats(); + lastFlushTime = Time.currentElapsedTime(); + while (true) { + ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_SIZE.add(queuedRequests.size()); + + long pollTime = Math.min(zks.getMaxWriteQueuePollTime(), getRemainingDelay()); + Request si = queuedRequests.poll(pollTime, TimeUnit.MILLISECONDS); + if (si == null) { + /* We timed out looking for more writes to batch, go ahead and flush immediately */ + flush(); + si = queuedRequests.take(); + } + + if (si == REQUEST_OF_DEATH) { + break; + } + + if (si == TURN_FORWARDING_DELAY_ON_REQUEST) { + nextProcessor.startDelaying(); + continue; + } + if (si == TURN_FORWARDING_DELAY_OFF_REQUEST) { + nextProcessor.flushAndStopDelaying(); + continue; + } + + if (si instanceof FlushRequest) { + flush(); + ((FlushRequest) si).latch.countDown(); + continue; + } + + long startProcessTime = Time.currentElapsedTime(); + ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_TIME.add(startProcessTime - si.syncQueueStartTime); + + // track the number of records written to the log + if (!si.isThrottled() && zks.getZKDatabase().append(si)) { + if (shouldSnapshot()) { + resetSnapshotStats(); + // roll the log + zks.getZKDatabase().rollLog(); + // take a snapshot + if (!snapThreadMutex.tryAcquire()) { + LOG.warn("Too busy to snap, skipping"); + } else { + new ZooKeeperThread("Snapshot Thread") { + public void run() { + try { + zks.takeSnapshot(); + } catch (Exception e) { + LOG.warn("Unexpected exception", e); + } finally { + snapThreadMutex.release(); + } + } + }.start(); + } + } + } else if (toFlush.isEmpty()) { + // optimization for read heavy workloads + // iff this is a read or a throttled request(which doesn't need to be written to the disk), + // and there are no pending flushes (writes), then just pass this to the next processor + if (nextProcessor != null) { + nextProcessor.processRequest(si); + nextProcessor.flush(); + } + continue; + } + toFlush.add(si); + if (shouldFlush()) { + flush(); + } + ServerMetrics.getMetrics().SYNC_PROCESS_TIME.add(Time.currentElapsedTime() - startProcessTime); + } + } catch (Throwable t) { + handleException(this.getName(), t); + } + LOG.info("SyncRequestProcessor exited!"); + } + + /** Flushes all pending writes, and waits for this to complete. */ + public void syncFlush() throws InterruptedException { + FlushRequest marker = new FlushRequest(); + queuedRequests.add(marker); + marker.latch.await(); + } + + public void setDelayForwarding(boolean delayForwarding) { + queuedRequests.add(delayForwarding ? TURN_FORWARDING_DELAY_ON_REQUEST : TURN_FORWARDING_DELAY_OFF_REQUEST); + } + + private void flush() throws IOException, RequestProcessorException { + if (this.toFlush.isEmpty()) { + return; + } + + ServerMetrics.getMetrics().BATCH_SIZE.add(toFlush.size()); + + long flushStartTime = Time.currentElapsedTime(); + zks.getZKDatabase().commit(); + ServerMetrics.getMetrics().SYNC_PROCESSOR_FLUSH_TIME.add(Time.currentElapsedTime() - flushStartTime); + + if (this.nextProcessor == null) { + this.toFlush.clear(); + } else { + while (!this.toFlush.isEmpty()) { + final Request i = this.toFlush.remove(); + long latency = Time.currentElapsedTime() - i.syncQueueStartTime; + ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_AND_FLUSH_TIME.add(latency); + this.nextProcessor.processRequest(i); + } + nextProcessor.flush(); + } + lastFlushTime = Time.currentElapsedTime(); + } + + public void shutdown() { + LOG.info("Shutting down"); + queuedRequests.add(REQUEST_OF_DEATH); + try { + this.join(); + this.flush(); + } catch (InterruptedException e) { + LOG.warn("Interrupted while wating for {} to finish", this); + Thread.currentThread().interrupt(); + } catch (IOException e) { + LOG.warn("Got IO exception during shutdown"); + } catch (RequestProcessorException e) { + LOG.warn("Got request processor exception during shutdown"); + } + if (nextProcessor != null) { + nextProcessor.shutdown(); + } + } + + public void processRequest(final Request request) { + Objects.requireNonNull(request, "Request cannot be null"); + + request.syncQueueStartTime = Time.currentElapsedTime(); + queuedRequests.add(request); + ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUED.add(1); + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java new file mode 100644 index 00000000000..114d2987fe2 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java @@ -0,0 +1,37 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package org.apache.zookeeper.server; + +import com.yahoo.vespa.zookeeper.Configurator; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.logging.Logger; + +/** + * Overrides secure setting with value from {@link Configurator}. + * Workaround for incorrect handling of clientSecurePort in combination with ZooKeeper Dynamic Reconfiguration in 3.6.2 + * See https://issues.apache.org/jira/browse/ZOOKEEPER-3577. + * + * Using package {@link org.apache.zookeeper.server} as {@link NettyServerCnxnFactory#NettyServerCnxnFactory()} is package-private. + * + * @author bjorncs + */ +public class VespaNettyServerCnxnFactory extends NettyServerCnxnFactory { + + private static final Logger log = Logger.getLogger(VespaNettyServerCnxnFactory.class.getName()); + + private final boolean isSecure; + + public VespaNettyServerCnxnFactory() { + super(); + this.isSecure = Configurator.VespaNettyServerCnxnFactory_isSecure; + boolean portUnificationEnabled = Boolean.getBoolean(NettyServerCnxnFactory.PORT_UNIFICATION_KEY); + log.info(String.format("For %h: isSecure=%b, portUnification=%b", this, isSecure, portUnificationEnabled)); + } + + @Override + public void configure(InetSocketAddress addr, int maxClientCnxns, int backlog, boolean secure) throws IOException { + log.info(String.format("For %h: configured() invoked with parameter 'secure'=%b, overridden to %b", this, secure, isSecure)); + super.configure(addr, maxClientCnxns, backlog, isSecure); + } +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java new file mode 100644 index 00000000000..895bbeffa5f --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java @@ -0,0 +1,2410 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.zip.Adler32; +import java.util.zip.CheckedInputStream; +import javax.security.sasl.SaslException; +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.InputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.Environment; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.Code; +import org.apache.zookeeper.KeeperException.SessionExpiredException; +import org.apache.zookeeper.Quotas; +import org.apache.zookeeper.StatsTrack; +import org.apache.zookeeper.Version; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.ZookeeperBanner; +import org.apache.zookeeper.common.PathUtils; +import org.apache.zookeeper.common.StringUtils; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.StatPersisted; +import org.apache.zookeeper.jmx.MBeanRegistry; +import org.apache.zookeeper.metrics.MetricsContext; +import org.apache.zookeeper.proto.AuthPacket; +import org.apache.zookeeper.proto.ConnectRequest; +import org.apache.zookeeper.proto.ConnectResponse; +import org.apache.zookeeper.proto.CreateRequest; +import org.apache.zookeeper.proto.DeleteRequest; +import org.apache.zookeeper.proto.GetSASLRequest; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.proto.RequestHeader; +import org.apache.zookeeper.proto.SetACLRequest; +import org.apache.zookeeper.proto.SetDataRequest; +import org.apache.zookeeper.proto.SetSASLResponse; +import org.apache.zookeeper.server.DataTree.ProcessTxnResult; +import org.apache.zookeeper.server.RequestProcessor.RequestProcessorException; +import org.apache.zookeeper.server.ServerCnxn.CloseRequestException; +import org.apache.zookeeper.server.SessionTracker.Session; +import org.apache.zookeeper.server.SessionTracker.SessionExpirer; +import org.apache.zookeeper.server.auth.ProviderRegistry; +import org.apache.zookeeper.server.auth.ServerAuthenticationProvider; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; +import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer; +import org.apache.zookeeper.server.util.JvmPauseMonitor; +import org.apache.zookeeper.server.util.OSMXBean; +import org.apache.zookeeper.server.util.QuotaMetricsUtils; +import org.apache.zookeeper.server.util.RequestPathMetricsCollector; +import org.apache.zookeeper.txn.CreateSessionTxn; +import org.apache.zookeeper.txn.TxnDigest; +import org.apache.zookeeper.txn.TxnHeader; +import org.apache.zookeeper.util.ServiceUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements a simple standalone ZooKeeperServer. It sets up the + * following chain of RequestProcessors to process requests: + * PrepRequestProcessor -> SyncRequestProcessor -> FinalRequestProcessor + */ +public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider { + + protected static final Logger LOG; + private static final RateLogger RATE_LOGGER; + + public static final String GLOBAL_OUTSTANDING_LIMIT = "zookeeper.globalOutstandingLimit"; + + public static final String ENABLE_EAGER_ACL_CHECK = "zookeeper.enableEagerACLCheck"; + public static final String SKIP_ACL = "zookeeper.skipACL"; + public static final String ENFORCE_QUOTA = "zookeeper.enforceQuota"; + + // When enabled, will check ACL constraints appertained to the requests first, + // before sending the requests to the quorum. + static boolean enableEagerACLCheck; + + static final boolean skipACL; + + public static final boolean enforceQuota; + + public static final String SASL_SUPER_USER = "zookeeper.superUser"; + + public static final String ALLOW_SASL_FAILED_CLIENTS = "zookeeper.allowSaslFailedClients"; + public static final String ZOOKEEPER_DIGEST_ENABLED = "zookeeper.digest.enabled"; + private static boolean digestEnabled; + + public static final String ZOOKEEPER_SERIALIZE_LAST_PROCESSED_ZXID_ENABLED = "zookeeper.serializeLastProcessedZxid.enabled"; + private static boolean serializeLastProcessedZxidEnabled; + + // Add a enable/disable option for now, we should remove this one when + // this feature is confirmed to be stable + public static final String CLOSE_SESSION_TXN_ENABLED = "zookeeper.closeSessionTxn.enabled"; + private static boolean closeSessionTxnEnabled = true; + private volatile CountDownLatch restoreLatch; + + static { + LOG = LoggerFactory.getLogger(ZooKeeperServer.class); + + RATE_LOGGER = new RateLogger(LOG); + + ZookeeperBanner.printBanner(LOG); + + Environment.logEnv("Server environment:", LOG); + + enableEagerACLCheck = Boolean.getBoolean(ENABLE_EAGER_ACL_CHECK); + LOG.info("{} = {}", ENABLE_EAGER_ACL_CHECK, enableEagerACLCheck); + + skipACL = System.getProperty(SKIP_ACL, "no").equals("yes"); + if (skipACL) { + LOG.info("{}==\"yes\", ACL checks will be skipped", SKIP_ACL); + } + + enforceQuota = Boolean.parseBoolean(System.getProperty(ENFORCE_QUOTA, "false")); + if (enforceQuota) { + LOG.info("{} = {}, Quota Enforce enables", ENFORCE_QUOTA, enforceQuota); + } + + digestEnabled = Boolean.parseBoolean(System.getProperty(ZOOKEEPER_DIGEST_ENABLED, "true")); + LOG.info("{} = {}", ZOOKEEPER_DIGEST_ENABLED, digestEnabled); + + closeSessionTxnEnabled = Boolean.parseBoolean( + System.getProperty(CLOSE_SESSION_TXN_ENABLED, "true")); + LOG.info("{} = {}", CLOSE_SESSION_TXN_ENABLED, closeSessionTxnEnabled); + + setSerializeLastProcessedZxidEnabled(Boolean.parseBoolean( + System.getProperty(ZOOKEEPER_SERIALIZE_LAST_PROCESSED_ZXID_ENABLED, "true"))); + } + + // @VisibleForTesting + public static boolean isEnableEagerACLCheck() { + return enableEagerACLCheck; + } + + // @VisibleForTesting + public static void setEnableEagerACLCheck(boolean enabled) { + ZooKeeperServer.enableEagerACLCheck = enabled; + LOG.info("Update {} to {}", ENABLE_EAGER_ACL_CHECK, enabled); + } + + public static boolean isCloseSessionTxnEnabled() { + return closeSessionTxnEnabled; + } + + public static void setCloseSessionTxnEnabled(boolean enabled) { + ZooKeeperServer.closeSessionTxnEnabled = enabled; + LOG.info("Update {} to {}", CLOSE_SESSION_TXN_ENABLED, + ZooKeeperServer.closeSessionTxnEnabled); + } + + protected ZooKeeperServerBean jmxServerBean; + protected DataTreeBean jmxDataTreeBean; + + public static final int DEFAULT_TICK_TIME = 3000; + protected int tickTime = DEFAULT_TICK_TIME; + public static final int DEFAULT_THROTTLED_OP_WAIT_TIME = 0; // disabled + protected static volatile int throttledOpWaitTime = + Integer.getInteger("zookeeper.throttled_op_wait_time", DEFAULT_THROTTLED_OP_WAIT_TIME); + /** value of -1 indicates unset, use default */ + protected int minSessionTimeout = -1; + /** value of -1 indicates unset, use default */ + protected int maxSessionTimeout = -1; + /** Socket listen backlog. Value of -1 indicates unset */ + protected int listenBacklog = -1; + protected SessionTracker sessionTracker; + private FileTxnSnapLog txnLogFactory = null; + private ZKDatabase zkDb; + private ResponseCache readResponseCache; + private ResponseCache getChildrenResponseCache; + private final AtomicLong hzxid = new AtomicLong(0); + public static final Exception ok = new Exception("No prob"); + protected RequestProcessor firstProcessor; + protected JvmPauseMonitor jvmPauseMonitor; + protected volatile State state = State.INITIAL; + private boolean isResponseCachingEnabled = true; + /* contains the configuration file content read at startup */ + protected String initialConfig; + protected boolean reconfigEnabled; + private final RequestPathMetricsCollector requestPathMetricsCollector; + private static final int DEFAULT_SNAP_COUNT = 100000; + private static final int DEFAULT_GLOBAL_OUTSTANDING_LIMIT = 1000; + + private boolean localSessionEnabled = false; + protected enum State { + INITIAL, + RUNNING, + SHUTDOWN, + ERROR + } + + /** + * This is the secret that we use to generate passwords. For the moment, + * it's more of a checksum that's used in reconnection, which carries no + * security weight, and is treated internally as if it carries no + * security weight. + */ + private static final long superSecret = 0XB3415C00L; + + private final AtomicInteger requestsInProcess = new AtomicInteger(0); + final Deque<ChangeRecord> outstandingChanges = new ArrayDeque<>(); + // this data structure must be accessed under the outstandingChanges lock + final Map<String, ChangeRecord> outstandingChangesForPath = new HashMap<>(); + + protected ServerCnxnFactory serverCnxnFactory; + protected ServerCnxnFactory secureServerCnxnFactory; + + private final ServerStats serverStats; + private final ZooKeeperServerListener listener; + private ZooKeeperServerShutdownHandler zkShutdownHandler; + private volatile int createSessionTrackerServerId = 1; + + private static final String FLUSH_DELAY = "zookeeper.flushDelay"; + private static volatile long flushDelay; + private static final String MAX_WRITE_QUEUE_POLL_SIZE = "zookeeper.maxWriteQueuePollTime"; + private static volatile long maxWriteQueuePollTime; + private static final String MAX_BATCH_SIZE = "zookeeper.maxBatchSize"; + private static volatile int maxBatchSize; + + /** + * Starting size of read and write ByteArroyOuputBuffers. Default is 32 bytes. + * Flag not used for small transfers like connectResponses. + */ + public static final String INT_BUFFER_STARTING_SIZE_BYTES = "zookeeper.intBufferStartingSizeBytes"; + public static final int DEFAULT_STARTING_BUFFER_SIZE = 1024; + public static final int intBufferStartingSizeBytes; + + public static final String GET_DATA_RESPONSE_CACHE_SIZE = "zookeeper.maxResponseCacheSize"; + public static final String GET_CHILDREN_RESPONSE_CACHE_SIZE = "zookeeper.maxGetChildrenResponseCacheSize"; + + static { + long configuredFlushDelay = Long.getLong(FLUSH_DELAY, 0); + setFlushDelay(configuredFlushDelay); + setMaxWriteQueuePollTime(Long.getLong(MAX_WRITE_QUEUE_POLL_SIZE, configuredFlushDelay / 3)); + setMaxBatchSize(Integer.getInteger(MAX_BATCH_SIZE, 1000)); + + intBufferStartingSizeBytes = Integer.getInteger(INT_BUFFER_STARTING_SIZE_BYTES, DEFAULT_STARTING_BUFFER_SIZE); + + if (intBufferStartingSizeBytes < 32) { + String msg = "Buffer starting size (" + intBufferStartingSizeBytes + ") must be greater than or equal to 32. " + + "Configure with \"-Dzookeeper.intBufferStartingSizeBytes=<size>\" "; + LOG.error(msg); + throw new IllegalArgumentException(msg); + } + + LOG.info("{} = {}", INT_BUFFER_STARTING_SIZE_BYTES, intBufferStartingSizeBytes); + } + + // Connection throttling + private final BlueThrottle connThrottle = new BlueThrottle(); + + private RequestThrottler requestThrottler; + public static final String SNAP_COUNT = "zookeeper.snapCount"; + + /** + * This setting sets a limit on the total number of large requests that + * can be inflight and is designed to prevent ZooKeeper from accepting + * too many large requests such that the JVM runs out of usable heap and + * ultimately crashes. + * + * The limit is enforced by the {@link #checkRequestSizeWhenReceivingMessage(int)} + * method which is called by the connection layer ({@link NIOServerCnxn}, + * {@link NettyServerCnxn}) before allocating a byte buffer and pulling + * data off the TCP socket. The limit is then checked again by the + * ZooKeeper server in {@link #processPacket(ServerCnxn, RequestHeader, RequestRecord)} which + * also atomically updates {@link #currentLargeRequestBytes}. The request is + * then marked as a large request, with the request size stored in the Request + * object so that it can later be decremented from {@link #currentLargeRequestBytes}. + * + * When a request is completed or dropped, the relevant code path calls the + * {@link #requestFinished(Request)} method which performs the decrement if + * needed. + */ + private volatile int largeRequestMaxBytes = 100 * 1024 * 1024; + + /** + * The size threshold after which a request is considered a large request + * and is checked against the large request byte limit. + */ + private volatile int largeRequestThreshold = -1; + + private final AtomicInteger currentLargeRequestBytes = new AtomicInteger(0); + + private final AuthenticationHelper authHelper = new AuthenticationHelper(); + + void removeCnxn(ServerCnxn cnxn) { + zkDb.removeCnxn(cnxn); + } + + /** + * Creates a ZooKeeperServer instance. Nothing is setup, use the setX + * methods to prepare the instance (eg datadir, datalogdir, ticktime, + * builder, etc...) + * + */ + public ZooKeeperServer() { + listener = new ZooKeeperServerListenerImpl(this); + serverStats = new ServerStats(this); + this.requestPathMetricsCollector = new RequestPathMetricsCollector(); + } + + /** + * Keeping this constructor for backward compatibility + */ + public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int clientPortListenBacklog, ZKDatabase zkDb, String initialConfig) { + this(txnLogFactory, tickTime, minSessionTimeout, maxSessionTimeout, clientPortListenBacklog, zkDb, initialConfig, QuorumPeerConfig.isReconfigEnabled()); + } + + /** + * * Creates a ZooKeeperServer instance. It sets everything up, but doesn't + * actually start listening for clients until run() is invoked. + * + */ + public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int clientPortListenBacklog, ZKDatabase zkDb, String initialConfig, boolean reconfigEnabled) { + serverStats = new ServerStats(this); + this.txnLogFactory = txnLogFactory; + this.txnLogFactory.setServerStats(this.serverStats); + this.zkDb = zkDb; + this.tickTime = tickTime; + setMinSessionTimeout(minSessionTimeout); + setMaxSessionTimeout(maxSessionTimeout); + this.listenBacklog = clientPortListenBacklog; + this.reconfigEnabled = reconfigEnabled; + + listener = new ZooKeeperServerListenerImpl(this); + + readResponseCache = new ResponseCache(Integer.getInteger( + GET_DATA_RESPONSE_CACHE_SIZE, + ResponseCache.DEFAULT_RESPONSE_CACHE_SIZE), "getData"); + + getChildrenResponseCache = new ResponseCache(Integer.getInteger( + GET_CHILDREN_RESPONSE_CACHE_SIZE, + ResponseCache.DEFAULT_RESPONSE_CACHE_SIZE), "getChildren"); + + this.initialConfig = initialConfig; + + this.requestPathMetricsCollector = new RequestPathMetricsCollector(); + + this.initLargeRequestThrottlingSettings(); + + LOG.info( + "Created server with" + + " tickTime {} ms" + + " minSessionTimeout {} ms" + + " maxSessionTimeout {} ms" + + " clientPortListenBacklog {}" + + " datadir {}" + + " snapdir {}", + tickTime, + getMinSessionTimeout(), + getMaxSessionTimeout(), + getClientPortListenBacklog(), + txnLogFactory.getDataDir(), + txnLogFactory.getSnapDir()); + } + + public String getInitialConfig() { + return initialConfig; + } + + /** + * Adds JvmPauseMonitor and calls + * {@link #ZooKeeperServer(FileTxnSnapLog, int, int, int, int, ZKDatabase, String)} + * + */ + public ZooKeeperServer(JvmPauseMonitor jvmPauseMonitor, FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int clientPortListenBacklog, ZKDatabase zkDb, String initialConfig) { + this(txnLogFactory, tickTime, minSessionTimeout, maxSessionTimeout, clientPortListenBacklog, zkDb, initialConfig, QuorumPeerConfig.isReconfigEnabled()); + this.jvmPauseMonitor = jvmPauseMonitor; + if (jvmPauseMonitor != null) { + LOG.info("Added JvmPauseMonitor to server"); + } + } + + /** + * creates a zookeeperserver instance. + * @param txnLogFactory the file transaction snapshot logging class + * @param tickTime the ticktime for the server + */ + public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, String initialConfig) { + this(txnLogFactory, tickTime, -1, -1, -1, new ZKDatabase(txnLogFactory), initialConfig, QuorumPeerConfig.isReconfigEnabled()); + } + + public ServerStats serverStats() { + return serverStats; + } + + public RequestPathMetricsCollector getRequestPathMetricsCollector() { + return requestPathMetricsCollector; + } + + public BlueThrottle connThrottle() { + return connThrottle; + } + + public void dumpConf(PrintWriter pwriter) { + pwriter.print("clientPort="); + pwriter.println(getClientPort()); + pwriter.print("secureClientPort="); + pwriter.println(getSecureClientPort()); + pwriter.print("dataDir="); + pwriter.println(zkDb.snapLog.getSnapDir().getAbsolutePath()); + pwriter.print("dataDirSize="); + pwriter.println(getDataDirSize()); + pwriter.print("dataLogDir="); + pwriter.println(zkDb.snapLog.getDataDir().getAbsolutePath()); + pwriter.print("dataLogSize="); + pwriter.println(getLogDirSize()); + pwriter.print("tickTime="); + pwriter.println(getTickTime()); + pwriter.print("maxClientCnxns="); + pwriter.println(getMaxClientCnxnsPerHost()); + pwriter.print("minSessionTimeout="); + pwriter.println(getMinSessionTimeout()); + pwriter.print("maxSessionTimeout="); + pwriter.println(getMaxSessionTimeout()); + pwriter.print("clientPortListenBacklog="); + pwriter.println(getClientPortListenBacklog()); + + pwriter.print("serverId="); + pwriter.println(getServerId()); + } + + public ZooKeeperServerConf getConf() { + return new ZooKeeperServerConf( + getClientPort(), + zkDb.snapLog.getSnapDir().getAbsolutePath(), + zkDb.snapLog.getDataDir().getAbsolutePath(), + getTickTime(), + getMaxClientCnxnsPerHost(), + getMinSessionTimeout(), + getMaxSessionTimeout(), + getServerId(), + getClientPortListenBacklog()); + } + + /** + * This constructor is for backward compatibility with the existing unit + * test code. + * It defaults to FileLogProvider persistence provider. + */ + public ZooKeeperServer(File snapDir, File logDir, int tickTime) throws IOException { + this(new FileTxnSnapLog(snapDir, logDir), tickTime, ""); + } + + /** + * Default constructor, relies on the config for its argument values + * + * @throws IOException + */ + public ZooKeeperServer(FileTxnSnapLog txnLogFactory) throws IOException { + this(txnLogFactory, DEFAULT_TICK_TIME, -1, -1, -1, new ZKDatabase(txnLogFactory), "", QuorumPeerConfig.isReconfigEnabled()); + } + + /** + * get the zookeeper database for this server + * @return the zookeeper database for this server + */ + public ZKDatabase getZKDatabase() { + return this.zkDb; + } + + /** + * set the zkdatabase for this zookeeper server + * @param zkDb + */ + public void setZKDatabase(ZKDatabase zkDb) { + this.zkDb = zkDb; + } + + /** + * Restore sessions and data + */ + public void loadData() throws IOException, InterruptedException { + /* + * When a new leader starts executing Leader#lead, it + * invokes this method. The database, however, has been + * initialized before running leader election so that + * the server could pick its zxid for its initial vote. + * It does it by invoking QuorumPeer#getLastLoggedZxid. + * Consequently, we don't need to initialize it once more + * and avoid the penalty of loading it a second time. Not + * reloading it is particularly important for applications + * that host a large database. + * + * The following if block checks whether the database has + * been initialized or not. Note that this method is + * invoked by at least one other method: + * ZooKeeperServer#startdata. + * + * See ZOOKEEPER-1642 for more detail. + */ + if (zkDb.isInitialized()) { + setZxid(zkDb.getDataTreeLastProcessedZxid()); + } else { + setZxid(zkDb.loadDataBase()); + } + + // Clean up dead sessions + zkDb.getSessions().stream() + .filter(session -> zkDb.getSessionWithTimeOuts().get(session) == null) + .forEach(session -> killSession(session, zkDb.getDataTreeLastProcessedZxid())); + + // Make a clean snapshot + takeSnapshot(); + } + + public File takeSnapshot() throws IOException { + return takeSnapshot(false); + } + + public File takeSnapshot(boolean syncSnap) throws IOException { + return takeSnapshot(syncSnap, true, false); + } + + /** + * Takes a snapshot on the server. + * + * @param syncSnap syncSnap sync the snapshot immediately after write + * @param isSevere if true system exist, otherwise throw IOException + * @param fastForwardFromEdits whether fast forward database to the latest recorded transactions + * + * @return file snapshot file object + * @throws IOException + */ + public synchronized File takeSnapshot(boolean syncSnap, boolean isSevere, boolean fastForwardFromEdits) throws IOException { + long start = Time.currentElapsedTime(); + File snapFile = null; + try { + if (fastForwardFromEdits) { + zkDb.fastForwardDataBase(); + } + snapFile = txnLogFactory.save(zkDb.getDataTree(), zkDb.getSessionWithTimeOuts(), syncSnap); + } catch (IOException e) { + if (isSevere) { + LOG.error("Severe unrecoverable error, exiting", e); + // This is a severe error that we cannot recover from, + // so we need to exit + ServiceUtils.requestSystemExit(ExitCode.TXNLOG_ERROR_TAKING_SNAPSHOT.getValue()); + } else { + throw e; + } + } + long elapsed = Time.currentElapsedTime() - start; + LOG.info("Snapshot taken in {} ms", elapsed); + ServerMetrics.getMetrics().SNAPSHOT_TIME.add(elapsed); + return snapFile; + } + + /** + * Restores database from a snapshot. It is used by the restore admin server command. + * + * @param inputStream input stream of snapshot + * @return last processed zxid + */ + public synchronized long restoreFromSnapshot(final InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new IllegalArgumentException("InputStream can not be null when restoring from snapshot"); + } + + long start = Time.currentElapsedTime(); + LOG.info("Before restore database. lastProcessedZxid={}, nodeCount={},sessionCount={}", + getZKDatabase().getDataTreeLastProcessedZxid(), + getZKDatabase().dataTree.getNodeCount(), + getZKDatabase().getSessionCount()); + + // restore to a new zkDatabase + final ZKDatabase newZKDatabase = new ZKDatabase(this.txnLogFactory); + final CheckedInputStream cis = new CheckedInputStream(new BufferedInputStream(inputStream), new Adler32()); + final InputArchive ia = BinaryInputArchive.getArchive(cis); + newZKDatabase.deserializeSnapshot(ia, cis); + LOG.info("Restored to a new database. lastProcessedZxid={}, nodeCount={}, sessionCount={}", + newZKDatabase.getDataTreeLastProcessedZxid(), + newZKDatabase.dataTree.getNodeCount(), + newZKDatabase.getSessionCount()); + + // create a CountDownLatch + restoreLatch = new CountDownLatch(1); + + try { + // set to the new zkDatabase + setZKDatabase(newZKDatabase); + + // re-create SessionTrack + createSessionTracker(); + } finally { + // unblock request submission + restoreLatch.countDown(); + restoreLatch = null; + } + + LOG.info("After restore database. lastProcessedZxid={}, nodeCount={}, sessionCount={}", + getZKDatabase().getDataTreeLastProcessedZxid(), + getZKDatabase().dataTree.getNodeCount(), + getZKDatabase().getSessionCount()); + + long elapsed = Time.currentElapsedTime() - start; + LOG.info("Restore taken in {} ms", elapsed); + ServerMetrics.getMetrics().RESTORE_TIME.add(elapsed); + + return getLastProcessedZxid(); + } + + public boolean shouldForceWriteInitialSnapshotAfterLeaderElection() { + return txnLogFactory.shouldForceWriteInitialSnapshotAfterLeaderElection(); + } + + @Override + public long getDataDirSize() { + if (zkDb == null) { + return 0L; + } + File path = zkDb.snapLog.getDataDir(); + return getDirSize(path); + } + + @Override + public long getLogDirSize() { + if (zkDb == null) { + return 0L; + } + File path = zkDb.snapLog.getSnapDir(); + return getDirSize(path); + } + + private long getDirSize(File file) { + long size = 0L; + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + size += getDirSize(f); + } + } + } else { + size = file.length(); + } + return size; + } + + public long getZxid() { + return hzxid.get(); + } + + public SessionTracker getSessionTracker() { + return sessionTracker; + } + + long getNextZxid() { + return hzxid.incrementAndGet(); + } + + public void setZxid(long zxid) { + hzxid.set(zxid); + } + + private void close(long sessionId) { + Request si = new Request(null, sessionId, 0, OpCode.closeSession, null, null); + submitRequest(si); + } + + public void closeSession(long sessionId) { + LOG.info("Closing session 0x{}", Long.toHexString(sessionId)); + + // we do not want to wait for a session close. send it as soon as we + // detect it! + close(sessionId); + } + + protected void killSession(long sessionId, long zxid) { + zkDb.killSession(sessionId, zxid); + if (LOG.isTraceEnabled()) { + ZooTrace.logTraceMessage( + LOG, + ZooTrace.SESSION_TRACE_MASK, + "ZooKeeperServer --- killSession: 0x" + Long.toHexString(sessionId)); + } + if (sessionTracker != null) { + sessionTracker.removeSession(sessionId); + } + } + + public void expire(Session session) { + long sessionId = session.getSessionId(); + LOG.info( + "Expiring session 0x{}, timeout of {}ms exceeded", + Long.toHexString(sessionId), + session.getTimeout()); + close(sessionId); + } + + public void expire(long sessionId) { + LOG.info("forcibly expiring session 0x{}", Long.toHexString(sessionId)); + + close(sessionId); + } + + public static class MissingSessionException extends IOException { + + private static final long serialVersionUID = 7467414635467261007L; + + public MissingSessionException(String msg) { + super(msg); + } + + } + + void touch(ServerCnxn cnxn) throws MissingSessionException { + if (cnxn == null) { + return; + } + long id = cnxn.getSessionId(); + int to = cnxn.getSessionTimeout(); + if (!sessionTracker.touchSession(id, to)) { + throw new MissingSessionException("No session with sessionid 0x" + + Long.toHexString(id) + + " exists, probably expired and removed"); + } + } + + protected void registerJMX() { + // register with JMX + try { + jmxServerBean = new ZooKeeperServerBean(this); + MBeanRegistry.getInstance().register(jmxServerBean, null); + + try { + jmxDataTreeBean = new DataTreeBean(zkDb.getDataTree()); + MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxDataTreeBean = null; + } + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxServerBean = null; + } + } + + public void startdata() throws IOException, InterruptedException { + //check to see if zkDb is not null + if (zkDb == null) { + zkDb = new ZKDatabase(this.txnLogFactory); + } + if (!zkDb.isInitialized()) { + loadData(); + } + } + + public synchronized void startup() { + startupWithServerState(State.RUNNING); + } + + public synchronized void startupWithoutServing() { + startupWithServerState(State.INITIAL); + } + + public synchronized void startServing() { + setState(State.RUNNING); + notifyAll(); + } + + private void startupWithServerState(State state) { + if (sessionTracker == null) { + createSessionTracker(); + } + startSessionTracker(); + setupRequestProcessors(); + + startRequestThrottler(); + + registerJMX(); + + startJvmPauseMonitor(); + + registerMetrics(); + + setState(state); + + requestPathMetricsCollector.start(); + + localSessionEnabled = sessionTracker.isLocalSessionsEnabled(); + + notifyAll(); + } + + protected void startJvmPauseMonitor() { + if (this.jvmPauseMonitor != null) { + this.jvmPauseMonitor.serviceStart(); + } + } + + protected void startRequestThrottler() { + requestThrottler = createRequestThrottler(); + requestThrottler.start(); + } + + protected RequestThrottler createRequestThrottler() { + return new RequestThrottler(this); + } + + protected void setupRequestProcessors() { + RequestProcessor finalProcessor = new FinalRequestProcessor(this); + RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor); + ((SyncRequestProcessor) syncProcessor).start(); + firstProcessor = new PrepRequestProcessor(this, syncProcessor); + ((PrepRequestProcessor) firstProcessor).start(); + } + + public ZooKeeperServerListener getZooKeeperServerListener() { + return listener; + } + + /** + * Change the server ID used by {@link #createSessionTracker()}. Must be called prior to + * {@link #startup()} being called + * + * @param newId ID to use + */ + public void setCreateSessionTrackerServerId(int newId) { + createSessionTrackerServerId = newId; + } + + protected void createSessionTracker() { + sessionTracker = new SessionTrackerImpl(this, zkDb.getSessionWithTimeOuts(), tickTime, createSessionTrackerServerId, getZooKeeperServerListener()); + } + + protected void startSessionTracker() { + ((SessionTrackerImpl) sessionTracker).start(); + } + + /** + * Sets the state of ZooKeeper server. After changing the state, it notifies + * the server state change to a registered shutdown handler, if any. + * <p> + * The following are the server state transitions: + * <ul><li>During startup the server will be in the INITIAL state.</li> + * <li>After successfully starting, the server sets the state to RUNNING. + * </li> + * <li>The server transitions to the ERROR state if it hits an internal + * error. {@link ZooKeeperServerListenerImpl} notifies any critical resource + * error events, e.g., SyncRequestProcessor not being able to write a txn to + * disk.</li> + * <li>During shutdown the server sets the state to SHUTDOWN, which + * corresponds to the server not running.</li> + * + * <li>During maintenance (e.g. restore) the server sets the state to MAINTENANCE + * </li></ul> + * + * @param state new server state. + */ + protected void setState(State state) { + this.state = state; + // Notify server state changes to the registered shutdown handler, if any. + if (zkShutdownHandler != null) { + zkShutdownHandler.handle(state); + } else { + LOG.debug( + "ZKShutdownHandler is not registered, so ZooKeeper server" + + " won't take any action on ERROR or SHUTDOWN server state changes"); + } + } + + /** + * This can be used while shutting down the server to see whether the server + * is already shutdown or not. + * + * @return true if the server is running or server hits an error, false + * otherwise. + */ + protected boolean canShutdown() { + return state == State.RUNNING || state == State.ERROR; + } + + /** + * @return true if the server is running, false otherwise. + */ + public boolean isRunning() { + return state == State.RUNNING; + } + + public void shutdown() { + shutdown(false); + } + + /** + * Shut down the server instance + * @param fullyShutDown true if another server using the same database will not replace this one in the same process + */ + public synchronized void shutdown(boolean fullyShutDown) { + if (!canShutdown()) { + if (fullyShutDown && zkDb != null) { + zkDb.clear(); + } + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + return; + } + LOG.info("shutting down"); + + // new RuntimeException("Calling shutdown").printStackTrace(); + setState(State.SHUTDOWN); + + // unregister all metrics that are keeping a strong reference to this object + // subclasses will do their specific clean up + unregisterMetrics(); + + if (requestThrottler != null) { + requestThrottler.shutdown(); + } + + // Since sessionTracker and syncThreads poll we just have to + // set running to false and they will detect it during the poll + // interval. + if (sessionTracker != null) { + sessionTracker.shutdown(); + } + if (firstProcessor != null) { + firstProcessor.shutdown(); + } + if (jvmPauseMonitor != null) { + jvmPauseMonitor.serviceStop(); + } + + if (zkDb != null) { + if (fullyShutDown) { + zkDb.clear(); + } else { + // else there is no need to clear the database + // * When a new quorum is established we can still apply the diff + // on top of the same zkDb data + // * If we fetch a new snapshot from leader, the zkDb will be + // cleared anyway before loading the snapshot + try { + // This will fast-forward the database to the latest recorded transactions + zkDb.fastForwardDataBase(); + } catch (IOException e) { + LOG.error("Error updating DB", e); + zkDb.clear(); + } + } + } + + requestPathMetricsCollector.shutdown(); + unregisterJMX(); + } + + protected void unregisterJMX() { + // unregister from JMX + try { + if (jmxDataTreeBean != null) { + MBeanRegistry.getInstance().unregister(jmxDataTreeBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + try { + if (jmxServerBean != null) { + MBeanRegistry.getInstance().unregister(jmxServerBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxServerBean = null; + jmxDataTreeBean = null; + } + + public void incInProcess() { + requestsInProcess.incrementAndGet(); + } + + public void decInProcess() { + requestsInProcess.decrementAndGet(); + if (requestThrottler != null) { + requestThrottler.throttleWake(); + } + } + + public int getInProcess() { + return requestsInProcess.get(); + } + + public int getInflight() { + return requestThrottleInflight(); + } + + private int requestThrottleInflight() { + if (requestThrottler != null) { + return requestThrottler.getInflight(); + } + return 0; + } + + static class PrecalculatedDigest { + final long nodeDigest; + final long treeDigest; + + PrecalculatedDigest(long nodeDigest, long treeDigest) { + this.nodeDigest = nodeDigest; + this.treeDigest = treeDigest; + } + } + + + /** + * This structure is used to facilitate information sharing between PrepRP + * and FinalRP. + */ + static class ChangeRecord { + PrecalculatedDigest precalculatedDigest; + byte[] data; + + ChangeRecord(long zxid, String path, StatPersisted stat, int childCount, List<ACL> acl) { + this.zxid = zxid; + this.path = path; + this.stat = stat; + this.childCount = childCount; + this.acl = acl; + } + + long zxid; + + String path; + + StatPersisted stat; /* Make sure to create a new object when changing */ + + int childCount; + + List<ACL> acl; /* Make sure to create a new object when changing */ + + ChangeRecord duplicate(long zxid) { + StatPersisted stat = new StatPersisted(); + if (this.stat != null) { + DataTree.copyStatPersisted(this.stat, stat); + } + ChangeRecord changeRecord = new ChangeRecord(zxid, path, stat, childCount, + acl == null ? new ArrayList<>() : new ArrayList<>(acl)); + changeRecord.precalculatedDigest = precalculatedDigest; + changeRecord.data = data; + return changeRecord; + } + + } + + byte[] generatePasswd(long id) { + Random r = new Random(id ^ superSecret); + byte[] p = new byte[16]; + r.nextBytes(p); + return p; + } + + protected boolean checkPasswd(long sessionId, byte[] passwd) { + return sessionId != 0 && Arrays.equals(passwd, generatePasswd(sessionId)); + } + + long createSession(ServerCnxn cnxn, byte[] passwd, int timeout) { + if (passwd == null) { + // Possible since it's just deserialized from a packet on the wire. + passwd = new byte[0]; + } + long sessionId = sessionTracker.createSession(timeout); + Random r = new Random(sessionId ^ superSecret); + r.nextBytes(passwd); + CreateSessionTxn txn = new CreateSessionTxn(timeout); + cnxn.setSessionId(sessionId); + Request si = new Request(cnxn, sessionId, 0, OpCode.createSession, RequestRecord.fromRecord(txn), null); + submitRequest(si); + return sessionId; + } + + /** + * set the owner of this session as owner + * @param id the session id + * @param owner the owner of the session + * @throws SessionExpiredException + */ + public void setOwner(long id, Object owner) throws SessionExpiredException { + sessionTracker.setOwner(id, owner); + } + + protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException { + boolean rc = sessionTracker.touchSession(sessionId, sessionTimeout); + if (LOG.isTraceEnabled()) { + ZooTrace.logTraceMessage( + LOG, + ZooTrace.SESSION_TRACE_MASK, + "Session 0x" + Long.toHexString(sessionId) + " is valid: " + rc); + } + finishSessionInit(cnxn, rc); + } + + public void reopenSession(ServerCnxn cnxn, long sessionId, byte[] passwd, int sessionTimeout) throws IOException { + if (checkPasswd(sessionId, passwd)) { + revalidateSession(cnxn, sessionId, sessionTimeout); + } else { + LOG.warn( + "Incorrect password from {} for session 0x{}", + cnxn.getRemoteSocketAddress(), + Long.toHexString(sessionId)); + finishSessionInit(cnxn, false); + } + } + + public void finishSessionInit(ServerCnxn cnxn, boolean valid) { + // register with JMX + try { + if (valid) { + if (serverCnxnFactory != null && serverCnxnFactory.cnxns.contains(cnxn)) { + serverCnxnFactory.registerConnection(cnxn); + } else if (secureServerCnxnFactory != null && secureServerCnxnFactory.cnxns.contains(cnxn)) { + secureServerCnxnFactory.registerConnection(cnxn); + } + } + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + } + + try { + ConnectResponse rsp = new ConnectResponse( + 0, + valid ? cnxn.getSessionTimeout() : 0, + valid ? cnxn.getSessionId() : 0, // send 0 if session is no + // longer valid + valid ? generatePasswd(cnxn.getSessionId()) : new byte[16], + this instanceof ReadOnlyZooKeeperServer); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive bos = BinaryOutputArchive.getArchive(baos); + bos.writeInt(-1, "len"); + rsp.serialize(bos, "connect"); + baos.close(); + ByteBuffer bb = ByteBuffer.wrap(baos.toByteArray()); + bb.putInt(bb.remaining() - 4).rewind(); + cnxn.sendBuffer(bb); + + if (valid) { + LOG.debug( + "Established session 0x{} with negotiated timeout {} for client {}", + Long.toHexString(cnxn.getSessionId()), + cnxn.getSessionTimeout(), + cnxn.getRemoteSocketAddress()); + cnxn.enableRecv(); + } else { + + LOG.info( + "Invalid session 0x{} for client {}, probably expired", + Long.toHexString(cnxn.getSessionId()), + cnxn.getRemoteSocketAddress()); + cnxn.sendBuffer(ServerCnxnFactory.closeConn); + } + + } catch (Exception e) { + LOG.warn("Exception while establishing session, closing", e); + cnxn.close(ServerCnxn.DisconnectReason.IO_EXCEPTION_IN_SESSION_INIT); + } + } + + public void closeSession(ServerCnxn cnxn, RequestHeader requestHeader) { + closeSession(cnxn.getSessionId()); + } + + public long getServerId() { + return 0; + } + + /** + * If the underlying Zookeeper server support local session, this method + * will set a isLocalSession to true if a request is associated with + * a local session. + * + * @param si + */ + protected void setLocalSessionFlag(Request si) { + } + + public void submitRequest(Request si) { + if (restoreLatch != null) { + try { + LOG.info("Blocking request submission while restore is in progress"); + restoreLatch.await(); + } catch (final InterruptedException e) { + LOG.warn("Unexpected interruption", e); + } + } + enqueueRequest(si); + } + + public void enqueueRequest(Request si) { + if (requestThrottler == null) { + synchronized (this) { + try { + // Since all requests are passed to the request + // processor it should wait for setting up the request + // processor chain. The state will be updated to RUNNING + // after the setup. + while (state == State.INITIAL) { + wait(1000); + } + } catch (InterruptedException e) { + LOG.warn("Unexpected interruption", e); + } + if (requestThrottler == null) { + throw new RuntimeException("Not started"); + } + } + } + requestThrottler.submitRequest(si); + } + + public void submitRequestNow(Request si) { + if (firstProcessor == null) { + synchronized (this) { + try { + // Since all requests are passed to the request + // processor it should wait for setting up the request + // processor chain. The state will be updated to RUNNING + // after the setup. + while (state == State.INITIAL) { + wait(1000); + } + } catch (InterruptedException e) { + LOG.warn("Unexpected interruption", e); + } + if (firstProcessor == null || state != State.RUNNING) { + throw new RuntimeException("Not started"); + } + } + } + try { + touch(si.cnxn); + boolean validpacket = Request.isValid(si.type); + if (validpacket) { + setLocalSessionFlag(si); + firstProcessor.processRequest(si); + if (si.cnxn != null) { + incInProcess(); + } + } else { + LOG.warn("Received packet at server of unknown type {}", si.type); + // Update request accounting/throttling limits + requestFinished(si); + new UnimplementedRequestProcessor().processRequest(si); + } + } catch (MissingSessionException e) { + LOG.debug("Dropping request.", e); + // Update request accounting/throttling limits + requestFinished(si); + } catch (RequestProcessorException e) { + LOG.error("Unable to process request", e); + // Update request accounting/throttling limits + requestFinished(si); + } + } + + public static int getSnapCount() { + int snapCount = Integer.getInteger(SNAP_COUNT, DEFAULT_SNAP_COUNT); + // snapCount must be 2 or more. See org.apache.zookeeper.server.SyncRequestProcessor + if (snapCount < 2) { + LOG.warn("SnapCount should be 2 or more. Now, snapCount is reset to 2"); + snapCount = 2; + } + return snapCount; + } + + public int getGlobalOutstandingLimit() { + return Integer.getInteger(GLOBAL_OUTSTANDING_LIMIT, DEFAULT_GLOBAL_OUTSTANDING_LIMIT); + } + + public static long getSnapSizeInBytes() { + long size = Long.getLong("zookeeper.snapSizeLimitInKb", 4194304L); // 4GB by default + if (size <= 0) { + LOG.info("zookeeper.snapSizeLimitInKb set to a non-positive value {}; disabling feature", size); + } + return size * 1024; // Convert to bytes + } + + public void setServerCnxnFactory(ServerCnxnFactory factory) { + serverCnxnFactory = factory; + } + + public ServerCnxnFactory getServerCnxnFactory() { + return serverCnxnFactory; + } + + public ServerCnxnFactory getSecureServerCnxnFactory() { + return secureServerCnxnFactory; + } + + public void setSecureServerCnxnFactory(ServerCnxnFactory factory) { + secureServerCnxnFactory = factory; + } + + /** + * return the last processed id from the + * datatree + */ + public long getLastProcessedZxid() { + return zkDb.getDataTreeLastProcessedZxid(); + } + + /** + * return the outstanding requests + * in the queue, which haven't been + * processed yet + */ + public long getOutstandingRequests() { + return getInProcess(); + } + + /** + * return the total number of client connections that are alive + * to this server + */ + public int getNumAliveConnections() { + int numAliveConnections = 0; + + if (serverCnxnFactory != null) { + numAliveConnections += serverCnxnFactory.getNumAliveConnections(); + } + + if (secureServerCnxnFactory != null) { + numAliveConnections += secureServerCnxnFactory.getNumAliveConnections(); + } + + return numAliveConnections; + } + + /** + * truncate the log to get in sync with others + * if in a quorum + * @param zxid the zxid that it needs to get in sync + * with others + * @throws IOException + */ + public void truncateLog(long zxid) throws IOException { + this.zkDb.truncateLog(zxid); + } + + public int getTickTime() { + return tickTime; + } + + public void setTickTime(int tickTime) { + LOG.info("tickTime set to {} ms", tickTime); + this.tickTime = tickTime; + } + + public static int getThrottledOpWaitTime() { + return throttledOpWaitTime; + } + + public static void setThrottledOpWaitTime(int time) { + LOG.info("throttledOpWaitTime set to {} ms", time); + throttledOpWaitTime = time; + } + + public int getMinSessionTimeout() { + return minSessionTimeout; + } + + public void setMinSessionTimeout(int min) { + this.minSessionTimeout = min == -1 ? tickTime * 2 : min; + LOG.info("minSessionTimeout set to {} ms", this.minSessionTimeout); + } + + public int getMaxSessionTimeout() { + return maxSessionTimeout; + } + + public void setMaxSessionTimeout(int max) { + this.maxSessionTimeout = max == -1 ? tickTime * 20 : max; + LOG.info("maxSessionTimeout set to {} ms", this.maxSessionTimeout); + } + + public int getClientPortListenBacklog() { + return listenBacklog; + } + + public void setClientPortListenBacklog(int backlog) { + this.listenBacklog = backlog; + LOG.info("clientPortListenBacklog set to {}", backlog); + } + + public int getClientPort() { + return serverCnxnFactory != null ? serverCnxnFactory.getLocalPort() : -1; + } + + public int getSecureClientPort() { + return secureServerCnxnFactory != null ? secureServerCnxnFactory.getLocalPort() : -1; + } + + /** Maximum number of connections allowed from particular host (ip) */ + public int getMaxClientCnxnsPerHost() { + if (serverCnxnFactory != null) { + return serverCnxnFactory.getMaxClientCnxnsPerHost(); + } + if (secureServerCnxnFactory != null) { + return secureServerCnxnFactory.getMaxClientCnxnsPerHost(); + } + return -1; + } + + public void setTxnLogFactory(FileTxnSnapLog txnLog) { + this.txnLogFactory = txnLog; + } + + public FileTxnSnapLog getTxnLogFactory() { + return this.txnLogFactory; + } + + /** + * Returns the elapsed sync of time of transaction log in milliseconds. + */ + public long getTxnLogElapsedSyncTime() { + return txnLogFactory.getTxnLogElapsedSyncTime(); + } + + public String getState() { + return "standalone"; + } + + public void dumpEphemerals(PrintWriter pwriter) { + zkDb.dumpEphemerals(pwriter); + } + + public Map<Long, Set<String>> getEphemerals() { + return zkDb.getEphemerals(); + } + + public double getConnectionDropChance() { + return connThrottle.getDropChance(); + } + + public void processConnectRequest(ServerCnxn cnxn, ConnectRequest request) throws IOException, ClientCnxnLimitException { + LOG.debug( + "Session establishment request from client {} client's lastZxid is 0x{}", + cnxn.getRemoteSocketAddress(), + Long.toHexString(request.getLastZxidSeen())); + + long sessionId = request.getSessionId(); + int tokensNeeded = 1; + if (connThrottle.isConnectionWeightEnabled()) { + if (sessionId == 0) { + if (localSessionEnabled) { + tokensNeeded = connThrottle.getRequiredTokensForLocal(); + } else { + tokensNeeded = connThrottle.getRequiredTokensForGlobal(); + } + } else { + tokensNeeded = connThrottle.getRequiredTokensForRenew(); + } + } + + if (!connThrottle.checkLimit(tokensNeeded)) { + throw new ClientCnxnLimitException(); + } + ServerMetrics.getMetrics().CONNECTION_TOKEN_DEFICIT.add(connThrottle.getDeficit()); + ServerMetrics.getMetrics().CONNECTION_REQUEST_COUNT.add(1); + + if (!cnxn.protocolManager.isReadonlyAvailable()) { + LOG.warn( + "Connection request from old client {}; will be dropped if server is in r-o mode", + cnxn.getRemoteSocketAddress()); + } + + if (!request.getReadOnly() && this instanceof ReadOnlyZooKeeperServer) { + String msg = "Refusing session request for not-read-only client " + cnxn.getRemoteSocketAddress(); + LOG.info(msg); + throw new CloseRequestException(msg, ServerCnxn.DisconnectReason.NOT_READ_ONLY_CLIENT); + } + if (request.getLastZxidSeen() > zkDb.dataTree.lastProcessedZxid) { + String msg = "Refusing session request for client " + + cnxn.getRemoteSocketAddress() + + " as it has seen zxid 0x" + + Long.toHexString(request.getLastZxidSeen()) + + " our last zxid is 0x" + + Long.toHexString(getZKDatabase().getDataTreeLastProcessedZxid()) + + " client must try another server"; + + LOG.info(msg); + throw new CloseRequestException(msg, ServerCnxn.DisconnectReason.CLIENT_ZXID_AHEAD); + } + int sessionTimeout = request.getTimeOut(); + byte[] passwd = request.getPasswd(); + int minSessionTimeout = getMinSessionTimeout(); + if (sessionTimeout < minSessionTimeout) { + sessionTimeout = minSessionTimeout; + } + int maxSessionTimeout = getMaxSessionTimeout(); + if (sessionTimeout > maxSessionTimeout) { + sessionTimeout = maxSessionTimeout; + } + cnxn.setSessionTimeout(sessionTimeout); + // We don't want to receive any packets until we are sure that the + // session is setup + cnxn.disableRecv(); + if (sessionId == 0) { + long id = createSession(cnxn, passwd, sessionTimeout); + LOG.debug( + "Client attempting to establish new session: session = 0x{}, zxid = 0x{}, timeout = {}, address = {}", + Long.toHexString(id), + Long.toHexString(request.getLastZxidSeen()), + request.getTimeOut(), + cnxn.getRemoteSocketAddress()); + } else { + validateSession(cnxn, sessionId); + LOG.debug( + "Client attempting to renew session: session = 0x{}, zxid = 0x{}, timeout = {}, address = {}", + Long.toHexString(sessionId), + Long.toHexString(request.getLastZxidSeen()), + request.getTimeOut(), + cnxn.getRemoteSocketAddress()); + if (serverCnxnFactory != null) { + serverCnxnFactory.closeSession(sessionId, ServerCnxn.DisconnectReason.CLIENT_RECONNECT); + } + if (secureServerCnxnFactory != null) { + secureServerCnxnFactory.closeSession(sessionId, ServerCnxn.DisconnectReason.CLIENT_RECONNECT); + } + cnxn.setSessionId(sessionId); + reopenSession(cnxn, sessionId, passwd, sessionTimeout); + ServerMetrics.getMetrics().CONNECTION_REVALIDATE_COUNT.add(1); + + } + } + + /** + * Validate if a particular session can be reestablished. + * + * @param cnxn + * @param sessionId + */ + protected void validateSession(ServerCnxn cnxn, long sessionId) + throws IOException { + // do nothing + } + + public boolean shouldThrottle(long outStandingCount) { + int globalOutstandingLimit = getGlobalOutstandingLimit(); + if (globalOutstandingLimit < getInflight() || globalOutstandingLimit < getInProcess()) { + return outStandingCount > 0; + } + return false; + } + + long getFlushDelay() { + return flushDelay; + } + + static void setFlushDelay(long delay) { + LOG.info("{} = {} ms", FLUSH_DELAY, delay); + flushDelay = delay; + } + + long getMaxWriteQueuePollTime() { + return maxWriteQueuePollTime; + } + + static void setMaxWriteQueuePollTime(long maxTime) { + LOG.info("{} = {} ms", MAX_WRITE_QUEUE_POLL_SIZE, maxTime); + maxWriteQueuePollTime = maxTime; + } + + int getMaxBatchSize() { + return maxBatchSize; + } + + static void setMaxBatchSize(int size) { + LOG.info("{}={}", MAX_BATCH_SIZE, size); + maxBatchSize = size; + } + + private void initLargeRequestThrottlingSettings() { + setLargeRequestMaxBytes(Integer.getInteger("zookeeper.largeRequestMaxBytes", largeRequestMaxBytes)); + setLargeRequestThreshold(Integer.getInteger("zookeeper.largeRequestThreshold", -1)); + } + + public int getLargeRequestMaxBytes() { + return largeRequestMaxBytes; + } + + public void setLargeRequestMaxBytes(int bytes) { + if (bytes <= 0) { + LOG.warn("Invalid max bytes for all large requests {}. It should be a positive number.", bytes); + LOG.warn("Will not change the setting. The max bytes stay at {}", largeRequestMaxBytes); + } else { + largeRequestMaxBytes = bytes; + LOG.info("The max bytes for all large requests are set to {}", largeRequestMaxBytes); + } + } + + public int getLargeRequestThreshold() { + return largeRequestThreshold; + } + + public void setLargeRequestThreshold(int threshold) { + if (threshold == 0 || threshold < -1) { + LOG.warn("Invalid large request threshold {}. It should be -1 or positive. Setting to -1 ", threshold); + largeRequestThreshold = -1; + } else { + largeRequestThreshold = threshold; + LOG.info("The large request threshold is set to {}", largeRequestThreshold); + } + } + + public int getLargeRequestBytes() { + return currentLargeRequestBytes.get(); + } + + private boolean isLargeRequest(int length) { + // The large request limit is disabled when threshold is -1 + if (largeRequestThreshold == -1) { + return false; + } + return length > largeRequestThreshold; + } + + public boolean checkRequestSizeWhenReceivingMessage(int length) throws IOException { + if (!isLargeRequest(length)) { + return true; + } + if (currentLargeRequestBytes.get() + length <= largeRequestMaxBytes) { + return true; + } else { + ServerMetrics.getMetrics().LARGE_REQUESTS_REJECTED.add(1); + throw new IOException("Rejecting large request"); + } + + } + + private boolean checkRequestSizeWhenMessageReceived(int length) throws IOException { + if (!isLargeRequest(length)) { + return true; + } + + int bytes = currentLargeRequestBytes.addAndGet(length); + if (bytes > largeRequestMaxBytes) { + currentLargeRequestBytes.addAndGet(-length); + ServerMetrics.getMetrics().LARGE_REQUESTS_REJECTED.add(1); + throw new IOException("Rejecting large request"); + } + return true; + } + + public void requestFinished(Request request) { + int largeRequestLength = request.getLargeRequestSize(); + if (largeRequestLength != -1) { + currentLargeRequestBytes.addAndGet(-largeRequestLength); + } + } + + public void processPacket(ServerCnxn cnxn, RequestHeader h, RequestRecord request) throws IOException { + // Need to increase the outstanding request count first, otherwise + // there might be a race condition that it enabled recv after + // processing request and then disabled when check throttling. + // + // Be aware that we're actually checking the global outstanding + // request before this request. + // + // It's fine if the IOException thrown before we decrease the count + // in cnxn, since it will close the cnxn anyway. + cnxn.incrOutstandingAndCheckThrottle(h); + + if (h.getType() == OpCode.auth) { + LOG.info("got auth packet {}", cnxn.getRemoteSocketAddress()); + AuthPacket authPacket = request.readRecord(AuthPacket::new); + String scheme = authPacket.getScheme(); + ServerAuthenticationProvider ap = ProviderRegistry.getServerProvider(scheme); + Code authReturn = KeeperException.Code.AUTHFAILED; + if (ap != null) { + try { + // handleAuthentication may close the connection, to allow the client to choose + // a different server to connect to. + authReturn = ap.handleAuthentication( + new ServerAuthenticationProvider.ServerObjs(this, cnxn), + authPacket.getAuth()); + } catch (RuntimeException e) { + LOG.warn("Caught runtime exception from AuthenticationProvider: {}", scheme, e); + authReturn = KeeperException.Code.AUTHFAILED; + } + } + if (authReturn == KeeperException.Code.OK) { + LOG.info("Session 0x{}: auth success for scheme {} and address {}", + Long.toHexString(cnxn.getSessionId()), scheme, + cnxn.getRemoteSocketAddress()); + ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.OK.intValue()); + cnxn.sendResponse(rh, null, null); + } else { + if (ap == null) { + LOG.warn( + "No authentication provider for scheme: {} has {}", + scheme, + ProviderRegistry.listProviders()); + } else { + LOG.warn("Authentication failed for scheme: {}", scheme); + } + // send a response... + ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.AUTHFAILED.intValue()); + cnxn.sendResponse(rh, null, null); + // ... and close connection + cnxn.sendBuffer(ServerCnxnFactory.closeConn); + cnxn.disableRecv(); + } + return; + } else if (h.getType() == OpCode.sasl) { + processSasl(request, cnxn, h); + } else { + if (!authHelper.enforceAuthentication(cnxn, h.getXid())) { + // Authentication enforcement is failed + // Already sent response to user about failure and closed the session, lets return + return; + } else { + Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), request, cnxn.getAuthInfo()); + int length = request.limit(); + if (isLargeRequest(length)) { + // checkRequestSize will throw IOException if request is rejected + checkRequestSizeWhenMessageReceived(length); + si.setLargeRequestSize(length); + } + si.setOwner(ServerCnxn.me); + submitRequest(si); + } + } + } + + private static boolean isSaslSuperUser(String id) { + if (id == null || id.isEmpty()) { + return false; + } + + Properties properties = System.getProperties(); + int prefixLen = SASL_SUPER_USER.length(); + + for (String k : properties.stringPropertyNames()) { + if (k.startsWith(SASL_SUPER_USER) + && (k.length() == prefixLen || k.charAt(prefixLen) == '.')) { + String value = properties.getProperty(k); + + if (value != null && value.equals(id)) { + return true; + } + } + } + + return false; + } + + private static boolean shouldAllowSaslFailedClientsConnect() { + return Boolean.getBoolean(ALLOW_SASL_FAILED_CLIENTS); + } + + private void processSasl(RequestRecord request, ServerCnxn cnxn, RequestHeader requestHeader) throws IOException { + LOG.debug("Responding to client SASL token."); + GetSASLRequest clientTokenRecord = request.readRecord(GetSASLRequest::new); + byte[] clientToken = clientTokenRecord.getToken(); + LOG.debug("Size of client SASL token: {}", clientToken.length); + byte[] responseToken = null; + try { + ZooKeeperSaslServer saslServer = cnxn.zooKeeperSaslServer; + try { + // note that clientToken might be empty (clientToken.length == 0): + // if using the DIGEST-MD5 mechanism, clientToken will be empty at the beginning of the + // SASL negotiation process. + responseToken = saslServer.evaluateResponse(clientToken); + if (saslServer.isComplete()) { + String authorizationID = saslServer.getAuthorizationID(); + LOG.info("Session 0x{}: adding SASL authorization for authorizationID: {}", + Long.toHexString(cnxn.getSessionId()), authorizationID); + cnxn.addAuthInfo(new Id("sasl", authorizationID)); + + if (isSaslSuperUser(authorizationID)) { + cnxn.addAuthInfo(new Id("super", "")); + LOG.info( + "Session 0x{}: Authenticated Id '{}' as super user", + Long.toHexString(cnxn.getSessionId()), + authorizationID); + } + } + } catch (SaslException e) { + LOG.warn("Client {} failed to SASL authenticate: {}", cnxn.getRemoteSocketAddress(), e); + if (shouldAllowSaslFailedClientsConnect() && !authHelper.isSaslAuthRequired()) { + LOG.warn("Maintaining client connection despite SASL authentication failure."); + } else { + int error; + if (authHelper.isSaslAuthRequired()) { + LOG.warn( + "Closing client connection due to server requires client SASL authenticaiton," + + "but client SASL authentication has failed, or client is not configured with SASL " + + "authentication."); + error = Code.SESSIONCLOSEDREQUIRESASLAUTH.intValue(); + } else { + LOG.warn("Closing client connection due to SASL authentication failure."); + error = Code.AUTHFAILED.intValue(); + } + + ReplyHeader replyHeader = new ReplyHeader(requestHeader.getXid(), 0, error); + cnxn.sendResponse(replyHeader, new SetSASLResponse(null), "response"); + cnxn.sendCloseSession(); + cnxn.disableRecv(); + return; + } + } + } catch (NullPointerException e) { + LOG.error("cnxn.saslServer is null: cnxn object did not initialize its saslServer properly."); + } + if (responseToken != null) { + LOG.debug("Size of server SASL response: {}", responseToken.length); + } + + ReplyHeader replyHeader = new ReplyHeader(requestHeader.getXid(), 0, Code.OK.intValue()); + Record record = new SetSASLResponse(responseToken); + cnxn.sendResponse(replyHeader, record, "response"); + } + + // entry point for quorum/Learner.java + public ProcessTxnResult processTxn(TxnHeader hdr, Record txn) { + processTxnForSessionEvents(null, hdr, txn); + return processTxnInDB(hdr, txn, null); + } + + // entry point for FinalRequestProcessor.java + public ProcessTxnResult processTxn(Request request) { + TxnHeader hdr = request.getHdr(); + processTxnForSessionEvents(request, hdr, request.getTxn()); + + final boolean writeRequest = (hdr != null); + final boolean quorumRequest = request.isQuorum(); + + // return fast w/o synchronization when we get a read + if (!writeRequest && !quorumRequest) { + return new ProcessTxnResult(); + } + synchronized (outstandingChanges) { + ProcessTxnResult rc = processTxnInDB(hdr, request.getTxn(), request.getTxnDigest()); + + // request.hdr is set for write requests, which are the only ones + // that add to outstandingChanges. + if (writeRequest) { + long zxid = hdr.getZxid(); + while (!outstandingChanges.isEmpty() + && outstandingChanges.peek().zxid <= zxid) { + ChangeRecord cr = outstandingChanges.remove(); + ServerMetrics.getMetrics().OUTSTANDING_CHANGES_REMOVED.add(1); + if (cr.zxid < zxid) { + LOG.warn( + "Zxid outstanding 0x{} is less than current 0x{}", + Long.toHexString(cr.zxid), + Long.toHexString(zxid)); + } + if (outstandingChangesForPath.get(cr.path) == cr) { + outstandingChangesForPath.remove(cr.path); + } + } + } + + // do not add non quorum packets to the queue. + if (quorumRequest) { + getZKDatabase().addCommittedProposal(request); + } + return rc; + } + } + + private void processTxnForSessionEvents(Request request, TxnHeader hdr, Record txn) { + int opCode = (request == null) ? hdr.getType() : request.type; + long sessionId = (request == null) ? hdr.getClientId() : request.sessionId; + + if (opCode == OpCode.createSession) { + if (hdr != null && txn instanceof CreateSessionTxn) { + CreateSessionTxn cst = (CreateSessionTxn) txn; + sessionTracker.commitSession(sessionId, cst.getTimeOut()); + } else if (request == null || !request.isLocalSession()) { + LOG.warn("*****>>>>> Got {} {}", txn.getClass(), txn.toString()); + } + } else if (opCode == OpCode.closeSession) { + sessionTracker.removeSession(sessionId); + } + } + + private ProcessTxnResult processTxnInDB(TxnHeader hdr, Record txn, TxnDigest digest) { + if (hdr == null) { + return new ProcessTxnResult(); + } else { + return getZKDatabase().processTxn(hdr, txn, digest); + } + } + + public Map<Long, Set<Long>> getSessionExpiryMap() { + return sessionTracker.getSessionExpiryMap(); + } + + /** + * This method is used to register the ZooKeeperServerShutdownHandler to get + * server's error or shutdown state change notifications. + * {@link ZooKeeperServerShutdownHandler#handle(State)} will be called for + * every server state changes {@link #setState(State)}. + * + * @param zkShutdownHandler shutdown handler + */ + void registerServerShutdownHandler(ZooKeeperServerShutdownHandler zkShutdownHandler) { + this.zkShutdownHandler = zkShutdownHandler; + } + + public boolean isResponseCachingEnabled() { + return isResponseCachingEnabled; + } + + public void setResponseCachingEnabled(boolean isEnabled) { + isResponseCachingEnabled = isEnabled; + } + + public ResponseCache getReadResponseCache() { + return isResponseCachingEnabled ? readResponseCache : null; + } + + public ResponseCache getGetChildrenResponseCache() { + return isResponseCachingEnabled ? getChildrenResponseCache : null; + } + + protected void registerMetrics() { + MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext(); + + final ZKDatabase zkdb = this.getZKDatabase(); + final ServerStats stats = this.serverStats(); + + rootContext.registerGauge("avg_latency", stats::getAvgLatency); + + rootContext.registerGauge("max_latency", stats::getMaxLatency); + rootContext.registerGauge("min_latency", stats::getMinLatency); + + rootContext.registerGauge("packets_received", stats::getPacketsReceived); + rootContext.registerGauge("packets_sent", stats::getPacketsSent); + rootContext.registerGauge("num_alive_connections", stats::getNumAliveClientConnections); + + rootContext.registerGauge("outstanding_requests", stats::getOutstandingRequests); + rootContext.registerGauge("uptime", stats::getUptime); + + rootContext.registerGauge("znode_count", zkdb::getNodeCount); + + rootContext.registerGauge("watch_count", zkdb.getDataTree()::getWatchCount); + rootContext.registerGauge("ephemerals_count", zkdb.getDataTree()::getEphemeralsCount); + + rootContext.registerGauge("approximate_data_size", zkdb.getDataTree()::cachedApproximateDataSize); + + rootContext.registerGauge("global_sessions", zkdb::getSessionCount); + rootContext.registerGauge("local_sessions", this.getSessionTracker()::getLocalSessionCount); + + OSMXBean osMbean = new OSMXBean(); + rootContext.registerGauge("open_file_descriptor_count", osMbean::getOpenFileDescriptorCount); + rootContext.registerGauge("max_file_descriptor_count", osMbean::getMaxFileDescriptorCount); + rootContext.registerGauge("connection_drop_probability", this::getConnectionDropChance); + + rootContext.registerGauge("last_client_response_size", stats.getClientResponseStats()::getLastBufferSize); + rootContext.registerGauge("max_client_response_size", stats.getClientResponseStats()::getMaxBufferSize); + rootContext.registerGauge("min_client_response_size", stats.getClientResponseStats()::getMinBufferSize); + + rootContext.registerGauge("outstanding_tls_handshake", this::getOutstandingHandshakeNum); + rootContext.registerGauge("auth_failed_count", stats::getAuthFailedCount); + rootContext.registerGauge("non_mtls_remote_conn_count", stats::getNonMTLSRemoteConnCount); + rootContext.registerGauge("non_mtls_local_conn_count", stats::getNonMTLSLocalConnCount); + + rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE, + () -> QuotaMetricsUtils.getQuotaCountLimit(zkDb.getDataTree())); + rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_LIMIT_PER_NAMESPACE, + () -> QuotaMetricsUtils.getQuotaBytesLimit(zkDb.getDataTree())); + rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE, + () -> QuotaMetricsUtils.getQuotaCountUsage(zkDb.getDataTree())); + rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_USAGE_PER_NAMESPACE, + () -> QuotaMetricsUtils.getQuotaBytesUsage(zkDb.getDataTree())); + } + + protected void unregisterMetrics() { + + MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext(); + + rootContext.unregisterGauge("avg_latency"); + + rootContext.unregisterGauge("max_latency"); + rootContext.unregisterGauge("min_latency"); + + rootContext.unregisterGauge("packets_received"); + rootContext.unregisterGauge("packets_sent"); + rootContext.unregisterGauge("num_alive_connections"); + + rootContext.unregisterGauge("outstanding_requests"); + rootContext.unregisterGauge("uptime"); + + rootContext.unregisterGauge("znode_count"); + + rootContext.unregisterGauge("watch_count"); + rootContext.unregisterGauge("ephemerals_count"); + rootContext.unregisterGauge("approximate_data_size"); + + rootContext.unregisterGauge("global_sessions"); + rootContext.unregisterGauge("local_sessions"); + + rootContext.unregisterGauge("open_file_descriptor_count"); + rootContext.unregisterGauge("max_file_descriptor_count"); + rootContext.unregisterGauge("connection_drop_probability"); + + rootContext.unregisterGauge("last_client_response_size"); + rootContext.unregisterGauge("max_client_response_size"); + rootContext.unregisterGauge("min_client_response_size"); + + rootContext.unregisterGauge("auth_failed_count"); + rootContext.unregisterGauge("non_mtls_remote_conn_count"); + rootContext.unregisterGauge("non_mtls_local_conn_count"); + + rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE); + rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_LIMIT_PER_NAMESPACE); + rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE); + rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_USAGE_PER_NAMESPACE); + } + + /** + * Hook into admin server, useful to expose additional data + * that do not represent metrics. + * + * @param response a sink which collects the data. + */ + public void dumpMonitorValues(BiConsumer<String, Object> response) { + ServerStats stats = serverStats(); + response.accept("version", Version.getFullVersion()); + response.accept("server_state", stats.getServerState()); + } + + /** + * Grant or deny authorization to an operation on a node as a function of: + * @param cnxn : the server connection or null for admin server commands + * @param acl : set of ACLs for the node + * @param perm : the permission that the client is requesting + * @param ids : the credentials supplied by the client + * @param path : the ZNode path + * @param setAcls : for set ACL operations, the list of ACLs being set. Otherwise null. + */ + public void checkACL(ServerCnxn cnxn, List<ACL> acl, int perm, List<Id> ids, String path, List<ACL> setAcls) throws KeeperException.NoAuthException { + if (skipACL) { + return; + } + + LOG.debug("Permission requested: {} ", perm); + LOG.debug("ACLs for node: {}", acl); + LOG.debug("Client credentials: {}", ids); + + if (acl == null || acl.size() == 0) { + return; + } + for (Id authId : ids) { + if (authId.getScheme().equals("super")) { + return; + } + } + for (ACL a : acl) { + Id id = a.getId(); + if ((a.getPerms() & perm) != 0) { + if (id.getScheme().equals("world") && id.getId().equals("anyone")) { + return; + } + ServerAuthenticationProvider ap = ProviderRegistry.getServerProvider(id.getScheme()); + if (ap != null) { + for (Id authId : ids) { + if (authId.getScheme().equals(id.getScheme()) + && ap.matches( + new ServerAuthenticationProvider.ServerObjs(this, cnxn), + new ServerAuthenticationProvider.MatchValues(path, authId.getId(), id.getId(), perm, setAcls))) { + return; + } + } + } + } + } + throw new KeeperException.NoAuthException(); + } + + /** + * check a path whether exceeded the quota. + * + * @param path + * the path of the node, used for the quota prefix check + * @param lastData + * the current node data, {@code null} for none + * @param data + * the data to be set, or {@code null} for none + * @param type + * currently, create and setData need to check quota + */ + public void checkQuota(String path, byte[] lastData, byte[] data, int type) throws KeeperException.QuotaExceededException { + if (!enforceQuota) { + return; + } + long dataBytes = (data == null) ? 0 : data.length; + ZKDatabase zkDatabase = getZKDatabase(); + String lastPrefix = zkDatabase.getDataTree().getMaxPrefixWithQuota(path); + if (StringUtils.isEmpty(lastPrefix)) { + return; + } + + final String namespace = PathUtils.getTopNamespace(path); + switch (type) { + case OpCode.create: + checkQuota(lastPrefix, dataBytes, 1, namespace); + break; + case OpCode.setData: + checkQuota(lastPrefix, dataBytes - (lastData == null ? 0 : lastData.length), 0, namespace); + break; + default: + throw new IllegalArgumentException("Unsupported OpCode for checkQuota: " + type); + } + } + + /** + * check a path whether exceeded the quota. + * + * @param lastPrefix + the path of the node which has a quota. + * @param bytesDiff + * the diff to be added to number of bytes + * @param countDiff + * the diff to be added to the count + * @param namespace + * the namespace for collecting quota exceeded errors + */ + private void checkQuota(String lastPrefix, long bytesDiff, long countDiff, String namespace) + throws KeeperException.QuotaExceededException { + LOG.debug("checkQuota: lastPrefix={}, bytesDiff={}, countDiff={}", lastPrefix, bytesDiff, countDiff); + + // now check the quota we set + String limitNode = Quotas.limitPath(lastPrefix); + DataNode node = getZKDatabase().getNode(limitNode); + StatsTrack limitStats; + if (node == null) { + // should not happen + LOG.error("Missing limit node for quota {}", limitNode); + return; + } + synchronized (node) { + limitStats = new StatsTrack(node.data); + } + //check the quota + boolean checkCountQuota = countDiff != 0 && (limitStats.getCount() > -1 || limitStats.getCountHardLimit() > -1); + boolean checkByteQuota = bytesDiff != 0 && (limitStats.getBytes() > -1 || limitStats.getByteHardLimit() > -1); + + if (!checkCountQuota && !checkByteQuota) { + return; + } + + //check the statPath quota + String statNode = Quotas.statPath(lastPrefix); + node = getZKDatabase().getNode(statNode); + + StatsTrack currentStats; + if (node == null) { + // should not happen + LOG.error("Missing node for stat {}", statNode); + return; + } + synchronized (node) { + currentStats = new StatsTrack(node.data); + } + + //check the Count Quota + if (checkCountQuota) { + long newCount = currentStats.getCount() + countDiff; + boolean isCountHardLimit = limitStats.getCountHardLimit() > -1; + long countLimit = isCountHardLimit ? limitStats.getCountHardLimit() : limitStats.getCount(); + + if (newCount > countLimit) { + String msg = "Quota exceeded: " + lastPrefix + " [current count=" + newCount + ", " + (isCountHardLimit ? "hard" : "soft") + "CountLimit=" + countLimit + "]"; + RATE_LOGGER.rateLimitLog(msg); + if (isCountHardLimit) { + updateQuotaExceededMetrics(namespace); + throw new KeeperException.QuotaExceededException(lastPrefix); + } + } + } + + //check the Byte Quota + if (checkByteQuota) { + long newBytes = currentStats.getBytes() + bytesDiff; + boolean isByteHardLimit = limitStats.getByteHardLimit() > -1; + long byteLimit = isByteHardLimit ? limitStats.getByteHardLimit() : limitStats.getBytes(); + if (newBytes > byteLimit) { + String msg = "Quota exceeded: " + lastPrefix + " [current bytes=" + newBytes + ", " + (isByteHardLimit ? "hard" : "soft") + "ByteLimit=" + byteLimit + "]"; + RATE_LOGGER.rateLimitLog(msg); + if (isByteHardLimit) { + updateQuotaExceededMetrics(namespace); + throw new KeeperException.QuotaExceededException(lastPrefix); + } + } + } + } + + public static boolean isDigestEnabled() { + return digestEnabled; + } + + public static void setDigestEnabled(boolean digestEnabled) { + LOG.info("{} = {}", ZOOKEEPER_DIGEST_ENABLED, digestEnabled); + ZooKeeperServer.digestEnabled = digestEnabled; + } + + public static boolean isSerializeLastProcessedZxidEnabled() { + return serializeLastProcessedZxidEnabled; + } + + public static void setSerializeLastProcessedZxidEnabled(boolean serializeLastZxidEnabled) { + serializeLastProcessedZxidEnabled = serializeLastZxidEnabled; + LOG.info("{} = {}", ZOOKEEPER_SERIALIZE_LAST_PROCESSED_ZXID_ENABLED, serializeLastZxidEnabled); + } + + /** + * Trim a path to get the immediate predecessor. + * + * @param path + * @return + * @throws KeeperException.BadArgumentsException + */ + private String parentPath(String path) throws KeeperException.BadArgumentsException { + int lastSlash = path.lastIndexOf('/'); + if (lastSlash == -1 || path.indexOf('\0') != -1 || getZKDatabase().isSpecialPath(path)) { + throw new KeeperException.BadArgumentsException(path); + } + return lastSlash == 0 ? "/" : path.substring(0, lastSlash); + } + + private String effectiveACLPath(Request request) throws KeeperException.BadArgumentsException, KeeperException.InvalidACLException { + boolean mustCheckACL = false; + String path = null; + List<ACL> acl = null; + + switch (request.type) { + case OpCode.create: + case OpCode.create2: { + CreateRequest req = request.readRequestRecordNoException(CreateRequest::new); + if (req != null) { + mustCheckACL = true; + acl = req.getAcl(); + path = parentPath(req.getPath()); + } + break; + } + case OpCode.delete: { + DeleteRequest req = request.readRequestRecordNoException(DeleteRequest::new); + if (req != null) { + path = parentPath(req.getPath()); + } + break; + } + case OpCode.setData: { + SetDataRequest req = request.readRequestRecordNoException(SetDataRequest::new); + if (req != null) { + path = req.getPath(); + } + break; + } + case OpCode.setACL: { + SetACLRequest req = request.readRequestRecordNoException(SetACLRequest::new); + if (req != null) { + mustCheckACL = true; + acl = req.getAcl(); + path = req.getPath(); + } + break; + } + } + + if (mustCheckACL) { + /* we ignore the extrapolated ACL returned by fixupACL because + * we only care about it being well-formed (and if it isn't, an + * exception will be raised). + */ + PrepRequestProcessor.fixupACL(path, request.authInfo, acl); + } + + return path; + } + + private int effectiveACLPerms(Request request) { + switch (request.type) { + case OpCode.create: + case OpCode.create2: + return ZooDefs.Perms.CREATE; + case OpCode.delete: + return ZooDefs.Perms.DELETE; + case OpCode.setData: + return ZooDefs.Perms.WRITE; + case OpCode.setACL: + return ZooDefs.Perms.ADMIN; + default: + return ZooDefs.Perms.ALL; + } + } + + /** + * Check Write Requests for Potential Access Restrictions + * <p> + * Before a request is being proposed to the quorum, lets check it + * against local ACLs. Non-write requests (read, session, etc.) + * are passed along. Invalid requests are sent a response. + * <p> + * While we are at it, if the request will set an ACL: make sure it's + * a valid one. + * + * @param request + * @return true if request is permitted, false if not. + */ + public boolean authWriteRequest(Request request) { + int err; + String pathToCheck; + + if (!enableEagerACLCheck) { + return true; + } + + err = KeeperException.Code.OK.intValue(); + + try { + pathToCheck = effectiveACLPath(request); + if (pathToCheck != null) { + checkACL(request.cnxn, zkDb.getACL(pathToCheck, null), effectiveACLPerms(request), request.authInfo, pathToCheck, null); + } + } catch (KeeperException.NoAuthException e) { + LOG.debug("Request failed ACL check", e); + err = e.code().intValue(); + } catch (KeeperException.InvalidACLException e) { + LOG.debug("Request has an invalid ACL check", e); + err = e.code().intValue(); + } catch (KeeperException.NoNodeException e) { + LOG.debug("ACL check against non-existent node: {}", e.getMessage()); + } catch (KeeperException.BadArgumentsException e) { + LOG.debug("ACL check against illegal node path: {}", e.getMessage()); + } catch (Throwable t) { + LOG.error("Uncaught exception in authWriteRequest with: ", t); + throw t; + } finally { + if (err != KeeperException.Code.OK.intValue()) { + /* This request has a bad ACL, so we are dismissing it early. */ + decInProcess(); + ReplyHeader rh = new ReplyHeader(request.cxid, 0, err); + try { + request.cnxn.sendResponse(rh, null, null); + } catch (IOException e) { + LOG.error("IOException : {}", e); + } + } + } + + return err == KeeperException.Code.OK.intValue(); + } + + public int getOutstandingHandshakeNum() { + if (serverCnxnFactory instanceof NettyServerCnxnFactory) { + return ((NettyServerCnxnFactory) serverCnxnFactory).getOutstandingHandshakeNum(); + } else { + return 0; + } + } + + public boolean isReconfigEnabled() { + return this.reconfigEnabled; + } + + public ZooKeeperServerShutdownHandler getZkShutdownHandler() { + return zkShutdownHandler; + } + + static void updateQuotaExceededMetrics(final String namespace) { + if (namespace == null) { + return; + } + ServerMetrics.getMetrics().QUOTA_EXCEEDED_ERROR_PER_NAMESPACE.add(namespace, 1); + } +} + diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java new file mode 100644 index 00000000000..1f629bed73d --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import javax.management.JMException; +import org.apache.zookeeper.KeeperException.SessionExpiredException; +import org.apache.zookeeper.jmx.MBeanRegistry; +import org.apache.zookeeper.metrics.MetricsContext; +import org.apache.zookeeper.server.ContainerManager; +import org.apache.zookeeper.server.DataTreeBean; +import org.apache.zookeeper.server.FinalRequestProcessor; +import org.apache.zookeeper.server.PrepRequestProcessor; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.RequestProcessor; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerMetrics; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; + +/** + * + * Just like the standard ZooKeeperServer. We just replace the request + * processors: PrepRequestProcessor -> ProposalRequestProcessor -> + * CommitProcessor -> Leader.ToBeAppliedRequestProcessor -> + * FinalRequestProcessor + */ +public class LeaderZooKeeperServer extends QuorumZooKeeperServer { + + private ContainerManager containerManager; // guarded by sync + + CommitProcessor commitProcessor; + + PrepRequestProcessor prepRequestProcessor; + + /** + * @throws IOException + */ + public LeaderZooKeeperServer(FileTxnSnapLog logFactory, QuorumPeer self, ZKDatabase zkDb) throws IOException { + super(logFactory, self.tickTime, self.minSessionTimeout, self.maxSessionTimeout, self.clientPortListenBacklog, zkDb, self); + } + + public Leader getLeader() { + return self.leader; + } + + @Override + protected void setupRequestProcessors() { + RequestProcessor finalProcessor = new FinalRequestProcessor(this); + RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(finalProcessor, getLeader()); + commitProcessor = new CommitProcessor(toBeAppliedProcessor, Long.toString(getServerId()), false, getZooKeeperServerListener()); + commitProcessor.start(); + ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this, commitProcessor); + proposalProcessor.initialize(); + prepRequestProcessor = new PrepRequestProcessor(this, proposalProcessor); + prepRequestProcessor.start(); + firstProcessor = new LeaderRequestProcessor(this, prepRequestProcessor); + + setupContainerManager(); + } + + private synchronized void setupContainerManager() { + containerManager = new ContainerManager( + getZKDatabase(), + prepRequestProcessor, + Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)), + Integer.getInteger("znode.container.maxPerMinute", 10000), + Long.getLong("znode.container.maxNeverUsedIntervalMs", 0) + ); + } + + @Override + public synchronized void startup() { + super.startup(); + if (containerManager != null) { + containerManager.start(); + } + } + + @Override + protected void registerMetrics() { + super.registerMetrics(); + + MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext(); + rootContext.registerGauge("learners", gaugeWithLeader( + (leader) -> leader.getLearners().size()) + ); + rootContext.registerGauge("synced_followers", gaugeWithLeader( + (leader) -> leader.getForwardingFollowers().size() + )); + rootContext.registerGauge("synced_non_voting_followers", gaugeWithLeader( + (leader) -> leader.getNonVotingFollowers().size() + )); + rootContext.registerGauge("synced_observers", self::getSynced_observers_metric); + rootContext.registerGauge("pending_syncs", gaugeWithLeader( + (leader) -> leader.getNumPendingSyncs() + )); + rootContext.registerGauge("leader_uptime", gaugeWithLeader( + (leader) -> leader.getUptime() + )); + rootContext.registerGauge("last_proposal_size", gaugeWithLeader( + (leader) -> leader.getProposalStats().getLastBufferSize() + )); + rootContext.registerGauge("max_proposal_size", gaugeWithLeader( + (leader) -> leader.getProposalStats().getMaxBufferSize() + )); + rootContext.registerGauge("min_proposal_size", gaugeWithLeader( + (leader) -> leader.getProposalStats().getMinBufferSize() + )); + } + + private org.apache.zookeeper.metrics.Gauge gaugeWithLeader(Function<Leader, Number> supplier) { + return () -> { + final Leader leader = getLeader(); + if (leader == null) { + return null; + } + return supplier.apply(leader); + }; + } + + @Override + protected void unregisterMetrics() { + super.unregisterMetrics(); + + MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext(); + rootContext.unregisterGauge("learners"); + rootContext.unregisterGauge("synced_followers"); + rootContext.unregisterGauge("synced_non_voting_followers"); + rootContext.unregisterGauge("synced_observers"); + rootContext.unregisterGauge("pending_syncs"); + rootContext.unregisterGauge("leader_uptime"); + + rootContext.unregisterGauge("last_proposal_size"); + rootContext.unregisterGauge("max_proposal_size"); + rootContext.unregisterGauge("min_proposal_size"); + } + + @Override + public synchronized void shutdown(boolean fullyShutDown) { + if (containerManager != null) { + containerManager.stop(); + } + super.shutdown(fullyShutDown); + } + + @Override + public int getGlobalOutstandingLimit() { + int divisor = self.getQuorumSize() > 2 ? self.getQuorumSize() - 1 : 1; + int globalOutstandingLimit = super.getGlobalOutstandingLimit() / divisor; + return globalOutstandingLimit; + } + + @Override + public void createSessionTracker() { + sessionTracker = new LeaderSessionTracker( + this, + getZKDatabase().getSessionWithTimeOuts(), + tickTime, + self.getMyId(), + self.areLocalSessionsEnabled(), + getZooKeeperServerListener()); + } + + public boolean touch(long sess, int to) { + return sessionTracker.touchSession(sess, to); + } + + public boolean checkIfValidGlobalSession(long sess, int to) { + if (self.areLocalSessionsEnabled() && !upgradeableSessionTracker.isGlobalSession(sess)) { + return false; + } + return sessionTracker.touchSession(sess, to); + } + + /** + * Requests coming from the learner should go directly to + * PrepRequestProcessor + * + * @param request + */ + public void submitLearnerRequest(Request request) { + /* + * Requests coming from the learner should have gone through + * submitRequest() on each server which already perform some request + * validation, so we don't need to do it again. + * + * Additionally, LearnerHandler should start submitting requests into + * the leader's pipeline only when the leader's server is started, so we + * can submit the request directly into PrepRequestProcessor. + * + * This is done so that requests from learners won't go through + * LeaderRequestProcessor which perform local session upgrade. + */ + prepRequestProcessor.processRequest(request); + } + + @Override + protected void registerJMX() { + // register with JMX + try { + jmxDataTreeBean = new DataTreeBean(getZKDatabase().getDataTree()); + MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxDataTreeBean = null; + } + } + + public void registerJMX(LeaderBean leaderBean, LocalPeerBean localPeerBean) { + // register with JMX + if (self.jmxLeaderElectionBean != null) { + try { + MBeanRegistry.getInstance().unregister(self.jmxLeaderElectionBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + } + self.jmxLeaderElectionBean = null; + } + + try { + jmxServerBean = leaderBean; + MBeanRegistry.getInstance().register(leaderBean, localPeerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxServerBean = null; + } + } + + boolean registerJMX(LearnerHandlerBean handlerBean) { + try { + MBeanRegistry.getInstance().register(handlerBean, jmxServerBean); + return true; + } catch (JMException e) { + LOG.warn("Could not register connection", e); + } + return false; + } + + @Override + protected void unregisterJMX() { + // unregister from JMX + try { + if (jmxDataTreeBean != null) { + MBeanRegistry.getInstance().unregister(jmxDataTreeBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxDataTreeBean = null; + } + + protected void unregisterJMX(Leader leader) { + // unregister from JMX + try { + if (jmxServerBean != null) { + MBeanRegistry.getInstance().unregister(jmxServerBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxServerBean = null; + } + + @Override + public String getState() { + return "leader"; + } + + /** + * Returns the id of the associated QuorumPeer, which will do for a unique + * id of this server. + */ + @Override + public long getServerId() { + return self.getMyId(); + } + + @Override + protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException { + super.revalidateSession(cnxn, sessionId, sessionTimeout); + try { + // setowner as the leader itself, unless updated + // via the follower handlers + setOwner(sessionId, ServerCnxn.me); + } catch (SessionExpiredException e) { + // this is ok, it just means that the session revalidation failed. + } + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java new file mode 100644 index 00000000000..8d8b6dabce8 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java @@ -0,0 +1,927 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLSocket; +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.InputArchive; +import org.apache.jute.OutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.common.X509Exception; +import org.apache.zookeeper.server.ExitCode; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerMetrics; +import org.apache.zookeeper.server.TxnLogEntry; +import org.apache.zookeeper.server.ZooTrace; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; +import org.apache.zookeeper.server.util.ConfigUtils; +import org.apache.zookeeper.server.util.MessageTracker; +import org.apache.zookeeper.server.util.SerializeUtils; +import org.apache.zookeeper.server.util.ZxidUtils; +import org.apache.zookeeper.txn.SetDataTxn; +import org.apache.zookeeper.txn.TxnDigest; +import org.apache.zookeeper.txn.TxnHeader; +import org.apache.zookeeper.util.ServiceUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is the superclass of two of the three main actors in a ZK + * ensemble: Followers and Observers. Both Followers and Observers share + * a good deal of code which is moved into Peer to avoid duplication. + */ +public class Learner { + + static class PacketInFlight { + + TxnHeader hdr; + Record rec; + TxnDigest digest; + + } + + QuorumPeer self; + LearnerZooKeeperServer zk; + + protected BufferedOutputStream bufferedOutput; + + protected Socket sock; + protected MultipleAddresses leaderAddr; + protected AtomicBoolean sockBeingClosed = new AtomicBoolean(false); + + /** + * Socket getter + */ + public Socket getSocket() { + return sock; + } + + LearnerSender sender = null; + protected InputArchive leaderIs; + protected OutputArchive leaderOs; + /** the protocol version of the leader */ + protected int leaderProtocolVersion = 0x01; + + private static final int BUFFERED_MESSAGE_SIZE = 10; + protected final MessageTracker messageTracker = new MessageTracker(BUFFERED_MESSAGE_SIZE); + + protected static final Logger LOG = LoggerFactory.getLogger(Learner.class); + + /** + * Time to wait after connection attempt with the Leader or LearnerMaster before this + * Learner tries to connect again. + */ + private static final int leaderConnectDelayDuringRetryMs = Integer.getInteger("zookeeper.leaderConnectDelayDuringRetryMs", 100); + + private static final boolean nodelay = System.getProperty("follower.nodelay", "true").equals("true"); + + public static final String LEARNER_ASYNC_SENDING = "zookeeper.learner.asyncSending"; + private static boolean asyncSending = + Boolean.parseBoolean(ConfigUtils.getPropertyBackwardCompatibleWay(LEARNER_ASYNC_SENDING)); + public static final String LEARNER_CLOSE_SOCKET_ASYNC = "zookeeper.learner.closeSocketAsync"; + public static final boolean closeSocketAsync = Boolean + .parseBoolean(ConfigUtils.getPropertyBackwardCompatibleWay(LEARNER_CLOSE_SOCKET_ASYNC)); + + static { + LOG.info("leaderConnectDelayDuringRetryMs: {}", leaderConnectDelayDuringRetryMs); + LOG.info("TCP NoDelay set to: {}", nodelay); + LOG.info("{} = {}", LEARNER_ASYNC_SENDING, asyncSending); + LOG.info("{} = {}", LEARNER_CLOSE_SOCKET_ASYNC, closeSocketAsync); + } + + final ConcurrentHashMap<Long, ServerCnxn> pendingRevalidations = new ConcurrentHashMap<>(); + + public int getPendingRevalidationsCount() { + return pendingRevalidations.size(); + } + + // for testing + protected static void setAsyncSending(boolean newMode) { + asyncSending = newMode; + LOG.info("{} = {}", LEARNER_ASYNC_SENDING, asyncSending); + + } + protected static boolean getAsyncSending() { + return asyncSending; + } + /** + * validate a session for a client + * + * @param clientId + * the client to be revalidated + * @param timeout + * the timeout for which the session is valid + * @throws IOException + */ + void validateSession(ServerCnxn cnxn, long clientId, int timeout) throws IOException { + LOG.info("Revalidating client: 0x{}", Long.toHexString(clientId)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeLong(clientId); + dos.writeInt(timeout); + dos.close(); + QuorumPacket qp = new QuorumPacket(Leader.REVALIDATE, -1, baos.toByteArray(), null); + pendingRevalidations.put(clientId, cnxn); + if (LOG.isTraceEnabled()) { + ZooTrace.logTraceMessage( + LOG, + ZooTrace.SESSION_TRACE_MASK, + "To validate session 0x" + Long.toHexString(clientId)); + } + writePacket(qp, true); + } + + /** + * write a packet to the leader. + * + * This method is called by multiple threads. We need to make sure that only one thread is writing to leaderOs at a time. + * When packets are sent synchronously, writing is done within a synchronization block. + * When packets are sent asynchronously, sender.queuePacket() is called, which writes to a BlockingQueue, which is thread-safe. + * Reading from this BlockingQueue and writing to leaderOs is the learner sender thread only. + * So we have only one thread writing to leaderOs at a time in either case. + * + * @param pp + * the proposal packet to be sent to the leader + * @throws IOException + */ + void writePacket(QuorumPacket pp, boolean flush) throws IOException { + if (asyncSending) { + sender.queuePacket(pp); + } else { + writePacketNow(pp, flush); + } + } + + void writePacketNow(QuorumPacket pp, boolean flush) throws IOException { + synchronized (leaderOs) { + if (pp != null) { + messageTracker.trackSent(pp.getType()); + leaderOs.writeRecord(pp, "packet"); + } + if (flush) { + bufferedOutput.flush(); + } + } + } + + /** + * Start thread that will forward any packet in the queue to the leader + */ + protected void startSendingThread() { + sender = new LearnerSender(this); + sender.start(); + } + + /** + * read a packet from the leader + * + * @param pp + * the packet to be instantiated + * @throws IOException + */ + void readPacket(QuorumPacket pp) throws IOException { + synchronized (leaderIs) { + leaderIs.readRecord(pp, "packet"); + messageTracker.trackReceived(pp.getType()); + } + if (LOG.isTraceEnabled()) { + final long traceMask = + (pp.getType() == Leader.PING) ? ZooTrace.SERVER_PING_TRACE_MASK + : ZooTrace.SERVER_PACKET_TRACE_MASK; + + ZooTrace.logQuorumPacket(LOG, traceMask, 'i', pp); + } + } + + /** + * send a request packet to the leader + * + * @param request + * the request from the client + * @throws IOException + */ + void request(Request request) throws IOException { + if (request.isThrottled()) { + LOG.error("Throttled request sent to leader: {}. Exiting", request); + ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue()); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream oa = new DataOutputStream(baos); + oa.writeLong(request.sessionId); + oa.writeInt(request.cxid); + oa.writeInt(request.type); + byte[] payload = request.readRequestBytes(); + if (payload != null) { + oa.write(payload); + } + oa.close(); + QuorumPacket qp = new QuorumPacket(Leader.REQUEST, -1, baos.toByteArray(), request.authInfo); + writePacket(qp, true); + } + + /** + * Returns the address of the node we think is the leader. + */ + protected QuorumServer findLeader() { + QuorumServer leaderServer = null; + // Find the leader by id + Vote current = self.getCurrentVote(); + for (QuorumServer s : self.getView().values()) { + if (s.id == current.getId()) { + // Ensure we have the leader's correct IP address before + // attempting to connect. + s.recreateSocketAddresses(); + leaderServer = s; + break; + } + } + if (leaderServer == null) { + LOG.warn("Couldn't find the leader with id = {}", current.getId()); + } + return leaderServer; + } + + /** + * Overridable helper method to return the System.nanoTime(). + * This method behaves identical to System.nanoTime(). + */ + protected long nanoTime() { + return System.nanoTime(); + } + + /** + * Overridable helper method to simply call sock.connect(). This can be + * overriden in tests to fake connection success/failure for connectToLeader. + */ + protected void sockConnect(Socket sock, InetSocketAddress addr, int timeout) throws IOException { + sock.connect(addr, timeout); + } + + /** + * Establish a connection with the LearnerMaster found by findLearnerMaster. + * Followers only connect to Leaders, Observers can connect to any active LearnerMaster. + * Retries until either initLimit time has elapsed or 5 tries have happened. + * @param multiAddr - the address of the Peer to connect to. + * @throws IOException - if the socket connection fails on the 5th attempt + * if there is an authentication failure while connecting to leader + */ + protected void connectToLeader(MultipleAddresses multiAddr, String hostname) throws IOException { + + this.leaderAddr = multiAddr; + Set<InetSocketAddress> addresses; + if (self.isMultiAddressReachabilityCheckEnabled()) { + // even if none of the addresses are reachable, we want to try to establish connection + // see ZOOKEEPER-3758 + addresses = multiAddr.getAllReachableAddressesOrAll(); + } else { + addresses = multiAddr.getAllAddresses(); + } + ExecutorService executor = Executors.newFixedThreadPool(addresses.size()); + CountDownLatch latch = new CountDownLatch(addresses.size()); + AtomicReference<Socket> socket = new AtomicReference<>(null); + addresses.stream().map(address -> new LeaderConnector(address, socket, latch)).forEach(executor::submit); + + try { + latch.await(); + } catch (InterruptedException e) { + LOG.warn("Interrupted while trying to connect to Leader", e); + } finally { + executor.shutdown(); + try { + if (!executor.awaitTermination(1, TimeUnit.SECONDS)) { + LOG.error("not all the LeaderConnector terminated properly"); + } + } catch (InterruptedException ie) { + LOG.error("Interrupted while terminating LeaderConnector executor.", ie); + } + } + + if (socket.get() == null) { + throw new IOException("Failed connect to " + multiAddr); + } else { + sock = socket.get(); + sockBeingClosed.set(false); + } + + self.authLearner.authenticate(sock, hostname); + + leaderIs = BinaryInputArchive.getArchive(new BufferedInputStream(sock.getInputStream())); + bufferedOutput = new BufferedOutputStream(sock.getOutputStream()); + leaderOs = BinaryOutputArchive.getArchive(bufferedOutput); + if (asyncSending) { + startSendingThread(); + } + } + + class LeaderConnector implements Runnable { + + private AtomicReference<Socket> socket; + private InetSocketAddress address; + private CountDownLatch latch; + + LeaderConnector(InetSocketAddress address, AtomicReference<Socket> socket, CountDownLatch latch) { + this.address = address; + this.socket = socket; + this.latch = latch; + } + + @Override + public void run() { + try { + Thread.currentThread().setName("LeaderConnector-" + address); + Socket sock = connectToLeader(); + + if (sock != null && sock.isConnected()) { + if (socket.compareAndSet(null, sock)) { + LOG.info("Successfully connected to leader, using address: {}", address); + } else { + LOG.info("Connection to the leader is already established, close the redundant connection"); + sock.close(); + } + } + + } catch (Exception e) { + LOG.error("Failed connect to {}", address, e); + } finally { + latch.countDown(); + } + } + + private Socket connectToLeader() throws IOException, X509Exception, InterruptedException { + Socket sock = createSocket(); + + // leader connection timeout defaults to tickTime * initLimit + int connectTimeout = self.tickTime * self.initLimit; + + // but if connectToLearnerMasterLimit is specified, use that value to calculate + // timeout instead of using the initLimit value + if (self.connectToLearnerMasterLimit > 0) { + connectTimeout = self.tickTime * self.connectToLearnerMasterLimit; + } + + int remainingTimeout; + long startNanoTime = nanoTime(); + + for (int tries = 0; tries < 5 && socket.get() == null; tries++) { + try { + // recalculate the init limit time because retries sleep for 1000 milliseconds + remainingTimeout = connectTimeout - (int) ((nanoTime() - startNanoTime) / 1_000_000); + if (remainingTimeout <= 0) { + LOG.error("connectToLeader exceeded on retries."); + throw new IOException("connectToLeader exceeded on retries."); + } + + sockConnect(sock, address, Math.min(connectTimeout, remainingTimeout)); + if (self.isSslQuorum()) { + ((SSLSocket) sock).startHandshake(); + } + sock.setTcpNoDelay(nodelay); + break; + } catch (IOException e) { + remainingTimeout = connectTimeout - (int) ((nanoTime() - startNanoTime) / 1_000_000); + + if (remainingTimeout <= leaderConnectDelayDuringRetryMs) { + LOG.error( + "Unexpected exception, connectToLeader exceeded. tries={}, remaining init limit={}, connecting to {}", + tries, + remainingTimeout, + address, + e); + throw e; + } else if (tries >= 4) { + LOG.error( + "Unexpected exception, retries exceeded. tries={}, remaining init limit={}, connecting to {}", + tries, + remainingTimeout, + address, + e); + throw e; + } else { + LOG.warn( + "Unexpected exception, tries={}, remaining init limit={}, connecting to {}", + tries, + remainingTimeout, + address, + e); + sock = createSocket(); + } + } + Thread.sleep(leaderConnectDelayDuringRetryMs); + } + + return sock; + } + } + + /** + * Creating a simple or and SSL socket. + * This can be overridden in tests to fake already connected sockets for connectToLeader. + */ + protected Socket createSocket() throws X509Exception, IOException { + Socket sock; + if (self.isSslQuorum()) { + sock = self.getX509Util().createSSLSocket(); + } else { + sock = new Socket(); + } + sock.setSoTimeout(self.tickTime * self.initLimit); + return sock; + } + + /** + * Once connected to the leader or learner master, perform the handshake + * protocol to establish a following / observing connection. + * @param pktType + * @return the zxid the Leader sends for synchronization purposes. + * @throws IOException + */ + protected long registerWithLeader(int pktType) throws IOException { + /* + * Send follower info, including last zxid and sid + */ + long lastLoggedZxid = self.getLastLoggedZxid(); + QuorumPacket qp = new QuorumPacket(); + qp.setType(pktType); + qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0)); + + /* + * Add sid to payload + */ + LearnerInfo li = new LearnerInfo(self.getMyId(), 0x10000, self.getQuorumVerifier().getVersion()); + ByteArrayOutputStream bsid = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid); + boa.writeRecord(li, "LearnerInfo"); + qp.setData(bsid.toByteArray()); + + writePacket(qp, true); + readPacket(qp); + final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid()); + if (qp.getType() == Leader.LEADERINFO) { + // we are connected to a 1.0 server so accept the new epoch and read the next packet + leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt(); + byte[] epochBytes = new byte[4]; + final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes); + if (newEpoch > self.getAcceptedEpoch()) { + wrappedEpochBytes.putInt((int) self.getCurrentEpoch()); + self.setAcceptedEpoch(newEpoch); + } else if (newEpoch == self.getAcceptedEpoch()) { + // since we have already acked an epoch equal to the leaders, we cannot ack + // again, but we still need to send our lastZxid to the leader so that we can + // sync with it if it does assume leadership of the epoch. + // the -1 indicates that this reply should not count as an ack for the new epoch + wrappedEpochBytes.putInt(-1); + } else { + throw new IOException("Leaders epoch, " + + newEpoch + + " is less than accepted epoch, " + + self.getAcceptedEpoch()); + } + QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null); + writePacket(ackNewEpoch, true); + return ZxidUtils.makeZxid(newEpoch, 0); + } else { + if (newEpoch > self.getAcceptedEpoch()) { + self.setAcceptedEpoch(newEpoch); + } + if (qp.getType() != Leader.NEWLEADER) { + LOG.error("First packet should have been NEWLEADER"); + throw new IOException("First packet should have been NEWLEADER"); + } + return qp.getZxid(); + } + } + + /** + * Finally, synchronize our history with the Leader (if Follower) + * or the LearnerMaster (if Observer). + * @param newLeaderZxid + * @throws IOException + * @throws InterruptedException + */ + protected void syncWithLeader(long newLeaderZxid) throws Exception { + long newEpoch = ZxidUtils.getEpochFromZxid(newLeaderZxid); + QuorumVerifier newLeaderQV = null; + + class SyncHelper { + + // In the DIFF case we don't need to do a snapshot because the transactions will sync on top of any existing snapshot. + // For SNAP and TRUNC the snapshot is needed to save that history. + boolean willSnapshot = true; + boolean syncSnapshot = false; + + // PROPOSALs received during sync, for matching up with COMMITs. + Deque<PacketInFlight> proposals = new ArrayDeque<>(); + + // PROPOSALs we delay forwarding to the ZK server until sync is done. + Deque<PacketInFlight> delayedProposals = new ArrayDeque<>(); + + // COMMITs we delay forwarding to the ZK server until sync is done. + Deque<Long> delayedCommits = new ArrayDeque<>(); + + void syncSnapshot() { + syncSnapshot = true; + } + + void noSnapshot() { + willSnapshot = false; + } + + void propose(PacketInFlight pif) { + proposals.add(pif); + delayedProposals.add(pif); + } + + PacketInFlight nextProposal() { + return proposals.peekFirst(); + } + + void commit() { + PacketInFlight packet = proposals.remove(); + if (willSnapshot) { + zk.processTxn(packet.hdr, packet.rec); + delayedProposals.remove(); + } else { + delayedCommits.add(packet.hdr.getZxid()); + } + } + + void writeState() throws IOException, InterruptedException { + // Ensure all received transaction PROPOSALs are written before we ACK the NEWLEADER, + // since this allows the leader to apply those transactions to its served state: + if (willSnapshot) { + zk.takeSnapshot(syncSnapshot); // either, the snapshot contains the transactions, + willSnapshot = false; // but anything after this needs to go to the transaction log; or + } + + self.setCurrentEpoch(newEpoch); + sock.setSoTimeout(self.tickTime * self.syncLimit); + self.setSyncMode(QuorumPeer.SyncMode.NONE); + zk.startupWithoutServing(); + + // if we're a follower, we need to ensure the transactions are safely logged before ACK'ing. + if (zk instanceof FollowerZooKeeperServer) { + FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk; + // The leader expects the NEWLEADER ACK to precede all the PROPOSAL ACKs, so we only write them first. + fzk.syncProcessor.setDelayForwarding(true); + for (PacketInFlight p : delayedProposals) { + fzk.logRequest(p.hdr, p.rec, p.digest); + } + delayedProposals.clear(); + fzk.syncProcessor.syncFlush(); + } + } + + void flushAcks() throws InterruptedException { + if (zk instanceof FollowerZooKeeperServer) { + // The NEWLEADER is ACK'ed, and we can now ACK the PROPOSALs we wrote in writeState. + FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk; + fzk.syncProcessor.setDelayForwarding(false); + fzk.syncProcessor.syncFlush(); // Ensure these are all ACK'ed before the UPTODATE ACK. + } + } + + void applyDelayedPackets() { + // Any delayed packets must now be applied: all PROPOSALs first, then any COMMITs. + if (zk instanceof FollowerZooKeeperServer) { + FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk; + for (PacketInFlight p : delayedProposals) { + fzk.logRequest(p.hdr, p.rec, p.digest); + } + for (Long zxid : delayedCommits) { + fzk.commit(zxid); + } + } else if (zk instanceof ObserverZooKeeperServer) { + ObserverZooKeeperServer ozk = (ObserverZooKeeperServer) zk; + for (PacketInFlight p : delayedProposals) { + Long zxid = delayedCommits.peekFirst(); + if (p.hdr.getZxid() != zxid) { + // log warning message if there is no matching commit + // old leader send outstanding proposal to observer + LOG.warn( + "Committing 0x{}, but next proposal is 0x{}", + Long.toHexString(zxid), + Long.toHexString(p.hdr.getZxid())); + continue; + } + delayedCommits.remove(); + Request request = new Request(p.hdr.getClientId(), p.hdr.getCxid(), p.hdr.getType(), p.hdr, p.rec, -1); + request.setTxnDigest(p.digest); + ozk.commitRequest(request); + } + } else { + // New server type need to handle in-flight packets + throw new UnsupportedOperationException("Unknown server type"); + } + } + + } + + SyncHelper helper = new SyncHelper(); + QuorumPacket qp = new QuorumPacket(); + readPacket(qp); + synchronized (zk) { + if (qp.getType() == Leader.DIFF) { + LOG.info("Getting a diff from the leader 0x{}", Long.toHexString(qp.getZxid())); + self.setSyncMode(QuorumPeer.SyncMode.DIFF); + if (zk.shouldForceWriteInitialSnapshotAfterLeaderElection()) { + LOG.info("Forcing a snapshot write as part of upgrading from an older Zookeeper. This should only happen while upgrading."); + helper.syncSnapshot(); + } else { + helper.noSnapshot(); + } + } else if (qp.getType() == Leader.SNAP) { + self.setSyncMode(QuorumPeer.SyncMode.SNAP); + LOG.info("Getting a snapshot from leader 0x{}", Long.toHexString(qp.getZxid())); + // The leader is going to dump the database + zk.getZKDatabase().deserializeSnapshot(leaderIs); + // ZOOKEEPER-2819: overwrite config node content extracted + // from leader snapshot with local config, to avoid potential + // inconsistency of config node content during rolling restart. + if (!self.isReconfigEnabled()) { + LOG.debug("Reset config node content from local config after deserialization of snapshot."); + zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier()); + } + String signature = leaderIs.readString("signature"); + if (!signature.equals("BenWasHere")) { + LOG.error("Missing signature. Got {}", signature); + throw new IOException("Missing signature"); + } + zk.getZKDatabase().setlastProcessedZxid(qp.getZxid()); + + // Immediately persist the latest snapshot when there is txn log gap + helper.syncSnapshot(); + } else if (qp.getType() == Leader.TRUNC) { + // We need to truncate the log to the lastZxid of the leader + self.setSyncMode(QuorumPeer.SyncMode.TRUNC); + LOG.warn("Truncating log to get in sync with the leader 0x{}", Long.toHexString(qp.getZxid())); + boolean truncated = zk.getZKDatabase().truncateLog(qp.getZxid()); + if (!truncated) { + LOG.error("Not able to truncate the log 0x{}", Long.toHexString(qp.getZxid())); + ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue()); + } + zk.getZKDatabase().setlastProcessedZxid(qp.getZxid()); + } else { + LOG.error("Got unexpected packet from leader: {}, exiting ... ", LearnerHandler.packetToString(qp)); + ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue()); + } + zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier()); + zk.createSessionTracker(); + + + // we are now going to start getting transactions to apply followed by an UPTODATE + long lastQueued = 0; + TxnLogEntry logEntry; + outerLoop: + while (self.isRunning()) { + readPacket(qp); + switch (qp.getType()) { + case Leader.PROPOSAL: + PacketInFlight pif = new PacketInFlight(); + logEntry = SerializeUtils.deserializeTxn(qp.getData()); + pif.hdr = logEntry.getHeader(); + pif.rec = logEntry.getTxn(); + pif.digest = logEntry.getDigest(); + if (pif.hdr.getZxid() != lastQueued + 1) { + LOG.warn( + "Got zxid 0x{} expected 0x{}", + Long.toHexString(pif.hdr.getZxid()), + Long.toHexString(lastQueued + 1)); + } + lastQueued = pif.hdr.getZxid(); + + if (pif.hdr.getType() == OpCode.reconfig) { + SetDataTxn setDataTxn = (SetDataTxn) pif.rec; + QuorumVerifier qv = self.configFromString(new String(setDataTxn.getData(), UTF_8)); + self.setLastSeenQuorumVerifier(qv, true); + } + helper.propose(pif); + break; + case Leader.COMMIT: + case Leader.COMMITANDACTIVATE: + pif = helper.nextProposal(); + if (pif.hdr.getZxid() != qp.getZxid()) { + LOG.warn( + "Committing 0x{}, but next proposal is 0x{}", + Long.toHexString(qp.getZxid()), + Long.toHexString(pif.hdr.getZxid())); + } else { + if (qp.getType() == Leader.COMMITANDACTIVATE) { + tryReconfig(pif, ByteBuffer.wrap(qp.getData()).getLong(), qp.getZxid()); + } + helper.commit(); + } + break; + case Leader.INFORM: + case Leader.INFORMANDACTIVATE: + PacketInFlight packet = new PacketInFlight(); + if (qp.getType() == Leader.INFORMANDACTIVATE) { + ByteBuffer buffer = ByteBuffer.wrap(qp.getData()); + long suggestedLeaderId = buffer.getLong(); + byte[] remainingData = new byte[buffer.remaining()]; + buffer.get(remainingData); + logEntry = SerializeUtils.deserializeTxn(remainingData); + packet.hdr = logEntry.getHeader(); + packet.rec = logEntry.getTxn(); + packet.digest = logEntry.getDigest(); + tryReconfig(packet, suggestedLeaderId, qp.getZxid()); + } else { + logEntry = SerializeUtils.deserializeTxn(qp.getData()); + packet.rec = logEntry.getTxn(); + packet.hdr = logEntry.getHeader(); + packet.digest = logEntry.getDigest(); + // Log warning message if txn comes out-of-order + if (packet.hdr.getZxid() != lastQueued + 1) { + LOG.warn( + "Got zxid 0x{} expected 0x{}", + Long.toHexString(packet.hdr.getZxid()), + Long.toHexString(lastQueued + 1)); + } + lastQueued = packet.hdr.getZxid(); + } + helper.propose(packet); + helper.commit(); + break; + case Leader.UPTODATE: + LOG.info("Learner received UPTODATE message"); + if (newLeaderQV != null) { + boolean majorChange = self.processReconfig(newLeaderQV, null, null, true); + if (majorChange) { + throw new Exception("changes proposed in reconfig"); + } + } + helper.flushAcks(); + self.setZooKeeperServer(zk); + self.adminServer.setZooKeeperServer(zk); + break outerLoop; + case Leader.NEWLEADER: // Getting NEWLEADER here instead of in discovery + LOG.info("Learner received NEWLEADER message"); + if (qp.getData() != null && qp.getData().length > 1) { + try { + QuorumVerifier qv = self.configFromString(new String(qp.getData(), UTF_8)); + self.setLastSeenQuorumVerifier(qv, true); + newLeaderQV = qv; + } catch (Exception e) { + e.printStackTrace(); + } + } + + helper.writeState(); + writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true); + break; + } + } + } + QuorumPacket ack = new QuorumPacket(Leader.ACK, 0, null, null); + ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0)); + writePacket(ack, true); + zk.startServing(); + /* + * Update the election vote here to ensure that all members of the + * ensemble report the same vote to new servers that start up and + * send leader election notifications to the ensemble. + * + * @see https://issues.apache.org/jira/browse/ZOOKEEPER-1732 + */ + self.updateElectionVote(newEpoch); + + helper.applyDelayedPackets(); + } + + private void tryReconfig(PacketInFlight pif, long newLeader, long zxid) throws Exception { + QuorumVerifier qv = self.configFromString(new String(((SetDataTxn) pif.rec).getData(), UTF_8)); + boolean majorChange = self.processReconfig(qv, newLeader, zxid, true); + if (majorChange) { + throw new Exception("changes proposed in reconfig"); + } + } + + protected void revalidate(QuorumPacket qp) throws IOException { + ByteArrayInputStream bis = new ByteArrayInputStream(qp.getData()); + DataInputStream dis = new DataInputStream(bis); + long sessionId = dis.readLong(); + boolean valid = dis.readBoolean(); + ServerCnxn cnxn = pendingRevalidations.remove(sessionId); + if (cnxn == null) { + LOG.warn("Missing session 0x{} for validation", Long.toHexString(sessionId)); + } else { + zk.finishSessionInit(cnxn, valid); + } + if (LOG.isTraceEnabled()) { + ZooTrace.logTraceMessage( + LOG, + ZooTrace.SESSION_TRACE_MASK, + "Session 0x" + Long.toHexString(sessionId) + " is valid: " + valid); + } + } + + protected void ping(QuorumPacket qp) throws IOException { + // Send back the ping with our session data + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + Map<Long, Integer> touchTable = zk.getTouchSnapshot(); + for (Entry<Long, Integer> entry : touchTable.entrySet()) { + dos.writeLong(entry.getKey()); + dos.writeInt(entry.getValue()); + } + + QuorumPacket pingReply = new QuorumPacket(qp.getType(), qp.getZxid(), bos.toByteArray(), qp.getAuthinfo()); + writePacket(pingReply, true); + } + + /** + * Shutdown the Peer + */ + public void shutdown() { + self.setZooKeeperServer(null); + self.closeAllConnections(); + self.adminServer.setZooKeeperServer(null); + + if (sender != null) { + sender.shutdown(); + } + + closeSocket(); + // shutdown previous zookeeper + if (zk != null) { + // If we haven't finished SNAP sync, force fully shutdown + // to avoid potential inconsistency + zk.shutdown(self.getSyncMode().equals(QuorumPeer.SyncMode.SNAP)); + } + } + + boolean isRunning() { + return self.isRunning() && zk.isRunning(); + } + + void closeSocket() { + if (sock != null) { + if (sockBeingClosed.compareAndSet(false, true)) { + if (closeSocketAsync) { + final Thread closingThread = new Thread(() -> closeSockSync(), "CloseSocketThread(sid:" + zk.getServerId()); + closingThread.setDaemon(true); + closingThread.start(); + } else { + closeSockSync(); + } + } + } + } + + void closeSockSync() { + try { + long startTime = Time.currentElapsedTime(); + if (sock != null) { + sock.close(); + sock = null; + } + ServerMetrics.getMetrics().SOCKET_CLOSING_TIME.add(Time.currentElapsedTime() - startTime); + } catch (IOException e) { + LOG.warn("Ignoring error closing connection to leader", e); + } + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java new file mode 100644 index 00000000000..8ea94fd4daf --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import org.apache.zookeeper.jmx.MBeanRegistry; +import org.apache.zookeeper.server.DataTreeBean; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.SyncRequestProcessor; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.ZooKeeperServerBean; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; + +/** + * Parent class for all ZooKeeperServers for Learners + */ +public abstract class LearnerZooKeeperServer extends QuorumZooKeeperServer { + + /* + * Request processors + */ + protected CommitProcessor commitProcessor; + protected SyncRequestProcessor syncProcessor; + + public LearnerZooKeeperServer(FileTxnSnapLog logFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int listenBacklog, ZKDatabase zkDb, QuorumPeer self) throws IOException { + super(logFactory, tickTime, minSessionTimeout, maxSessionTimeout, listenBacklog, zkDb, self); + } + + /** + * Abstract method to return the learner associated with this server. + * Since the Learner may change under our feet (when QuorumPeer reassigns + * it) we can't simply take a reference here. Instead, we need the + * subclasses to implement this. + */ + public abstract Learner getLearner(); + + /** + * Returns the current state of the session tracker. This is only currently + * used by a Learner to build a ping response packet. + * + */ + protected Map<Long, Integer> getTouchSnapshot() { + if (sessionTracker != null) { + return ((LearnerSessionTracker) sessionTracker).snapshot(); + } + Map<Long, Integer> map = Collections.emptyMap(); + return map; + } + + /** + * Returns the id of the associated QuorumPeer, which will do for a unique + * id of this server. + */ + @Override + public long getServerId() { + return self.getMyId(); + } + + @Override + public void createSessionTracker() { + sessionTracker = new LearnerSessionTracker( + this, + getZKDatabase().getSessionWithTimeOuts(), + this.tickTime, + self.getMyId(), + self.areLocalSessionsEnabled(), + getZooKeeperServerListener()); + } + + @Override + protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException { + if (upgradeableSessionTracker.isLocalSession(sessionId)) { + super.revalidateSession(cnxn, sessionId, sessionTimeout); + } else { + getLearner().validateSession(cnxn, sessionId, sessionTimeout); + } + } + + @Override + protected void registerJMX() { + // register with JMX + try { + jmxDataTreeBean = new DataTreeBean(getZKDatabase().getDataTree()); + MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxDataTreeBean = null; + } + } + + public void registerJMX(ZooKeeperServerBean serverBean, LocalPeerBean localPeerBean) { + // register with JMX + if (self.jmxLeaderElectionBean != null) { + try { + MBeanRegistry.getInstance().unregister(self.jmxLeaderElectionBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + } + self.jmxLeaderElectionBean = null; + } + + try { + jmxServerBean = serverBean; + MBeanRegistry.getInstance().register(serverBean, localPeerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxServerBean = null; + } + } + + @Override + protected void unregisterJMX() { + // unregister from JMX + try { + if (jmxDataTreeBean != null) { + MBeanRegistry.getInstance().unregister(jmxDataTreeBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxDataTreeBean = null; + } + + protected void unregisterJMX(Learner peer) { + // unregister from JMX + try { + if (jmxServerBean != null) { + MBeanRegistry.getInstance().unregister(jmxServerBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxServerBean = null; + } + + @Override + public synchronized void shutdown(boolean fullyShutDown) { + if (!canShutdown()) { + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + } else { + LOG.info("Shutting down"); + try { + if (syncProcessor != null) { + // Shutting down the syncProcessor here, first, ensures queued transactions here are written to + // permanent storage, which ensures that crash recovery data is consistent with what is used for a + // leader election immediately following shutdown, because of the old leader going down; and also + // that any state on its way to being written is also loaded in the potential call to + // fast-forward-from-edits, in super.shutdown(...), so we avoid getting a DIFF from the new leader + // that contains entries we have already written to our transaction log. + syncProcessor.shutdown(); + } + } catch (Exception e) { + LOG.warn("Ignoring unexpected exception in syncprocessor shutdown", e); + } + } + try { + super.shutdown(fullyShutDown); + } catch (Exception e) { + LOG.warn("Ignoring unexpected exception during shutdown", e); + } + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java new file mode 100644 index 00000000000..1a44a98e6e7 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import java.io.IOException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.BiConsumer; +import org.apache.zookeeper.server.FinalRequestProcessor; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.RequestProcessor; +import org.apache.zookeeper.server.SyncRequestProcessor; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A ZooKeeperServer for the Observer node type. Not much is different, but + * we anticipate specializing the request processors in the future. + * + */ +public class ObserverZooKeeperServer extends LearnerZooKeeperServer { + + private static final Logger LOG = LoggerFactory.getLogger(ObserverZooKeeperServer.class); + + /** + * Enable since request processor for writing txnlog to disk and + * take periodic snapshot. Default is ON. + */ + + private boolean syncRequestProcessorEnabled = this.self.getSyncEnabled(); + + /* + * Pending sync requests + */ ConcurrentLinkedQueue<Request> pendingSyncs = new ConcurrentLinkedQueue<>(); + + ObserverZooKeeperServer(FileTxnSnapLog logFactory, QuorumPeer self, ZKDatabase zkDb) throws IOException { + super(logFactory, self.tickTime, self.minSessionTimeout, self.maxSessionTimeout, self.clientPortListenBacklog, zkDb, self); + LOG.info("syncEnabled ={}", syncRequestProcessorEnabled); + } + + public Observer getObserver() { + return self.observer; + } + + @Override + public Learner getLearner() { + return self.observer; + } + + /** + * Unlike a Follower, which sees a full request only during the PROPOSAL + * phase, Observers get all the data required with the INFORM packet. + * This method commits a request that has been unpacked by from an INFORM + * received from the Leader. + * + * @param request + */ + public void commitRequest(Request request) { + if (syncProcessor != null) { + // Write to txnlog and take periodic snapshot + syncProcessor.processRequest(request); + } + commitProcessor.commit(request); + } + + /** + * Set up the request processors for an Observer: + * firstProcesor->commitProcessor->finalProcessor + */ + @Override + protected void setupRequestProcessors() { + // We might consider changing the processor behaviour of + // Observers to, for example, remove the disk sync requirements. + // Currently, they behave almost exactly the same as followers. + RequestProcessor finalProcessor = new FinalRequestProcessor(this); + commitProcessor = new CommitProcessor(finalProcessor, Long.toString(getServerId()), true, getZooKeeperServerListener()); + commitProcessor.start(); + firstProcessor = new ObserverRequestProcessor(this, commitProcessor); + ((ObserverRequestProcessor) firstProcessor).start(); + + /* + * Observer should write to disk, so that the it won't request + * too old txn from the leader which may lead to getting an entire + * snapshot. + * + * However, this may degrade performance as it has to write to disk + * and do periodic snapshot which may double the memory requirements + */ + if (syncRequestProcessorEnabled) { + syncProcessor = new SyncRequestProcessor(this, null); + syncProcessor.start(); + } + } + + /* + * Process a sync request + */ + public synchronized void sync() { + if (pendingSyncs.size() == 0) { + LOG.warn("Not expecting a sync."); + return; + } + + Request r = pendingSyncs.remove(); + commitProcessor.commit(r); + } + + @Override + public String getState() { + return "observer"; + } + + @Override + public void dumpMonitorValues(BiConsumer<String, Object> response) { + super.dumpMonitorValues(response); + response.accept("observer_master_id", getObserver().getLearnerMasterId()); + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java new file mode 100644 index 00000000000..f6fc87d7716 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java @@ -0,0 +1,2711 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import static org.apache.zookeeper.common.NetUtils.formatInetAddr; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.security.sasl.SaslException; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.KeeperException.BadArgumentsException; +import org.apache.zookeeper.common.AtomicFileOutputStream; +import org.apache.zookeeper.common.AtomicFileWritingIdiom; +import org.apache.zookeeper.common.AtomicFileWritingIdiom.WriterStatement; +import org.apache.zookeeper.common.QuorumX509Util; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.common.X509Exception; +import org.apache.zookeeper.jmx.MBeanRegistry; +import org.apache.zookeeper.jmx.ZKMBeanInfo; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.ServerMetrics; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.ZooKeeperThread; +import org.apache.zookeeper.server.admin.AdminServer; +import org.apache.zookeeper.server.admin.AdminServer.AdminServerException; +import org.apache.zookeeper.server.admin.AdminServerFactory; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; +import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthServer; +import org.apache.zookeeper.server.quorum.auth.QuorumAuth; +import org.apache.zookeeper.server.quorum.auth.QuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.QuorumAuthServer; +import org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthServer; +import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; +import org.apache.zookeeper.server.quorum.flexible.QuorumOracleMaj; +import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; +import org.apache.zookeeper.server.util.ConfigUtils; +import org.apache.zookeeper.server.util.JvmPauseMonitor; +import org.apache.zookeeper.server.util.ZxidUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class manages the quorum protocol. There are three states this server + * can be in: + * <ol> + * <li>Leader election - each server will elect a leader (proposing itself as a + * leader initially).</li> + * <li>Follower - the server will synchronize with the leader and replicate any + * transactions.</li> + * <li>Leader - the server will process requests and forward them to followers. + * A majority of followers must log the request before it can be accepted. + * </ol> + * + * This class will setup a datagram socket that will always respond with its + * view of the current leader. The response will take the form of: + * + * <pre> + * int xid; + * + * long myid; + * + * long leader_id; + * + * long leader_zxid; + * </pre> + * + * The request for the current leader will consist solely of an xid: int xid; + */ +public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider { + + private static final Logger LOG = LoggerFactory.getLogger(QuorumPeer.class); + + public static final String CONFIG_KEY_KERBEROS_CANONICALIZE_HOST_NAMES = "zookeeper.kerberos.canonicalizeHostNames"; + public static final String CONFIG_DEFAULT_KERBEROS_CANONICALIZE_HOST_NAMES = "false"; + + private QuorumBean jmxQuorumBean; + LocalPeerBean jmxLocalPeerBean; + private Map<Long, RemotePeerBean> jmxRemotePeerBean; + LeaderElectionBean jmxLeaderElectionBean; + + // The QuorumCnxManager is held through an AtomicReference to ensure cross-thread visibility + // of updates; see the implementation comment at setLastSeenQuorumVerifier(). + private AtomicReference<QuorumCnxManager> qcmRef = new AtomicReference<>(); + + QuorumAuthServer authServer; + QuorumAuthLearner authLearner; + + /** + * ZKDatabase is a top level member of quorumpeer + * which will be used in all the zookeeperservers + * instantiated later. Also, it is created once on + * bootup and only thrown away in case of a truncate + * message from the leader + */ + private ZKDatabase zkDb; + + private JvmPauseMonitor jvmPauseMonitor; + + private final AtomicBoolean suspended = new AtomicBoolean(false); + + public static final class AddressTuple { + + public final MultipleAddresses quorumAddr; + public final MultipleAddresses electionAddr; + public final InetSocketAddress clientAddr; + + public AddressTuple(MultipleAddresses quorumAddr, MultipleAddresses electionAddr, InetSocketAddress clientAddr) { + this.quorumAddr = quorumAddr; + this.electionAddr = electionAddr; + this.clientAddr = clientAddr; + } + + } + + private int observerMasterPort; + + public int getObserverMasterPort() { + return observerMasterPort; + } + + public void setObserverMasterPort(int observerMasterPort) { + this.observerMasterPort = observerMasterPort; + } + + public static final String CONFIG_KEY_MULTI_ADDRESS_ENABLED = "zookeeper.multiAddress.enabled"; + public static final String CONFIG_DEFAULT_MULTI_ADDRESS_ENABLED = "false"; + + private boolean multiAddressEnabled = true; + public boolean isMultiAddressEnabled() { + return multiAddressEnabled; + } + + public void setMultiAddressEnabled(boolean multiAddressEnabled) { + this.multiAddressEnabled = multiAddressEnabled; + LOG.info("multiAddress.enabled set to {}", multiAddressEnabled); + } + + public static final String CONFIG_KEY_MULTI_ADDRESS_REACHABILITY_CHECK_TIMEOUT_MS = "zookeeper.multiAddress.reachabilityCheckTimeoutMs"; + + private int multiAddressReachabilityCheckTimeoutMs = (int) MultipleAddresses.DEFAULT_TIMEOUT.toMillis(); + public int getMultiAddressReachabilityCheckTimeoutMs() { + return multiAddressReachabilityCheckTimeoutMs; + } + + public void setMultiAddressReachabilityCheckTimeoutMs(int multiAddressReachabilityCheckTimeoutMs) { + this.multiAddressReachabilityCheckTimeoutMs = multiAddressReachabilityCheckTimeoutMs; + LOG.info("multiAddress.reachabilityCheckTimeoutMs set to {}", multiAddressReachabilityCheckTimeoutMs); + } + + public static final String CONFIG_KEY_MULTI_ADDRESS_REACHABILITY_CHECK_ENABLED = "zookeeper.multiAddress.reachabilityCheckEnabled"; + + private boolean multiAddressReachabilityCheckEnabled = true; + + public boolean isMultiAddressReachabilityCheckEnabled() { + return multiAddressReachabilityCheckEnabled; + } + + public void setMultiAddressReachabilityCheckEnabled(boolean multiAddressReachabilityCheckEnabled) { + this.multiAddressReachabilityCheckEnabled = multiAddressReachabilityCheckEnabled; + LOG.info("multiAddress.reachabilityCheckEnabled set to {}", multiAddressReachabilityCheckEnabled); + } + + public static class QuorumServer { + + public MultipleAddresses addr = new MultipleAddresses(); + + public MultipleAddresses electionAddr = new MultipleAddresses(); + + public InetSocketAddress clientAddr = null; + + public long id; + + public String hostname; + + public LearnerType type = LearnerType.PARTICIPANT; + + public boolean isClientAddrFromStatic = false; + + private List<InetSocketAddress> myAddrs; + + public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, InetSocketAddress clientAddr) { + this(id, addr, electionAddr, clientAddr, LearnerType.PARTICIPANT); + } + + public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr) { + this(id, addr, electionAddr, null, LearnerType.PARTICIPANT); + } + + // VisibleForTesting + public QuorumServer(long id, InetSocketAddress addr) { + this(id, addr, null, null, LearnerType.PARTICIPANT); + } + + public long getId() { + return id; + } + + /** + * Performs a DNS lookup for server address and election address. + * + * If the DNS lookup fails, this.addr and electionAddr remain + * unmodified. + */ + public void recreateSocketAddresses() { + if (this.addr.isEmpty()) { + LOG.warn("Server address has not been initialized"); + return; + } + if (this.electionAddr.isEmpty()) { + LOG.warn("Election address has not been initialized"); + return; + } + this.addr.recreateSocketAddresses(); + this.electionAddr.recreateSocketAddresses(); + } + + private LearnerType getType(String s) throws ConfigException { + switch (s.trim().toLowerCase()) { + case "observer": + return LearnerType.OBSERVER; + case "participant": + return LearnerType.PARTICIPANT; + default: + throw new ConfigException("Unrecognised peertype: " + s); + } + } + + public QuorumServer(long sid, String addressStr) throws ConfigException { + this(sid, addressStr, QuorumServer::getInetAddress); + } + + QuorumServer(long sid, String addressStr, Function<InetSocketAddress, InetAddress> getInetAddress) throws ConfigException { + this.id = sid; + initializeWithAddressString(addressStr, getInetAddress); + } + + public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, LearnerType type) { + this(id, addr, electionAddr, null, type); + } + + public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, InetSocketAddress clientAddr, LearnerType type) { + this.id = id; + if (addr != null) { + this.addr.addAddress(addr); + } + if (electionAddr != null) { + this.electionAddr.addAddress(electionAddr); + } + this.type = type; + this.clientAddr = clientAddr; + + setMyAddrs(); + } + + private static final String wrongFormat = + " does not have the form server_config or server_config;client_config" + + " where server_config is the pipe separated list of host:port:port or host:port:port:type" + + " and client_config is port or host:port"; + + private void initializeWithAddressString(String addressStr, Function<InetSocketAddress, InetAddress> getInetAddress) throws ConfigException { + LearnerType newType = null; + String[] serverClientParts = addressStr.split(";"); + String[] serverAddresses = serverClientParts[0].split("\\|"); + + if (serverClientParts.length == 2) { + String[] clientParts = ConfigUtils.getHostAndPort(serverClientParts[1]); + if (clientParts.length > 2) { + throw new ConfigException(addressStr + wrongFormat); + } + + // is client_config a host:port or just a port + String clientHostName = (clientParts.length == 2) ? clientParts[0] : "0.0.0.0"; + try { + clientAddr = new InetSocketAddress(clientHostName, Integer.parseInt(clientParts[clientParts.length - 1])); + } catch (NumberFormatException e) { + throw new ConfigException("Address unresolved: " + hostname + ":" + clientParts[clientParts.length - 1]); + } + } + + boolean multiAddressEnabled = Boolean.parseBoolean( + System.getProperty(QuorumPeer.CONFIG_KEY_MULTI_ADDRESS_ENABLED, QuorumPeer.CONFIG_DEFAULT_MULTI_ADDRESS_ENABLED)); + if (!multiAddressEnabled && serverAddresses.length > 1) { + throw new ConfigException("Multiple address feature is disabled, but multiple addresses were specified for sid " + this.id); + } + + boolean canonicalize = Boolean.parseBoolean( + System.getProperty( + CONFIG_KEY_KERBEROS_CANONICALIZE_HOST_NAMES, + CONFIG_DEFAULT_KERBEROS_CANONICALIZE_HOST_NAMES)); + + for (String serverAddress : serverAddresses) { + String serverParts[] = ConfigUtils.getHostAndPort(serverAddress); + if ((serverClientParts.length > 2) || (serverParts.length < 3) + || (serverParts.length > 4)) { + throw new ConfigException(addressStr + wrongFormat); + } + + String serverHostName = serverParts[0]; + + // server_config should be either host:port:port or host:port:port:type + InetSocketAddress tempAddress; + InetSocketAddress tempElectionAddress; + try { + tempAddress = new InetSocketAddress(serverHostName, Integer.parseInt(serverParts[1])); + addr.addAddress(tempAddress); + } catch (NumberFormatException e) { + throw new ConfigException("Address unresolved: " + serverHostName + ":" + serverParts[1]); + } + try { + tempElectionAddress = new InetSocketAddress(serverHostName, Integer.parseInt(serverParts[2])); + electionAddr.addAddress(tempElectionAddress); + } catch (NumberFormatException e) { + throw new ConfigException("Address unresolved: " + serverHostName + ":" + serverParts[2]); + } + + if (tempAddress.getPort() == tempElectionAddress.getPort()) { + throw new ConfigException("Client and election port must be different! Please update the " + + "configuration file on server." + this.id); + } + + if (canonicalize) { + InetAddress ia = getInetAddress.apply(tempAddress); + if (ia == null) { + throw new ConfigException("Unable to canonicalize address " + serverHostName + " because it's not resolvable"); + } + + String canonicalHostName = ia.getCanonicalHostName(); + + if (!canonicalHostName.equals(serverHostName) + // Avoid using literal IP address when + // security check fails + && !canonicalHostName.equals(ia.getHostAddress())) { + LOG.info("Host name for quorum server {} " + + "canonicalized from {} to {}", + this.id, serverHostName, canonicalHostName); + serverHostName = canonicalHostName; + } + } + + if (serverParts.length == 4) { + LearnerType tempType = getType(serverParts[3]); + if (newType == null) { + newType = tempType; + } + + if (newType != tempType) { + throw new ConfigException("Multiple addresses should have similar roles: " + type + " vs " + tempType); + } + } + + this.hostname = serverHostName; + } + + if (newType != null) { + type = newType; + } + + setMyAddrs(); + } + + private static InetAddress getInetAddress(InetSocketAddress addr) { + return addr.getAddress(); + } + + private void setMyAddrs() { + this.myAddrs = new ArrayList<>(); + this.myAddrs.addAll(this.addr.getAllAddresses()); + this.myAddrs.add(this.clientAddr); + this.myAddrs.addAll(this.electionAddr.getAllAddresses()); + this.myAddrs = excludedSpecialAddresses(this.myAddrs); + } + + public static String delimitedHostString(InetSocketAddress addr) { + String host = addr.getHostString(); + if (host.contains(":")) { + return "[" + host + "]"; + } else { + return host; + } + } + + public String toString() { + StringWriter sw = new StringWriter(); + + List<InetSocketAddress> addrList = new LinkedList<>(addr.getAllAddresses()); + List<InetSocketAddress> electionAddrList = new LinkedList<>(electionAddr.getAllAddresses()); + + if (addrList.size() > 0 && electionAddrList.size() > 0) { + addrList.sort(Comparator.comparing(InetSocketAddress::getHostString)); + electionAddrList.sort(Comparator.comparing(InetSocketAddress::getHostString)); + sw.append(IntStream.range(0, addrList.size()).mapToObj(i -> String.format("%s:%d:%d", + delimitedHostString(addrList.get(i)), addrList.get(i).getPort(), electionAddrList.get(i).getPort())) + .collect(Collectors.joining("|"))); + } + + if (type == LearnerType.OBSERVER) { + sw.append(":observer"); + } else if (type == LearnerType.PARTICIPANT) { + sw.append(":participant"); + } + + if (clientAddr != null && !isClientAddrFromStatic) { + sw.append(";"); + sw.append(delimitedHostString(clientAddr)); + sw.append(":"); + sw.append(String.valueOf(clientAddr.getPort())); + } + + return sw.toString(); + } + + public int hashCode() { + assert false : "hashCode not designed"; + return 42; // any arbitrary constant will do + } + + private boolean checkAddressesEqual(InetSocketAddress addr1, InetSocketAddress addr2) { + return (addr1 != null || addr2 == null) + && (addr1 == null || addr2 != null) + && (addr1 == null || addr2 == null || addr1.equals(addr2)); + } + + public boolean equals(Object o) { + if (!(o instanceof QuorumServer)) { + return false; + } + QuorumServer qs = (QuorumServer) o; + if ((qs.id != id) || (qs.type != type)) { + return false; + } + if (!addr.equals(qs.addr)) { + return false; + } + if (!electionAddr.equals(qs.electionAddr)) { + return false; + } + return checkAddressesEqual(clientAddr, qs.clientAddr); + } + + public void checkAddressDuplicate(QuorumServer s) throws BadArgumentsException { + List<InetSocketAddress> otherAddrs = new ArrayList<>(s.addr.getAllAddresses()); + otherAddrs.add(s.clientAddr); + otherAddrs.addAll(s.electionAddr.getAllAddresses()); + otherAddrs = excludedSpecialAddresses(otherAddrs); + + for (InetSocketAddress my : this.myAddrs) { + + for (InetSocketAddress other : otherAddrs) { + if (my.equals(other)) { + String error = String.format("%s of server.%d conflicts %s of server.%d", my, this.id, other, s.id); + throw new BadArgumentsException(error); + } + } + } + } + + private List<InetSocketAddress> excludedSpecialAddresses(List<InetSocketAddress> addrs) { + List<InetSocketAddress> included = new ArrayList<>(); + + for (InetSocketAddress addr : addrs) { + if (addr == null) { + continue; + } + InetAddress inetaddr = addr.getAddress(); + + if (inetaddr == null || inetaddr.isAnyLocalAddress() // wildCard addresses (0.0.0.0 or [::]) + || inetaddr.isLoopbackAddress()) { // loopback address(localhost/127.0.0.1) + continue; + } + included.add(addr); + } + return included; + } + + } + + public enum ServerState { + LOOKING, + FOLLOWING, + LEADING, + OBSERVING + } + + /** + * (Used for monitoring) shows the current phase of + * Zab protocol that peer is running. + */ + public enum ZabState { + ELECTION, + DISCOVERY, + SYNCHRONIZATION, + BROADCAST + } + + /** + * (Used for monitoring) When peer is in synchronization phase, this shows + * which synchronization mechanism is being used + */ + public enum SyncMode { + NONE, + DIFF, + SNAP, + TRUNC + } + + /* + * A peer can either be participating, which implies that it is willing to + * both vote in instances of consensus and to elect or become a Leader, or + * it may be observing in which case it isn't. + * + * We need this distinction to decide which ServerState to move to when + * conditions change (e.g. which state to become after LOOKING). + */ + public enum LearnerType { + PARTICIPANT, + OBSERVER + } + + /* + * To enable observers to have no identifier, we need a generic identifier + * at least for QuorumCnxManager. We use the following constant to as the + * value of such a generic identifier. + */ + + static final long OBSERVER_ID = Long.MAX_VALUE; + + /* + * Record leader election time + */ + public long start_fle, end_fle; // fle = fast leader election + public static final String FLE_TIME_UNIT = "MS"; + private long unavailableStartTime; + + /* + * Default value of peer is participant + */ + private LearnerType learnerType = LearnerType.PARTICIPANT; + + public LearnerType getLearnerType() { + return learnerType; + } + + /** + * Sets the LearnerType + */ + public void setLearnerType(LearnerType p) { + learnerType = p; + } + + protected synchronized void setConfigFileName(String s) { + configFilename = s; + } + + private String configFilename = null; + + public int getQuorumSize() { + return getVotingView().size(); + } + + public void setJvmPauseMonitor(JvmPauseMonitor jvmPauseMonitor) { + this.jvmPauseMonitor = jvmPauseMonitor; + } + + /** + * QuorumVerifier implementation; default (majority). + */ + + //last committed quorum verifier + private QuorumVerifier quorumVerifier; + + //last proposed quorum verifier + private QuorumVerifier lastSeenQuorumVerifier = null; + + // Lock object that guard access to quorumVerifier and lastSeenQuorumVerifier. + final Object QV_LOCK = new Object(); + + /** + * My id + */ + private long myid; + + /** + * get the id of this quorum peer. + */ + public long getMyId() { + return myid; + } + + // VisibleForTesting + void setId(long id) { + this.myid = id; + } + + private boolean sslQuorum; + private boolean shouldUsePortUnification; + + public boolean isSslQuorum() { + return sslQuorum; + } + + public boolean shouldUsePortUnification() { + return shouldUsePortUnification; + } + + private final QuorumX509Util x509Util; + + QuorumX509Util getX509Util() { + return x509Util; + } + + /** + * This is who I think the leader currently is. + */ + private volatile Vote currentVote; + + public synchronized Vote getCurrentVote() { + return currentVote; + } + + public synchronized void setCurrentVote(Vote v) { + currentVote = v; + } + + private volatile boolean running = true; + + private String initialConfig; + + /** + * The number of milliseconds of each tick + */ + protected int tickTime; + + /** + * Whether learners in this quorum should create new sessions as local. + * False by default to preserve existing behavior. + */ + protected boolean localSessionsEnabled = false; + + /** + * Whether learners in this quorum should upgrade local sessions to + * global. Only matters if local sessions are enabled. + */ + protected boolean localSessionsUpgradingEnabled = true; + + /** + * Minimum number of milliseconds to allow for session timeout. + * A value of -1 indicates unset, use default. + */ + protected int minSessionTimeout = -1; + + /** + * Maximum number of milliseconds to allow for session timeout. + * A value of -1 indicates unset, use default. + */ + protected int maxSessionTimeout = -1; + + /** + * The ZooKeeper server's socket backlog length. The number of connections + * that will be queued to be read before new connections are dropped. A + * value of one indicates the default backlog will be used. + */ + protected int clientPortListenBacklog = -1; + + /** + * The number of ticks that the initial synchronization phase can take + */ + protected volatile int initLimit; + + /** + * The number of ticks that can pass between sending a request and getting + * an acknowledgment + */ + protected volatile int syncLimit; + + /** + * The number of ticks that can pass before retrying to connect to learner master + */ + protected volatile int connectToLearnerMasterLimit; + + /** + * Enables/Disables sync request processor. This option is enabled + * by default and is to be used with observers. + */ + protected boolean syncEnabled = true; + + /** + * The current tick + */ + protected AtomicInteger tick = new AtomicInteger(); + + /** + * Whether or not to listen on all IPs for the two quorum ports + * (broadcast and fast leader election). + */ + protected boolean quorumListenOnAllIPs = false; + + /** + * Keeps time taken for leader election in milliseconds. Sets the value to + * this variable only after the completion of leader election. + */ + private long electionTimeTaken = -1; + + /** + * Enable/Disables quorum authentication using sasl. Defaulting to false. + */ + protected boolean quorumSaslEnableAuth; + + /** + * If this is false, quorum peer server will accept another quorum peer client + * connection even if the authentication did not succeed. This can be used while + * upgrading ZooKeeper server. Defaulting to false (required). + */ + protected boolean quorumServerSaslAuthRequired; + + /** + * If this is false, quorum peer learner will talk to quorum peer server + * without authentication. This can be used while upgrading ZooKeeper + * server. Defaulting to false (required). + */ + protected boolean quorumLearnerSaslAuthRequired; + + /** + * Kerberos quorum service principal. Defaulting to 'zkquorum/localhost'. + */ + protected String quorumServicePrincipal; + + /** + * Quorum learner login context name in jaas-conf file to read the kerberos + * security details. Defaulting to 'QuorumLearner'. + */ + protected String quorumLearnerLoginContext; + + /** + * Quorum server login context name in jaas-conf file to read the kerberos + * security details. Defaulting to 'QuorumServer'. + */ + protected String quorumServerLoginContext; + + // TODO: need to tune the default value of thread size + private static final int QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE = 20; + /** + * The maximum number of threads to allow in the connectionExecutors thread + * pool which will be used to initiate quorum server connections. + */ + protected int quorumCnxnThreadsSize = QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE; + + public static final String QUORUM_CNXN_TIMEOUT_MS = "zookeeper.quorumCnxnTimeoutMs"; + private static int quorumCnxnTimeoutMs; + + static { + quorumCnxnTimeoutMs = Integer.getInteger(QUORUM_CNXN_TIMEOUT_MS, -1); + LOG.info("{}={}", QUORUM_CNXN_TIMEOUT_MS, quorumCnxnTimeoutMs); + } + + /** + * @deprecated As of release 3.4.0, this class has been deprecated, since + * it is used with one of the udp-based versions of leader election, which + * we are also deprecating. + * + * This class simply responds to requests for the current leader of this + * node. + * <p> + * The request contains just an xid generated by the requestor. + * <p> + * The response has the xid, the id of this server, the id of the leader, + * and the zxid of the leader. + * + * + */ + @Deprecated + class ResponderThread extends ZooKeeperThread { + + ResponderThread() { + super("ResponderThread"); + } + + volatile boolean running = true; + + @Override + public void run() { + try { + byte[] b = new byte[36]; + ByteBuffer responseBuffer = ByteBuffer.wrap(b); + DatagramPacket packet = new DatagramPacket(b, b.length); + while (running) { + udpSocket.receive(packet); + if (packet.getLength() != 4) { + LOG.warn("Got more than just an xid! Len = {}", packet.getLength()); + } else { + responseBuffer.clear(); + responseBuffer.getInt(); // Skip the xid + responseBuffer.putLong(myid); + Vote current = getCurrentVote(); + switch (getPeerState()) { + case LOOKING: + responseBuffer.putLong(current.getId()); + responseBuffer.putLong(current.getZxid()); + break; + case LEADING: + responseBuffer.putLong(myid); + try { + long proposed; + synchronized (leader) { + proposed = leader.lastProposed; + } + responseBuffer.putLong(proposed); + } catch (NullPointerException npe) { + // This can happen in state transitions, + // just ignore the request + } + break; + case FOLLOWING: + responseBuffer.putLong(current.getId()); + try { + responseBuffer.putLong(follower.getZxid()); + } catch (NullPointerException npe) { + // This can happen in state transitions, + // just ignore the request + } + break; + case OBSERVING: + // Do nothing, Observers keep themselves to + // themselves. + break; + } + packet.setData(b); + udpSocket.send(packet); + } + packet.setLength(b.length); + } + } catch (RuntimeException e) { + LOG.warn("Unexpected runtime exception in ResponderThread", e); + } catch (IOException e) { + LOG.warn("Unexpected IO exception in ResponderThread", e); + } finally { + LOG.warn("QuorumPeer responder thread exited"); + } + } + + } + + private ServerState state = ServerState.LOOKING; + + private AtomicReference<ZabState> zabState = new AtomicReference<>(ZabState.ELECTION); + private AtomicReference<SyncMode> syncMode = new AtomicReference<>(SyncMode.NONE); + private AtomicReference<String> leaderAddress = new AtomicReference<>(""); + private AtomicLong leaderId = new AtomicLong(-1); + + private boolean reconfigFlag = false; // indicates that a reconfig just committed + + public synchronized void setPeerState(ServerState newState) { + state = newState; + if (newState == ServerState.LOOKING) { + setLeaderAddressAndId(null, -1); + setZabState(ZabState.ELECTION); + } else { + LOG.info("Peer state changed: {}", getDetailedPeerState()); + } + } + + public void setZabState(ZabState zabState) { + if ((zabState == ZabState.BROADCAST) && (unavailableStartTime != 0)) { + long unavailableTime = Time.currentElapsedTime() - unavailableStartTime; + ServerMetrics.getMetrics().UNAVAILABLE_TIME.add(unavailableTime); + if (getPeerState() == ServerState.LEADING) { + ServerMetrics.getMetrics().LEADER_UNAVAILABLE_TIME.add(unavailableTime); + } + unavailableStartTime = 0; + } + this.zabState.set(zabState); + LOG.info("Peer state changed: {}", getDetailedPeerState()); + } + + public void setSyncMode(SyncMode syncMode) { + this.syncMode.set(syncMode); + LOG.info("Peer state changed: {}", getDetailedPeerState()); + } + + public ZabState getZabState() { + return zabState.get(); + } + + public SyncMode getSyncMode() { + return syncMode.get(); + } + + public void setLeaderAddressAndId(MultipleAddresses addr, long newId) { + if (addr != null) { + leaderAddress.set(String.join("|", addr.getAllHostStrings())); + } else { + leaderAddress.set(null); + } + leaderId.set(newId); + } + + public String getLeaderAddress() { + return leaderAddress.get(); + } + + public long getLeaderId() { + return leaderId.get(); + } + + public String getDetailedPeerState() { + final StringBuilder sb = new StringBuilder(getPeerState().toString().toLowerCase()); + final ZabState zabState = getZabState(); + if (!ZabState.ELECTION.equals(zabState)) { + sb.append(" - ").append(zabState.toString().toLowerCase()); + } + final SyncMode syncMode = getSyncMode(); + if (!SyncMode.NONE.equals(syncMode)) { + sb.append(" - ").append(syncMode.toString().toLowerCase()); + } + return sb.toString(); + } + + public synchronized void reconfigFlagSet() { + reconfigFlag = true; + } + public synchronized void reconfigFlagClear() { + reconfigFlag = false; + } + public synchronized boolean isReconfigStateChange() { + return reconfigFlag; + } + public synchronized ServerState getPeerState() { + return state; + } + + DatagramSocket udpSocket; + + private final AtomicReference<AddressTuple> myAddrs = new AtomicReference<>(); + + /** + * Resolves hostname for a given server ID. + * + * This method resolves hostname for a given server ID in both quorumVerifer + * and lastSeenQuorumVerifier. If the server ID matches the local server ID, + * it also updates myAddrs. + */ + public void recreateSocketAddresses(long id) { + QuorumVerifier qv = getQuorumVerifier(); + if (qv != null) { + QuorumServer qs = qv.getAllMembers().get(id); + if (qs != null) { + qs.recreateSocketAddresses(); + if (id == getMyId()) { + setAddrs(qs.addr, qs.electionAddr, qs.clientAddr); + } + } + } + qv = getLastSeenQuorumVerifier(); + if (qv != null) { + QuorumServer qs = qv.getAllMembers().get(id); + if (qs != null) { + qs.recreateSocketAddresses(); + } + } + } + + private AddressTuple getAddrs() { + AddressTuple addrs = myAddrs.get(); + if (addrs != null) { + return addrs; + } + try { + synchronized (QV_LOCK) { + addrs = myAddrs.get(); + while (addrs == null) { + QV_LOCK.wait(); + addrs = myAddrs.get(); + } + return addrs; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + + public MultipleAddresses getQuorumAddress() { + return getAddrs().quorumAddr; + } + + public MultipleAddresses getElectionAddress() { + return getAddrs().electionAddr; + } + + public InetSocketAddress getClientAddress() { + final AddressTuple addrs = myAddrs.get(); + return (addrs == null) ? null : addrs.clientAddr; + } + + private void setAddrs(MultipleAddresses quorumAddr, MultipleAddresses electionAddr, InetSocketAddress clientAddr) { + synchronized (QV_LOCK) { + myAddrs.set(new AddressTuple(quorumAddr, electionAddr, clientAddr)); + QV_LOCK.notifyAll(); + } + } + + private int electionType; + + Election electionAlg; + + ServerCnxnFactory cnxnFactory; + ServerCnxnFactory secureCnxnFactory; + + private FileTxnSnapLog logFactory = null; + + private final QuorumStats quorumStats; + + AdminServer adminServer; + + private final boolean reconfigEnabled; + + public static QuorumPeer testingQuorumPeer() throws SaslException { + return new QuorumPeer(); + } + + public QuorumPeer() throws SaslException { + super("QuorumPeer"); + quorumStats = new QuorumStats(this); + jmxRemotePeerBean = new HashMap<>(); + adminServer = AdminServerFactory.createAdminServer(); + x509Util = createX509Util(); + initialize(); + reconfigEnabled = QuorumPeerConfig.isReconfigEnabled(); + } + + // VisibleForTesting + QuorumX509Util createX509Util() { + return new QuorumX509Util(); + } + + /** + * For backward compatibility purposes, we instantiate QuorumMaj by default. + */ + + public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File dataDir, File dataLogDir, int electionType, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, ServerCnxnFactory cnxnFactory) throws IOException { + this(quorumPeers, dataDir, dataLogDir, electionType, myid, tickTime, initLimit, syncLimit, connectToLearnerMasterLimit, false, cnxnFactory, new QuorumMaj(quorumPeers)); + } + + public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File dataDir, File dataLogDir, int electionType, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, boolean quorumListenOnAllIPs, ServerCnxnFactory cnxnFactory, QuorumVerifier quorumConfig) throws IOException { + this(); + this.cnxnFactory = cnxnFactory; + this.electionType = electionType; + this.myid = myid; + this.tickTime = tickTime; + this.initLimit = initLimit; + this.syncLimit = syncLimit; + this.connectToLearnerMasterLimit = connectToLearnerMasterLimit; + this.quorumListenOnAllIPs = quorumListenOnAllIPs; + this.logFactory = new FileTxnSnapLog(dataLogDir, dataDir); + this.zkDb = new ZKDatabase(this.logFactory); + if (quorumConfig == null) { + quorumConfig = new QuorumMaj(quorumPeers); + } + setQuorumVerifier(quorumConfig, false); + adminServer = AdminServerFactory.createAdminServer(); + } + + public void initialize() throws SaslException { + // init quorum auth server & learner + if (isQuorumSaslAuthEnabled()) { + Set<String> authzHosts = new HashSet<>(); + for (QuorumServer qs : getView().values()) { + authzHosts.add(qs.hostname); + } + authServer = new SaslQuorumAuthServer(isQuorumServerSaslAuthRequired(), quorumServerLoginContext, authzHosts); + authLearner = new SaslQuorumAuthLearner(isQuorumLearnerSaslAuthRequired(), quorumServicePrincipal, quorumLearnerLoginContext); + } else { + authServer = new NullQuorumAuthServer(); + authLearner = new NullQuorumAuthLearner(); + } + } + + QuorumStats quorumStats() { + return quorumStats; + } + + @Override + public synchronized void start() { + if (!getView().containsKey(myid)) { + throw new RuntimeException("My id " + myid + " not in the peer list"); + } + loadDataBase(); + startServerCnxnFactory(); + try { + adminServer.start(); + } catch (AdminServerException e) { + LOG.warn("Problem starting AdminServer", e); + } + startLeaderElection(); + startJvmPauseMonitor(); + super.start(); + } + + private void loadDataBase() { + try { + zkDb.loadDataBase(); + + // load the epochs + long lastProcessedZxid = zkDb.getDataTree().lastProcessedZxid; + long epochOfZxid = ZxidUtils.getEpochFromZxid(lastProcessedZxid); + try { + currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME); + } catch (FileNotFoundException e) { + // pick a reasonable epoch number + // this should only happen once when moving to a + // new code version + currentEpoch = epochOfZxid; + LOG.info( + "{} not found! Creating with a reasonable default of {}. " + + "This should only happen when you are upgrading your installation", + CURRENT_EPOCH_FILENAME, + currentEpoch); + writeLongToFile(CURRENT_EPOCH_FILENAME, currentEpoch); + } + if (epochOfZxid > currentEpoch) { + // acceptedEpoch.tmp file in snapshot directory + File currentTmp = new File(getTxnFactory().getSnapDir(), + CURRENT_EPOCH_FILENAME + AtomicFileOutputStream.TMP_EXTENSION); + if (currentTmp.exists()) { + long epochOfTmp = readLongFromFile(currentTmp.getName()); + LOG.info("{} found. Setting current epoch to {}.", currentTmp, epochOfTmp); + setCurrentEpoch(epochOfTmp); + } else { + throw new IOException( + "The current epoch, " + ZxidUtils.zxidToString(currentEpoch) + + ", is older than the last zxid, " + lastProcessedZxid); + } + } + try { + acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME); + } catch (FileNotFoundException e) { + // pick a reasonable epoch number + // this should only happen once when moving to a + // new code version + acceptedEpoch = epochOfZxid; + LOG.info( + "{} not found! Creating with a reasonable default of {}. " + + "This should only happen when you are upgrading your installation", + ACCEPTED_EPOCH_FILENAME, + acceptedEpoch); + writeLongToFile(ACCEPTED_EPOCH_FILENAME, acceptedEpoch); + } + if (acceptedEpoch < currentEpoch) { + throw new IOException("The accepted epoch, " + + ZxidUtils.zxidToString(acceptedEpoch) + + " is less than the current epoch, " + + ZxidUtils.zxidToString(currentEpoch)); + } + } catch (IOException ie) { + LOG.error("Unable to load database on disk", ie); + throw new RuntimeException("Unable to run quorum server ", ie); + } + } + + ResponderThread responder; + + public synchronized void stopLeaderElection() { + responder.running = false; + responder.interrupt(); + } + public synchronized void startLeaderElection() { + try { + if (getPeerState() == ServerState.LOOKING) { + currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch()); + } + } catch (IOException e) { + RuntimeException re = new RuntimeException(e.getMessage()); + re.setStackTrace(e.getStackTrace()); + throw re; + } + + this.electionAlg = createElectionAlgorithm(electionType); + } + + private void startJvmPauseMonitor() { + if (this.jvmPauseMonitor != null) { + this.jvmPauseMonitor.serviceStart(); + } + } + + /** + * Count the number of nodes in the map that could be followers. + * @param peers + * @return The number of followers in the map + */ + protected static int countParticipants(Map<Long, QuorumServer> peers) { + int count = 0; + for (QuorumServer q : peers.values()) { + if (q.type == LearnerType.PARTICIPANT) { + count++; + } + } + return count; + } + + /** + * This constructor is only used by the existing unit test code. + * It defaults to FileLogProvider persistence provider. + */ + public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File snapDir, File logDir, int clientPort, int electionAlg, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit) throws IOException { + this( + quorumPeers, + snapDir, + logDir, + electionAlg, + myid, + tickTime, + initLimit, + syncLimit, + connectToLearnerMasterLimit, + false, + ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1), + new QuorumMaj(quorumPeers)); + } + + public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File snapDir, File logDir, int clientPort, int electionAlg, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, String oraclePath) throws IOException { + this( + quorumPeers, + snapDir, + logDir, + electionAlg, + myid, + tickTime, + initLimit, + syncLimit, + connectToLearnerMasterLimit, + false, + ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1), + new QuorumOracleMaj(quorumPeers, oraclePath)); + } + + /** + * This constructor is only used by the existing unit test code. + * It defaults to FileLogProvider persistence provider. + */ + public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File snapDir, File logDir, int clientPort, int electionAlg, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, QuorumVerifier quorumConfig) throws IOException { + this( + quorumPeers, + snapDir, + logDir, + electionAlg, + myid, + tickTime, + initLimit, + syncLimit, + connectToLearnerMasterLimit, + false, + ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1), + quorumConfig); + } + + private static InetSocketAddress getClientAddress(Map<Long, QuorumServer> quorumPeers, long myid, int clientPort) throws IOException { + QuorumServer quorumServer = quorumPeers.get(myid); + if (null == quorumServer) { + throw new IOException("No QuorumServer correspoding to myid " + myid); + } + if (null == quorumServer.clientAddr) { + return new InetSocketAddress(clientPort); + } + if (quorumServer.clientAddr.getPort() != clientPort) { + throw new IOException("QuorumServer port " + + quorumServer.clientAddr.getPort() + + " does not match with given port " + + clientPort); + } + return quorumServer.clientAddr; + } + + /** + * returns the highest zxid that this host has seen + * + * @return the highest zxid for this host + */ + public long getLastLoggedZxid() { + if (!zkDb.isInitialized()) { + loadDataBase(); + } + return zkDb.getDataTreeLastProcessedZxid(); + } + + public Follower follower; + public Leader leader; + public Observer observer; + + protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException { + return new Follower(this, new FollowerZooKeeperServer(logFactory, this, this.zkDb)); + } + + protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException, X509Exception { + return new Leader(this, new LeaderZooKeeperServer(logFactory, this, this.zkDb)); + } + + protected Observer makeObserver(FileTxnSnapLog logFactory) throws IOException { + return new Observer(this, new ObserverZooKeeperServer(logFactory, this, this.zkDb)); + } + + @SuppressWarnings("deprecation") + protected Election createElectionAlgorithm(int electionAlgorithm) { + Election le = null; + + //TODO: use a factory rather than a switch + switch (electionAlgorithm) { + case 1: + throw new UnsupportedOperationException("Election Algorithm 1 is not supported."); + case 2: + throw new UnsupportedOperationException("Election Algorithm 2 is not supported."); + case 3: + QuorumCnxManager qcm = createCnxnManager(); + QuorumCnxManager oldQcm = qcmRef.getAndSet(qcm); + if (oldQcm != null) { + LOG.warn("Clobbering already-set QuorumCnxManager (restarting leader election?)"); + oldQcm.halt(); + } + QuorumCnxManager.Listener listener = qcm.listener; + if (listener != null) { + listener.start(); + FastLeaderElection fle = new FastLeaderElection(this, qcm); + fle.start(); + le = fle; + } else { + LOG.error("Null listener when initializing cnx manager"); + } + break; + default: + assert false; + } + return le; + } + + @SuppressWarnings("deprecation") + protected Election makeLEStrategy() { + LOG.debug("Initializing leader election protocol..."); + return electionAlg; + } + + protected synchronized void setLeader(Leader newLeader) { + leader = newLeader; + } + + protected synchronized void setFollower(Follower newFollower) { + follower = newFollower; + } + + protected synchronized void setObserver(Observer newObserver) { + observer = newObserver; + } + + public synchronized ZooKeeperServer getActiveServer() { + if (leader != null) { + return leader.zk; + } else if (follower != null) { + return follower.zk; + } else if (observer != null) { + return observer.zk; + } + return null; + } + + boolean shuttingDownLE = false; + + public void setSuspended(boolean suspended) { + this.suspended.set(suspended); + } + private void checkSuspended() { + try { + while (suspended.get()) { + Thread.sleep(10); + } + } catch (InterruptedException err) { + Thread.currentThread().interrupt(); + } + } + + @Override + public void run() { + updateThreadName(); + + LOG.debug("Starting quorum peer"); + try { + jmxQuorumBean = new QuorumBean(this); + MBeanRegistry.getInstance().register(jmxQuorumBean, null); + for (QuorumServer s : getView().values()) { + ZKMBeanInfo p; + if (getMyId() == s.id) { + p = jmxLocalPeerBean = new LocalPeerBean(this); + try { + MBeanRegistry.getInstance().register(p, jmxQuorumBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxLocalPeerBean = null; + } + } else { + RemotePeerBean rBean = new RemotePeerBean(this, s); + try { + MBeanRegistry.getInstance().register(rBean, jmxQuorumBean); + jmxRemotePeerBean.put(s.id, rBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + } + } + } + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxQuorumBean = null; + } + + try { + /* + * Main loop + */ + while (running) { + if (unavailableStartTime == 0) { + unavailableStartTime = Time.currentElapsedTime(); + } + + switch (getPeerState()) { + case LOOKING: + LOG.info("LOOKING"); + ServerMetrics.getMetrics().LOOKING_COUNT.add(1); + + if (Boolean.getBoolean("readonlymode.enabled")) { + LOG.info("Attempting to start ReadOnlyZooKeeperServer"); + + // Create read-only server but don't start it immediately + final ReadOnlyZooKeeperServer roZk = new ReadOnlyZooKeeperServer(logFactory, this, this.zkDb); + + // Instead of starting roZk immediately, wait some grace + // period before we decide we're partitioned. + // + // Thread is used here because otherwise it would require + // changes in each of election strategy classes which is + // unnecessary code coupling. + Thread roZkMgr = new Thread() { + public void run() { + try { + // lower-bound grace period to 2 secs + sleep(Math.max(2000, tickTime)); + if (ServerState.LOOKING.equals(getPeerState())) { + roZk.startup(); + } + } catch (InterruptedException e) { + LOG.info("Interrupted while attempting to start ReadOnlyZooKeeperServer, not started"); + } catch (Exception e) { + LOG.error("FAILED to start ReadOnlyZooKeeperServer", e); + } + } + }; + try { + roZkMgr.start(); + reconfigFlagClear(); + if (shuttingDownLE) { + shuttingDownLE = false; + startLeaderElection(); + } + setCurrentVote(makeLEStrategy().lookForLeader()); + checkSuspended(); + } catch (Exception e) { + LOG.warn("Unexpected exception", e); + setPeerState(ServerState.LOOKING); + } finally { + // If the thread is in the the grace period, interrupt + // to come out of waiting. + roZkMgr.interrupt(); + roZk.shutdown(); + } + } else { + try { + reconfigFlagClear(); + if (shuttingDownLE) { + shuttingDownLE = false; + startLeaderElection(); + } + setCurrentVote(makeLEStrategy().lookForLeader()); + } catch (Exception e) { + LOG.warn("Unexpected exception", e); + setPeerState(ServerState.LOOKING); + } + } + break; + case OBSERVING: + try { + LOG.info("OBSERVING"); + setObserver(makeObserver(logFactory)); + observer.observeLeader(); + } catch (Exception e) { + LOG.warn("Unexpected exception", e); + } finally { + observer.shutdown(); + setObserver(null); + updateServerState(); + + // Add delay jitter before we switch to LOOKING + // state to reduce the load of ObserverMaster + if (isRunning()) { + Observer.waitForObserverElectionDelay(); + } + } + break; + case FOLLOWING: + try { + LOG.info("FOLLOWING"); + setFollower(makeFollower(logFactory)); + follower.followLeader(); + } catch (Exception e) { + LOG.warn("Unexpected exception", e); + } finally { + follower.shutdown(); + setFollower(null); + updateServerState(); + } + break; + case LEADING: + LOG.info("LEADING"); + try { + setLeader(makeLeader(logFactory)); + leader.lead(); + setLeader(null); + } catch (Exception e) { + LOG.warn("Unexpected exception", e); + } finally { + if (leader != null) { + leader.shutdown("Forcing shutdown"); + setLeader(null); + } + updateServerState(); + } + break; + } + } + } finally { + LOG.warn("QuorumPeer main thread exited"); + MBeanRegistry instance = MBeanRegistry.getInstance(); + instance.unregister(jmxQuorumBean); + instance.unregister(jmxLocalPeerBean); + + for (RemotePeerBean remotePeerBean : jmxRemotePeerBean.values()) { + instance.unregister(remotePeerBean); + } + + jmxQuorumBean = null; + jmxLocalPeerBean = null; + jmxRemotePeerBean = null; + } + } + + private synchronized void updateServerState() { + if (!reconfigFlag) { + setPeerState(ServerState.LOOKING); + LOG.warn("PeerState set to LOOKING"); + return; + } + + if (getMyId() == getCurrentVote().getId()) { + setPeerState(ServerState.LEADING); + LOG.debug("PeerState set to LEADING"); + } else if (getLearnerType() == LearnerType.PARTICIPANT) { + setPeerState(ServerState.FOLLOWING); + LOG.debug("PeerState set to FOLLOWING"); + } else if (getLearnerType() == LearnerType.OBSERVER) { + setPeerState(ServerState.OBSERVING); + LOG.debug("PeerState set to OBSERVER"); + } else { // currently shouldn't happen since there are only 2 learner types + setPeerState(ServerState.LOOKING); + LOG.debug("Should not be here"); + } + reconfigFlag = false; + } + + public void shutdown() { + running = false; + x509Util.close(); + if (leader != null) { + leader.shutdown("quorum Peer shutdown"); + } + if (follower != null) { + follower.shutdown(); + } + shutdownServerCnxnFactory(); + if (udpSocket != null) { + udpSocket.close(); + } + if (jvmPauseMonitor != null) { + jvmPauseMonitor.serviceStop(); + } + + try { + adminServer.shutdown(); + } catch (AdminServerException e) { + LOG.warn("Problem stopping AdminServer", e); + } + + if (getElectionAlg() != null) { + this.interrupt(); + getElectionAlg().shutdown(); + } + try { + zkDb.close(); + } catch (IOException ie) { + LOG.warn("Error closing logs ", ie); + } + } + + /** + * A 'view' is a node's current opinion of the membership of the entire + * ensemble. + */ + public Map<Long, QuorumPeer.QuorumServer> getView() { + return Collections.unmodifiableMap(getQuorumVerifier().getAllMembers()); + } + + /** + * Observers are not contained in this view, only nodes with + * PeerType=PARTICIPANT. + */ + public Map<Long, QuorumPeer.QuorumServer> getVotingView() { + return getQuorumVerifier().getVotingMembers(); + } + + /** + * Returns only observers, no followers. + */ + public Map<Long, QuorumPeer.QuorumServer> getObservingView() { + return getQuorumVerifier().getObservingMembers(); + } + + public synchronized Set<Long> getCurrentAndNextConfigVoters() { + Set<Long> voterIds = new HashSet<>(getQuorumVerifier().getVotingMembers().keySet()); + if (getLastSeenQuorumVerifier() != null) { + voterIds.addAll(getLastSeenQuorumVerifier().getVotingMembers().keySet()); + } + return voterIds; + } + + /** + * Check if a node is in the current view. With static membership, the + * result of this check will never change; only when dynamic membership + * is introduced will this be more useful. + */ + public boolean viewContains(Long sid) { + return this.getView().containsKey(sid); + } + + /** + * Only used by QuorumStats at the moment + */ + public String[] getQuorumPeers() { + List<String> l = new ArrayList<>(); + synchronized (this) { + if (leader != null) { + for (LearnerHandler fh : leader.getLearners()) { + if (fh.getSocket() != null) { + String s = formatInetAddr((InetSocketAddress) fh.getSocket().getRemoteSocketAddress()); + if (leader.isLearnerSynced(fh)) { + s += "*"; + } + l.add(s); + } + } + } else if (follower != null) { + l.add(formatInetAddr((InetSocketAddress) follower.sock.getRemoteSocketAddress())); + } + } + return l.toArray(new String[0]); + } + + public String getServerState() { + switch (getPeerState()) { + case LOOKING: + return QuorumStats.Provider.LOOKING_STATE; + case LEADING: + return QuorumStats.Provider.LEADING_STATE; + case FOLLOWING: + return QuorumStats.Provider.FOLLOWING_STATE; + case OBSERVING: + return QuorumStats.Provider.OBSERVING_STATE; + } + return QuorumStats.Provider.UNKNOWN_STATE; + } + + /** + * set the id of this quorum peer. + */ + public void setMyid(long myid) { + this.myid = myid; + } + + public void setInitialConfig(String initialConfig) { + this.initialConfig = initialConfig; + } + + public String getInitialConfig() { + return initialConfig; + } + + /** + * Get the number of milliseconds of each tick + */ + public int getTickTime() { + return tickTime; + } + + /** + * Set the number of milliseconds of each tick + */ + public void setTickTime(int tickTime) { + LOG.info("tickTime set to {}", tickTime); + this.tickTime = tickTime; + } + + /** Maximum number of connections allowed from particular host (ip) */ + public int getMaxClientCnxnsPerHost() { + if (cnxnFactory != null) { + return cnxnFactory.getMaxClientCnxnsPerHost(); + } + if (secureCnxnFactory != null) { + return secureCnxnFactory.getMaxClientCnxnsPerHost(); + } + return -1; + } + + /** Whether local sessions are enabled */ + public boolean areLocalSessionsEnabled() { + return localSessionsEnabled; + } + + /** Whether to enable local sessions */ + public void enableLocalSessions(boolean flag) { + LOG.info("Local sessions {}", (flag ? "enabled" : "disabled")); + localSessionsEnabled = flag; + } + + /** Whether local sessions are allowed to upgrade to global sessions */ + public boolean isLocalSessionsUpgradingEnabled() { + return localSessionsUpgradingEnabled; + } + + /** Whether to allow local sessions to upgrade to global sessions */ + public void enableLocalSessionsUpgrading(boolean flag) { + LOG.info("Local session upgrading {}", (flag ? "enabled" : "disabled")); + localSessionsUpgradingEnabled = flag; + } + + /** minimum session timeout in milliseconds */ + public int getMinSessionTimeout() { + return minSessionTimeout; + } + + /** minimum session timeout in milliseconds */ + public void setMinSessionTimeout(int min) { + LOG.info("minSessionTimeout set to {}", min); + this.minSessionTimeout = min; + } + + /** maximum session timeout in milliseconds */ + public int getMaxSessionTimeout() { + return maxSessionTimeout; + } + + /** maximum session timeout in milliseconds */ + public void setMaxSessionTimeout(int max) { + LOG.info("maxSessionTimeout set to {}", max); + this.maxSessionTimeout = max; + } + + /** The server socket's listen backlog length */ + public int getClientPortListenBacklog() { + return this.clientPortListenBacklog; + } + + /** Sets the server socket's listen backlog length. */ + public void setClientPortListenBacklog(int backlog) { + this.clientPortListenBacklog = backlog; + } + + /** + * Get the number of ticks that the initial synchronization phase can take + */ + public int getInitLimit() { + return initLimit; + } + + /** + * Set the number of ticks that the initial synchronization phase can take + */ + public void setInitLimit(int initLimit) { + LOG.info("initLimit set to {}", initLimit); + this.initLimit = initLimit; + } + + /** + * Get the current tick + */ + public int getTick() { + return tick.get(); + } + + public QuorumVerifier configFromString(String s) throws IOException, ConfigException { + Properties props = new Properties(); + props.load(new StringReader(s)); + return QuorumPeerConfig.parseDynamicConfig(props, electionType, false, false, getQuorumVerifier().getOraclePath()); + } + + /** + * Return QuorumVerifier object for the last committed configuration. + */ + public QuorumVerifier getQuorumVerifier() { + synchronized (QV_LOCK) { + return quorumVerifier; + } + } + + /** + * Return QuorumVerifier object for the last proposed configuration. + */ + public QuorumVerifier getLastSeenQuorumVerifier() { + synchronized (QV_LOCK) { + return lastSeenQuorumVerifier; + } + } + + public synchronized void restartLeaderElection(QuorumVerifier qvOLD, QuorumVerifier qvNEW) { + if (qvOLD == null || !qvOLD.equals(qvNEW)) { + LOG.warn("Restarting Leader Election"); + getElectionAlg().shutdown(); + shuttingDownLE = false; + startLeaderElection(); + } + } + + public String getNextDynamicConfigFilename() { + if (configFilename == null) { + LOG.warn("configFilename is null! This should only happen in tests."); + return null; + } + return configFilename + QuorumPeerConfig.nextDynamicConfigFileSuffix; + } + + // On entry to this method, qcm must be non-null and the locks on both qcm and QV_LOCK + // must be held. We don't want quorumVerifier/lastSeenQuorumVerifier to change out from + // under us, so we have to hold QV_LOCK; and since the call to qcm.connectOne() will take + // the lock on qcm (and take QV_LOCK again inside that), the caller needs to have taken + // qcm outside QV_LOCK to avoid a deadlock against other callers of qcm.connectOne(). + private void connectNewPeers(QuorumCnxManager qcm) { + if (quorumVerifier != null && lastSeenQuorumVerifier != null) { + Map<Long, QuorumServer> committedView = quorumVerifier.getAllMembers(); + for (Entry<Long, QuorumServer> e : lastSeenQuorumVerifier.getAllMembers().entrySet()) { + if (e.getKey() != getMyId() && !committedView.containsKey(e.getKey())) { + qcm.connectOne(e.getKey()); + } + } + } + } + + public void setLastSeenQuorumVerifier(QuorumVerifier qv, boolean writeToDisk) { + if (!isReconfigEnabled()) { + LOG.info("Dynamic reconfig is disabled, we don't store the last seen config."); + return; + } + + // If qcm is non-null, we may call qcm.connectOne(), which will take the lock on qcm + // and then take QV_LOCK. Take the locks in the same order to ensure that we don't + // deadlock against other callers of connectOne(). If qcmRef gets set in another + // thread while we're inside the synchronized block, that does no harm; if we didn't + // take a lock on qcm (because it was null when we sampled it), we won't call + // connectOne() on it. (Use of an AtomicReference is enough to guarantee visibility + // of updates that provably happen in another thread before entering this method.) + QuorumCnxManager qcm = qcmRef.get(); + Object outerLockObject = (qcm != null) ? qcm : QV_LOCK; + synchronized (outerLockObject) { + synchronized (QV_LOCK) { + if (lastSeenQuorumVerifier != null && lastSeenQuorumVerifier.getVersion() > qv.getVersion()) { + LOG.error("setLastSeenQuorumVerifier called with stale config " + + qv.getVersion() + + ". Current version: " + + quorumVerifier.getVersion()); + } + // assuming that a version uniquely identifies a configuration, so if + // version is the same, nothing to do here. + if (lastSeenQuorumVerifier != null && lastSeenQuorumVerifier.getVersion() == qv.getVersion()) { + return; + } + lastSeenQuorumVerifier = qv; + if (qcm != null) { + connectNewPeers(qcm); + } + + if (writeToDisk) { + try { + String fileName = getNextDynamicConfigFilename(); + if (fileName != null) { + QuorumPeerConfig.writeDynamicConfig(fileName, qv, true); + } + } catch (IOException e) { + LOG.error("Error writing next dynamic config file to disk", e); + } + } + } + } + } + + public QuorumVerifier setQuorumVerifier(QuorumVerifier qv, boolean writeToDisk) { + synchronized (QV_LOCK) { + if ((quorumVerifier != null) && (quorumVerifier.getVersion() >= qv.getVersion())) { + // this is normal. For example - server found out about new config through FastLeaderElection gossiping + // and then got the same config in UPTODATE message so its already known + LOG.debug( + "{} setQuorumVerifier called with known or old config {}. Current version: {}", + getMyId(), + qv.getVersion(), + quorumVerifier.getVersion()); + return quorumVerifier; + } + QuorumVerifier prevQV = quorumVerifier; + quorumVerifier = qv; + if (lastSeenQuorumVerifier == null || (qv.getVersion() > lastSeenQuorumVerifier.getVersion())) { + lastSeenQuorumVerifier = qv; + } + + if (writeToDisk) { + // some tests initialize QuorumPeer without a static config file + if (configFilename != null) { + try { + String dynamicConfigFilename = makeDynamicConfigFilename(qv.getVersion()); + QuorumPeerConfig.writeDynamicConfig(dynamicConfigFilename, qv, false); + QuorumPeerConfig.editStaticConfig(configFilename, dynamicConfigFilename, needEraseClientInfoFromStaticConfig()); + } catch (IOException e) { + LOG.error("Error closing file", e); + } + } else { + LOG.info("writeToDisk == true but configFilename == null"); + } + } + + if (qv.getVersion() == lastSeenQuorumVerifier.getVersion()) { + QuorumPeerConfig.deleteFile(getNextDynamicConfigFilename()); + } + QuorumServer qs = qv.getAllMembers().get(getMyId()); + if (qs != null) { + setAddrs(qs.addr, qs.electionAddr, qs.clientAddr); + } + updateObserverMasterList(); + return prevQV; + } + } + + private String makeDynamicConfigFilename(long version) { + return configFilename + ".dynamic." + Long.toHexString(version); + } + + private boolean needEraseClientInfoFromStaticConfig() { + QuorumServer server = quorumVerifier.getAllMembers().get(getMyId()); + return (server != null && server.clientAddr != null && !server.isClientAddrFromStatic); + } + + /** + * Get an instance of LeaderElection + */ + public Election getElectionAlg() { + return electionAlg; + } + + /** + * Get the synclimit + */ + public int getSyncLimit() { + return syncLimit; + } + + /** + * Set the synclimit + */ + public void setSyncLimit(int syncLimit) { + LOG.info("syncLimit set to {}", syncLimit); + this.syncLimit = syncLimit; + } + + /** + * Get the connectToLearnerMasterLimit + */ + public int getConnectToLearnerMasterLimit() { + return connectToLearnerMasterLimit; + } + + /** + * Set the connectToLearnerMasterLimit + */ + public void setConnectToLearnerMasterLimit(int connectToLearnerMasterLimit) { + LOG.info("connectToLearnerMasterLimit set to {}", connectToLearnerMasterLimit); + this.connectToLearnerMasterLimit = connectToLearnerMasterLimit; + } + + /** + * The syncEnabled can also be set via a system property. + */ + public static final String SYNC_ENABLED = "zookeeper.observer.syncEnabled"; + + /** + * Return syncEnabled. + */ + public boolean getSyncEnabled() { + if (System.getProperty(SYNC_ENABLED) != null) { + LOG.info("{}={}", SYNC_ENABLED, Boolean.getBoolean(SYNC_ENABLED)); + return Boolean.getBoolean(SYNC_ENABLED); + } else { + return syncEnabled; + } + } + + /** + * Set syncEnabled. + * + * @param syncEnabled + */ + public void setSyncEnabled(boolean syncEnabled) { + this.syncEnabled = syncEnabled; + } + + /** + * Gets the election type + */ + public int getElectionType() { + return electionType; + } + + /** + * Sets the election type + */ + public void setElectionType(int electionType) { + this.electionType = electionType; + } + + public boolean getQuorumListenOnAllIPs() { + return quorumListenOnAllIPs; + } + + public void setQuorumListenOnAllIPs(boolean quorumListenOnAllIPs) { + this.quorumListenOnAllIPs = quorumListenOnAllIPs; + } + + public void setCnxnFactory(ServerCnxnFactory cnxnFactory) { + this.cnxnFactory = cnxnFactory; + } + + public void setSecureCnxnFactory(ServerCnxnFactory secureCnxnFactory) { + this.secureCnxnFactory = secureCnxnFactory; + } + + public void setSslQuorum(boolean sslQuorum) { + if (sslQuorum) { + LOG.info("Using TLS encrypted quorum communication"); + } else { + LOG.info("Using insecure (non-TLS) quorum communication"); + } + this.sslQuorum = sslQuorum; + } + + public void setUsePortUnification(boolean shouldUsePortUnification) { + LOG.info("Port unification {}", shouldUsePortUnification ? "enabled" : "disabled"); + this.shouldUsePortUnification = shouldUsePortUnification; + } + + private void startServerCnxnFactory() { + if (cnxnFactory != null) { + cnxnFactory.start(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.start(); + } + } + + private void shutdownServerCnxnFactory() { + if (cnxnFactory != null) { + cnxnFactory.shutdown(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.shutdown(); + } + } + + // Leader and learner will control the zookeeper server and pass it into QuorumPeer. + public void setZooKeeperServer(ZooKeeperServer zks) { + if (cnxnFactory != null) { + cnxnFactory.setZooKeeperServer(zks); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.setZooKeeperServer(zks); + } + } + + public void closeAllConnections() { + if (cnxnFactory != null) { + cnxnFactory.closeAll(ServerCnxn.DisconnectReason.SERVER_SHUTDOWN); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.closeAll(ServerCnxn.DisconnectReason.SERVER_SHUTDOWN); + } + } + + public int getClientPort() { + if (cnxnFactory != null) { + return cnxnFactory.getLocalPort(); + } + return -1; + } + + public int getSecureClientPort() { + if (secureCnxnFactory != null) { + return secureCnxnFactory.getLocalPort(); + } + return -1; + } + + public void setTxnFactory(FileTxnSnapLog factory) { + this.logFactory = factory; + } + + public FileTxnSnapLog getTxnFactory() { + return this.logFactory; + } + + /** + * set zk database for this node + * @param database + */ + public void setZKDatabase(ZKDatabase database) { + this.zkDb = database; + } + + protected ZKDatabase getZkDb() { + return zkDb; + } + + public synchronized void initConfigInZKDatabase() { + if (zkDb != null) { + zkDb.initConfigInZKDatabase(getQuorumVerifier()); + } + } + + public boolean isRunning() { + return running; + } + + /** + * get reference to QuorumCnxManager + */ + public QuorumCnxManager getQuorumCnxManager() { + return qcmRef.get(); + } + private long readLongFromFile(String name) throws IOException { + File file = new File(logFactory.getSnapDir(), name); + BufferedReader br = new BufferedReader(new FileReader(file)); + String line = ""; + try { + line = br.readLine(); + return Long.parseLong(line); + } catch (NumberFormatException e) { + throw new IOException("Found " + line + " in " + file); + } finally { + br.close(); + } + } + + private long acceptedEpoch = -1; + private long currentEpoch = -1; + + public static final String CURRENT_EPOCH_FILENAME = "currentEpoch"; + + public static final String ACCEPTED_EPOCH_FILENAME = "acceptedEpoch"; + + /** + * Write a long value to disk atomically. Either succeeds or an exception + * is thrown. + * @param name file name to write the long to + * @param value the long value to write to the named file + * @throws IOException if the file cannot be written atomically + */ + // visibleForTest + void writeLongToFile(String name, final long value) throws IOException { + File file = new File(logFactory.getSnapDir(), name); + new AtomicFileWritingIdiom(file, new WriterStatement() { + @Override + public void write(Writer bw) throws IOException { + bw.write(Long.toString(value)); + } + }); + } + + public long getCurrentEpoch() throws IOException { + if (currentEpoch == -1) { + currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME); + } + return currentEpoch; + } + + public long getAcceptedEpoch() throws IOException { + if (acceptedEpoch == -1) { + acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME); + } + return acceptedEpoch; + } + + public void setCurrentEpoch(long e) throws IOException { + writeLongToFile(CURRENT_EPOCH_FILENAME, e); + currentEpoch = e; + } + + public void setAcceptedEpoch(long e) throws IOException { + writeLongToFile(ACCEPTED_EPOCH_FILENAME, e); + acceptedEpoch = e; + } + + public boolean processReconfig(QuorumVerifier qv, Long suggestedLeaderId, Long zxid, boolean restartLE) { + if (!isReconfigEnabled()) { + LOG.debug("Reconfig feature is disabled, skip reconfig processing."); + return false; + } + + InetSocketAddress oldClientAddr = getClientAddress(); + + // update last committed quorum verifier, write the new config to disk + // and restart leader election if config changed. + QuorumVerifier prevQV = setQuorumVerifier(qv, true); + + // There is no log record for the initial config, thus after syncing + // with leader + // /zookeeper/config is empty! it is also possible that last committed + // config is propagated during leader election + // without the propagation the corresponding log records. + // so we should explicitly do this (this is not necessary when we're + // already a Follower/Observer, only + // for Learner): + initConfigInZKDatabase(); + + if (prevQV.getVersion() < qv.getVersion() && !prevQV.equals(qv)) { + Map<Long, QuorumServer> newMembers = qv.getAllMembers(); + updateRemotePeerMXBeans(newMembers); + if (restartLE) { + restartLeaderElection(prevQV, qv); + } + + QuorumServer myNewQS = newMembers.get(getMyId()); + if (myNewQS != null && myNewQS.clientAddr != null && !myNewQS.clientAddr.equals(oldClientAddr)) { + cnxnFactory.reconfigure(myNewQS.clientAddr); + updateThreadName(); + } + + boolean roleChange = updateLearnerType(qv); + boolean leaderChange = false; + if (suggestedLeaderId != null) { + // zxid should be non-null too + leaderChange = updateVote(suggestedLeaderId, zxid); + } else { + long currentLeaderId = getCurrentVote().getId(); + QuorumServer myleaderInCurQV = prevQV.getVotingMembers().get(currentLeaderId); + QuorumServer myleaderInNewQV = qv.getVotingMembers().get(currentLeaderId); + leaderChange = (myleaderInCurQV == null + || myleaderInCurQV.addr == null + || myleaderInNewQV == null + || !myleaderInCurQV.addr.equals(myleaderInNewQV.addr)); + // we don't have a designated leader - need to go into leader + // election + reconfigFlagClear(); + } + + return roleChange || leaderChange; + } + return false; + + } + + private void updateRemotePeerMXBeans(Map<Long, QuorumServer> newMembers) { + Set<Long> existingMembers = new HashSet<>(newMembers.keySet()); + existingMembers.retainAll(jmxRemotePeerBean.keySet()); + for (Long id : existingMembers) { + RemotePeerBean rBean = jmxRemotePeerBean.get(id); + rBean.setQuorumServer(newMembers.get(id)); + } + + Set<Long> joiningMembers = new HashSet<>(newMembers.keySet()); + joiningMembers.removeAll(jmxRemotePeerBean.keySet()); + joiningMembers.remove(getMyId()); // remove self as it is local bean + for (Long id : joiningMembers) { + QuorumServer qs = newMembers.get(id); + RemotePeerBean rBean = new RemotePeerBean(this, qs); + try { + MBeanRegistry.getInstance().register(rBean, jmxQuorumBean); + jmxRemotePeerBean.put(qs.id, rBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + } + } + + Set<Long> leavingMembers = new HashSet<>(jmxRemotePeerBean.keySet()); + leavingMembers.removeAll(newMembers.keySet()); + for (Long id : leavingMembers) { + RemotePeerBean rBean = jmxRemotePeerBean.remove(id); + try { + MBeanRegistry.getInstance().unregister(rBean); + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + } + } + + private ArrayList<QuorumServer> observerMasters = new ArrayList<>(); + private void updateObserverMasterList() { + if (observerMasterPort <= 0) { + return; // observer masters not enabled + } + observerMasters.clear(); + StringBuilder sb = new StringBuilder(); + for (QuorumServer server : quorumVerifier.getVotingMembers().values()) { + InetAddress address = server.addr.getReachableOrOne().getAddress(); + InetSocketAddress addr = new InetSocketAddress(address, observerMasterPort); + observerMasters.add(new QuorumServer(server.id, addr)); + sb.append(addr).append(","); + } + LOG.info("Updated learner master list to be {}", sb.toString()); + Collections.shuffle(observerMasters); + // Reset the internal index of the observerMaster when + // the observerMaster List is refreshed + nextObserverMaster = 0; + } + + private boolean useObserverMasters() { + return getLearnerType() == LearnerType.OBSERVER && observerMasters.size() > 0; + } + + private int nextObserverMaster = 0; + private QuorumServer nextObserverMaster() { + if (nextObserverMaster >= observerMasters.size()) { + nextObserverMaster = 0; + // Add a reconnect delay only after the observer + // has exhausted trying to connect to all the masters + // from the observerMasterList + if (isRunning()) { + Observer.waitForReconnectDelay(); + } + } + return observerMasters.get(nextObserverMaster++); + } + + QuorumServer findLearnerMaster(QuorumServer leader) { + if (useObserverMasters()) { + return nextObserverMaster(); + } else { + // Add delay jitter to reduce the load on the leader + if (isRunning()) { + Observer.waitForReconnectDelay(); + } + return leader; + } + } + + /** + * Vet a given learner master's information. + * Allows specification by server id, ip only, or ip and port + */ + QuorumServer validateLearnerMaster(String desiredMaster) { + if (useObserverMasters()) { + Long sid; + try { + sid = Long.parseLong(desiredMaster); + } catch (NumberFormatException e) { + sid = null; + } + for (QuorumServer server : observerMasters) { + if (sid == null) { + for (InetSocketAddress address : server.addr.getAllAddresses()) { + String serverAddr = address.getAddress().getHostAddress() + ':' + address.getPort(); + if (serverAddr.startsWith(desiredMaster)) { + return server; + } + } + } else { + if (sid.equals(server.id)) { + return server; + } + } + } + if (sid == null) { + LOG.info("could not find learner master address={}", desiredMaster); + } else { + LOG.warn("could not find learner master sid={}", sid); + } + } else { + LOG.info("cannot validate request, observer masters not enabled"); + } + return null; + } + + private boolean updateLearnerType(QuorumVerifier newQV) { + //check if I'm an observer in new config + if (newQV.getObservingMembers().containsKey(getMyId())) { + if (getLearnerType() != LearnerType.OBSERVER) { + setLearnerType(LearnerType.OBSERVER); + LOG.info("Becoming an observer"); + reconfigFlagSet(); + return true; + } else { + return false; + } + } else if (newQV.getVotingMembers().containsKey(getMyId())) { + if (getLearnerType() != LearnerType.PARTICIPANT) { + setLearnerType(LearnerType.PARTICIPANT); + LOG.info("Becoming a voting participant"); + reconfigFlagSet(); + return true; + } else { + return false; + } + } + // I'm not in the view + if (getLearnerType() != LearnerType.PARTICIPANT) { + setLearnerType(LearnerType.PARTICIPANT); + LOG.info("Becoming a non-voting participant"); + reconfigFlagSet(); + return true; + } + return false; + } + + private boolean updateVote(long designatedLeader, long zxid) { + Vote currentVote = getCurrentVote(); + if (currentVote != null && designatedLeader != currentVote.getId()) { + setCurrentVote(new Vote(designatedLeader, zxid)); + reconfigFlagSet(); + LOG.warn("Suggested leader: {}", designatedLeader); + return true; + } + return false; + } + + /** + * Updates leader election info to avoid inconsistencies when + * a new server tries to join the ensemble. + * + * Here is the inconsistency scenario we try to solve by updating the peer + * epoch after following leader: + * + * Let's say we have an ensemble with 3 servers z1, z2 and z3. + * + * 1. z1, z2 were following z3 with peerEpoch to be 0xb8, the new epoch is + * 0xb9, aka current accepted epoch on disk. + * 2. z2 get restarted, which will use 0xb9 as it's peer epoch when loading + * the current accept epoch from disk. + * 3. z2 received notification from z1 and z3, which is following z3 with + * epoch 0xb8, so it started following z3 again with peer epoch 0xb8. + * 4. before z2 successfully connected to z3, z3 get restarted with new + * epoch 0xb9. + * 5. z2 will retry around a few round (default 5s) before giving up, + * meanwhile it will report z3 as leader. + * 6. z1 restarted, and looking with peer epoch 0xb9. + * 7. z1 voted z3, and z3 was elected as leader again with peer epoch 0xb9. + * 8. z2 successfully connected to z3 before giving up, but with peer + * epoch 0xb8. + * 9. z1 get restarted, looking for leader with peer epoch 0xba, but cannot + * join, because z2 is reporting peer epoch 0xb8, while z3 is reporting + * 0xb9. + * + * By updating the election vote after actually following leader, we can + * avoid this kind of stuck happened. + * + * Btw, the zxid and electionEpoch could be inconsistent because of the same + * reason, it's better to update these as well after syncing with leader, but + * that required protocol change which is non trivial. This problem is worked + * around by skipping comparing the zxid and electionEpoch when counting for + * votes for out of election servers during looking for leader. + * + * See https://issues.apache.org/jira/browse/ZOOKEEPER-1732 + */ + protected void updateElectionVote(long newEpoch) { + Vote currentVote = getCurrentVote(); + if (currentVote != null) { + setCurrentVote(new Vote(currentVote.getId(), currentVote.getZxid(), currentVote.getElectionEpoch(), newEpoch, currentVote + .getState())); + } + } + + private void updateThreadName() { + String plain = cnxnFactory != null + ? cnxnFactory.getLocalAddress() != null + ? formatInetAddr(cnxnFactory.getLocalAddress()) + : "disabled" + : "disabled"; + String secure = secureCnxnFactory != null ? formatInetAddr(secureCnxnFactory.getLocalAddress()) : "disabled"; + setName(String.format("QuorumPeer[myid=%d](plain=%s)(secure=%s)", getMyId(), plain, secure)); + } + + /** + * Sets the time taken for leader election in milliseconds. + * + * @param electionTimeTaken time taken for leader election + */ + void setElectionTimeTaken(long electionTimeTaken) { + this.electionTimeTaken = electionTimeTaken; + } + + /** + * @return the time taken for leader election in milliseconds. + */ + long getElectionTimeTaken() { + return electionTimeTaken; + } + + void setQuorumServerSaslRequired(boolean serverSaslRequired) { + quorumServerSaslAuthRequired = serverSaslRequired; + LOG.info("{} set to {}", QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, serverSaslRequired); + } + + void setQuorumLearnerSaslRequired(boolean learnerSaslRequired) { + quorumLearnerSaslAuthRequired = learnerSaslRequired; + LOG.info("{} set to {}", QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, learnerSaslRequired); + } + + void setQuorumSaslEnabled(boolean enableAuth) { + quorumSaslEnableAuth = enableAuth; + if (!quorumSaslEnableAuth) { + LOG.info("QuorumPeer communication is not secured! (SASL auth disabled)"); + } else { + LOG.info("{} set to {}", QuorumAuth.QUORUM_SASL_AUTH_ENABLED, enableAuth); + } + } + + void setQuorumServicePrincipal(String servicePrincipal) { + quorumServicePrincipal = servicePrincipal; + LOG.info("{} set to {}", QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL, quorumServicePrincipal); + } + + void setQuorumLearnerLoginContext(String learnerContext) { + quorumLearnerLoginContext = learnerContext; + LOG.info("{} set to {}", QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, quorumLearnerLoginContext); + } + + void setQuorumServerLoginContext(String serverContext) { + quorumServerLoginContext = serverContext; + LOG.info("{} set to {}", QuorumAuth.QUORUM_SERVER_SASL_LOGIN_CONTEXT, quorumServerLoginContext); + } + + void setQuorumCnxnThreadsSize(int qCnxnThreadsSize) { + if (qCnxnThreadsSize > QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE) { + quorumCnxnThreadsSize = qCnxnThreadsSize; + } + LOG.info("quorum.cnxn.threads.size set to {}", quorumCnxnThreadsSize); + } + + boolean isQuorumSaslAuthEnabled() { + return quorumSaslEnableAuth; + } + + private boolean isQuorumServerSaslAuthRequired() { + return quorumServerSaslAuthRequired; + } + + private boolean isQuorumLearnerSaslAuthRequired() { + return quorumLearnerSaslAuthRequired; + } + + public QuorumCnxManager createCnxnManager() { + int timeout = quorumCnxnTimeoutMs > 0 ? quorumCnxnTimeoutMs : this.tickTime * this.syncLimit; + LOG.info("Using {}ms as the quorum cnxn socket timeout", timeout); + return new QuorumCnxManager( + this, + this.getMyId(), + this.getView(), + this.authServer, + this.authLearner, + timeout, + this.getQuorumListenOnAllIPs(), + this.quorumCnxnThreadsSize, + this.isQuorumSaslAuthEnabled()); + } + + boolean isLeader(long id) { + Vote vote = getCurrentVote(); + return vote != null && id == vote.getId(); + } + + public boolean isReconfigEnabled() { + return reconfigEnabled; + } + + @InterfaceAudience.Private + /** + * This is a metric that depends on the status of the peer. + */ public Integer getSynced_observers_metric() { + if (leader != null) { + return leader.getObservingLearners().size(); + } else if (follower != null) { + return follower.getSyncedObserverSize(); + } else { + return null; + } + } + + /** + * Create a new QuorumPeer and apply all the values per the already-parsed config. + * + * @param config The appertained quorum peer config. + * @return A QuorumPeer instantiated with specified peer config. Note this peer + * is not fully initialized; caller should finish initialization through + * additional configurations (connection factory settings, etc). + * + * @throws IOException + */ + public static QuorumPeer createFromConfig(QuorumPeerConfig config) throws IOException { + QuorumPeer quorumPeer = new QuorumPeer(); + quorumPeer.setTxnFactory(new FileTxnSnapLog(config.getDataLogDir(), config.getDataDir())); + quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled()); + quorumPeer.enableLocalSessionsUpgrading(config.isLocalSessionsUpgradingEnabled()); + quorumPeer.setElectionType(config.getElectionAlg()); + quorumPeer.setMyid(config.getServerId()); + quorumPeer.setTickTime(config.getTickTime()); + quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout()); + quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout()); + quorumPeer.setInitLimit(config.getInitLimit()); + quorumPeer.setSyncLimit(config.getSyncLimit()); + quorumPeer.setConnectToLearnerMasterLimit(config.getConnectToLearnerMasterLimit()); + quorumPeer.setObserverMasterPort(config.getObserverMasterPort()); + quorumPeer.setConfigFileName(config.getConfigFilename()); + quorumPeer.setClientPortListenBacklog(config.getClientPortListenBacklog()); + quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory())); + quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false); + if (config.getLastSeenQuorumVerifier() != null) { + quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false); + } + quorumPeer.initConfigInZKDatabase(); + quorumPeer.setSslQuorum(config.isSslQuorum()); + quorumPeer.setUsePortUnification(config.shouldUsePortUnification()); + quorumPeer.setLearnerType(config.getPeerType()); + quorumPeer.setSyncEnabled(config.getSyncEnabled()); + quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs()); + if (config.sslQuorumReloadCertFiles) { + quorumPeer.getX509Util().enableCertFileReloading(); + } + quorumPeer.setMultiAddressEnabled(config.isMultiAddressEnabled()); + quorumPeer.setMultiAddressReachabilityCheckEnabled(config.isMultiAddressReachabilityCheckEnabled()); + quorumPeer.setMultiAddressReachabilityCheckTimeoutMs(config.getMultiAddressReachabilityCheckTimeoutMs()); + + // sets quorum sasl authentication configurations + quorumPeer.setQuorumSaslEnabled(config.quorumEnableSasl); + if (quorumPeer.isQuorumSaslAuthEnabled()) { + quorumPeer.setQuorumServerSaslRequired(config.quorumServerRequireSasl); + quorumPeer.setQuorumLearnerSaslRequired(config.quorumLearnerRequireSasl); + quorumPeer.setQuorumServicePrincipal(config.quorumServicePrincipal); + quorumPeer.setQuorumServerLoginContext(config.quorumServerLoginContext); + quorumPeer.setQuorumLearnerLoginContext(config.quorumLearnerLoginContext); + } + quorumPeer.setQuorumCnxnThreadsSize(config.quorumCnxnThreadsSize); + + if (config.jvmPauseMonitorToRun) { + quorumPeer.setJvmPauseMonitor(new JvmPauseMonitor(config)); + } + + return quorumPeer; + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java new file mode 100644 index 00000000000..a96a395b03b --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.jmx.MBeanRegistry; +import org.apache.zookeeper.server.DataTreeBean; +import org.apache.zookeeper.server.FinalRequestProcessor; +import org.apache.zookeeper.server.PrepRequestProcessor; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.RequestProcessor; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.ZooKeeperServerBean; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; + +/** + * A ZooKeeperServer which comes into play when peer is partitioned from the + * majority. Handles read-only clients, but drops connections from not-read-only + * ones. + * <p> + * The very first processor in the chain of request processors is a + * ReadOnlyRequestProcessor which drops state-changing requests. + */ +public class ReadOnlyZooKeeperServer extends ZooKeeperServer { + + protected final QuorumPeer self; + private volatile boolean shutdown = false; + + ReadOnlyZooKeeperServer(FileTxnSnapLog logFactory, QuorumPeer self, ZKDatabase zkDb) { + super( + logFactory, + self.tickTime, + self.minSessionTimeout, + self.maxSessionTimeout, + self.clientPortListenBacklog, + zkDb, + self.getInitialConfig(), + self.isReconfigEnabled()); + this.self = self; + } + + @Override + protected void setupRequestProcessors() { + RequestProcessor finalProcessor = new FinalRequestProcessor(this); + RequestProcessor prepProcessor = new PrepRequestProcessor(this, finalProcessor); + ((PrepRequestProcessor) prepProcessor).start(); + firstProcessor = new ReadOnlyRequestProcessor(this, prepProcessor); + ((ReadOnlyRequestProcessor) firstProcessor).start(); + } + + @Override + public synchronized void startup() { + // check to avoid startup follows shutdown + if (shutdown) { + LOG.warn("Not starting Read-only server as startup follows shutdown!"); + return; + } + registerJMX(new ReadOnlyBean(this), self.jmxLocalPeerBean); + super.startup(); + self.setZooKeeperServer(this); + self.adminServer.setZooKeeperServer(this); + LOG.info("Read-only server started"); + } + + @Override + public void createSessionTracker() { + sessionTracker = new LearnerSessionTracker( + this, getZKDatabase().getSessionWithTimeOuts(), + this.tickTime, self.getMyId(), self.areLocalSessionsEnabled(), + getZooKeeperServerListener()); + } + + @Override + protected void startSessionTracker() { + ((LearnerSessionTracker) sessionTracker).start(); + } + + @Override + protected void setLocalSessionFlag(Request si) { + switch (si.type) { + case OpCode.createSession: + if (self.areLocalSessionsEnabled()) { + si.setLocalSession(true); + } + break; + case OpCode.closeSession: + if (((UpgradeableSessionTracker) sessionTracker).isLocalSession(si.sessionId)) { + si.setLocalSession(true); + } else { + LOG.warn("Submitting global closeSession request for session 0x{} in ReadOnly mode", + Long.toHexString(si.sessionId)); + } + break; + default: + break; + } + } + + @Override + protected void validateSession(ServerCnxn cnxn, long sessionId) throws IOException { + if (((LearnerSessionTracker) sessionTracker).isGlobalSession(sessionId)) { + String msg = "Refusing global session reconnection in RO mode " + cnxn.getRemoteSocketAddress(); + LOG.info(msg); + throw new ServerCnxn.CloseRequestException(msg, ServerCnxn.DisconnectReason.RENEW_GLOBAL_SESSION_IN_RO_MODE); + } + } + + @Override + protected void registerJMX() { + // register with JMX + try { + jmxDataTreeBean = new DataTreeBean(getZKDatabase().getDataTree()); + MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxDataTreeBean = null; + } + } + + public void registerJMX(ZooKeeperServerBean serverBean, LocalPeerBean localPeerBean) { + // register with JMX + try { + jmxServerBean = serverBean; + MBeanRegistry.getInstance().register(serverBean, localPeerBean); + } catch (Exception e) { + LOG.warn("Failed to register with JMX", e); + jmxServerBean = null; + } + } + + @Override + protected void unregisterJMX() { + // unregister from JMX + try { + if (jmxDataTreeBean != null) { + MBeanRegistry.getInstance().unregister(jmxDataTreeBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxDataTreeBean = null; + } + + protected void unregisterJMX(ZooKeeperServer zks) { + // unregister from JMX + try { + if (jmxServerBean != null) { + MBeanRegistry.getInstance().unregister(jmxServerBean); + } + } catch (Exception e) { + LOG.warn("Failed to unregister with JMX", e); + } + jmxServerBean = null; + } + + @Override + public String getState() { + return "read-only"; + } + + /** + * Returns the id of the associated QuorumPeer, which will do for a unique + * id of this server. + */ + @Override + public long getServerId() { + return self.getMyId(); + } + + @Override + public synchronized void shutdown(boolean fullyShutDown) { + if (!canShutdown()) { + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + } else { + shutdown = true; + unregisterJMX(this); + + // set peer's server to null + self.setZooKeeperServer(null); + // clear all the connections + self.closeAllConnections(); + + self.adminServer.setZooKeeperServer(null); + } + // shutdown the server itself + super.shutdown(fullyShutDown); + } + + @Override + public void dumpConf(PrintWriter pwriter) { + super.dumpConf(pwriter); + + pwriter.print("initLimit="); + pwriter.println(self.getInitLimit()); + pwriter.print("syncLimit="); + pwriter.println(self.getSyncLimit()); + pwriter.print("electionAlg="); + pwriter.println(self.getElectionType()); + pwriter.print("electionPort="); + pwriter.println(self.getElectionAddress().getAllPorts() + .stream().map(Objects::toString).collect(Collectors.joining("|"))); + pwriter.print("quorumPort="); + pwriter.println(self.getQuorumAddress().getAllPorts() + .stream().map(Objects::toString).collect(Collectors.joining("|"))); + pwriter.print("peerType="); + pwriter.println(self.getLearnerType().ordinal()); + } + + @Override + protected void setState(State state) { + this.state = state; + } + +} diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java new file mode 100644 index 00000000000..d65ead216f0 --- /dev/null +++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import java.io.Flushable; +import java.io.IOException; +import java.net.Socket; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.RequestProcessor; +import org.apache.zookeeper.server.ServerMetrics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SendAckRequestProcessor implements RequestProcessor, Flushable { + + private static final Logger LOG = LoggerFactory.getLogger(SendAckRequestProcessor.class); + + final Learner learner; + + SendAckRequestProcessor(Learner peer) { + this.learner = peer; + } + + public void processRequest(Request si) { + if (si.type != OpCode.sync) { + QuorumPacket qp = new QuorumPacket(Leader.ACK, si.getHdr().getZxid(), null, null); + try { + si.logLatency(ServerMetrics.getMetrics().PROPOSAL_ACK_CREATION_LATENCY); + + learner.writePacket(qp, false); + } catch (IOException e) { + LOG.warn("Closing connection to leader, exception during packet send", e); + try { + if (!learner.sock.isClosed()) { + learner.sock.close(); + } + } catch (IOException e1) { + // Nothing to do, we are shutting things down, so an exception here is irrelevant + LOG.debug("Ignoring error closing the connection", e1); + } + } + } + } + + public void flush() throws IOException { + try { + learner.writePacket(null, true); + } catch (IOException e) { + LOG.warn("Closing connection to leader, exception during packet send", e); + try { + Socket socket = learner.sock; + if (socket != null && !socket.isClosed()) { + learner.sock.close(); + } + } catch (IOException e1) { + // Nothing to do, we are shutting things down, so an exception here is irrelevant + LOG.debug("Ignoring error closing the connection", e1); + } + } + } + + public void shutdown() { + // Nothing needed + } + +} |