diff options
74 files changed, 729 insertions, 544 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ea469733695..863ce8dd9de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,8 +150,7 @@ add_subdirectory(vespajlib) add_subdirectory(vespalib) add_subdirectory(vespalog) if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin" AND - NOT DEFINED VESPA_USE_SANITIZER AND - CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + NOT DEFINED VESPA_USE_SANITIZER) add_subdirectory(vespamalloc) endif() add_subdirectory(vsm) diff --git a/build_settings.cmake b/build_settings.cmake index a0b9ce5160a..6c3740f1bce 100644 --- a/build_settings.cmake +++ b/build_settings.cmake @@ -35,6 +35,7 @@ endif() # Warnings that are specific to C++ compilation # Note: this is not a union of C_WARN_OPTS, since CMAKE_CXX_FLAGS already includes CMAKE_C_FLAGS, which in turn includes C_WARN_OPTS transitively +set(VESPA_ATOMIC_LIB "atomic") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") set(CXX_SPECIFIC_WARN_OPTS "-Wnon-virtual-dtor -Wformat-security -Wno-overloaded-virtual") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-delete-null-pointer-checks -fsized-deallocation") @@ -43,7 +44,6 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" ST set(VESPA_GCC_LIB "") set(VESPA_STDCXX_FS_LIB "") else() - set(VESPA_ATOMIC_LIB "atomic") if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0) set(VESPA_GCC_LIB "gcc") set(VESPA_STDCXX_FS_LIB "stdc++fs") @@ -54,17 +54,6 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" ST endif() else() set(CXX_SPECIFIC_WARN_OPTS "-Wnoexcept -Wsuggest-override -Wnon-virtual-dtor -Wformat-security") - if(VESPA_OS_DISTRO_COMBINED STREQUAL "centos 8" OR - (VESPA_OS_DISTRO STREQUAL "rocky" AND - VESPA_OS_DISTRO_VERSION VERSION_GREATER_EQUAL "8" AND - VESPA_OS_DISTRO_VERSION VERSION_LESS "9") OR - (VESPA_OS_DISTRO STREQUAL "rhel" AND - VESPA_OS_DISTRO_VERSION VERSION_GREATER_EQUAL "8" AND - VESPA_OS_DISTRO_VERSION VERSION_LESS "9")) - set(VESPA_ATOMIC_LIB "") - else() - set(VESPA_ATOMIC_LIB "atomic") - endif() set(VESPA_GCC_LIB "gcc") set(VESPA_STDCXX_FS_LIB "stdc++fs") endif() diff --git a/client/go/auth/auth.go b/client/go/auth/auth.go index 397e410924d..100af7ea1d2 100644 --- a/client/go/auth/auth.go +++ b/client/go/auth/auth.go @@ -4,7 +4,6 @@ package auth import ( "context" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -15,7 +14,6 @@ import ( ) const ( - audiencePath = "/api/v2/" waitThresholdInSeconds = 3 // SecretsNamespace namespace used to set/get values from the keychain SecretsNamespace = "vespa-cli" @@ -39,8 +37,6 @@ type SecretStore interface { } type Result struct { - Tenant string - Domain string RefreshToken string AccessToken string ExpiresIn int64 @@ -114,17 +110,10 @@ func (a *Authenticator) Wait(ctx context.Context, state State) (Result, error) { return Result{}, errors.New(res.ErrorDescription) } - ten, domain, err := parseTenant(res.AccessToken) - if err != nil { - return Result{}, fmt.Errorf("cannot parse tenant from the given access token: %w", err) - } - return Result{ RefreshToken: res.RefreshToken, AccessToken: res.AccessToken, ExpiresIn: res.ExpiresIn, - Tenant: ten, - Domain: domain, }, nil } } @@ -148,28 +137,3 @@ func (a *Authenticator) getDeviceCode(ctx context.Context) (State, error) { } return res, nil } - -func parseTenant(accessToken string) (tenant, domain string, err error) { - parts := strings.Split(accessToken, ".") - v, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return "", "", err - } - var payload struct { - AUDs []string `json:"aud"` - } - if err := json.Unmarshal(v, &payload); err != nil { - return "", "", err - } - for _, aud := range payload.AUDs { - u, err := url.Parse(aud) - if err != nil { - return "", "", err - } - if u.Path == audiencePath { - parts := strings.Split(u.Host, ".") - return parts[0], u.Host, nil - } - } - return "", "", fmt.Errorf("audience not found for %s", audiencePath) -} diff --git a/client/go/auth/token.go b/client/go/auth/token.go index e9b90b8994e..120c2602bad 100644 --- a/client/go/auth/token.go +++ b/client/go/auth/token.go @@ -25,16 +25,16 @@ type TokenRetriever struct { Client *http.Client } -// Delete deletes the given tenant from the secrets' storage. -func (t *TokenRetriever) Delete(tenant string) error { - return t.Secrets.Delete(SecretsNamespace, tenant) +// Delete deletes the given system from the secrets' storage. +func (t *TokenRetriever) Delete(system string) error { + return t.Secrets.Delete(SecretsNamespace, system) } // Refresh gets a new access token from the provided refresh token, // The request is used the default client_id and endpoint for device authentication. -func (t *TokenRetriever) Refresh(ctx context.Context, tenant string) (TokenResponse, error) { +func (t *TokenRetriever) Refresh(ctx context.Context, system string) (TokenResponse, error) { // get stored refresh token: - refreshToken, err := t.Secrets.Get(SecretsNamespace, tenant) + refreshToken, err := t.Secrets.Get(SecretsNamespace, system) if err != nil { return TokenResponse{}, fmt.Errorf("cannot get the stored refresh token: %w", err) } diff --git a/client/go/cli/cli.go b/client/go/cli/cli.go index e1dde387b89..22c40f195b4 100644 --- a/client/go/cli/cli.go +++ b/client/go/cli/cli.go @@ -7,9 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/joeshaw/envdecode" - "github.com/pkg/browser" - "github.com/vespa-engine/vespa/client/go/util" "io/ioutil" "net/http" "os" @@ -19,8 +16,11 @@ import ( "sync" "time" + "github.com/joeshaw/envdecode" "github.com/lestrrat-go/jwx/jwt" + "github.com/pkg/browser" "github.com/vespa-engine/vespa/client/go/auth" + "github.com/vespa-engine/vespa/client/go/util" ) const accessTokenExpThreshold = 5 * time.Minute @@ -28,55 +28,24 @@ const accessTokenExpThreshold = 5 * time.Minute var errUnauthenticated = errors.New("not logged in. Try 'vespa login'") type config struct { - InstallID string `json:"install_id,omitempty"` - DefaultTenant string `json:"default_tenant"` - Tenants map[string]Tenant `json:"tenants"` + Systems map[string]System `json:"systems"` } -// Tenant is an auth0 Tenant. -type Tenant struct { - Name string `json:"name"` - Domain string `json:"domain"` - AccessToken string `json:"access_token,omitempty"` - Scopes []string `json:"scopes,omitempty"` - ExpiresAt time.Time `json:"expires_at"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` +type System struct { + AccessToken string `json:"access_token,omitempty"` + Scopes []string `json:"scopes,omitempty"` + ExpiresAt time.Time `json:"expires_at"` } type Cli struct { Authenticator *auth.Authenticator - tenant string + system string initOnce sync.Once errOnce error Path string config config } -// IsLoggedIn encodes the domain logic for determining whether we're -// logged in. This might check our config storage, or just in memory. -func (c *Cli) IsLoggedIn() bool { - // No need to check errors for initializing context. - _ = c.init() - - if c.tenant == "" { - return false - } - - // Parse the access token for the tenant. - t, err := jwt.ParseString(c.config.Tenants[c.tenant].AccessToken) - if err != nil { - return false - } - - // Check if token is valid. - if err = jwt.Validate(t, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil { - return false - } - - return true -} - // default to vespa-cd.auth0.com var ( authCfg struct { @@ -99,11 +68,12 @@ func ContextWithCancel() context.Context { return ctx } -// Setup will try to initialize the config context, as well as figure out if -// there's a readily available tenant. -func GetCli(configPath string) (*Cli, error) { +// GetCli will try to initialize the config context, as well as figure out if +// there's a readily available system. +func GetCli(configPath string, systemName string) (*Cli, error) { c := Cli{} c.Path = configPath + c.system = systemName if err := envdecode.StrictDecode(&authCfg); err != nil { return nil, fmt.Errorf("could not decode env: %w", err) } @@ -116,29 +86,49 @@ func GetCli(configPath string) (*Cli, error) { return &c, nil } -// prepareTenant loads the Tenant, refreshing its token if necessary. -// The Tenant access token needs a refresh if: -// 1. the Tenant scopes are different from the currently required scopes. -// 2. the access token is expired. -func (c *Cli) PrepareTenant(ctx context.Context) (Tenant, error) { - if err := c.init(); err != nil { - return Tenant{}, err +// IsLoggedIn encodes the domain logic for determining whether we're +// logged in. This might check our config storage, or just in memory. +func (c *Cli) IsLoggedIn() bool { + // No need to check errors for initializing context. + _ = c.init() + + if c.system == "" { + return false } - t, err := c.getTenant() + + // Parse the access token for the system. + token, err := jwt.ParseString(c.config.Systems[c.system].AccessToken) if err != nil { - return Tenant{}, err + return false } - if t.ClientID != "" && t.ClientSecret != "" { - return t, nil + // Check if token is valid. + if err = jwt.Validate(token, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil { + return false } - if t.AccessToken == "" || scopesChanged(t) { - t, err = RunLogin(ctx, c, true) + return true +} + +// PrepareSystem loads the System, refreshing its token if necessary. +// The System access token needs a refresh if: +// 1. the System scopes are different from the currently required scopes - (auth0 changes). +// 2. the access token is expired. +func (c *Cli) PrepareSystem(ctx context.Context) (System, error) { + if err := c.init(); err != nil { + return System{}, err + } + s, err := c.getSystem() + if err != nil { + return System{}, err + } + + if s.AccessToken == "" || scopesChanged(s) { + s, err = RunLogin(ctx, c, true) if err != nil { - return Tenant{}, err + return System{}, err } - } else if isExpired(t.ExpiresAt, accessTokenExpThreshold) { + } else if isExpired(s.ExpiresAt, accessTokenExpThreshold) { // check if the stored access token is expired: // use the refresh token to get a new access token: tr := &auth.TokenRetriever{ @@ -147,29 +137,29 @@ func (c *Cli) PrepareTenant(ctx context.Context) (Tenant, error) { Client: http.DefaultClient, } - res, err := tr.Refresh(ctx, t.Domain) + res, err := tr.Refresh(ctx, c.system) if err != nil { // ask and guide the user through the login process: fmt.Println(fmt.Errorf("failed to renew access token, %s", err)) - t, err = RunLogin(ctx, c, true) + s, err = RunLogin(ctx, c, true) if err != nil { - return Tenant{}, err + return System{}, err } } else { - // persist the updated tenant with renewed access token - t.AccessToken = res.AccessToken - t.ExpiresAt = time.Now().Add( + // persist the updated system with renewed access token + s.AccessToken = res.AccessToken + s.ExpiresAt = time.Now().Add( time.Duration(res.ExpiresIn) * time.Second, ) - err = c.AddTenant(t) + err = c.AddSystem(s) if err != nil { - return Tenant{}, err + return System{}, err } } } - return t, nil + return s, nil } // isExpired is true if now() + a threshold is after the given date @@ -177,11 +167,11 @@ func isExpired(t time.Time, threshold time.Duration) bool { return time.Now().Add(threshold).After(t) } -// scopesChanged compare the Tenant scopes +// scopesChanged compare the System scopes // with the currently required scopes. -func scopesChanged(t Tenant) bool { +func scopesChanged(s System) bool { want := auth.RequiredScopes() - got := t.Scopes + got := s.Scopes sort.Strings(want) sort.Strings(got) @@ -194,7 +184,7 @@ func scopesChanged(t Tenant) bool { return true } - for i := range t.Scopes { + for i := range s.Scopes { if want[i] != got[i] { return true } @@ -203,34 +193,30 @@ func scopesChanged(t Tenant) bool { return false } -func (c *Cli) getTenant() (Tenant, error) { +func (c *Cli) getSystem() (System, error) { if err := c.init(); err != nil { - return Tenant{}, err + return System{}, err } - t, ok := c.config.Tenants[c.tenant] + s, ok := c.config.Systems[c.system] if !ok { - return Tenant{}, fmt.Errorf("unable to find tenant: %s; run 'vespa login' to configure a new tenant", c.tenant) + return System{}, fmt.Errorf("unable to find system: %s; run 'vespa login' to configure a new system", c.system) } - return t, nil + return s, nil } -// AddTenant assigns an existing, or new Tenant. This is expected to be called +// AddSystem assigns an existing, or new System. This is expected to be called // after a login has completed. -func (c *Cli) AddTenant(ten Tenant) error { +func (c *Cli) AddSystem(s System) error { _ = c.init() - if c.config.DefaultTenant == "" { - c.config.DefaultTenant = ten.Domain - } - // If we're dealing with an empty file, we'll need to initialize this map. - if c.config.Tenants == nil { - c.config.Tenants = map[string]Tenant{} + if c.config.Systems == nil { + c.config.Systems = map[string]System{} } - c.config.Tenants[ten.Domain] = ten + c.config.Systems[c.system] = s if err := c.persistConfig(); err != nil { return fmt.Errorf("unexpected error persisting config: %w", err) @@ -282,14 +268,6 @@ func (c *Cli) initContext() (err error) { return err } - if c.tenant == "" && c.config.DefaultTenant == "" { - return errUnauthenticated - } - - if c.tenant == "" { - c.tenant = c.config.DefaultTenant - } - return nil } @@ -297,14 +275,14 @@ func (c *Cli) initContext() (err error) { // by showing the login instructions, opening the browser. // Use `expired` to run the login from other commands setup: // this will only affect the messages. -func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) { +func RunLogin(ctx context.Context, c *Cli, expired bool) (System, error) { if expired { fmt.Println("Please sign in to re-authorize the CLI.") } - state, err := cli.Authenticator.Start(ctx) + state, err := c.Authenticator.Start(ctx) if err != nil { - return Tenant{}, fmt.Errorf("could not start the authentication process: %w", err) + return System{}, fmt.Errorf("could not start the authentication process: %w", err) } fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode) @@ -319,12 +297,12 @@ func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) { var res auth.Result err = util.Spinner("Waiting for login to complete in browser", func() error { - res, err = cli.Authenticator.Wait(ctx, state) + res, err = c.Authenticator.Wait(ctx, state) return err }) if err != nil { - return Tenant{}, fmt.Errorf("login error: %w", err) + return System{}, fmt.Errorf("login error: %w", err) } fmt.Print("\n") @@ -333,23 +311,21 @@ func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) { // store the refresh token secretsStore := &auth.Keyring{} - err = secretsStore.Set(auth.SecretsNamespace, res.Domain, res.RefreshToken) + err = secretsStore.Set(auth.SecretsNamespace, c.system, res.RefreshToken) if err != nil { // log the error but move on fmt.Println("Could not store the refresh token locally, please expect to login again once your access token expired.") } - t := Tenant{ - Name: res.Tenant, - Domain: res.Domain, + s := System{ AccessToken: res.AccessToken, ExpiresAt: time.Now().Add(time.Duration(res.ExpiresIn) * time.Second), Scopes: auth.RequiredScopes(), } - err = cli.AddTenant(t) + err = c.AddSystem(s) if err != nil { - return Tenant{}, fmt.Errorf("could not add tenant to config: %w", err) + return System{}, fmt.Errorf("could not add system to config: %w", err) } - return t, nil + return s, nil } diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go index 54d8798b71d..084148f9bdc 100644 --- a/client/go/cmd/helpers.go +++ b/client/go/cmd/helpers.go @@ -144,6 +144,13 @@ func getService(service string, sessionOrRunID int64) *vespa.Service { func getSystem() string { return os.Getenv("VESPA_CLI_CLOUD_SYSTEM") } +func getSystemName() string { + if getSystem() == "publiccd" { + return "publiccd" + } + return "public" +} + func getConsoleURL() string { if getSystem() == "publiccd" { return "https://console-cd.vespa.oath.cloud" @@ -153,17 +160,10 @@ func getConsoleURL() string { } func getApiURL() string { - if vespa.Auth0AccessTokenEnabled() { - if getSystem() == "publiccd" { - return "https://api.vespa-external-cd.aws.oath.cloud:443" - } - return "https://api.vespa-external.aws.oath.cloud:443" - } else { - if getSystem() == "publiccd" { - return "https://api.vespa-external-cd.aws.oath.cloud:4443" - } - return "https://api.vespa-external.aws.oath.cloud:4443" + if getSystem() == "publiccd" { + return "https://api.vespa-external-cd.aws.oath.cloud:4443" } + return "https://api.vespa-external.aws.oath.cloud:4443" } func getTarget() vespa.Target { @@ -212,7 +212,8 @@ func getTarget() vespa.Target { Writer: stdout, Level: vespa.LogLevel(logLevelArg), }, - cfg.AuthConfigPath()) + cfg.AuthConfigPath(), + getSystemName()) } fatalErrHint(fmt.Errorf("Invalid target: %s", targetType), "Valid targets are 'local', 'cloud' or an URL") return nil diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go index 767d462b0be..415d44b75db 100644 --- a/client/go/cmd/login.go +++ b/client/go/cmd/login.go @@ -24,7 +24,7 @@ var loginCmd = &cobra.Command{ if err != nil { return err } - c, err := cli.GetCli(cfg.AuthConfigPath()) + c, err := cli.GetCli(cfg.AuthConfigPath(), getSystemName()) if err != nil { return err } diff --git a/client/go/vespa/target.go b/client/go/vespa/target.go index f497bf5b3cd..367685df34d 100644 --- a/client/go/vespa/target.go +++ b/client/go/vespa/target.go @@ -1,4 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + package vespa import ( @@ -6,7 +7,6 @@ import ( "crypto/tls" "encoding/json" "fmt" - "github.com/vespa-engine/vespa/client/go/cli" "io" "io/ioutil" "math" @@ -18,6 +18,7 @@ import ( "strings" "time" + "github.com/vespa-engine/vespa/client/go/cli" "github.com/vespa-engine/vespa/client/go/util" ) @@ -211,6 +212,7 @@ type cloudTarget struct { queryURL string documentURL string authConfigPath string + systemName string } func (t *cloudTarget) Type() string { return t.targetType } @@ -253,12 +255,12 @@ func (t *cloudTarget) PrepareApiRequest(req *http.Request, sigKeyId string) erro } func (t *cloudTarget) addAuth0AccessToken(request *http.Request) error { - c, err := cli.GetCli(t.authConfigPath) - tenant, err := c.PrepareTenant(cli.ContextWithCancel()) + c, err := cli.GetCli(t.authConfigPath, t.systemName) + system, err := c.PrepareSystem(cli.ContextWithCancel()) if err != nil { return err } - request.Header.Set("Authorization", "Bearer "+tenant.AccessToken) + request.Header.Set("Authorization", "Bearer "+system.AccessToken) return nil } @@ -446,8 +448,7 @@ func CustomTarget(baseURL string) Target { } // CloudTarget creates a Target for the Vespa Cloud platform. -func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions, - authConfigPath string) Target { +func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions, authConfigPath string, systemName string) Target { return &cloudTarget{ apiURL: apiURL, targetType: cloudTargetType, @@ -456,6 +457,7 @@ func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions tlsOptions: tlsOptions, logOptions: logOptions, authConfigPath: authConfigPath, + systemName: systemName, } } diff --git a/client/go/vespa/target_test.go b/client/go/vespa/target_test.go index d4d23901513..62bde3044bf 100644 --- a/client/go/vespa/target_test.go +++ b/client/go/vespa/target_test.go @@ -149,15 +149,10 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target { } assert.Nil(t, err) - target := CloudTarget( - "https://example.com", - Deployment{ - Application: ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"}, - Zone: ZoneID{Environment: "dev", Region: "us-north-1"}, - }, - apiKey, - TLSOptions{KeyPair: x509KeyPair}, - LogOptions{Writer: logWriter}, "") + target := CloudTarget("https://example.com", Deployment{ + Application: ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"}, + Zone: ZoneID{Environment: "dev", Region: "us-north-1"}, + }, apiKey, TLSOptions{KeyPair: x509KeyPair}, LogOptions{Writer: logWriter}, "", "") if ct, ok := target.(*cloudTarget); ok { ct.apiURL = url } else { diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java index 1c45e4ba5dd..0154e5d3b13 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ApplicationClusterEndpoint.java @@ -29,12 +29,12 @@ public class ApplicationClusterEndpoint { private final String clusterId; private ApplicationClusterEndpoint(DnsName dnsName, Scope scope, RoutingMethod routingMethod, int weight, List<String> hostNames, String clusterId) { - this.dnsName = Objects.requireNonNull(dnsName); - this.scope = Objects.requireNonNull(scope); - this.routingMethod = Objects.requireNonNull(routingMethod); + this.dnsName = dnsName; + this.scope = scope; + this.routingMethod = routingMethod; this.weight = weight; - this.hostNames = List.copyOf(Objects.requireNonNull(hostNames)); - this.clusterId = Objects.requireNonNull(clusterId); + this.hostNames = List.copyOf(hostNames); + this.clusterId = clusterId; } public DnsName dnsName() { diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index c5781c2805d..2b2344c5fdb 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -100,11 +100,13 @@ public interface ModelContext { @ModelFeatureFlag(owners = {"hmusum"}) default double resourceLimitMemory() { return 0.8; } @ModelFeatureFlag(owners = {"geirst", "vekterli"}) default double minNodeRatioPerGroup() { return 0.0; } @ModelFeatureFlag(owners = {"arnej"}) default boolean newLocationBrokerLogic() { return true; } - @ModelFeatureFlag(owners = {"bjorncs"}) default int maxConnectionLifeInHosted() { return 45; } + @ModelFeatureFlag(owners = {"bjorncs"}, removeAfter = "7.504") default int maxConnectionLifeInHosted() { return 45; } @ModelFeatureFlag(owners = {"geirst", "vekterli"}) default int distributorMergeBusyWait() { return 10; } @ModelFeatureFlag(owners = {"vekterli", "geirst"}) default boolean distributorEnhancedMaintenanceScheduling() { return false; } @ModelFeatureFlag(owners = {"arnej"}) default boolean forwardIssuesAsErrors() { return true; } @ModelFeatureFlag(owners = {"geirst", "vekterli"}) default boolean asyncApplyBucketDiff() { return false; } + @ModelFeatureFlag(owners = {"arnej"}) default boolean ignoreThreadStackSizes() { return false; } + @ModelFeatureFlag(owners = {"vekterli", "geirst"}) default boolean unorderedMergeChaining() { return false; } } /** Warning: As elsewhere in this package, do not make backwards incompatible changes that will break old config models! */ diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index 49c968a1d91..9f2f3620c73 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -72,6 +72,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea private double diskBloatFactor = 0.2; private boolean distributorEnhancedMaintenanceScheduling = false; private boolean asyncApplyBucketDiff = false; + private boolean unorderedMergeChaining = false; private List<String> zoneDnsSuffixes = List.of(); @Override public ModelContext.FeatureFlags featureFlags() { return this; } @@ -125,6 +126,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea @Override public boolean distributorEnhancedMaintenanceScheduling() { return distributorEnhancedMaintenanceScheduling; } @Override public int maxUnCommittedMemory() { return maxUnCommittedMemory; } @Override public boolean asyncApplyBucketDiff() { return asyncApplyBucketDiff; } + @Override public boolean unorderedMergeChaining() { return unorderedMergeChaining; } @Override public List<String> zoneDnsSuffixes() { return zoneDnsSuffixes; } public TestProperties maxUnCommittedMemory(int maxUnCommittedMemory) { @@ -322,6 +324,11 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea return this; } + public TestProperties setUnorderedMergeChaining(boolean unordered) { + unorderedMergeChaining = unordered; + return this; + } + public TestProperties setZoneDnsSuffixes(List<String> zoneDnsSuffixes) { this.zoneDnsSuffixes = List.copyOf(zoneDnsSuffixes); return this; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ConfigSentinel.java b/config-model/src/main/java/com/yahoo/vespa/model/ConfigSentinel.java index c86cda0bbd1..217f6cff778 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ConfigSentinel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ConfigSentinel.java @@ -19,6 +19,7 @@ public class ConfigSentinel extends AbstractService implements SentinelConfig.Pr private final ApplicationId applicationId; private final Zone zone; + private final boolean ignoreRequestedStackSizes; /** * Constructs a new ConfigSentinel for the given host. @@ -31,6 +32,7 @@ public class ConfigSentinel extends AbstractService implements SentinelConfig.Pr super(host, "sentinel"); this.applicationId = applicationId; this.zone = zone; + this.ignoreRequestedStackSizes = featureFlags.ignoreThreadStackSizes(); portsMeta.on(0).tag("rpc").tag("admin"); portsMeta.on(1).tag("telnet").tag("interactive").tag("http").tag("state"); setProp("clustertype", "hosts"); @@ -73,6 +75,7 @@ public class ConfigSentinel extends AbstractService implements SentinelConfig.Pr @Override public void getConfig(SentinelConfig.Builder builder) { builder.application(getApplicationConfig()); + builder.ignoreRequestedStackSizes(ignoreRequestedStackSizes); for (Service s : getHostResource().getServices()) { if (s.getStartupCommand() != null) { builder.service(getServiceConfig(s)); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java index 3eba4ad84a6..b7bacb34b05 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java @@ -25,17 +25,16 @@ public class HostedSslConnectorFactory extends ConnectorFactory { private final boolean enforceClientAuth; private final boolean enforceHandshakeClientAuth; private final Collection<String> tlsCiphersOverride; - private final Duration maxConnectionLife; /** * Create connector factory that uses a certificate provided by the config-model / configserver and default hosted Vespa truststore. */ public static HostedSslConnectorFactory withProvidedCertificate( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth, - Collection<String> tlsCiphersOverride, Duration maxConnectionLife) { + Collection<String> tlsCiphersOverride) { ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null, enforceHandshakeClientAuth); - return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth, tlsCiphersOverride, maxConnectionLife); + return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth, tlsCiphersOverride); } /** @@ -43,28 +42,25 @@ public class HostedSslConnectorFactory extends ConnectorFactory { */ public static HostedSslConnectorFactory withProvidedCertificateAndTruststore( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates, - Collection<String> tlsCiphersOverride, Duration maxConnectionLife) { + Collection<String> tlsCiphersOverride) { ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates, false); - return new HostedSslConnectorFactory(sslProvider, true, false, tlsCiphersOverride, maxConnectionLife); + return new HostedSslConnectorFactory(sslProvider, true, false, tlsCiphersOverride); } /** * Create connector factory that uses the default certificate and truststore provided by Vespa (through Vespa-global TLS configuration). */ - public static HostedSslConnectorFactory withDefaultCertificateAndTruststore( - String serverName, Collection<String> tlsCiphersOverride, Duration maxConnectionLife) { - return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false, tlsCiphersOverride, maxConnectionLife); + public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String serverName, Collection<String> tlsCiphersOverride) { + return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false, tlsCiphersOverride); } private HostedSslConnectorFactory(SslProvider sslProvider, boolean enforceClientAuth, - boolean enforceHandshakeClientAuth, Collection<String> tlsCiphersOverride, - Duration maxConnectionLife) { + boolean enforceHandshakeClientAuth, Collection<String> tlsCiphersOverride) { super(new Builder("tls4443", 4443).sslProvider(sslProvider)); this.enforceClientAuth = enforceClientAuth; this.enforceHandshakeClientAuth = enforceHandshakeClientAuth; this.tlsCiphersOverride = tlsCiphersOverride; - this.maxConnectionLife = maxConnectionLife; } private static ConfiguredDirectSslProvider createConfiguredDirectSslProvider( @@ -100,6 +96,6 @@ public class HostedSslConnectorFactory extends ConnectorFactory { connectorBuilder .proxyProtocol(new ConnectorConfig.ProxyProtocol.Builder().enabled(true).mixedMode(true)) .idleTimeout(Duration.ofSeconds(30).toSeconds()) - .maxConnectionLife(maxConnectionLife.toSeconds()); + .maxConnectionLife(Duration.ofSeconds(45).toSeconds()); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 527897a3266..d65fbba6a5e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -94,7 +94,6 @@ import org.w3c.dom.Node; import java.net.URI; import java.security.cert.X509Certificate; -import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -443,7 +442,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { // If the deployment contains certificate/private key reference, setup TLS port HostedSslConnectorFactory connectorFactory; Collection<String> tlsCiphersOverride = deployState.getProperties().tlsCiphersOverride(); - Duration maxConnectionLife = Duration.ofSeconds(deployState.featureFlags().maxConnectionLifeInHosted()); if (deployState.endpointCertificateSecrets().isPresent()) { boolean authorizeClient = deployState.zone().system().isPublic(); if (authorizeClient && deployState.tlsClientAuthority().isEmpty()) { @@ -458,11 +456,11 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { connectorFactory = authorizeClient ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore( - serverName, endpointCertificateSecrets, getTlsClientAuthorities(deployState), tlsCiphersOverride, maxConnectionLife) + serverName, endpointCertificateSecrets, getTlsClientAuthorities(deployState), tlsCiphersOverride) : HostedSslConnectorFactory.withProvidedCertificate( - serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride, maxConnectionLife); + serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride); } else { - connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName, tlsCiphersOverride, maxConnectionLife); + connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName, tlsCiphersOverride); } cluster.getHttp().getAccessControl().ifPresent(accessControl -> accessControl.configureHostedConnector(connectorFactory)); server.addConnector(connectorFactory); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java index fa91dbc2e42..518c3f73a8f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java @@ -45,6 +45,7 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl private final int maxActivationInhibitedOutOfSyncGroups; private final int mergeBusyWait; private final boolean enhancedMaintenanceScheduling; + private final boolean unorderedMergeChaining; public static class Builder extends VespaDomBuilder.DomConfigProducerBuilder<DistributorCluster> { @@ -109,12 +110,14 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl int maxInhibitedGroups = deployState.getProperties().featureFlags().maxActivationInhibitedOutOfSyncGroups(); int mergeBusyWait = deployState.getProperties().featureFlags().distributorMergeBusyWait(); boolean useEnhancedMaintenanceScheduling = deployState.getProperties().featureFlags().distributorEnhancedMaintenanceScheduling(); + boolean unorderedMergeChaining = deployState.getProperties().featureFlags().unorderedMergeChaining(); return new DistributorCluster(parent, new BucketSplitting.Builder().build(new ModelElement(producerSpec)), gc, hasIndexedDocumentType, useThreePhaseUpdates, maxInhibitedGroups, mergeBusyWait, - useEnhancedMaintenanceScheduling); + useEnhancedMaintenanceScheduling, + unorderedMergeChaining); } } @@ -123,7 +126,8 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl boolean useThreePhaseUpdates, int maxActivationInhibitedOutOfSyncGroups, int mergeBusyWait, - boolean enhancedMaintenanceScheduling) + boolean enhancedMaintenanceScheduling, + boolean unorderedMergeChaining) { super(parent, "distributor"); this.parent = parent; @@ -134,6 +138,7 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl this.maxActivationInhibitedOutOfSyncGroups = maxActivationInhibitedOutOfSyncGroups; this.mergeBusyWait = mergeBusyWait; this.enhancedMaintenanceScheduling = enhancedMaintenanceScheduling; + this.unorderedMergeChaining = unorderedMergeChaining; } @Override @@ -149,6 +154,7 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl builder.max_activation_inhibited_out_of_sync_groups(maxActivationInhibitedOutOfSyncGroups); builder.inhibit_merge_sending_on_busy_node_duration_sec(mergeBusyWait); builder.implicitly_clear_bucket_priority_on_schedule(enhancedMaintenanceScheduling); + builder.use_unordered_merge_chaining(unorderedMergeChaining); bucketSplitting.getConfig(builder); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java index 0fb7e82c095..729348a0e3a 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java @@ -1119,6 +1119,23 @@ public class ContentClusterTest extends ContentBaseTest { } @Test + public void unordered_merge_chaining_config_controlled_by_properties() throws Exception { + assertFalse(resolveUnorderedMergeChainingConfig(false)); + assertTrue(resolveUnorderedMergeChainingConfig(true)); + } + + private boolean resolveUnorderedMergeChainingConfig(boolean unorderedMergeChaining) throws Exception { + var props = new TestProperties(); + if (unorderedMergeChaining) { + props.setUnorderedMergeChaining(true); + } + var cluster = createOneNodeCluster(props); + var builder = new StorDistributormanagerConfig.Builder(); + cluster.getDistributorNodes().getConfig(builder); + return (new StorDistributormanagerConfig(builder)).use_unordered_merge_chaining(); + } + + @Test public void testDedicatedClusterControllers() { VespaModel noContentModel = createEnd2EndOneNode(new TestProperties().setHostedVespa(true) .setMultitenant(true), diff --git a/config/src/main/java/com/yahoo/vespa/config/ConnectionPool.java b/config/src/main/java/com/yahoo/vespa/config/ConnectionPool.java index 31f85ee4fd5..efcb30213ad 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConnectionPool.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConnectionPool.java @@ -11,8 +11,8 @@ public interface ConnectionPool extends AutoCloseable { Connection getCurrent(); /** - * Switches to another (healthy, if one exists) Connection instance. - * Returns the resulting Connection. + * Switches to another (healthy, if possible) Connection instance. {@link #getCurrent()} will + * return this instance afterwards, which is also the return value. * * @return a Connection */ diff --git a/config/src/main/java/com/yahoo/vespa/config/JRTConnectionPool.java b/config/src/main/java/com/yahoo/vespa/config/JRTConnectionPool.java index f6708a1432c..a64121e2553 100644 --- a/config/src/main/java/com/yahoo/vespa/config/JRTConnectionPool.java +++ b/config/src/main/java/com/yahoo/vespa/config/JRTConnectionPool.java @@ -15,7 +15,7 @@ import java.util.logging.Logger; /** * A pool of JRT connections to a config source (either a config server or a config proxy). - * The current connection is chosen randomly when calling {#link {@link #switchConnection()}} + * The current connection is chosen randomly when calling {@link #switchConnection(Connection)} * (it will continue to use the same connection if there is only one source). * The current connection is available with {@link #getCurrent()}. * diff --git a/configd/src/apps/sentinel/manager.cpp b/configd/src/apps/sentinel/manager.cpp index 631f63febd5..ffa9c9281fa 100644 --- a/configd/src/apps/sentinel/manager.cpp +++ b/configd/src/apps/sentinel/manager.cpp @@ -79,6 +79,11 @@ Manager::doConfigure() LOG_ASSERT(_env.configOwner().hasConfig()); const SentinelConfig& config(_env.configOwner().getConfig()); + if (config.ignoreRequestedStackSizes) { + setenv("VESPA_IGNORE_REQUESTED_STACK_SIZES", "true", 1); + } else { + unsetenv("VESPA_IGNORE_REQUESTED_STACK_SIZES"); + } _env.rpcPort(config.port.rpc); _env.statePort(config.port.telnet); diff --git a/configdefinitions/src/vespa/sentinel.def b/configdefinitions/src/vespa/sentinel.def index cccf931938a..7bd4a000055 100644 --- a/configdefinitions/src/vespa/sentinel.def +++ b/configdefinitions/src/vespa/sentinel.def @@ -28,6 +28,9 @@ connectivity.minOkPercent int default=50 ## Absolute number of nodes with confirmed network connectivity problems, maximum connectivity.maxBadCount int default=1 +## Ask low-level thread creation to honor requested stack size +ignoreRequestedStackSizes bool default=false restart + ## The command to run. This will be run by sh -c, and the following ## environment variables are defined: $ROOT, $VESPA_SERVICE_NAME, ## $VESPA_CONFIG_ID diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 0b55c4bb53e..3630215913e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -190,7 +190,6 @@ public class ModelContextImpl implements ModelContext { private final int metricsproxyNumThreads; private final boolean containerDumpHeapOnShutdownTimeout; private final double containerShutdownTimeout; - private final int maxConnectionLifeInHosted; private final int distributorMergeBusyWait; private final int docstoreCompressionLevel; private final double diskBloatFactor; @@ -198,6 +197,8 @@ public class ModelContextImpl implements ModelContext { private final int maxUnCommittedMemory; private final boolean forwardIssuesAsErrors; private final boolean asyncApplyBucketDiff; + private final boolean ignoreThreadStackSizes; + private final boolean unorderedMergeChaining; public FeatureFlags(FlagSource source, ApplicationId appId) { this.defaultTermwiseLimit = flagValue(source, appId, Flags.DEFAULT_TERM_WISE_LIMIT); @@ -226,7 +227,6 @@ public class ModelContextImpl implements ModelContext { this.metricsproxyNumThreads = flagValue(source, appId, Flags.METRICSPROXY_NUM_THREADS); this.containerDumpHeapOnShutdownTimeout = flagValue(source, appId, Flags.CONTAINER_DUMP_HEAP_ON_SHUTDOWN_TIMEOUT); this.containerShutdownTimeout = flagValue(source, appId,Flags.CONTAINER_SHUTDOWN_TIMEOUT); - this.maxConnectionLifeInHosted = flagValue(source, appId, Flags.MAX_CONNECTION_LIFE_IN_HOSTED); this.distributorMergeBusyWait = flagValue(source, appId, Flags.DISTRIBUTOR_MERGE_BUSY_WAIT); this.docstoreCompressionLevel = flagValue(source, appId, Flags.DOCSTORE_COMPRESSION_LEVEL); this.diskBloatFactor = flagValue(source, appId, Flags.DISK_BLOAT_FACTOR); @@ -234,6 +234,8 @@ public class ModelContextImpl implements ModelContext { this.maxUnCommittedMemory = flagValue(source, appId, Flags.MAX_UNCOMMITTED_MEMORY);; this.forwardIssuesAsErrors = flagValue(source, appId, PermanentFlags.FORWARD_ISSUES_AS_ERRORS); this.asyncApplyBucketDiff = flagValue(source, appId, Flags.ASYNC_APPLY_BUCKET_DIFF); + this.ignoreThreadStackSizes = flagValue(source, appId, Flags.IGNORE_THREAD_STACK_SIZES); + this.unorderedMergeChaining = flagValue(source, appId, Flags.UNORDERED_MERGE_CHAINING); } @Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; } @@ -264,7 +266,6 @@ public class ModelContextImpl implements ModelContext { @Override public int metricsproxyNumThreads() { return metricsproxyNumThreads; } @Override public double containerShutdownTimeout() { return containerShutdownTimeout; } @Override public boolean containerDumpHeapOnShutdownTimeout() { return containerDumpHeapOnShutdownTimeout; } - @Override public int maxConnectionLifeInHosted() { return maxConnectionLifeInHosted; } @Override public int distributorMergeBusyWait() { return distributorMergeBusyWait; } @Override public double diskBloatFactor() { return diskBloatFactor; } @Override public int docstoreCompressionLevel() { return docstoreCompressionLevel; } @@ -272,6 +273,8 @@ public class ModelContextImpl implements ModelContext { @Override public int maxUnCommittedMemory() { return maxUnCommittedMemory; } @Override public boolean forwardIssuesAsErrors() { return forwardIssuesAsErrors; } @Override public boolean asyncApplyBucketDiff() { return asyncApplyBucketDiff; } + @Override public boolean ignoreThreadStackSizes() { return ignoreThreadStackSizes; } + @Override public boolean unorderedMergeChaining() { return unorderedMergeChaining; } private static <V> V flagValue(FlagSource source, ApplicationId appId, UnboundFlag<? extends V, ?, ?> flag) { return flag.bindTo(source) diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index 0aeea5ce2d5..95be59e4d26 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -53,8 +53,10 @@ import org.apache.zookeeper.KeeperException; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -80,6 +82,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import static com.yahoo.vespa.curator.Curator.CompletionWaiter; +import static java.nio.file.Files.readAttributes; /** * @@ -593,9 +596,27 @@ public class SessionRepository { return candidate.getCreateTime().plus(sessionLifetime).isBefore(clock.instant()); } - // Sessions with state other than UNKNOWN or ACTIVATE + // Sessions with state other than UNKNOWN or ACTIVATE or old sessions in UNKNOWN state private boolean canBeDeleted(LocalSession candidate) { - return ! List.of(Session.Status.UNKNOWN, Session.Status.ACTIVATE).contains(candidate.getStatus()); + return ! List.of(Session.Status.UNKNOWN, Session.Status.ACTIVATE).contains(candidate.getStatus()) + || oldSessionDirWithNonExistingSession(candidate); + } + + private boolean oldSessionDirWithNonExistingSession(LocalSession session) { + File sessionDir = tenantFileSystemDirs.getUserApplicationDir(session.getSessionId()); + return sessionDir.exists() + && session.getStatus() == Session.Status.UNKNOWN + && created(sessionDir).plus(Duration.ofDays(30)).isBefore(clock.instant()); + } + + private Instant created(File file) { + BasicFileAttributes fileAttributes; + try { + fileAttributes = readAttributes(file.toPath(), BasicFileAttributes.class); + return fileAttributes.creationTime().toInstant(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } private void ensureSessionPathDoesNotExist(long sessionId) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java index ce926016bd4..63c569fb17a 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java @@ -58,7 +58,10 @@ import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -462,10 +465,10 @@ public class ApplicationRepositoryTest { assertEquals(1, sessionRepository.getLocalSessions().size()); // Create a local session without any data in zookeeper (corner case seen in production occasionally) - // and check that expiring local sessions still work + // and check that expiring local sessions still works int sessionId = 6; TenantName tenantName = tester.tenant().getName(); - Files.createDirectory(new TenantFileSystemDirs(serverdb, tenantName).getUserApplicationDir(sessionId).toPath()); + java.nio.file.Path dir = Files.createDirectory(new TenantFileSystemDirs(serverdb, tenantName).getUserApplicationDir(sessionId).toPath()); LocalSession localSession2 = new LocalSession(tenant1, sessionId, FilesApplicationPackage.fromFile(testApp), @@ -490,6 +493,12 @@ public class ApplicationRepositoryTest { // Check that trying to expire when there are no active sessions works tester.applicationRepository().deleteExpiredLocalSessions(); + assertEquals(2, sessionRepository.getLocalSessions().size()); + + // Set older created timestamp for session dir for local session without any data in zookeeper, should be deleted + setCreatedTime(dir, Instant.now().minus(Duration.ofDays(31))); + tester.applicationRepository().deleteExpiredLocalSessions(); + assertEquals(1, sessionRepository.getLocalSessions().size()); } @Test @@ -740,6 +749,16 @@ public class ApplicationRepositoryTest { return applicationRepository.getMetadataFromLocalSession(tenant, sessionId); } + private void setCreatedTime(java.nio.file.Path file, Instant createdTime) { + try { + BasicFileAttributeView attributes = Files.getFileAttributeView(file, BasicFileAttributeView.class); + FileTime time = FileTime.fromMillis(createdTime.toEpochMilli()); + attributes.setTimes(time, time, time); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + /** Stores all added or set values for each metric and context. */ static class MockMetric implements Metric { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java index 44daec42b88..4b66715fcf7 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java @@ -76,6 +76,7 @@ class ServletOutputStreamWriter { void writeBuffer(ByteBuffer buf, CompletionHandler handler) { boolean thisThreadShouldWrite = false; + Throwable registrationFailure = null; synchronized (monitor) { if (state == State.FINISHED_OR_ERROR) { @@ -85,8 +86,12 @@ class ServletOutputStreamWriter { responseContentQueue.addLast(new ResponseContentPart(buf, handler)); switch (state) { case NOT_STARTED: - state = State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK; - outputStream.setWriteListener(writeListener); + try { + outputStream.setWriteListener(writeListener); + state = State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK; + } catch (Throwable t) { + registrationFailure = t; + } break; case WAITING_FOR_WRITE_POSSIBLE_CALLBACK: case WRITING_BUFFERS: @@ -99,6 +104,9 @@ class ServletOutputStreamWriter { throw new IllegalStateException("Invalid state " + state); } } + if (registrationFailure != null) { + setFinished(registrationFailure); + } if (thisThreadShouldWrite) { writeBuffersInQueueToOutputStream(); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ProxyResponse.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ProxyResponse.java index 1e9a3b4c4c8..28aaa29408e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ProxyResponse.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ProxyResponse.java @@ -5,7 +5,6 @@ import com.yahoo.container.jdisc.HttpResponse; import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import java.util.Optional; /** @@ -13,10 +12,10 @@ import java.util.Optional; */ public class ProxyResponse extends HttpResponse { - private final String content; + private final byte[] content; private final String contentType; - public ProxyResponse(String content, String contentType, int status) { + public ProxyResponse(byte[] content, String contentType, int status) { super(status); this.content = content; this.contentType = contentType; @@ -24,7 +23,7 @@ public class ProxyResponse extends HttpResponse { @Override public void render(OutputStream outputStream) throws IOException { - outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + outputStream.write(content); } @Override diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 2d7f9b27334..f3368d0918b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -7,7 +7,6 @@ import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.DockerImage; -import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; @@ -44,14 +43,14 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId; import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter; import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore; import com.yahoo.vespa.hosted.controller.application.ActivateResult; -import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; -import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackageValidator; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.DeploymentQuotaCalculator; import com.yahoo.vespa.hosted.controller.application.QuotaUsage; import com.yahoo.vespa.hosted.controller.application.SystemApplication; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackageValidator; import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade; import com.yahoo.vespa.hosted.controller.certificate.EndpointCertificates; import com.yahoo.vespa.hosted.controller.concurrent.Once; @@ -376,9 +375,9 @@ public class ApplicationController { && run.testerCertificate().isPresent()) applicationPackage = applicationPackage.withTrustedCertificate(run.testerCertificate().get()); - endpointCertificateMetadata = endpointCertificates.getMetadata(instance, zone, applicationPackage.deploymentSpec().instance(instance.name())); + endpointCertificateMetadata = endpointCertificates.getMetadata(instance, zone, applicationPackage.deploymentSpec()); - containerEndpoints = controller.routing().containerEndpointsOf(application.get(), job.application().instance(), zone); + containerEndpoints = controller.routing().containerEndpointsOf(application, job.application().instance(), zone); } // Release application lock while doing the deployment, which is a lengthy task. @@ -434,10 +433,6 @@ public class ApplicationController { application = withoutDeletedDeployments(application, name); } - for (InstanceName instance : declaredInstances) - if (applicationPackage.deploymentSpec().requireInstance(instance).concerns(Environment.prod)) - application = controller.routing().assignRotations(application, instance); - // Validate new deployment spec thoroughly before storing it. controller.jobController().deploymentStatus(application.get()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java index 16f12b3ac07..b6b02a7cab4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java @@ -173,27 +173,41 @@ public class RoutingController { } /** Returns certificate DNS names (CN and SAN values) for given deployment */ - public List<String> certificateDnsNames(DeploymentId deployment) { + public List<String> certificateDnsNames(DeploymentId deployment, DeploymentSpec deploymentSpec) { List<String> endpointDnsNames = new ArrayList<>(); // We add first an endpoint name based on a hash of the application ID, // as the certificate provider requires the first CN to be < 64 characters long. endpointDnsNames.add(commonNameHashOf(deployment.applicationId(), controller.system())); - // Add wildcard names for global endpoints when deploying to production List<Endpoint.EndpointBuilder> builders = new ArrayList<>(); if (deployment.zoneId().environment().isProduction()) { + // Add default and wildcard names for global endpoints builders.add(Endpoint.of(deployment.applicationId()).target(EndpointId.defaultId())); builders.add(Endpoint.of(deployment.applicationId()).wildcard()); + + // Add default and wildcard names for each region targeted by application endpoints + List<DeploymentId> deploymentTargets = deploymentSpec.endpoints().stream() + .map(com.yahoo.config.application.api.Endpoint::targets) + .flatMap(Collection::stream) + .map(com.yahoo.config.application.api.Endpoint.Target::region) + .distinct() + .map(region -> new DeploymentId(deployment.applicationId(), ZoneId.from(Environment.prod, region))) + .collect(Collectors.toUnmodifiableList()); + for (var targetDeployment : deploymentTargets) { + builders.add(Endpoint.of(targetDeployment.applicationId()).targetApplication(EndpointId.defaultId(), targetDeployment)); + builders.add(Endpoint.of(targetDeployment.applicationId()).wildcardApplication(targetDeployment)); + } } - // Add wildcard names for zone endpoints + // Add default and wildcard names for zone endpoints builders.add(Endpoint.of(deployment.applicationId()).target(ClusterSpec.Id.from("default"), deployment)); builders.add(Endpoint.of(deployment.applicationId()).wildcard(deployment)); - // Build all endpoints + // Build all certificate names for (var builder : builders) { - Endpoint endpoint = builder.routingMethod(RoutingMethod.exclusive) + Endpoint endpoint = builder.certificateName() + .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(controller.system()); endpointDnsNames.add(endpoint.dnsName()); @@ -227,7 +241,7 @@ public class RoutingController { * Assigns one or more global rotations to given application, if eligible. The given application is implicitly * stored, ensuring that the assigned rotation(s) are persisted when this returns. */ - public LockedApplication assignRotations(LockedApplication application, InstanceName instanceName) { + private LockedApplication assignRotations(LockedApplication application, InstanceName instanceName) { try (RotationLock rotationLock = rotationRepository.lock()) { var rotations = rotationRepository.getOrAssignRotations(application.get().deploymentSpec(), application.get().require(instanceName), @@ -239,14 +253,21 @@ public class RoutingController { } /** Returns the global and application-level endpoints for given deployment, as container endpoints */ - public Set<ContainerEndpoint> containerEndpointsOf(Application application, InstanceName instanceName, ZoneId zone) { - Instance instance = application.require(instanceName); - boolean registerLegacyNames = requiresLegacyNames(application.deploymentSpec(), instanceName); + public Set<ContainerEndpoint> containerEndpointsOf(LockedApplication application, InstanceName instanceName, ZoneId zone) { + // Assign rotations to application + for (var deploymentInstanceSpec : application.get().deploymentSpec().instances()) { + if (deploymentInstanceSpec.concerns(Environment.prod)) { + application = controller.routing().assignRotations(application, deploymentInstanceSpec.name()); + } + } + + // Add endpoints backed by a rotation, and register them in DNS if necessary + boolean registerLegacyNames = requiresLegacyNames(application.get().deploymentSpec(), instanceName); + Instance instance = application.get().require(instanceName); Set<ContainerEndpoint> containerEndpoints = new HashSet<>(); DeploymentId deployment = new DeploymentId(instance.id(), zone); - EndpointList endpoints = declaredEndpointsOf(application).targets(deployment); + EndpointList endpoints = declaredEndpointsOf(application.get()).targets(deployment); EndpointList globalEndpoints = endpoints.scope(Endpoint.Scope.global); - // Add endpoints backed by a rotation, and register them in DNS if necessary for (var assignedRotation : instance.rotations()) { var names = new ArrayList<String>(); EndpointList rotationEndpoints = globalEndpoints.named(assignedRotation.endpointId()) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java index 35601dd94dd..1e917dd1376 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java @@ -46,7 +46,7 @@ public class Endpoint { private Endpoint(TenantAndApplicationId application, Optional<InstanceName> instanceName, EndpointId id, ClusterSpec.Id cluster, URI url, List<Target> targets, Scope scope, Port port, boolean legacy, - RoutingMethod routingMethod) { + RoutingMethod routingMethod, boolean certificateName) { Objects.requireNonNull(application, "application must be non-null"); Objects.requireNonNull(instanceName, "instanceName must be non-null"); Objects.requireNonNull(cluster, "cluster must be non-null"); @@ -56,6 +56,9 @@ public class Endpoint { Objects.requireNonNull(routingMethod, "routingMethod must be non-null"); if (scope.multiDeployment()) { if (id == null) throw new IllegalArgumentException("Endpoint ID must be set for multi-deployment endpoints"); + if (!certificateName && targets.isEmpty()) { + throw new IllegalArgumentException("At least one target must be given for " + scope + " endpoints"); + } } else { if (scope == Scope.zone && id != null) throw new IllegalArgumentException("Endpoint ID cannot be set for " + scope + " endpoints"); if (targets.size() != 1) throw new IllegalArgumentException("A single target must be given for " + scope + " endpoints"); @@ -89,11 +92,14 @@ public class Endpoint { this.legacy = legacy; this.routingMethod = routingMethod; this.tls = port.tls; + if (!certificateName && "*".equals(name())) { + throw new IllegalArgumentException("Wildcard found in endpoint that is not intended as a certificate name"); + } } private Endpoint(EndpointId id, ClusterSpec.Id cluster, TenantAndApplicationId application, Optional<InstanceName> instance, List<Target> targets, Scope scope, SystemName system, Port port, - boolean legacy, RoutingMethod routingMethod) { + boolean legacy, RoutingMethod routingMethod, boolean certificateName) { this(application, instance, id, @@ -107,7 +113,7 @@ public class Endpoint { Objects.requireNonNull(port, "port must be non-null"), legacy, routingMethod), - targets, scope, port, legacy, routingMethod); + targets, scope, port, legacy, routingMethod, certificateName); } /** @@ -446,7 +452,7 @@ public class Endpoint { ClusterSpec.Id.from("admin"), url, List.of(new Target(new DeploymentId(systemApplication.id(), zone))), - Scope.zone, port, false, routingMethod); + Scope.zone, port, false, routingMethod, false); } /** A target of an endpoint */ @@ -491,6 +497,7 @@ public class Endpoint { private Port port; private RoutingMethod routingMethod = RoutingMethod.shared; private boolean legacy = false; + private boolean certificateName = false; private EndpointBuilder(TenantAndApplicationId application, Optional<InstanceName> instance) { this.application = Objects.requireNonNull(application); @@ -499,33 +506,41 @@ public class Endpoint { /** Sets the deployment target for this */ public EndpointBuilder target(ClusterSpec.Id cluster, DeploymentId deployment) { - checkScope(); this.cluster = cluster; - this.scope = Scope.zone; + this.scope = requireUnset(Scope.zone); this.targets = List.of(new Target(deployment)); return this; } - /** Sets the global target with given ID and pointing to the default cluster */ - public EndpointBuilder target(EndpointId endpointId) { - return target(endpointId, ClusterSpec.Id.from("default"), List.of()); - } - /** Sets the global target with given ID, deployments and cluster (as defined in deployments.xml) */ public EndpointBuilder target(EndpointId endpointId, ClusterSpec.Id cluster, List<DeploymentId> deployments) { - checkScope(); this.endpointId = endpointId; this.cluster = cluster; this.targets = deployments.stream().map(Target::new).collect(Collectors.toUnmodifiableList()); - this.scope = Scope.global; + this.scope = requireUnset(Scope.global); return this; } + /** Sets the global target with given ID and pointing to the default cluster */ + public EndpointBuilder target(EndpointId endpointId) { + return target(endpointId, ClusterSpec.Id.from("default"), List.of()); + } + + /** Sets the application target with given ID and pointing to the default cluster */ + public EndpointBuilder targetApplication(EndpointId endpointId, DeploymentId deployment) { + return targetApplication(endpointId, ClusterSpec.Id.from("default"), Map.of(deployment, 1)); + } + /** Sets the global wildcard target for this */ public EndpointBuilder wildcard() { return target(EndpointId.of("*"), ClusterSpec.Id.from("*"), List.of()); } + /** Sets the application wildcard target for this */ + public EndpointBuilder wildcardApplication(DeploymentId deployment) { + return targetApplication(EndpointId.of("*"), ClusterSpec.Id.from("*"), Map.of(deployment, 1)); + } + /** Sets the zone wildcard target for this */ public EndpointBuilder wildcard(DeploymentId deployment) { return target(ClusterSpec.Id.from("*"), deployment); @@ -533,7 +548,6 @@ public class Endpoint { /** Sets the application target with given ID, cluster, deployments and their weights */ public EndpointBuilder targetApplication(EndpointId endpointId, ClusterSpec.Id cluster, Map<DeploymentId, Integer> deployments) { - checkScope(); this.endpointId = endpointId; this.cluster = cluster; this.targets = deployments.entrySet().stream() @@ -545,9 +559,8 @@ public class Endpoint { /** Sets the region target for this, deduced from given zone */ public EndpointBuilder targetRegion(ClusterSpec.Id cluster, ZoneId zone) { - checkScope(); this.cluster = cluster; - this.scope = Scope.weighted; + this.scope = requireUnset(Scope.weighted); this.targets = List.of(new Target(new DeploymentId(application.instance(instance.get()), effectiveZone(zone)))); return this; } @@ -570,6 +583,12 @@ public class Endpoint { return this; } + /** Sets whether we're building a name for inclusion in a certificate */ + public EndpointBuilder certificateName() { + this.certificateName = true; + return this; + } + /** Sets the system that owns this */ public Endpoint in(SystemName system) { if (system.isPublic() && routingMethod != RoutingMethod.exclusive) { @@ -578,13 +597,14 @@ public class Endpoint { if (routingMethod.isDirect() && !port.isDefault()) { throw new IllegalArgumentException("Routing method " + routingMethod + " can only use default port"); } - return new Endpoint(endpointId, cluster, application, instance, targets, scope, system, port, legacy, routingMethod); + return new Endpoint(endpointId, cluster, application, instance, targets, scope, system, port, legacy, routingMethod, certificateName); } - private void checkScope() { - if (scope != null) { + private Scope requireUnset(Scope scope) { + if (this.scope != null) { throw new IllegalArgumentException("Cannot change endpoint scope. Already set to " + scope); } + return scope; } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java index 684648ed70a..e3091b704e4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.certificate; import com.yahoo.config.application.api.DeploymentInstanceSpec; +import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.text.Text; import com.yahoo.vespa.hosted.controller.Controller; @@ -52,9 +53,9 @@ public class EndpointCertificates { } /** Returns certificate metadata for endpoints of given instance and zone */ - public Optional<EndpointCertificateMetadata> getMetadata(Instance instance, ZoneId zone, Optional<DeploymentInstanceSpec> instanceSpec) { + public Optional<EndpointCertificateMetadata> getMetadata(Instance instance, ZoneId zone, DeploymentSpec deploymentSpec) { Instant start = clock.instant(); - Optional<EndpointCertificateMetadata> metadata = getOrProvision(instance, zone, instanceSpec); + Optional<EndpointCertificateMetadata> metadata = getOrProvision(instance, zone, deploymentSpec); metadata.ifPresent(m -> curator.writeEndpointCertificateMetadata(instance.id(), m.withLastRequested(clock.instant().getEpochSecond()))); Duration duration = Duration.between(start, clock.instant()); if (duration.toSeconds() > 30) @@ -62,13 +63,12 @@ public class EndpointCertificates { return metadata; } - private Optional<EndpointCertificateMetadata> getOrProvision(Instance instance, ZoneId zone, Optional<DeploymentInstanceSpec> instanceSpec) { - final var currentCertificateMetadata = curator.readEndpointCertificateMetadata(instance.id()); - + private Optional<EndpointCertificateMetadata> getOrProvision(Instance instance, ZoneId zone, DeploymentSpec deploymentSpec) { + Optional<EndpointCertificateMetadata> currentCertificateMetadata = curator.readEndpointCertificateMetadata(instance.id()); DeploymentId deployment = new DeploymentId(instance.id(), zone); if (currentCertificateMetadata.isEmpty()) { - var provisionedCertificateMetadata = provisionEndpointCertificate(deployment, Optional.empty(), instanceSpec); + var provisionedCertificateMetadata = provisionEndpointCertificate(deployment, Optional.empty(), deploymentSpec); // We do not verify the certificate if one has never existed before - because we do not want to // wait for it to be available before we deploy. This allows the config server to start // provisioning nodes ASAP, and the risk is small for a new deployment. @@ -77,10 +77,10 @@ public class EndpointCertificates { } // Re-provision certificate if it is missing SANs for the zone we are deploying to - var requiredSansForZone = controller.routing().certificateDnsNames(deployment); + var requiredSansForZone = controller.routing().certificateDnsNames(deployment, deploymentSpec); if (!currentCertificateMetadata.get().requestedDnsSans().containsAll(requiredSansForZone)) { var reprovisionedCertificateMetadata = - provisionEndpointCertificate(deployment, currentCertificateMetadata, instanceSpec) + provisionEndpointCertificate(deployment, currentCertificateMetadata, deploymentSpec) .withRequestId(currentCertificateMetadata.get().requestId()); // We're required to keep the original request ID curator.writeEndpointCertificateMetadata(instance.id(), reprovisionedCertificateMetadata); // Verification is unlikely to succeed in this case, as certificate must be available first - controller will retry @@ -94,12 +94,13 @@ public class EndpointCertificates { private EndpointCertificateMetadata provisionEndpointCertificate(DeploymentId deployment, Optional<EndpointCertificateMetadata> currentMetadata, - Optional<DeploymentInstanceSpec> instanceSpec) { + DeploymentSpec deploymentSpec) { List<ZoneId> zonesInSystem = controller.zoneRegistry().zones().controllerUpgraded().ids(); Set<ZoneId> requiredZones = new LinkedHashSet<>(); requiredZones.add(deployment.zoneId()); if (!deployment.zoneId().environment().isManuallyDeployed()) { // If not deploying to a dev or perf zone, require all prod zones in deployment spec + test and staging + Optional<DeploymentInstanceSpec> instanceSpec = deploymentSpec.instance(deployment.applicationId().instance()); zonesInSystem.stream() .filter(zone -> zone.environment().isTest() || (instanceSpec.isPresent() && @@ -107,14 +108,16 @@ public class EndpointCertificates { .forEach(requiredZones::add); } Set<String> requiredNames = requiredZones.stream() - .flatMap(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone)).stream()) + .flatMap(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone), + deploymentSpec) + .stream()) .collect(Collectors.toCollection(LinkedHashSet::new)); // Preserve any currently present names that are still valid List<String> currentNames = currentMetadata.map(EndpointCertificateMetadata::requestedDnsSans) .orElseGet(List::of); zonesInSystem.stream() - .map(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone))) + .map(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone), deploymentSpec)) .filter(currentNames::containsAll) .forEach(requiredNames::addAll); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java index 1c29bdd397f..2b9e3dd0733 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java @@ -1,7 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.deployment; -import com.google.common.collect.ImmutableList; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import java.security.cert.X509Certificate; @@ -267,26 +266,26 @@ public class Run { /** Returns the list of unfinished steps whose prerequisites have all succeeded. */ private List<Step> normalSteps() { - return ImmutableList.copyOf(steps.entrySet().stream() - .filter(entry -> entry.getValue().status() == unfinished - && entry.getKey().prerequisites().stream() - .allMatch(step -> steps.get(step) == null - || steps.get(step).status() == succeeded)) - .map(Map.Entry::getKey) - .iterator()); + return steps.entrySet().stream() + .filter(entry -> entry.getValue().status() == unfinished + && entry.getKey().prerequisites().stream() + .allMatch(step -> steps.get(step) == null + || steps.get(step).status() == succeeded)) + .map(Map.Entry::getKey) + .collect(Collectors.toUnmodifiableList()); } /** Returns the list of not-yet-run run-always steps whose run-always prerequisites have all run. */ private List<Step> forcedSteps() { - return ImmutableList.copyOf(steps.entrySet().stream() - .filter(entry -> entry.getValue().status() == unfinished - && entry.getKey().alwaysRun() - && entry.getKey().prerequisites().stream() - .filter(Step::alwaysRun) - .allMatch(step -> steps.get(step) == null - || steps.get(step).status() != unfinished)) - .map(Map.Entry::getKey) - .iterator()); + return steps.entrySet().stream() + .filter(entry -> entry.getValue().status() == unfinished + && entry.getKey().alwaysRun() + && entry.getKey().prerequisites().stream() + .filter(Step::alwaysRun) + .allMatch(step -> steps.get(step) == null + || steps.get(step).status() != unfinished)) + .map(Map.Entry::getKey) + .collect(Collectors.toUnmodifiableList()); } private void requireActive() { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index ef129dd76f7..5cd5a70e4a4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -2042,19 +2042,20 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { return new MessageResponse("Deactivated " + id); } - /** Returns test config for indicated job, with production deployments of the default instance. */ + /** Returns test config for indicated job, with production deployments of the default instance if the given is not in deployment spec. */ private HttpResponse testConfig(ApplicationId id, JobType type) { - // TODO jonmv: Support non-default instances as well; requires API change in clients. - ApplicationId defaultInstanceId = TenantAndApplicationId.from(id).defaultInstance(); + Application application = controller.applications().requireApplication(TenantAndApplicationId.from(id)); + ApplicationId prodInstanceId = application.deploymentSpec().instance(id.instance()).isPresent() + ? id : TenantAndApplicationId.from(id).defaultInstance(); HashSet<DeploymentId> deployments = controller.applications() - .getInstance(defaultInstanceId).stream() - .flatMap(instance -> instance.productionDeployments().keySet().stream()) - .map(zone -> new DeploymentId(defaultInstanceId, zone)) - .collect(Collectors.toCollection(HashSet::new)); - var testedZone = type.zone(controller.system()); - - // If a production job is specified, the production deployment of the _default instance_ is the relevant one, - // as user instances should not exist in prod. TODO jonmv: Remove this when multiple instances are supported (above). + .getInstance(prodInstanceId).stream() + .flatMap(instance -> instance.productionDeployments().keySet().stream()) + .map(zone -> new DeploymentId(prodInstanceId, zone)) + .collect(Collectors.toCollection(HashSet::new)); + ZoneId testedZone = type.zone(controller.system()); + + // If a production job is specified, the production deployment of the orchestrated instance is the relevant one, + // as user instances should not exist in prod. if ( ! type.isProduction()) deployments.add(new DeploymentId(id, testedZone)); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java index c1f9137ecfa..fa4a871d423 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java @@ -27,122 +27,128 @@ public class EndpointTest { @Test public void global_endpoints() { + DeploymentId deployment1 = new DeploymentId(instance1, ZoneId.from("prod", "us-north-1")); + DeploymentId deployment2 = new DeploymentId(instance2, ZoneId.from("prod", "us-north-1")); + ClusterSpec.Id cluster = ClusterSpec.Id.from("default"); EndpointId endpointId = EndpointId.defaultId(); Map<String, Endpoint> tests = Map.of( // Legacy endpoint "http://a1.t1.global.vespa.yahooapis.com:4080/", - Endpoint.of(instance1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.plain(4080)).legacy().in(SystemName.main), // Legacy endpoint with TLS "https://a1--t1.global.vespa.yahooapis.com:4443/", - Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls(4443)).legacy().in(SystemName.main), // Main endpoint "https://a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls(4443)).in(SystemName.main), // Main endpoint in CD "https://cd--a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint in CD "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/", - Endpoint.of(instance2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance2).target(endpointId, cluster, List.of(deployment2)).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint with direct routing and default TLS port "https://a1.t1.global.vespa.oath.cloud/", - Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint with custom rotation name "https://r1.a1.t1.global.vespa.oath.cloud/", - Endpoint.of(instance1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(EndpointId.of("r1"), cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance in default rotation "https://i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(instance2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(endpointId, cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance with custom rotation name "https://r2.i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(instance2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(EndpointId.of("r2"), cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint in public system "https://a1.t1.g.vespa-app.cloud/", - Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); Map<String, Endpoint> tests2 = Map.of( // Default endpoint in public system "https://a1.t1.g.vespa-app.cloud/", - Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), // Default endpoint in public CD system "https://a1.t1.g.cd.vespa-app.cloud/", - Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd), // Custom instance in public system "https://i2.a2.t2.g.vespa-app.cloud/", - Endpoint.of(instance2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) + Endpoint.of(instance2).target(endpointId, cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) ); tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); } @Test public void global_endpoints_with_endpoint_id() { - var endpointId = EndpointId.defaultId(); + DeploymentId deployment1 = new DeploymentId(instance1, ZoneId.from("prod", "us-north-1")); + DeploymentId deployment2 = new DeploymentId(instance2, ZoneId.from("prod", "us-north-1")); + ClusterSpec.Id cluster = ClusterSpec.Id.from("default"); + EndpointId endpointId = EndpointId.defaultId(); Map<String, Endpoint> tests = Map.of( // Legacy endpoint "http://a1.t1.global.vespa.yahooapis.com:4080/", - Endpoint.of(instance1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.plain(4080)).legacy().in(SystemName.main), // Legacy endpoint with TLS "https://a1--t1.global.vespa.yahooapis.com:4443/", - Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls(4443)).legacy().in(SystemName.main), // Main endpoint "https://a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls(4443)).in(SystemName.main), // Main endpoint in CD "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/", - Endpoint.of(instance2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance2).target(endpointId, cluster, List.of(deployment2)).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint in CD "https://cd--a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint with direct routing and default TLS port "https://a1.t1.global.vespa.oath.cloud/", - Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint with custom rotation name "https://r1.a1.t1.global.vespa.oath.cloud/", - Endpoint.of(instance1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(EndpointId.of("r1"), cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance in default rotation "https://i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(instance2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(endpointId, cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance with custom rotation name "https://r2.i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(instance2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(EndpointId.of("r2"), cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint in public system "https://a1.t1.g.vespa-app.cloud/", - Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) + Endpoint.of(instance1).target(endpointId, cluster, List.of(deployment1)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); Map<String, Endpoint> tests2 = Map.of( // Custom endpoint and instance in public CD system) "https://foo.i2.a2.t2.g.cd.vespa-app.cloud/", - Endpoint.of(instance2).target(EndpointId.of("foo")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd), + Endpoint.of(instance2).target(EndpointId.of("foo"), cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd), // Custom endpoint and instance in public system "https://foo.i2.a2.t2.g.vespa-app.cloud/", - Endpoint.of(instance2).target(EndpointId.of("foo")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) + Endpoint.of(instance2).target(EndpointId.of("foo"), cluster, List.of(deployment2)).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) ); tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); } @@ -228,6 +234,7 @@ public class EndpointTest { "https://a1.t1.g.vespa-app.cloud/", Endpoint.of(instance1) .target(EndpointId.defaultId()) + .certificateName() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public), @@ -236,6 +243,7 @@ public class EndpointTest { "https://*.a1.t1.g.vespa-app.cloud/", Endpoint.of(instance1) .wildcard() + .certificateName() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public), @@ -244,6 +252,7 @@ public class EndpointTest { "https://a1.t1.us-north-1.z.vespa-app.cloud/", Endpoint.of(instance1) .target(defaultCluster, prodZone) + .certificateName() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public), @@ -252,6 +261,7 @@ public class EndpointTest { "https://a1.t1.us-north-2.test.z.vespa-app.cloud/", Endpoint.of(instance1) .target(defaultCluster, testZone) + .certificateName() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public), @@ -260,6 +270,7 @@ public class EndpointTest { "https://*.a1.t1.us-north-2.test.z.vespa-app.cloud/", Endpoint.of(instance1) .wildcard(testZone) + .certificateName() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public), @@ -268,6 +279,7 @@ public class EndpointTest { "https://*.a1.t1.us-north-1.z.vespa-app.cloud/", Endpoint.of(instance1) .wildcard(prodZone) + .certificateName() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public) @@ -353,7 +365,7 @@ public class EndpointTest { var tests1 = Map.of( // With default cluster "a1.t1.us-north-1.prod", - Endpoint.of(instance1).target(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(EndpointId.defaultId(), ClusterSpec.Id.from("default"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main), // With non-default cluster "c1.a1.t1.us-north-1.prod", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java index c6a9c12027f..0a93c936b41 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java @@ -5,6 +5,8 @@ import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.ZoneId; @@ -19,6 +21,8 @@ import com.yahoo.vespa.hosted.controller.Instance; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorImpl; +import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.integration.SecretStoreMock; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import org.junit.Before; @@ -31,6 +35,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -111,7 +116,7 @@ public class EndpointCertificatesTest { @Test public void provisions_new_certificate_in_dev() { ZoneId testZone = tester.zoneRegistry().zones().routingMethod(RoutingMethod.exclusive).in(Environment.dev).zones().stream().findFirst().orElseThrow().getId(); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, DeploymentSpec.empty); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); @@ -121,7 +126,7 @@ public class EndpointCertificatesTest { @Test public void provisions_new_certificate_in_prod() { - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, DeploymentSpec.empty); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); @@ -145,7 +150,7 @@ public class EndpointCertificatesTest { "default.default.aws-us-east-1c.staging.z.vespa-app.cloud", "*.default.default.aws-us-east-1c.staging.z.vespa-app.cloud" ); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, DeploymentSpec.empty); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); @@ -164,7 +169,7 @@ public class EndpointCertificatesTest { "", Optional.empty(), Optional.empty())); secretStore.setSecret(testKeyName, KeyUtils.toPem(testKeyPair.getPrivate()), 7); secretStore.setSecret(testCertName, X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 7); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, DeploymentSpec.empty); assertTrue(endpointCertificateMetadata.isPresent()); assertEquals(testKeyName, endpointCertificateMetadata.get().keyName()); assertEquals(testCertName, endpointCertificateMetadata.get().certName()); @@ -176,7 +181,7 @@ public class EndpointCertificatesTest { mockCuratorDb.writeEndpointCertificateMetadata(testInstance.id(), new EndpointCertificateMetadata(testKeyName, testCertName, -1, 0, "uuid", List.of(), "issuer", Optional.empty(), Optional.empty())); secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 0); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, DeploymentSpec.empty); assertTrue(endpointCertificateMetadata.isPresent()); assertEquals(0, endpointCertificateMetadata.get().version()); assertEquals(endpointCertificateMetadata, mockCuratorDb.readEndpointCertificateMetadata(testInstance.id())); @@ -193,7 +198,7 @@ public class EndpointCertificatesTest { secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate2) + X509CertificateUtils.toPem(testCertificate2), 0); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, DeploymentSpec.empty); assertTrue(endpointCertificateMetadata.isPresent()); assertEquals(0, endpointCertificateMetadata.get().version()); assertEquals(endpointCertificateMetadata, mockCuratorDb.readEndpointCertificateMetadata(testInstance.id())); @@ -203,7 +208,6 @@ public class EndpointCertificatesTest { @Test public void includes_zones_in_deployment_spec_when_deploying_to_staging() { - DeploymentSpec deploymentSpec = new DeploymentSpecXmlReader(true).read( "<deployment version=\"1.0\">\n" + " <instance id=\"default\">\n" + @@ -215,7 +219,7 @@ public class EndpointCertificatesTest { "</deployment>\n"); ZoneId testZone = tester.zoneRegistry().zones().all().in(Environment.staging).zones().stream().findFirst().orElseThrow().getId(); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.of(deploymentSpec.requireInstance("default"))); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, deploymentSpec); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); @@ -223,4 +227,49 @@ public class EndpointCertificatesTest { assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(endpointCertificateMetadata.get().requestedDnsSans())); } + @Test + public void includes_application_endpoint_when_declared() { + Instance instance = new Instance(ApplicationId.from("t1", "a1", "default")); + ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c")); + ZoneId zone2 = ZoneId.from(Environment.prod, RegionName.from("aws-us-west-2a")); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .instances("beta,main") + .region(zone1.region()) + .region(zone2.region()) + .applicationEndpoint("a", "qrs", zone2.region().value(), + Map.of(InstanceName.from("beta"), 2, + InstanceName.from("main"), 8)) + .applicationEndpoint("b", "qrs", zone2.region().value(), + Map.of(InstanceName.from("beta"), 1, + InstanceName.from("main"), 1)) + .applicationEndpoint("c", "qrs", zone1.region().value(), + Map.of(InstanceName.from("beta"), 4, + InstanceName.from("main"), 6)) + .build(); + ControllerTester tester = new ControllerTester(SystemName.Public); + EndpointCertificateValidatorImpl endpointCertificateValidator = new EndpointCertificateValidatorImpl(secretStore, clock); + EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateMock, endpointCertificateValidator); + List<String> expectedSans = List.of( + "vlfms2wpoa4nyrka2s5lktucypjtxkqhv.internal.vespa-app.cloud", + "a1.t1.g.vespa-app.cloud", + "*.a1.t1.g.vespa-app.cloud", + "a1.t1.aws-us-west-2a.r.vespa-app.cloud", + "*.a1.t1.aws-us-west-2a.r.vespa-app.cloud", + "a1.t1.aws-us-east-1c.r.vespa-app.cloud", + "*.a1.t1.aws-us-east-1c.r.vespa-app.cloud", + "a1.t1.aws-us-east-1c.z.vespa-app.cloud", + "*.a1.t1.aws-us-east-1c.z.vespa-app.cloud", + "a1.t1.aws-us-east-1c.test.z.vespa-app.cloud", + "*.a1.t1.aws-us-east-1c.test.z.vespa-app.cloud", + "a1.t1.aws-us-east-1c.staging.z.vespa-app.cloud", + "*.a1.t1.aws-us-east-1c.staging.z.vespa-app.cloud" + ); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(instance, zone1, applicationPackage.deploymentSpec()); + assertTrue(endpointCertificateMetadata.isPresent()); + assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.t1.a1.*-key")); + assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.t1.a1.*-cert")); + assertEquals(0, endpointCertificateMetadata.get().version()); + assertEquals(expectedSans, endpointCertificateMetadata.get().requestedDnsSans()); + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java index b9c1e9f3e72..d98789591ab 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java @@ -53,6 +53,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.failed; import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded; import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished; import static org.junit.Assert.assertEquals; @@ -292,13 +293,32 @@ public class DeploymentContext { /** Fail current deployment in given job */ private DeploymentContext failDeployment(JobType type, RuntimeException exception) { + configServer().throwOnNextPrepare(exception); + runJobExpectingFailure(type, Optional.empty()); + return this; + } + + /** Run given job and expect it to fail with given message, if any */ + public DeploymentContext runJobExpectingFailure(JobType type, Optional<String> messagePart) { triggerJobs(); var job = jobId(type); RunId id = currentRun(job).id(); - configServer().throwOnNextPrepare(exception); runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasFailed()); - assertTrue(jobs.run(id).get().hasEnded()); + Run run = jobs.run(id).get(); + assertTrue(run.hasFailed()); + assertTrue(run.hasEnded()); + if (messagePart.isPresent()) { + Optional<Step> firstFailing = run.stepStatuses().entrySet().stream() + .filter(kv -> kv.getValue() == failed) + .map(Map.Entry::getKey) + .findFirst(); + assertTrue("Found failing step", firstFailing.isPresent()); + Optional<RunLog> details = jobs.details(id); + assertTrue("Found log entries for run " + id, details.isPresent()); + assertTrue("Found log message containing '" + messagePart.get() + "'", + details.get().get(firstFailing.get()).stream() + .anyMatch(entry -> entry.message().contains(messagePart.get()))); + } return this; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java index 4ad70c4e9dd..61eca05cc67 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java @@ -2,9 +2,11 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.slime.SlimeUtils; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.application.Endpoint; import com.yahoo.vespa.hosted.controller.application.EndpointId; @@ -31,7 +33,9 @@ public class TestConfigSerializerTest { JobType.systemTest, true, Map.of(zone, List.of(Endpoint.of(ApplicationId.defaultId()) - .target(EndpointId.of("ai")) + .target(EndpointId.of("ai"), ClusterSpec.Id.from("qrs"), + List.of(new DeploymentId(ApplicationId.defaultId(), + ZoneId.defaultId()))) .on(Endpoint.Port.tls()) .in(SystemName.main))), Map.of(zone, List.of("facts"))); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java index a3674fa27bd..f2fc624630c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java @@ -567,7 +567,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer @Override public ProxyResponse getApplicationPackageContent(DeploymentId deployment, String path, URI requestUri) { - return new ProxyResponse("{\"path\":\"" + path + "\"}", "application/json", 200); + return new ProxyResponse(("{\"path\":\"" + path + "\"}").getBytes(StandardCharsets.UTF_8), "application/json", 200); } public void setLogStream(Supplier<String> log) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java index b767e8a791f..e7c2eacbd02 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java @@ -5,19 +5,20 @@ import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.vespa.hosted.controller.ControllerTester; -import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.application.AssignedRotation; +import com.yahoo.vespa.hosted.controller.application.SystemApplication; +import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.net.URI; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -30,22 +31,19 @@ import static org.junit.Assert.assertTrue; */ public class RotationRepositoryTest { - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private final RotationsConfig rotationsConfig = new RotationsConfig( + private static final RotationsConfig rotationsConfig = new RotationsConfig( new RotationsConfig.Builder() .rotations("foo-1", "foo-1.com") .rotations("foo-2", "foo-2.com") ); - private final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig( + private static final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig( new RotationsConfig.Builder() .rotations("foo-1", "\n \t foo-1.com \n") .rotations("foo-2", "foo-2.com") ); - private final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + private static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .globalServiceId("foo") .region("us-east-3") .region("us-west-1") @@ -57,8 +55,8 @@ public class RotationRepositoryTest { @Test public void assigns_and_reuses_rotation() { - // Submitting assigns a rotation - application.submit(applicationPackage); + // Deploying assigns a rotation + application.submit(applicationPackage).deploy(); Rotation expected = new Rotation(new RotationId("foo-1"), "foo-1.com"); assertEquals(List.of(expected.id()), rotationIds(application.instance().rotations())); @@ -108,17 +106,16 @@ public class RotationRepositoryTest { @Test public void out_of_rotations() { // Assigns 1 rotation - application.submit(applicationPackage); + application.submit(applicationPackage).deploy(); // Assigns 1 more var application2 = tester.newDeploymentContext("tenant2", "app2", "default"); - application2.submit(applicationPackage); + application2.submit(applicationPackage).deploy(); - // We're now out of rotations - thrown.expect(IllegalStateException.class); - thrown.expectMessage("out of rotations"); + // We're now out of rotations and next deployment fails var application3 = tester.newDeploymentContext("tenant3", "app3", "default"); - application3.submit(applicationPackage); + application3.submit(applicationPackage) + .runJobExpectingFailure(JobType.systemTest, Optional.of("out of rotations")); } @Test @@ -127,9 +124,7 @@ public class RotationRepositoryTest { .globalServiceId("foo") .region("us-east-3") .build(); - thrown.expect(RuntimeException.class); - thrown.expectMessage("less than 2 prod zones are defined"); - application.submit(applicationPackage); + application.submit(applicationPackage).runJobExpectingFailure(JobType.systemTest, Optional.of("less than 2 prod zones are defined")); } @Test @@ -149,13 +144,18 @@ public class RotationRepositoryTest { .region("cd-us-east-1") .region("cd-us-west-1") .build(); - var zones = List.of(ZoneApiMock.fromId("prod.cd-us-east-1"), ZoneApiMock.fromId("prod.cd-us-west-1")); + var zones = List.of( + ZoneApiMock.fromId("test.cd-us-west-1"), + ZoneApiMock.fromId("staging.cd-us-west-1"), + ZoneApiMock.fromId("prod.cd-us-east-1"), + ZoneApiMock.fromId("prod.cd-us-west-1")); tester.controllerTester().zoneRegistry() .setZones(zones) .setRoutingMethod(zones, RoutingMethod.shared) .setSystemName(SystemName.cd); + tester.configServer().bootstrap(tester.controllerTester().zoneRegistry().zones().all().ids(), SystemApplication.notController()); var application2 = tester.newDeploymentContext("tenant2", "app2", "default"); - application2.submit(applicationPackage); + application2.submit(applicationPackage).deploy(); assertEquals(List.of(new RotationId("foo-1")), rotationIds(application2.instance().rotations())); assertEquals("https://cd--app2--tenant2.global.vespa.oath.cloud:4443/", tester.controller().routing().readDeclaredEndpointsOf(application2.instanceId()).primary().get().url().toString()); @@ -169,7 +169,9 @@ public class RotationRepositoryTest { .parallel("us-west-1", "us-east-3") .globalServiceId("global") .build(); - var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage); + var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1") + .submit(applicationPackage) + .deploy(); var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2"); assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations())); assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations())); @@ -187,7 +189,9 @@ public class RotationRepositoryTest { .parallel("us-west-1", "us-east-3") .endpoint("default", "foo", "us-central-1", "us-west-1") .build(); - var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage); + var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1") + .submit(applicationPackage) + .deploy(); var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2"); assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations())); diff --git a/default_build_settings.cmake b/default_build_settings.cmake index 36368cbce3e..e482439dd7d 100644 --- a/default_build_settings.cmake +++ b/default_build_settings.cmake @@ -36,10 +36,6 @@ function(setup_vespa_default_build_settings_centos_8) else() set(DEFAULT_VESPA_LLVM_VERSION "11" PARENT_SCOPE) endif() - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") - set(DEFAULT_CMAKE_SHARED_LINKER_FLAGS " -latomic " PARENT_SCOPE) - set(DEFAULT_CMAKE_LINKER_FLAGS " -latomic " PARENT_SCOPE) - endif() endfunction() function(setup_vespa_default_build_settings_rocky_8_4) @@ -236,6 +232,7 @@ function(vespa_use_default_build_settings) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${DEFAULT_CMAKE_SHARED_LINKER_FLAGS}" PARENT_SCOPE) endif() if(NOT DEFINED DEFAULT_VESPA_CPU_ARCH_FLAGS) + message("-- CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}") if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") if(VESPA_OS_DISTRO STREQUAL "fedora" AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(DEFAULT_VESPA_CPU_ARCH_FLAGS "-march=westmere -mtune=haswell") @@ -243,6 +240,8 @@ function(vespa_use_default_build_settings) else() set(DEFAULT_VESPA_CPU_ARCH_FLAGS "-mtune=intel") endif() + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + set(DEFAULT_VESPA_CPU_ARCH_FLAGS "-march=armv8.2-a+fp16+rcpc+dotprod+crypto -mtune=neoverse-n1") endif() endif() if(DEFINED DEFAULT_CMAKE_PREFIX_PATH) diff --git a/dist/vespa.spec b/dist/vespa.spec index 81a180348c2..031213ac693 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -59,6 +59,7 @@ BuildRequires: python3-devel %if 0%{?el8} BuildRequires: gcc-toolset-10-gcc-c++ BuildRequires: gcc-toolset-10-binutils +BuildRequires: gcc-toolset-10-libatomic-devel %define _devtoolset_enable /opt/rh/gcc-toolset-10/enable BuildRequires: maven BuildRequires: pybind11-devel diff --git a/fastos/src/vespa/fastos/unix_thread.cpp b/fastos/src/vespa/fastos/unix_thread.cpp index d7a0ed879c0..a45d90426ef 100644 --- a/fastos/src/vespa/fastos/unix_thread.cpp +++ b/fastos/src/vespa/fastos/unix_thread.cpp @@ -71,7 +71,12 @@ bool FastOS_UNIX_Thread::Initialize (int stackSize, int stackGuardSize) #else adjusted_stack_size += PTHREAD_STACK_MIN; #endif - pthread_attr_setstacksize(&attr, adjusted_stack_size); + if (getenv("VESPA_IGNORE_REQUESTED_STACK_SIZES") == nullptr) { + //fprintf(stderr, "pthread_create: using adjusted stack size %zd\n", adjusted_stack_size); + pthread_attr_setstacksize(&attr, adjusted_stack_size); + } else { + //fprintf(stderr, "pthread_create: ignoring requested stack size %d\n", stackSize); + } rc = (0 == pthread_create(&_handle, &attr, FastOS_ThreadHook, this)); if (rc) diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java index 13b6f5d8cc3..aab2f5a53fd 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java @@ -36,7 +36,6 @@ public class FileDownloader implements AutoCloseable { private final Supervisor supervisor; private final File downloadDirectory; private final Duration timeout; - private final Duration sleepBetweenRetries; private final FileReferenceDownloader fileReferenceDownloader; private final Downloads downloads = new Downloads(); @@ -61,7 +60,6 @@ public class FileDownloader implements AutoCloseable { this.supervisor = supervisor; this.downloadDirectory = downloadDirectory; this.timeout = timeout; - this.sleepBetweenRetries = sleepBetweenRetries; // Needed to receive RPC receiveFile* calls from server after starting download of file reference new FileReceiver(supervisor, downloads, downloadDirectory); this.fileReferenceDownloader = new FileReferenceDownloader(connectionPool, diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java index 1bb6b7586f5..7b24098526c 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java @@ -55,14 +55,20 @@ public class FileReferenceDownloader { private void waitUntilDownloadStarted(FileReferenceDownload fileReferenceDownload) { FileReference fileReference = fileReferenceDownload.fileReference(); int retryCount = 0; + Connection connection = connectionPool.getCurrent(); do { if (FileDownloader.fileReferenceExists(fileReference, downloadDirectory)) return; - if (startDownloadRpc(fileReferenceDownload, retryCount)) + if (startDownloadRpc(fileReferenceDownload, retryCount, connection)) return; try { Thread.sleep(sleepBetweenRetries.toMillis()); } catch (InterruptedException e) { /* ignored */} retryCount++; + + // There is no one connection that will always work for each file reference (each file reference might + // exist on just one config server, and which one could be different for each file reference), so we + // should get a new connection for every retry + connection = connectionPool.switchConnection(connection); } while (retryCount < 5); fileReferenceDownload.future().completeExceptionally(new RuntimeException("Failed getting " + fileReference)); @@ -84,28 +90,25 @@ public class FileReferenceDownloader { downloads.remove(fileReference); } - private boolean startDownloadRpc(FileReferenceDownload fileReferenceDownload, int retryCount) { + private boolean startDownloadRpc(FileReferenceDownload fileReferenceDownload, int retryCount, Connection connection) { Request request = createRequest(fileReferenceDownload); - Connection connection = connectionPool.getCurrent(); connection.invokeSync(request, rpcTimeout(retryCount).getSeconds()); Level logLevel = (retryCount > 3 ? Level.INFO : Level.FINE); FileReference fileReference = fileReferenceDownload.fileReference(); if (validateResponse(request)) { - log.log(Level.FINE, () -> "Request callback, OK. Req: " + request + "\nSpec: " + connection + ", retry count " + retryCount); + log.log(Level.FINE, () -> "Request callback, OK. Req: " + request + "\nSpec: " + connection); if (request.returnValues().get(0).asInt32() == 0) { log.log(Level.FINE, () -> "Found '" + fileReference + "' available at " + connection.getAddress()); return true; } else { log.log(logLevel, "'" + fileReference + "' not found at " + connection.getAddress()); - connectionPool.switchConnection(connection); return false; } } else { log.log(logLevel, "Downloading " + fileReference + " from " + connection.getAddress() + " failed: " + request + ", error: " + request.errorMessage() + ", will switch config server for next request" + " (retry " + retryCount + ", rpc timeout " + rpcTimeout(retryCount)); - connectionPool.switchConnection(connection); return false; } } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 3cbdb9e1c56..b7b0c9aea30 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -308,13 +308,6 @@ public class Flags { TENANT_ID ); - public static final UnboundIntFlag MAX_CONNECTION_LIFE_IN_HOSTED = defineIntFlag( - "max-connection-life-in-hosted", 45, - List.of("bjorncs"), "2021-09-30", "2021-12-31", - "Max connection life for connections to jdisc endpoints in hosted", - "Takes effect at redeployment", - APPLICATION_ID); - public static final UnboundBooleanFlag ENABLE_ROUTING_REUSE_PORT = defineFeatureFlag( "enable-routing-reuse-port", false, List.of("mortent"), "2021-09-29", "2021-12-31", @@ -353,6 +346,13 @@ public class Flags { "Takes effect at redeploy", ZONE_ID, APPLICATION_ID); + public static final UnboundBooleanFlag UNORDERED_MERGE_CHAINING = defineFeatureFlag( + "unordered-merge-chaining", false, + List.of("vekterli", "geirst"), "2021-11-15", "2022-03-01", + "Enables the use of unordered merge chains for data merge operations", + "Takes effect at redeploy", + ZONE_ID, APPLICATION_ID); + public static final UnboundStringFlag JDK_VERSION = defineStringFlag( "jdk-version", "11", List.of("hmusum"), "2021-10-25", "2021-11-25", @@ -363,6 +363,13 @@ public class Flags { HOSTNAME, NODE_TYPE); + public static final UnboundBooleanFlag IGNORE_THREAD_STACK_SIZES = defineFeatureFlag( + "ignore-thread-stack-sizes", false, + List.of("arnej"), "2021-11-12", "2022-01-31", + "Whether C++ thread creation should ignore any requested stack size", + "Triggers restart, takes effect immediately", + ZONE_ID, APPLICATION_ID); + public static final UnboundBooleanFlag USE_FILE_DISTRIBUTION_CONNECTION_POOL = defineFeatureFlag( "use-file-distribution-connection-pool", false, List.of("hmusum"), "2021-11-16", "2021-12-16", diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java index 92c98505cf1..3c017181bbd 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java @@ -2,6 +2,7 @@ package com.yahoo.jdisc.http.filter.security.base; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.yahoo.component.AbstractComponent; @@ -11,10 +12,11 @@ import com.yahoo.jdisc.handler.ResponseDispatch; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import java.util.logging.Level; import java.io.UncheckedIOException; +import java.util.Objects; import java.util.Optional; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -35,16 +37,26 @@ public abstract class JsonSecurityRequestFilterBase extends AbstractComponent im protected abstract Optional<ErrorResponse> filter(DiscFilterRequest request); + protected ObjectMapper jsonMapper() { return mapper; } + private void writeResponse(DiscFilterRequest request, ErrorResponse error, ResponseHandler responseHandler) { - ObjectNode errorMessage = mapper.createObjectNode(); - errorMessage.put("code", error.errorCode); - errorMessage.put("message", error.message); + JsonNode json; + if (error.customJson != null) { + json = error.customJson; + } else { + ObjectNode o = mapper.createObjectNode(); + if (error.errorCodeAsInt != null) o.put("code", error.errorCodeAsInt); + else if (error.errorCodeAsString != null) o.put("code", error.errorCodeAsString); + if (error.message != null) o.put("message", error.message); + json = o; + } error.response.headers().put("Content-Type", "application/json"); // Note: Overwrites header if already exists error.response.headers().put("Cache-Control", "must-revalidate,no-cache,no-store"); - log.log(Level.FINE, () -> String.format("Error response for '%s': statusCode=%d, errorCode=%d, message='%s'", - request, error.response.getStatus(), error.errorCode, error.message)); try (FastContentWriter writer = ResponseDispatch.newInstance(error.response).connectFastWriter(responseHandler)) { - writer.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errorMessage)); + String jsonAsStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json); + log.log(Level.FINE, () -> String.format("Error response for '%s': statusCode=%d, json='%s'", + request, error.response.getStatus(), jsonAsStr)); + writer.write(jsonAsStr); } catch (JsonProcessingException e) { throw new UncheckedIOException(e); } @@ -56,15 +68,24 @@ public abstract class JsonSecurityRequestFilterBase extends AbstractComponent im */ protected static class ErrorResponse { private final Response response; - private final int errorCode; + private final JsonNode customJson; + private final Integer errorCodeAsInt; + private final String errorCodeAsString; private final String message; - public ErrorResponse(Response response, int errorCode, String message) { - this.response = response; - this.errorCode = errorCode; + private ErrorResponse(Response response, JsonNode customJson, Integer errorCodeAsInt, String errorCodeAsString, + String message) { + this.response = Objects.requireNonNull(response); + this.customJson = customJson; + this.errorCodeAsInt = errorCodeAsInt; + this.errorCodeAsString = errorCodeAsString; this.message = message; } + public ErrorResponse(Response response, int errorCode, String message) { + this(response, null, errorCode, null, message); + } + public ErrorResponse(Response response, String message) { this(response, response.getStatus(), message); } @@ -73,21 +94,23 @@ public abstract class JsonSecurityRequestFilterBase extends AbstractComponent im this(new Response(httpStatusCode), errorCode, message); } + public ErrorResponse(int httpStatusCode, String errorCode, String message) { + this(new Response(httpStatusCode), null, null, errorCode, message); + } + public ErrorResponse(int httpStatusCode, String message) { this(new Response(httpStatusCode), message); } - public Response getResponse() { - return response; + public ErrorResponse(Response response, JsonNode json) { + this(response, json, null, null, null); } - public int getErrorCode() { - return errorCode; + public Response getResponse() { + return response; } - public String getMessage() { - return message; - } + public Optional<String> getMessage() { return Optional.ofNullable(message); } } } diff --git a/searchcore/src/tests/proton/attribute/attribute_test.cpp b/searchcore/src/tests/proton/attribute/attribute_test.cpp index 2151556b89d..d4ad85d88d9 100644 --- a/searchcore/src/tests/proton/attribute/attribute_test.cpp +++ b/searchcore/src/tests/proton/attribute/attribute_test.cpp @@ -42,9 +42,11 @@ #include <vespa/vespalib/gtest/gtest.h> #include <vespa/vespalib/io/fileutil.h> #include <vespa/vespalib/test/insertion_operators.h> +#include <vespa/vespalib/util/destructor_callbacks.h> #include <vespa/vespalib/util/exceptions.h> #include <vespa/vespalib/util/foreground_thread_executor.h> #include <vespa/vespalib/util/foregroundtaskexecutor.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/vespalib/util/sequencedtaskexecutorobserver.h> #include <vespa/log/log.h> @@ -86,6 +88,7 @@ using vespalib::eval::SimpleValue; using vespalib::eval::TensorSpec; using vespalib::eval::Value; using vespalib::eval::ValueType; +using vespalib::GateCallback; using vespalib::IDestructorCallback; using AVBasicType = search::attribute::BasicType; @@ -199,8 +202,9 @@ public: EXPECT_EQ(includeCommit, _attributeFieldWriter->getExecuteHistory()); } SerialNum test_force_commit(AttributeVector &attr, SerialNum serialNum) { - commit(serialNum); - _attributeFieldWriter->sync_all(); + vespalib::Gate gate; + _aw->forceCommit(serialNum, std::make_shared<GateCallback>(gate)); + gate.await(); return attr.getStatus().getLastSyncToken(); } }; diff --git a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp index e20c4268f88..b2acc8703f3 100644 --- a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp +++ b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp @@ -287,7 +287,7 @@ struct FastAccessFixture vespalib::mkdir(BASE_DIR); } ~FastAccessFixture() { - _writeService.sync(); + _writeService.sync_all_executors(); } }; diff --git a/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp b/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp index 5e42231d866..091292b3151 100644 --- a/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp +++ b/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp @@ -313,9 +313,9 @@ struct FixtureBase init(); } ~FixtureBase() { - _writeService.sync(); + _writeService.sync_all_executors(); _writeService.master().execute(makeLambdaTask([this]() { _subDb.close(); })); - _writeService.sync(); + _writeService.sync_all_executors(); } template <typename FunctionType> void runInMaster(FunctionType func) { @@ -591,20 +591,20 @@ TEST_F("require that attribute compaction config reflect retirement", FastAccess auto calc = std::make_shared<proton::test::BucketStateCalculator>(); calc->setNodeRetired(true); f._subDb.setBucketStateCalculator(calc); - f._writeService.sync(); + f._writeService.sync_all_executors(); guard = f._subDb.getAttributeManager()->getAttribute("attr1"); EXPECT_EQUAL(retired_cfg, (*guard)->getConfig().getCompactionStrategy()); EXPECT_EQUAL(retired_cfg, dynamic_cast<const proton::DocumentMetaStore &>(f._subDb.getDocumentMetaStoreContext().get()).getConfig().getCompactionStrategy()); f.basicReconfig(10); - f._writeService.sync(); + f._writeService.sync_all_executors(); guard = f._subDb.getAttributeManager()->getAttribute("attr1"); EXPECT_EQUAL(retired_cfg, (*guard)->getConfig().getCompactionStrategy()); EXPECT_EQUAL(retired_cfg, dynamic_cast<const proton::DocumentMetaStore &>(f._subDb.getDocumentMetaStoreContext().get()).getConfig().getCompactionStrategy()); calc->setNodeRetired(false); f._subDb.setBucketStateCalculator(calc); - f._writeService.sync(); + f._writeService.sync_all_executors(); guard = f._subDb.getAttributeManager()->getAttribute("attr1"); EXPECT_EQUAL(default_cfg, (*guard)->getConfig().getCompactionStrategy()); EXPECT_EQUAL(default_cfg, dynamic_cast<const proton::DocumentMetaStore &>(f._subDb.getDocumentMetaStoreContext().get()).getConfig().getCompactionStrategy()); diff --git a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp index 5384a985af0..977c899ab11 100644 --- a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp +++ b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp @@ -455,7 +455,7 @@ struct FeedHandlerFixture } ~FeedHandlerFixture() { - writeService.sync(); + writeService.sync_all_executors(); } template <class FunctionType> inline void runAsMaster(FunctionType &&function) { diff --git a/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp b/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp index 97faa81b48a..e53468e0dd4 100644 --- a/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp +++ b/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp @@ -516,11 +516,11 @@ struct FixtureBase } void syncIndex() { - _writeService.sync(); + _writeService.sync_all_executors(); } void sync() { - _writeServiceReal.sync(); + _writeServiceReal.sync_all_executors(); } const test::DocumentMetaStoreObserver &metaStoreObserver() { @@ -701,7 +701,7 @@ FixtureBase::FixtureBase() } FixtureBase::~FixtureBase() { - _writeServiceReal.sync(); + _writeServiceReal.sync_all_executors(); } void diff --git a/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp b/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp index 5146a16272a..eb398b9ee48 100644 --- a/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp +++ b/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp @@ -246,7 +246,7 @@ struct FixtureBase { void force_commit() { runInMaster([this] () { static_cast<IFeedView&>(*feedview).forceCommit(serial_num); }); - writeService.sync(); + writeService.sync_all_executors(); } }; diff --git a/searchcore/src/tests/proton/documentmetastore/lidreusedelayer/lidreusedelayer_test.cpp b/searchcore/src/tests/proton/documentmetastore/lidreusedelayer/lidreusedelayer_test.cpp index c49ec67f220..9162972a4cb 100644 --- a/searchcore/src/tests/proton/documentmetastore/lidreusedelayer/lidreusedelayer_test.cpp +++ b/searchcore/src/tests/proton/documentmetastore/lidreusedelayer/lidreusedelayer_test.cpp @@ -185,7 +185,7 @@ public: runInMaster([&] () { cycleLids(_lidReuseDelayer->getReuseLids()); }); } - void sync() { _writeService.sync(); } + void sync() { _writeService.sync_all_executors(); } }; diff --git a/searchcore/src/tests/proton/index/indexmanager_test.cpp b/searchcore/src/tests/proton/index/indexmanager_test.cpp index 4c442d38443..d34e2ae667e 100644 --- a/searchcore/src/tests/proton/index/indexmanager_test.cpp +++ b/searchcore/src/tests/proton/index/indexmanager_test.cpp @@ -128,7 +128,7 @@ struct IndexManagerTest : public ::testing::Test { { removeTestData(); vespalib::mkdir(index_dir, false); - _writeService.sync(); + _writeService.sync_all_executors(); resetIndexManager(); } diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp index 587ec8dda1f..f3d90d37e42 100644 --- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp +++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp @@ -14,6 +14,8 @@ #include <vespa/searchlib/attribute/imported_attribute_vector.h> #include <vespa/searchlib/tensor/prepare_result.h> #include <vespa/vespalib/stllike/hash_map.hpp> +#include <vespa/vespalib/util/destructor_callbacks.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/vespalib/util/idestructorcallback.h> #include <vespa/vespalib/util/threadexecutor.h> #include <future> @@ -27,6 +29,7 @@ using namespace search; using ExecutorId = vespalib::ISequencedTaskExecutor::ExecutorId; using search::attribute::ImportedAttributeVector; using search::tensor::PrepareResult; +using vespalib::GateCallback; using vespalib::ISequencedTaskExecutor; namespace proton { @@ -821,24 +824,38 @@ AttributeWriter::forceCommit(const CommitParam & param, OnWriteDoneType onWriteD void AttributeWriter::onReplayDone(uint32_t docIdLimit) { - for (auto entry : _attrMap) { - _attributeFieldWriter.execute(entry.second.executor_id, - [docIdLimit, attr = entry.second.attribute]() - { applyReplayDone(docIdLimit, *attr); }); + vespalib::Gate gate; + { + auto on_write_done = std::make_shared<GateCallback>(gate); + for (auto entry : _attrMap) { + _attributeFieldWriter.execute(entry.second.executor_id, + [docIdLimit, attr = entry.second.attribute, on_write_done]() + { + (void) on_write_done; + applyReplayDone(docIdLimit, *attr); + }); + } } - _attributeFieldWriter.sync_all(); + gate.await(); } void AttributeWriter::compactLidSpace(uint32_t wantedLidLimit, SerialNum serialNum) { - for (auto entry : _attrMap) { - _attributeFieldWriter.execute(entry.second.executor_id, - [wantedLidLimit, serialNum, attr=entry.second.attribute]() - { applyCompactLidSpace(wantedLidLimit, serialNum, *attr); }); + vespalib::Gate gate; + { + auto on_write_done = std::make_shared<GateCallback>(gate); + for (auto entry : _attrMap) { + _attributeFieldWriter.execute(entry.second.executor_id, + [wantedLidLimit, serialNum, attr=entry.second.attribute, on_write_done]() + { + (void) on_write_done; + applyCompactLidSpace(wantedLidLimit, serialNum, *attr); + }); + } } - _attributeFieldWriter.sync_all(); + gate.await(); } bool diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdb.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdb.cpp index 645c9b15f07..3f2fb6c4634 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdb.cpp @@ -62,6 +62,8 @@ using storage::spi::Timestamp; using search::common::FileHeaderContext; using proton::initializer::InitializerTask; using proton::initializer::TaskRunner; +using vespalib::GateCallback; +using vespalib::IDestructorCallback; using vespalib::makeLambdaTask; using searchcorespi::IFlushTarget; @@ -186,7 +188,6 @@ DocumentDB::DocumentDB(const vespalib::string &baseDir, _metricsHook(std::make_unique<MetricsUpdateHook>(*this)), _feedView(), _refCount(), - _syncFeedViewEnabled(false), _owner(owner), _bucketExecutor(bucketExecutor), _state(), @@ -313,9 +314,9 @@ void DocumentDB::initFinish(DocumentDBConfig::SP configSnapshot) { // Called by executor thread + assert(_writeService.master().isCurrentThread()); _bucketHandler.setReadyBucketHandler(_subDBs.getReadySubDB()->getDocumentMetaStoreContext().get()); _subDBs.initViews(*configSnapshot, _sessionManager); - _syncFeedViewEnabled = true; syncFeedView(); // Check that feed view has been activated. assert(_feedView.get()); @@ -376,9 +377,13 @@ void DocumentDB::enterOnlineState() { // Called by executor thread - // Ensure that all replayed operations are committed to memory structures - _feedView.get()->forceCommit(CommitParam(_feedHandler->getSerialNum())); - _writeService.sync(); + assert(_writeService.master().isCurrentThread()); + { + vespalib::Gate gate; + // Ensure that all replayed operations are committed to memory structures + _feedView.get()->forceCommit(CommitParam(_feedHandler->getSerialNum()), std::make_shared<GateCallback>(gate)); + gate.await(); + } (void) _state.enterOnlineState(); // Consider delayed pruning of transaction log and config history @@ -464,10 +469,11 @@ DocumentDB::applyConfig(DocumentDBConfig::SP configSnapshot, SerialNum serialNum } { bool elidedConfigSave = equalReplayConfig && tlsReplayDone; + vespalib::Gate gate; // Flush changes to attributes and memory index, cf. visibilityDelay _feedView.get()->forceCommit(CommitParam(elidedConfigSave ? serialNum : serialNum - 1), - std::make_shared<vespalib::KeepAlive<FeedHandler::CommitResult>>(std::move(commit_result))); - _writeService.sync(); + std::make_shared<vespalib::KeepAlive<std::pair<FeedHandler::CommitResult, std::shared_ptr<IDestructorCallback>>>>(std::make_pair(std::move(commit_result), std::make_shared<GateCallback>(gate)))); + gate.await(); } if (params.shouldMaintenanceControllerChange()) { _maintenanceController.killJobs(); @@ -510,20 +516,11 @@ DocumentDB::applyConfig(DocumentDBConfig::SP configSnapshot, SerialNum serialNum } -namespace { -void -doNothing(IFeedView::SP) -{ - // Called by index executor, delays when feed view is dropped. -} -} // namespace - void DocumentDB::performDropFeedView(IFeedView::SP feedView) { - // Called by executor task, delays when feed view is dropped. - // Also called by DocumentDB::receive() method to keep feed view alive - + // Delays when feed view is dropped. + assert(_writeService.master().isCurrentThread()); _writeService.attributeFieldWriter().sync_all(); _writeService.summary().sync(); @@ -534,11 +531,11 @@ DocumentDB::performDropFeedView(IFeedView::SP feedView) void DocumentDB::performDropFeedView2(IFeedView::SP feedView) { - // Called by executor task, delays when feed view is dropped. - // Also called by DocumentDB::receive() method to keep feed view alive + // Delays when feed view is dropped. + assert(_writeService.index().isCurrentThread()); _writeService.indexFieldInverter().sync_all(); _writeService.indexFieldWriter().sync_all(); - masterExecute([feedView]() { doNothing(feedView); }); + masterExecute([feedView]() { (void) feedView; }); } @@ -575,15 +572,15 @@ DocumentDB::close() // Caller should have removed document DB from feed router. _refCount.waitForZeroRefCount(); - _writeService.sync(); + _writeService.sync_all_executors(); // The attributes in the ready sub db is also the total set of attributes. DocumentDBTaggedMetrics &metrics = getMetrics(); _metricsWireService.cleanAttributes(metrics.ready.attributes); _metricsWireService.cleanAttributes(metrics.notReady.attributes); - _writeService.sync(); + _writeService.sync_all_executors(); masterExecute([this] () { closeSubDBs(); } ); - _writeService.sync(); + _writeService.sync_all_executors(); // What about queued tasks ? _writeService.shutdown(); _maintenanceController.kill(); @@ -912,15 +909,12 @@ DocumentDB::getActiveGeneration() const { void DocumentDB::syncFeedView() { - // Called by executor or while in rendezvous with executor - - if (!_syncFeedViewEnabled) - return; + assert(_writeService.master().isCurrentThread()); IFeedView::SP oldFeedView(_feedView.get()); IFeedView::SP newFeedView(_subDBs.getFeedView()); _maintenanceController.killJobs(); - _writeService.sync(); + _writeService.sync_all_executors(); _feedView.set(newFeedView); _feedHandler->setActiveFeedView(newFeedView.get()); @@ -994,7 +988,7 @@ void DocumentDB::stopMaintenance() { _maintenanceController.stop(); - _writeService.sync(); + _writeService.sync_all_executors(); } void diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdb.h b/searchcore/src/vespa/searchcore/proton/server/documentdb.h index 014bba11f83..6b855cd40a8 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdb.h +++ b/searchcore/src/vespa/searchcore/proton/server/documentdb.h @@ -112,7 +112,6 @@ private: std::unique_ptr<metrics::UpdateHook> _metricsHook; vespalib::VarHolder<IFeedView::SP> _feedView; vespalib::MonitoredRefCount _refCount; - bool _syncFeedViewEnabled; IDocumentDBOwner &_owner; storage::spi::BucketExecutor &_bucketExecutor; DDBState _state; diff --git a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp index d35aaf9f909..0e9ba7a24c8 100644 --- a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp @@ -88,13 +88,12 @@ ExecutorThreadingService::ExecutorThreadingService(vespalib::ThreadExecutor & sh ExecutorThreadingService::~ExecutorThreadingService() = default; -vespalib::Syncable & -ExecutorThreadingService::sync() { +void +ExecutorThreadingService::sync_all_executors() { // We have multiple patterns where task A posts to B which post back to A for (size_t i = 0; i < 2; i++) { syncOnce(); } - return *this; } void diff --git a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h index 51da27586f7..e571e205f47 100644 --- a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h +++ b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h @@ -17,7 +17,7 @@ class ThreadingServiceConfig; class ExecutorThreadingService : public searchcorespi::index::IThreadingService { private: - vespalib::ThreadExecutor & _sharedExecutor; + vespalib::ThreadExecutor & _sharedExecutor; vespalib::ThreadStackExecutor _masterExecutor; std::unique_ptr<vespalib::SyncableThreadExecutor> _indexExecutor; std::unique_ptr<vespalib::SyncableThreadExecutor> _summaryExecutor; @@ -46,10 +46,7 @@ public: ExecutorThreadingService(vespalib::ThreadExecutor &sharedExecutor, uint32_t num_treads = 1); ~ExecutorThreadingService() override; - /** - * Implements vespalib::Syncable - */ - vespalib::Syncable &sync() override; + void sync_all_executors() override; void shutdown(); @@ -67,9 +64,6 @@ public: return *_summaryExecutor; } - /** - * Implements IThreadingService - */ searchcorespi::index::IThreadService &master() override { return _masterService; } @@ -90,6 +84,6 @@ public: ExecutorThreadingServiceStats getStats(); }; -} // namespace proton +} diff --git a/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp b/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp index 8451f3268b8..40a1a1a45f3 100644 --- a/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp @@ -17,6 +17,8 @@ #include <vespa/searchcore/proton/reprocessing/attribute_reprocessing_initializer.h> #include <vespa/searchcore/proton/reprocessing/reprocess_documents_task.h> #include <vespa/searchlib/docstore/document_store_visitor_progress.h> +#include <vespa/vespalib/util/destructor_callbacks.h> + #include <vespa/log/log.h> LOG_SETUP(".proton.server.fast_access_doc_subdb"); @@ -314,9 +316,13 @@ FastAccessDocSubDB::onReprocessDone(SerialNum serialNum) { IFeedView::SP feedView = _iFeedView.get(); IAttributeWriter::SP attrWriter = static_cast<FastAccessFeedView &>(*feedView).getAttributeWriter(); - attrWriter->forceCommit(serialNum, std::shared_ptr<vespalib::IDestructorCallback>()); - _writeService.attributeFieldWriter().sync_all(); - _writeService.summary().sync(); + vespalib::Gate gate; + { + auto onDone = std::make_shared<vespalib::GateCallback>(gate); + attrWriter->forceCommit(serialNum, onDone); + _writeService.summary().execute(vespalib::makeLambdaTask([done = std::move(onDone)]() { (void) done; })); + } + gate.await(); Parent::onReprocessDone(serialNum); } diff --git a/searchcore/src/vespa/searchcore/proton/server/fast_access_feed_view.cpp b/searchcore/src/vespa/searchcore/proton/server/fast_access_feed_view.cpp index ce7f1d70195..db2bb7ed2cb 100644 --- a/searchcore/src/vespa/searchcore/proton/server/fast_access_feed_view.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/fast_access_feed_view.cpp @@ -73,7 +73,7 @@ void FastAccessFeedView::handleCompactLidSpace(const CompactLidSpaceOperation &op) { // Drain pending PutDoneContext and ForceCommitContext objects - _writeService.sync(); + _writeService.sync_all_executors(); _docIdLimit.set(op.getLidLimit()); getAttributeWriter()->compactLidSpace(op.getLidLimit(), op.getSerialNum()); Parent::handleCompactLidSpace(op); diff --git a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp index af746f9debb..c9294150f16 100644 --- a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp @@ -303,7 +303,7 @@ void FeedHandler::performEof() { assert(_writeService.master().isCurrentThread()); - _writeService.sync(); + _writeService.sync_all_executors(); LOG(debug, "Visiting done for transaction log domain '%s', eof received", _tlsMgr.getDomainName().c_str()); // Replay must be complete if (_replay_end_serial_num != _serialNum) { diff --git a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp index b512143c5fd..bf3589457f9 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp @@ -244,7 +244,7 @@ SearchableDocSubDB::reconfigure(std::unique_ptr<Configure> configure) { assert(_writeService.master().isCurrentThread()); - _writeService.sync(); + _writeService.sync_all_executors(); // Everything should be quiet now. diff --git a/searchcore/src/vespa/searchcore/proton/test/thread_utils.h b/searchcore/src/vespa/searchcore/proton/test/thread_utils.h index 84b7c12cba6..6b08eecf61f 100644 --- a/searchcore/src/vespa/searchcore/proton/test/thread_utils.h +++ b/searchcore/src/vespa/searchcore/proton/test/thread_utils.h @@ -14,7 +14,7 @@ void runInMaster(searchcorespi::index::IThreadingService &writeService, FunctionType func) { writeService.master().execute(vespalib::makeLambdaTask(std::move(func))); - writeService.sync(); + writeService.sync_all_executors(); } } diff --git a/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h b/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h index 94b62962f04..1d379f439fa 100644 --- a/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h +++ b/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h @@ -32,19 +32,9 @@ public: const ThreadServiceObserver &summaryObserver() const { return _summary; } - const vespalib::SequencedTaskExecutorObserver &indexFieldInverterObserver() const { - return _indexFieldInverter; - } - const vespalib::SequencedTaskExecutorObserver &indexFieldWriterObserver() const { - return _indexFieldWriter; - } - - const vespalib::SequencedTaskExecutorObserver &attributeFieldWriterObserver() const { - return _attributeFieldWriter; - } - vespalib::Syncable &sync() override { - return _service.sync(); + void sync_all_executors() override { + _service.sync_all_executors(); } searchcorespi::index::IThreadService &master() override { diff --git a/searchcorespi/src/vespa/searchcorespi/index/ithreadingservice.h b/searchcorespi/src/vespa/searchcorespi/index/ithreadingservice.h index be8c9ef7d86..f30aec94d53 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/ithreadingservice.h +++ b/searchcorespi/src/vespa/searchcorespi/index/ithreadingservice.h @@ -2,7 +2,6 @@ #pragma once #include "i_thread_service.h" -#include <vespa/vespalib/util/syncable.h> namespace vespalib { class ISequencedTaskExecutor; } namespace searchcorespi::index { @@ -57,13 +56,15 @@ namespace searchcorespi::index { * TODO: * indexFieldInverter and indexFieldWriter can be collapsed to one. Both need sequencing, * but they sequence on different things so efficiency will be the same and just depends on #threads */ -struct IThreadingService : public vespalib::Syncable +struct IThreadingService { IThreadingService(const IThreadingService &) = delete; IThreadingService & operator = (const IThreadingService &) = delete; IThreadingService() = default; virtual ~IThreadingService() = default; + virtual void sync_all_executors() = 0; + virtual IThreadService &master() = 0; virtual IThreadService &index() = 0; virtual IThreadService &summary() = 0; diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp index da6cfdba158..2a12867dd33 100644 --- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp +++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp @@ -182,31 +182,31 @@ struct Compiler : public Blueprint::DependencyHandler { } FeatureRef resolve_feature(const vespalib::string &feature_name, Accept accept_type) { - FeatureNameParser parser(feature_name); - if (!parser.valid()) { + auto parser = std::make_unique<FeatureNameParser>(feature_name); + if (!parser->valid()) { return fail(feature_name, "malformed name"); } - if (failed_set.count(parser.featureName()) > 0) { - return fail(parser.featureName(), "already failed"); + if (failed_set.count(parser->featureName()) > 0) { + return fail(parser->featureName(), "already failed"); } - auto old_feature = feature_map.find(parser.featureName()); + auto old_feature = feature_map.find(parser->featureName()); if (old_feature != feature_map.end()) { - return verify_type(parser, old_feature->second, accept_type); + return verify_type(*parser, old_feature->second, accept_type); } if ((resolve_stack.size() + 1) > BlueprintResolver::MAX_DEP_DEPTH) { - return fail(parser.featureName(), "dependency graph too deep"); + return fail(parser->featureName(), "dependency graph too deep"); } for (const Frame &frame: resolve_stack) { - if (frame.parser.executorName() == parser.executorName()) { - return fail(parser.featureName(), "dependency cycle detected"); + if (frame.parser.executorName() == parser->executorName()) { + return fail(parser->featureName(), "dependency cycle detected"); } } - setup_executor(parser); - auto new_feature = feature_map.find(parser.featureName()); + setup_executor(*parser); + auto new_feature = feature_map.find(parser->featureName()); if (new_feature != feature_map.end()) { - return verify_type(parser, new_feature->second, accept_type); + return verify_type(*parser, new_feature->second, accept_type); } - return fail(parser.featureName(), fmt("unknown output: '%s'", parser.output().c_str())); + return fail(parser->featureName(), fmt("unknown output: '%s'", parser->output().c_str())); } std::optional<FeatureType> resolve_input(const vespalib::string &feature_name, Accept accept_type) override { diff --git a/staging_vespalib/src/vespa/vespalib/util/sequencedtaskexecutor.h b/staging_vespalib/src/vespa/vespalib/util/sequencedtaskexecutor.h index 06e7fa65ac2..7bb56424849 100644 --- a/staging_vespalib/src/vespa/vespalib/util/sequencedtaskexecutor.h +++ b/staging_vespalib/src/vespa/vespalib/util/sequencedtaskexecutor.h @@ -4,6 +4,7 @@ #include "isequencedtaskexecutor.h" #include <vespa/vespalib/util/time.h> #include <vespa/vespalib/util/runnable.h> +#include <optional> namespace vespalib { diff --git a/storage/src/vespa/storage/distributor/node_supported_features_repo.cpp b/storage/src/vespa/storage/distributor/node_supported_features_repo.cpp index e125f360cec..2e5335c012a 100644 --- a/storage/src/vespa/storage/distributor/node_supported_features_repo.cpp +++ b/storage/src/vespa/storage/distributor/node_supported_features_repo.cpp @@ -9,7 +9,7 @@ NodeSupportedFeaturesRepo::NodeSupportedFeaturesRepo() = default; NodeSupportedFeaturesRepo::NodeSupportedFeaturesRepo( vespalib::hash_map<uint16_t, NodeSupportedFeatures> features, - PrivateCtorTag) + PrivateCtorTag) noexcept : _node_features(std::move(features)) {} diff --git a/storage/src/vespa/storage/distributor/node_supported_features_repo.h b/storage/src/vespa/storage/distributor/node_supported_features_repo.h index cc40c27b8e2..2167858ad28 100644 --- a/storage/src/vespa/storage/distributor/node_supported_features_repo.h +++ b/storage/src/vespa/storage/distributor/node_supported_features_repo.h @@ -20,7 +20,7 @@ class NodeSupportedFeaturesRepo { public: NodeSupportedFeaturesRepo(); - NodeSupportedFeaturesRepo(vespalib::hash_map<uint16_t, NodeSupportedFeatures> features, PrivateCtorTag); + NodeSupportedFeaturesRepo(vespalib::hash_map<uint16_t, NodeSupportedFeatures> features, PrivateCtorTag) noexcept; ~NodeSupportedFeaturesRepo(); // Returns supported node features for node with distribution key node_idx, or a default feature set diff --git a/storage/src/vespa/storage/persistence/processallhandler.cpp b/storage/src/vespa/storage/persistence/processallhandler.cpp index 9308ae2a807..04afdc9eb85 100644 --- a/storage/src/vespa/storage/persistence/processallhandler.cpp +++ b/storage/src/vespa/storage/persistence/processallhandler.cpp @@ -86,7 +86,7 @@ ProcessAllHandler::handleRemoveLocation(api::RemoveLocationCommand& cmd, Message spi::Bucket bucket(cmd.getBucket()); UnrevertableRemoveEntryProcessor processor(_spi, bucket, tracker->context()); BucketProcessor::iterateAll(_spi, bucket, cmd.getDocumentSelection(), - std::make_shared<document::AllFields>(), + std::make_shared<document::DocIdOnly>(), processor, spi::NEWEST_DOCUMENT_ONLY,tracker->context()); tracker->setReply(std::make_shared<api::RemoveLocationReply>(cmd, processor._n_removed)); @@ -104,7 +104,7 @@ ProcessAllHandler::handleStatBucket(api::StatBucketCommand& cmd, MessageTracker: spi::Bucket bucket(cmd.getBucket()); StatEntryProcessor processor(ost); BucketProcessor::iterateAll(_spi, bucket, cmd.getDocumentSelection(), - std::make_shared<document::AllFields>(), + std::make_shared<document::DocIdOnly>(), processor, spi::ALL_VERSIONS,tracker->context()); tracker->setReply(std::make_shared<api::StatBucketReply>(cmd, ost.str())); diff --git a/vespabase/src/rhel-prestart.sh b/vespabase/src/rhel-prestart.sh index 395a3e3d91d..dbc77879efe 100755 --- a/vespabase/src/rhel-prestart.sh +++ b/vespabase/src/rhel-prestart.sh @@ -78,6 +78,9 @@ findhost if [ "$VESPA_USER" = "" ]; then VESPA_USER=$(id -run) fi +if [ "$VESPA_GROUP" = "" ]; then + VESPA_GROUP=$(id -rgn) +fi cd $VESPA_HOME || { echo "Cannot cd to $VESPA_HOME" 1>&2; exit 1; } @@ -96,32 +99,32 @@ fixdir () { # BEGIN directory fixups -fixdir root root 1777 logs -fixdir root root 1777 tmp -fixdir root root 1777 var/run -fixdir ${VESPA_USER} root 1777 var/crash -fixdir ${VESPA_USER} root 1777 logs/vespa -fixdir ${VESPA_USER} root 1777 tmp/vespa -fixdir root root 755 var -fixdir ${VESPA_USER} root 755 libexec/vespa/plugins/qrs -fixdir ${VESPA_USER} root 755 logs/vespa/configserver -fixdir ${VESPA_USER} root 755 logs/vespa/qrs -fixdir ${VESPA_USER} root 755 logs/vespa/search -fixdir ${VESPA_USER} root 755 var/db/vespa -fixdir ${VESPA_USER} root 755 var/db/vespa/tmp -fixdir ${VESPA_USER} root 755 var/db/vespa/config_server -fixdir ${VESPA_USER} root 755 var/db/vespa/config_server/serverdb -fixdir ${VESPA_USER} root 755 var/db/vespa/config_server/serverdb/tenants -fixdir ${VESPA_USER} root 755 var/db/vespa/filedistribution -fixdir ${VESPA_USER} root 755 var/db/vespa/index -fixdir ${VESPA_USER} root 755 var/db/vespa/logcontrol -fixdir ${VESPA_USER} root 755 var/db/vespa/search -fixdir ${VESPA_USER} root 755 var/jdisc_container -fixdir ${VESPA_USER} root 755 var/vespa -fixdir ${VESPA_USER} root 755 var/vespa/application -fixdir ${VESPA_USER} root 755 var/vespa/bundlecache -fixdir ${VESPA_USER} root 755 var/vespa/bundlecache/configserver -fixdir ${VESPA_USER} root 755 var/vespa/cache/config/ +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 libexec/vespa/plugins/qrs +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 logs +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 logs/vespa +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 logs/vespa/configserver +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 logs/vespa/qrs +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 logs/vespa/search +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 tmp +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 tmp/vespa +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/crash +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/config_server +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/config_server/serverdb +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/config_server/serverdb/tenants +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/filedistribution +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/index +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/logcontrol +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/search +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/tmp +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/jdisc_container +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/run +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/vespa +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/vespa/application +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/vespa/bundlecache +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/vespa/bundlecache/configserver +fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/vespa/cache/config/ if [ "${VESPA_UNPRIVILEGED}" != yes ]; then chown -hR ${VESPA_USER} logs/vespa diff --git a/vespamalloc/src/tests/thread/thread_test.sh b/vespamalloc/src/tests/thread/thread_test.sh index 45734bab3a7..cf92f8eb0fc 100755 --- a/vespamalloc/src/tests/thread/thread_test.sh +++ b/vespamalloc/src/tests/thread/thread_test.sh @@ -4,8 +4,8 @@ set -e echo "Trying to find limit for processes:" if ulimit -u; then - echo "Fixing limit to 31100" - ulimit -u 31100 + echo "Fixing limit to 14100" + ulimit -u 14100 elif [ "$RETRYEXEC" ]; then echo "Already tried to re-exec script, giving up." exit 1 diff --git a/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt b/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt index 9e6e235f6b4..b7871cfbde1 100644 --- a/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt +++ b/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt @@ -1,4 +1,4 @@ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. install_fat_java_artifact(zookeeper-server-3.6.3) # Needs to be included when this is the wanted default version (and symlinks for other versions need to be removed) -install_symlink(lib/jars/zookeeper-server-3.6.3-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) +#install_symlink(lib/jars/zookeeper-server-3.6.3-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) diff --git a/zookeeper-server/zookeeper-server-3.7.0/CMakeLists.txt b/zookeeper-server/zookeeper-server-3.7.0/CMakeLists.txt index c106a26fffc..8a41fc8b8fa 100644 --- a/zookeeper-server/zookeeper-server-3.7.0/CMakeLists.txt +++ b/zookeeper-server/zookeeper-server-3.7.0/CMakeLists.txt @@ -1,4 +1,4 @@ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. install_fat_java_artifact(zookeeper-server-3.7.0) # Needs to be included when this is the wanted default version (and symlinks for other versions need to be removed) -#install_symlink(lib/jars/zookeeper-server-3.7.0-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) +install_symlink(lib/jars/zookeeper-server-3.7.0-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) |