diff --git a/integration/integration.cabal b/integration/integration.cabal index 8e29915dbb2..7053817cbf6 100644 --- a/integration/integration.cabal +++ b/integration/integration.cabal @@ -119,6 +119,7 @@ library Testlib.JSON Testlib.ModService Testlib.Options + Testlib.Ports Testlib.Prekeys Testlib.Prelude Testlib.Printing @@ -126,6 +127,7 @@ library Testlib.ResourcePool Testlib.Run Testlib.RunServices + Testlib.Service Testlib.Types build-depends: diff --git a/integration/test/API/Cargohold.hs b/integration/test/API/Cargohold.hs index 34276fecb38..5e08d84d794 100644 --- a/integration/test/API/Cargohold.hs +++ b/integration/test/API/Cargohold.hs @@ -122,11 +122,8 @@ buildMultipartBody header body bodyMimeType = MIME.mime_val_content = MIME.Single ((decodeUtf8 . LBS.toStrict) c) } -downloadAsset :: (HasCallStack, MakesValue user, MakesValue assetDomain, MakesValue key) => user -> assetDomain -> key -> (HTTP.Request -> HTTP.Request) -> App Response -downloadAsset user assetDomain key trans = downloadAsset' user assetDomain key "nginz-https.example.com" trans - -downloadAsset' :: (HasCallStack, MakesValue user, MakesValue key, MakesValue assetDomain) => user -> assetDomain -> key -> String -> (HTTP.Request -> HTTP.Request) -> App Response -downloadAsset' user assetDomain key zHostHeader trans = do +downloadAsset :: (HasCallStack, MakesValue user, MakesValue key, MakesValue assetDomain) => user -> assetDomain -> key -> String -> (HTTP.Request -> HTTP.Request) -> App Response +downloadAsset user assetDomain key zHostHeader trans = do uid <- objId user domain <- objDomain assetDomain key' <- asString key diff --git a/integration/test/API/GalleyInternal.hs b/integration/test/API/GalleyInternal.hs index bfdff81b50a..0d7f8c9f602 100644 --- a/integration/test/API/GalleyInternal.hs +++ b/integration/test/API/GalleyInternal.hs @@ -32,9 +32,9 @@ putTeamMember user team perms = do ] req -getTeamFeature :: HasCallStack => String -> String -> App Response -getTeamFeature featureName tid = do - req <- baseRequest OwnDomain Galley Unversioned $ joinHttpPath ["i", "teams", tid, "features", featureName] +getTeamFeature :: (HasCallStack, MakesValue domain_) => domain_ -> String -> String -> App Response +getTeamFeature domain_ featureName tid = do + req <- baseRequest domain_ Galley Unversioned $ joinHttpPath ["i", "teams", tid, "features", featureName] submit "GET" $ req getFederationStatus :: diff --git a/integration/test/SetupHelpers.hs b/integration/test/SetupHelpers.hs index f6ba6f46768..8851e258900 100644 --- a/integration/test/SetupHelpers.hs +++ b/integration/test/SetupHelpers.hs @@ -106,7 +106,7 @@ addFullSearchFor domains val = fullSearchWithAll :: ServiceOverrides fullSearchWithAll = def - { dbBrig = \val -> do + { brigCfg = \val -> do ownDomain <- asString =<< val %. "optSettings.setFederationDomain" env <- ask let remoteDomains = List.delete ownDomain $ [env.domain1, env.domain2] <> env.dynamicDomains @@ -120,9 +120,9 @@ withFederatingBackendsAllowDynamic n k = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig} + [ def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig} ] $ \dynDomains -> do domains@[domainA, domainB, domainC] <- pure dynDomains diff --git a/integration/test/Test/AssetDownload.hs b/integration/test/Test/AssetDownload.hs index e9d7b968b4c..c595c84f0e7 100644 --- a/integration/test/Test/AssetDownload.hs +++ b/integration/test/Test/AssetDownload.hs @@ -15,7 +15,7 @@ testDownloadAsset = do resp.status `shouldMatchInt` 201 resp.json %. "key" - bindResponse (downloadAsset user user key id) $ \resp -> do + bindResponse (downloadAsset user user key "nginz-https.example.com" id) $ \resp -> do resp.status `shouldMatchInt` 200 assertBool ("Expect 'Hello World!' as text asset content. Got: " ++ show resp.body) @@ -27,37 +27,53 @@ testDownloadAssetMultiIngressS3DownloadUrl = do -- multi-ingress disabled key <- doUploadAsset user - checkAssetDownload user key - withModifiedService Cargohold modifyConfig $ \_ -> do - -- multi-ingress enabled - key' <- doUploadAsset user - checkAssetDownload user key' - where - checkAssetDownload :: HasCallStack => Value -> Value -> App () - checkAssetDownload user key = withModifiedService Cargohold modifyConfig $ \_ -> do - bindResponse (downloadAsset user user key noRedirects) $ \resp -> do - resp.status `shouldMatchInt` 404 - bindResponse (downloadAsset' user user key "red.example.com" noRedirects) $ \resp -> do - resp.status `shouldMatchInt` 302 - locationHeaderHost resp `shouldMatch` "s3-download.red.example.com" - bindResponse (downloadAsset' user user key "green.example.com" noRedirects) $ \resp -> do - resp.status `shouldMatchInt` 302 - locationHeaderHost resp `shouldMatch` "s3-download.green.example.com" - bindResponse (downloadAsset' user user key "unknown.example.com" noRedirects) $ \resp -> do - resp.status `shouldMatchInt` 404 - resp.json %. "label" `shouldMatch` "not-found" + bindResponse (downloadAsset user user key "nginz-https.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 302 + + bindResponse (downloadAsset user user key "red.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 302 + + bindResponse (downloadAsset user user key "green.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 302 + + bindResponse (downloadAsset user user key "unknown.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 302 + + -- multi-ingress enabled + withModifiedBackend modifyConfig $ \domain -> do + user' <- randomUser domain def + key' <- doUploadAsset user' + bindResponse (downloadAsset user' user' key' "nginz-https.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 404 + resp.json %. "label" `shouldMatch` "not-found" + + bindResponse (downloadAsset user' user' key' "red.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 302 + locationHeaderHost resp `shouldMatch` "s3-download.red.example.com" + + bindResponse (downloadAsset user' user' key' "green.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 302 + locationHeaderHost resp `shouldMatch` "s3-download.green.example.com" + + bindResponse (downloadAsset user' user' key' "unknown.example.com" noRedirects) $ \resp -> do + resp.status `shouldMatchInt` 404 + resp.json %. "label" `shouldMatch` "not-found" + where noRedirects :: HTTP.Request -> HTTP.Request noRedirects req = (req {redirectCount = 0}) - modifyConfig :: Value -> App Value + modifyConfig :: ServiceOverrides modifyConfig = - setField "aws.multiIngress" $ - object - [ "red.example.com" .= "http://s3-download.red.example.com", - "green.example.com" .= "http://s3-download.green.example.com" - ] + def + { cargoholdCfg = + setField "aws.multiIngress" $ + object + [ "red.example.com" .= "http://s3-download.red.example.com", + "green.example.com" .= "http://s3-download.green.example.com" + ] + } doUploadAsset :: HasCallStack => Value -> App Value doUploadAsset user = bindResponse (uploadAsset user) $ \resp -> do diff --git a/integration/test/Test/Brig.hs b/integration/test/Test/Brig.hs index 3380d8400ca..a229f8cfd6d 100644 --- a/integration/test/Test/Brig.hs +++ b/integration/test/Test/Brig.hs @@ -30,11 +30,13 @@ testCrudFederationRemotes :: HasCallStack => App () testCrudFederationRemotes = do otherDomain <- asString OtherDomain let overrides = - ( setField - "optSettings.setFederationDomainConfigs" - [object ["domain" .= otherDomain, "search_policy" .= "full_search"]] - ) - withModifiedService Brig overrides $ \_ -> do + def + { brigCfg = + setField + "optSettings.setFederationDomainConfigs" + [object ["domain" .= otherDomain, "search_policy" .= "full_search"]] + } + withModifiedBackend overrides $ \ownDomain -> do let parseFedConns :: HasCallStack => Response -> App [Value] parseFedConns resp = -- Pick out the list of federation domain configs @@ -45,38 +47,38 @@ testCrudFederationRemotes = do addOnce :: (MakesValue fedConn, Ord fedConn2, ToJSON fedConn2, MakesValue fedConn2, HasCallStack) => fedConn -> [fedConn2] -> App () addOnce fedConn want = do - bindResponse (Internal.createFedConn OwnDomain fedConn) $ \res -> do + bindResponse (Internal.createFedConn ownDomain fedConn) $ \res -> do addFailureContext ("res = " <> show res) $ res.status `shouldMatchInt` 200 - res2 <- parseFedConns =<< Internal.readFedConns OwnDomain + res2 <- parseFedConns =<< Internal.readFedConns ownDomain sort res2 `shouldMatch` sort want addFail :: HasCallStack => MakesValue fedConn => fedConn -> App () addFail fedConn = do - bindResponse (Internal.createFedConn' OwnDomain fedConn) $ \res -> do + bindResponse (Internal.createFedConn' ownDomain fedConn) $ \res -> do addFailureContext ("res = " <> show res) $ res.status `shouldMatchInt` 533 deleteOnce :: (Ord fedConn, ToJSON fedConn, MakesValue fedConn) => String -> [fedConn] -> App () deleteOnce domain want = do - bindResponse (Internal.deleteFedConn OwnDomain domain) $ \res -> do + bindResponse (Internal.deleteFedConn ownDomain domain) $ \res -> do addFailureContext ("res = " <> show res) $ res.status `shouldMatchInt` 200 - res2 <- parseFedConns =<< Internal.readFedConns OwnDomain + res2 <- parseFedConns =<< Internal.readFedConns ownDomain sort res2 `shouldMatch` sort want deleteFail :: HasCallStack => String -> App () deleteFail del = do - bindResponse (Internal.deleteFedConn' OwnDomain del) $ \res -> do + bindResponse (Internal.deleteFedConn' ownDomain del) $ \res -> do addFailureContext ("res = " <> show res) $ res.status `shouldMatchInt` 533 updateOnce :: (MakesValue fedConn, Ord fedConn2, ToJSON fedConn2, MakesValue fedConn2, HasCallStack) => String -> fedConn -> [fedConn2] -> App () updateOnce domain fedConn want = do - bindResponse (Internal.updateFedConn OwnDomain domain fedConn) $ \res -> do + bindResponse (Internal.updateFedConn ownDomain domain fedConn) $ \res -> do addFailureContext ("res = " <> show res) $ res.status `shouldMatchInt` 200 - res2 <- parseFedConns =<< Internal.readFedConns OwnDomain + res2 <- parseFedConns =<< Internal.readFedConns ownDomain sort res2 `shouldMatch` sort want updateFail :: (MakesValue fedConn, HasCallStack) => String -> fedConn -> App () updateFail domain fedConn = do - bindResponse (Internal.updateFedConn' OwnDomain domain fedConn) $ \res -> do + bindResponse (Internal.updateFedConn' ownDomain domain fedConn) $ \res -> do addFailureContext ("res = " <> show res) $ res.status `shouldMatchInt` 533 dom1 :: String <- (<> ".example.com") . UUID.toString <$> liftIO UUID.nextRandom @@ -93,8 +95,8 @@ testCrudFederationRemotes = do remote1J <- make remote1 remote1J' <- make remote1' - resetFedConns OwnDomain - cfgRemotes <- parseFedConns =<< Internal.readFedConns OwnDomain + resetFedConns ownDomain + cfgRemotes <- parseFedConns =<< Internal.readFedConns ownDomain cfgRemotes `shouldMatch` [cfgRemotesExpect] -- entries present in the config file can be idempotently added if identical, but cannot be -- updated, deleted or updated. @@ -185,7 +187,7 @@ testRemoteUserSearch = do setField "optSettings.setFederationStrategy" "allowDynamic" >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) - startDynamicBackends [def {dbBrig = overrides}, def {dbBrig = overrides}] $ \dynDomains -> do + startDynamicBackends [def {brigCfg = overrides}, def {brigCfg = overrides}] $ \dynDomains -> do domains@[d1, d2] <- pure dynDomains connectAllDomainsAndWaitToSync 1 domains [u1, u2] <- createAndConnectUsers [d1, d2] diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index 3d38b417307..7e11aff0700 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -18,7 +18,7 @@ import Testlib.Prelude testDynamicBackendsFullyConnectedWhenAllowAll :: HasCallStack => App () testDynamicBackendsFullyConnectedWhenAllowAll = do let overrides = - def {dbBrig = setField "optSettings.setFederationStrategy" "allowAll"} + def {brigCfg = setField "optSettings.setFederationStrategy" "allowAll"} <> fullSearchWithAll startDynamicBackends [overrides, overrides, overrides] $ \dynDomains -> do [domainA, domainB, domainC] <- pure dynDomains @@ -41,7 +41,7 @@ testDynamicBackendsNotFederating :: HasCallStack => App () testDynamicBackendsNotFederating = do let overrides = def - { dbBrig = + { brigCfg = setField "optSettings.setFederationStrategy" "allowNone" } startDynamicBackends [overrides, overrides, overrides] $ @@ -62,9 +62,9 @@ testDynamicBackendsFullyConnectedWhenAllowDynamic = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = overrides}, - def {dbBrig = overrides}, - def {dbBrig = overrides} + [ def {brigCfg = overrides}, + def {brigCfg = overrides}, + def {brigCfg = overrides} ] $ \dynDomains -> do domains@[domainA, domainB, domainC] <- pure dynDomains @@ -86,7 +86,7 @@ testDynamicBackendsNotFullyConnected :: HasCallStack => App () testDynamicBackendsNotFullyConnected = do let overrides = def - { dbBrig = + { brigCfg = setField "optSettings.setFederationStrategy" "allowDynamic" >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) @@ -140,9 +140,9 @@ testCreateConversationFullyConnected = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig} + [ def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig} ] $ \dynDomains -> do domains@[domainA, domainB, domainC] <- pure dynDomains @@ -158,9 +158,9 @@ testCreateConversationNonFullyConnected = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig} + [ def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig} ] $ \dynDomains -> do domains@[domainA, domainB, domainC] <- pure dynDomains @@ -181,8 +181,8 @@ testDefederationGroupConversation = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig} + [ def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig} ] $ \dynDomains -> do domains@[domainA, domainB] <- pure dynDomains @@ -247,8 +247,8 @@ testDefederationOneOnOne = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig} + [ def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig} ] $ \dynDomains -> do domains@[domainA, domainB] <- pure dynDomains @@ -342,7 +342,7 @@ testAddMembersNonFullyConnectedProteus = do testConvWithUnreachableRemoteUsers :: HasCallStack => App () testConvWithUnreachableRemoteUsers = do let overrides = - def {dbBrig = setField "optSettings.setFederationStrategy" "allowAll"} + def {brigCfg = setField "optSettings.setFederationStrategy" "allowAll"} <> fullSearchWithAll ([alice, alex, bob, charlie, dylan], domains) <- startDynamicBackends [overrides, overrides] $ \domains -> do @@ -363,7 +363,7 @@ testConvWithUnreachableRemoteUsers = do testAddReachableWithUnreachableRemoteUsers :: HasCallStack => App () testAddReachableWithUnreachableRemoteUsers = do let overrides = - def {dbBrig = setField "optSettings.setFederationStrategy" "allowAll"} + def {brigCfg = setField "optSettings.setFederationStrategy" "allowAll"} <> fullSearchWithAll ([alex, bob], conv, domains) <- startDynamicBackends [overrides, overrides] $ \domains -> do @@ -389,7 +389,7 @@ testAddReachableWithUnreachableRemoteUsers = do testAddUnreachable :: HasCallStack => App () testAddUnreachable = do let overrides = - def {dbBrig = setField "optSettings.setFederationStrategy" "allowAll"} + def {brigCfg = setField "optSettings.setFederationStrategy" "allowAll"} <> fullSearchWithAll ([alex, charlie], [charlieDomain, dylanDomain], conv) <- startDynamicBackends [overrides, overrides] $ \domains -> do @@ -412,7 +412,7 @@ testAddingUserNonFullyConnectedFederation :: HasCallStack => App () testAddingUserNonFullyConnectedFederation = do let overrides = def - { dbBrig = + { brigCfg = setField "optSettings.setFederationStrategy" "allowDynamic" >=> removeField "optSettings.setFederationDomainConfigs" } diff --git a/integration/test/Test/Defederation.hs b/integration/test/Test/Defederation.hs index 73399d96280..513efe535f6 100644 --- a/integration/test/Test/Defederation.hs +++ b/integration/test/Test/Defederation.hs @@ -33,9 +33,9 @@ testDefederationNonFullyConnectedGraph = do >=> removeField "optSettings.setFederationDomainConfigs" >=> setField "optSettings.setFederationDomainConfigsUpdateFreq" (Aeson.Number 1) startDynamicBackends - [ def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig}, - def {dbBrig = setFederationConfig} + [ def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig}, + def {brigCfg = setFederationConfig} ] $ \dynDomains -> do domains@[domainA, domainB, domainC] <- pure dynDomains diff --git a/integration/test/Test/Demo.hs b/integration/test/Test/Demo.hs index 45d240c1548..69d3cf1b0c4 100644 --- a/integration/test/Test/Demo.hs +++ b/integration/test/Test/Demo.hs @@ -5,9 +5,7 @@ import API.Brig qualified as Public import API.BrigInternal qualified as Internal import API.GalleyInternal qualified as Internal import API.Nginz qualified as Nginz -import Control.Monad.Codensity import Control.Monad.Cont -import Data.Map qualified as Map import GHC.Stack import SetupHelpers import Testlib.Prelude @@ -34,11 +32,10 @@ testDeleteUnknownClient = do testModifiedBrig :: HasCallStack => App () testModifiedBrig = do - withModifiedService - Brig - (setField "optSettings.setFederationDomain" "overridden.example.com") - $ \_domain -> do - bindResponse (Public.getAPIVersion OwnDomain) + withModifiedBackend + (def {brigCfg = setField "optSettings.setFederationDomain" "overridden.example.com"}) + $ \domain -> do + bindResponse (Public.getAPIVersion domain) $ \resp -> do resp.status `shouldMatchInt` 200 (resp.json %. "domain") `shouldMatch` "overridden.example.com" @@ -47,54 +44,56 @@ testModifiedGalley :: HasCallStack => App () testModifiedGalley = do (_user, tid) <- createTeam OwnDomain - let getFeatureStatus = do - bindResponse (Internal.getTeamFeature "searchVisibility" tid) $ \res -> do + let getFeatureStatus :: (MakesValue domain) => domain -> String -> App Value + getFeatureStatus domain team = do + bindResponse (Internal.getTeamFeature domain "searchVisibility" team) $ \res -> do res.status `shouldMatchInt` 200 res.json %. "status" - do - getFeatureStatus `shouldMatch` "disabled" + getFeatureStatus OwnDomain tid `shouldMatch` "disabled" - withModifiedService - Galley - (setField "settings.featureFlags.teamSearchVisibility" "enabled-by-default") - $ \_ -> getFeatureStatus `shouldMatch` "enabled" + withModifiedBackend + def {galleyCfg = setField "settings.featureFlags.teamSearchVisibility" "enabled-by-default"} + $ \domain -> do + (_user, tid') <- createTeam domain + getFeatureStatus domain tid' `shouldMatch` "enabled" testModifiedCannon :: HasCallStack => App () testModifiedCannon = do - withModifiedService Cannon pure $ \_ -> pure () + withModifiedBackend def $ \_ -> pure () testModifiedGundeck :: HasCallStack => App () testModifiedGundeck = do - withModifiedService Gundeck pure $ \_ -> pure () + withModifiedBackend def $ \_ -> pure () testModifiedCargohold :: HasCallStack => App () testModifiedCargohold = do - withModifiedService Cargohold pure $ \_ -> pure () + withModifiedBackend def $ \_ -> pure () testModifiedSpar :: HasCallStack => App () testModifiedSpar = do - withModifiedService Spar pure $ \_ -> pure () + withModifiedBackend def $ \_ -> pure () testModifiedServices :: HasCallStack => App () testModifiedServices = do let serviceMap = - Map.fromList - [ (Brig, setField "optSettings.setFederationDomain" "overridden.example.com"), - (Galley, setField "settings.featureFlags.teamSearchVisibility" "enabled-by-default") - ] - runCodensity (withModifiedServices serviceMap) $ \_domain -> do - (_user, tid) <- createTeam OwnDomain - bindResponse (Internal.getTeamFeature "searchVisibility" tid) $ \res -> do + def + { brigCfg = setField "optSettings.setFederationDomain" "overridden.example.com", + galleyCfg = setField "settings.featureFlags.teamSearchVisibility" "enabled-by-default" + } + + withModifiedBackend serviceMap $ \domain -> do + (_user, tid) <- createTeam domain + bindResponse (Internal.getTeamFeature domain "searchVisibility" tid) $ \res -> do res.status `shouldMatchInt` 200 res.json %. "status" `shouldMatch` "enabled" - bindResponse (Public.getAPIVersion OwnDomain) $ + bindResponse (Public.getAPIVersion domain) $ \resp -> do resp.status `shouldMatchInt` 200 (resp.json %. "domain") `shouldMatch` "overridden.example.com" - bindResponse (Nginz.getSystemSettingsUnAuthorized OwnDomain) $ + bindResponse (Nginz.getSystemSettingsUnAuthorized domain) $ \resp -> do resp.status `shouldMatchInt` 200 resp.json %. "setRestrictUserCreation" `shouldMatch` False diff --git a/integration/test/Test/Federation.hs b/integration/test/Test/Federation.hs index d7cf2c3a1e3..a16b3e9077f 100644 --- a/integration/test/Test/Federation.hs +++ b/integration/test/Test/Federation.hs @@ -30,7 +30,7 @@ testNotificationsForOfflineBackends = do -- We call it 'downBackend' because it is down for the most of this test -- except for setup and assertions. Perhaps there is a better name. runCodensity (acquireResources 1 resourcePool) $ \[downBackend] -> do - (downUser1, downClient1, downUser2, upBackendConv, downBackendConv) <- runCodensity (startDynamicBackend downBackend mempty mempty) $ \_ -> do + (downUser1, downClient1, downUser2, upBackendConv, downBackendConv) <- runCodensity (startDynamicBackend downBackend mempty) $ \_ -> do downUser1 <- randomUser downBackend.berDomain def downUser2 <- randomUser downBackend.berDomain def downClient1 <- objId $ bindResponse (API.addClient downUser1 def) $ getJSON 201 @@ -103,7 +103,7 @@ testNotificationsForOfflineBackends = do delUserDeletedNotif <- nPayload $ awaitNotification otherUser otherClient (Just newMsgNotif) 1 isDeleteUserNotif objQid delUserDeletedNotif `shouldMatch` objQid delUser - runCodensity (startDynamicBackend downBackend mempty mempty) $ \_ -> do + runCodensity (startDynamicBackend downBackend mempty) $ \_ -> do newMsgNotif <- awaitNotification downUser1 downClient1 noValue 5 isNewMessageNotif newMsgNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv newMsgNotif %. "payload.0.data.text" `shouldMatchBase64` "success message for down user" diff --git a/integration/test/Testlib/App.hs b/integration/test/Testlib/App.hs index a7018959898..b219f3da9e1 100644 --- a/integration/test/Testlib/App.hs +++ b/integration/test/Testlib/App.hs @@ -12,6 +12,7 @@ import GHC.Stack (HasCallStack) import System.FilePath import Testlib.Env import Testlib.JSON +import Testlib.Service import Testlib.Types import Prelude diff --git a/integration/test/Testlib/Cannon.hs b/integration/test/Testlib/Cannon.hs index 4fefd3dfe3f..162dd58fb0f 100644 --- a/integration/test/Testlib/Cannon.hs +++ b/integration/test/Testlib/Cannon.hs @@ -68,6 +68,7 @@ import Testlib.Env import Testlib.HTTP import Testlib.JSON import Testlib.Printing +import Testlib.Service import Testlib.Types import Prelude diff --git a/integration/test/Testlib/Env.hs b/integration/test/Testlib/Env.hs index d0a6a541850..1390ceed213 100644 --- a/integration/test/Testlib/Env.hs +++ b/integration/test/Testlib/Env.hs @@ -23,6 +23,7 @@ import System.IO import System.IO.Temp import Testlib.Prekeys import Testlib.ResourcePool +import Testlib.Service import Prelude -- | Initialised once per test. @@ -107,42 +108,6 @@ data HostPort = HostPort instance FromJSON HostPort -data Service = Brig | Galley | Cannon | Gundeck | Cargohold | Nginz | Spar | BackgroundWorker | Stern | FederatorInternal - deriving - ( Show, - Eq, - Ord, - Enum, - Bounded - ) - -serviceName :: Service -> String -serviceName = \case - Brig -> "brig" - Galley -> "galley" - Cannon -> "cannon" - Gundeck -> "gundeck" - Cargohold -> "cargohold" - Nginz -> "nginz" - Spar -> "spar" - BackgroundWorker -> "backgroundWorker" - Stern -> "stern" - FederatorInternal -> "federator" - --- | Converts the service name to kebab-case. -configName :: Service -> String -configName = \case - Brig -> "brig" - Galley -> "galley" - Cannon -> "cannon" - Gundeck -> "gundeck" - Cargohold -> "cargohold" - Nginz -> "nginz" - Spar -> "spar" - BackgroundWorker -> "background-worker" - Stern -> "stern" - FederatorInternal -> "federator" - serviceHostPort :: ServiceMap -> Service -> HostPort serviceHostPort m Brig = m.brig serviceHostPort m Galley = m.galley diff --git a/integration/test/Testlib/HTTP.hs b/integration/test/Testlib/HTTP.hs index 5dde2f9a187..e07bac156a9 100644 --- a/integration/test/Testlib/HTTP.hs +++ b/integration/test/Testlib/HTTP.hs @@ -25,6 +25,7 @@ import Network.URI (URI (..), URIAuth (..), parseURI) import Testlib.Assertions import Testlib.Env import Testlib.JSON +import Testlib.Service import Testlib.Types import Prelude diff --git a/integration/test/Testlib/ModService.hs b/integration/test/Testlib/ModService.hs index 44606a6f238..5b2ce20dc5e 100644 --- a/integration/test/Testlib/ModService.hs +++ b/integration/test/Testlib/ModService.hs @@ -1,15 +1,13 @@ {-# LANGUAGE OverloadedStrings #-} module Testlib.ModService - ( withModifiedService, - withModifiedServices, + ( withModifiedBackend, startDynamicBackend, startDynamicBackends, traverseConcurrentlyCodensity, ) where -import Control.Applicative ((<|>)) import Control.Concurrent import Control.Concurrent.Async import Control.Exception (finally) @@ -20,8 +18,7 @@ import Control.Monad.Extra import Control.Monad.Reader import Control.Retry (fibonacciBackoff, limitRetriesByCumulativeDelay, retrying) import Data.Aeson hiding ((.=)) -import Data.Attoparsec.ByteString.Char8 -import Data.Either.Extra (eitherToMaybe) +import Data.Default import Data.Foldable import Data.Function import Data.Functor @@ -37,11 +34,9 @@ import Data.Word (Word16) import Data.Yaml qualified as Yaml import GHC.Stack import Network.HTTP.Client qualified as HTTP -import Network.Socket qualified as N import System.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, listDirectory, removeDirectoryRecursive, removeFile) import System.FilePath import System.IO -import System.IO.Error qualified as Error import System.IO.Temp (createTempDirectory, writeTempFile) import System.Posix (killProcess, signalProcess) import System.Process (CreateProcess (..), ProcessHandle, StdStream (..), createProcess, getPid, proc, terminateProcess, waitForProcess) @@ -52,17 +47,14 @@ import Testlib.HTTP import Testlib.JSON import Testlib.Printing import Testlib.ResourcePool +import Testlib.Service import Testlib.Types import Text.RawString.QQ import Prelude -withModifiedService :: - Service -> - -- | function that edits the config - (Value -> App Value) -> - (String -> App a) -> - App a -withModifiedService srv modConfig = runCodensity $ withModifiedServices (Map.singleton srv modConfig) +withModifiedBackend :: HasCallStack => ServiceOverrides -> (HasCallStack => String -> App a) -> App a +withModifiedBackend overrides k = + startDynamicBackends [overrides] (\domains -> k (head domains)) copyDirectoryRecursively :: FilePath -> FilePath -> IO () copyDirectoryRecursively from to = do @@ -145,169 +137,108 @@ startDynamicBackends beOverrides k = when (Prelude.length beOverrides > 3) $ lift $ failApp "Too many backends. Currently only 3 are supported." pool <- asks (.resourcePool) resources <- acquireResources (Prelude.length beOverrides) pool - void $ traverseConcurrentlyCodensity (\(res, overrides) -> startDynamicBackend res mempty overrides) (zip resources beOverrides) + void $ traverseConcurrentlyCodensity (uncurry startDynamicBackend) (zip resources beOverrides) pure $ map (.berDomain) resources ) k -startDynamicBackend :: HasCallStack => BackendResource -> Map.Map Service Word16 -> ServiceOverrides -> Codensity App (Env -> Env) -startDynamicBackend resource staticPorts beOverrides = do - defDomain <- asks (.domain1) - let services = - withOverrides beOverrides $ - Map.mapWithKey - ( \srv conf -> - conf - >=> setKeyspace srv - >=> setEsIndex srv - >=> setFederationSettings srv - >=> setAwsConfigs srv - >=> setLogLevel srv - ) - defaultServiceOverridesToMap - startBackend - resource.berDomain - staticPorts - (Just resource.berNginzSslPort) - (Just setFederatorConfig) - services - ( \ports sm -> do - let templateBackend = fromMaybe (error "no default domain found in backends") $ sm & Map.lookup defDomain - in Map.insert resource.berDomain (setFederatorPorts resource $ updateServiceMap ports templateBackend) sm - ) +startDynamicBackend :: HasCallStack => BackendResource -> ServiceOverrides -> Codensity App (Env -> Env) +startDynamicBackend resource beOverrides = do + let overrides = + mconcat + [ setKeyspace, + setEsIndex, + setFederationSettings, + setAwsConfigs, + setLogLevel, + beOverrides + ] + startBackend resource overrides allServices where - setAwsConfigs :: Service -> Value -> App Value - setAwsConfigs = \case - Brig -> - setField "aws.userJournalQueue" resource.berAwsUserJournalQueue - >=> setField "aws.prekeyTable" resource.berAwsPrekeyTable - >=> setField "internalEvents.queueName" resource.berBrigInternalEvents - >=> setField "emailSMS.email.sesQueue" resource.berEmailSMSSesQueue - >=> setField "emailSMS.general.emailSender" resource.berEmailSMSEmailSender - Cargohold -> setField "aws.s3Bucket" resource.berAwsS3Bucket - Gundeck -> setField "aws.queueName" resource.berAwsQueueName - Galley -> - setField "journal.queueName" resource.berGalleyJournal - >=> setField "rabbitmq.vHost" resource.berVHost - BackgroundWorker -> setField "rabbitmq.vHost" resource.berVHost - _ -> pure - - setFederationSettings :: Service -> Value -> App Value + setAwsConfigs :: ServiceOverrides + setAwsConfigs = + def + { brigCfg = + setField "aws.userJournalQueue" resource.berAwsUserJournalQueue + >=> setField "aws.prekeyTable" resource.berAwsPrekeyTable + >=> setField "internalEvents.queueName" resource.berBrigInternalEvents + >=> setField "emailSMS.email.sesQueue" resource.berEmailSMSSesQueue + >=> setField "emailSMS.general.emailSender" resource.berEmailSMSEmailSender, + cargoholdCfg = setField "aws.s3Bucket" resource.berAwsS3Bucket, + gundeckCfg = setField "aws.queueName" resource.berAwsQueueName, + galleyCfg = setField "journal.queueName" resource.berGalleyJournal + } + + setFederationSettings :: ServiceOverrides setFederationSettings = - \case - Brig -> - setField "optSettings.setFederationDomain" resource.berDomain - >=> setField "optSettings.setFederationDomainConfigs" ([] :: [Value]) - >=> setField "federatorInternal.port" resource.berFederatorInternal - >=> setField "federatorInternal.host" ("127.0.0.1" :: String) - >=> setField "rabbitmq.vHost" resource.berVHost - Cargohold -> - setField "settings.federationDomain" resource.berDomain - >=> setField "federator.host" ("127.0.0.1" :: String) - >=> setField "federator.port" resource.berFederatorInternal - Galley -> - setField "settings.federationDomain" resource.berDomain - >=> setField "settings.featureFlags.classifiedDomains.config.domains" [resource.berDomain] - >=> setField "federator.host" ("127.0.0.1" :: String) - >=> setField "federator.port" resource.berFederatorInternal - >=> setField "rabbitmq.vHost" resource.berVHost - Gundeck -> setField "settings.federationDomain" resource.berDomain - BackgroundWorker -> - setField "federatorInternal.port" resource.berFederatorInternal - >=> setField "federatorInternal.host" ("127.0.0.1" :: String) - >=> setField "rabbitmq.vHost" resource.berVHost - _ -> pure - - setFederatorConfig :: Value -> App Value - setFederatorConfig = - setField "federatorInternal.port" resource.berFederatorInternal - >=> setField "federatorExternal.port" resource.berFederatorExternal - >=> setField "optSettings.setFederationDomain" resource.berDomain - - setKeyspace :: Service -> Value -> App Value - setKeyspace = \case - Galley -> setField "cassandra.keyspace" resource.berGalleyKeyspace - Brig -> setField "cassandra.keyspace" resource.berBrigKeyspace - Spar -> setField "cassandra.keyspace" resource.berSparKeyspace - Gundeck -> setField "cassandra.keyspace" resource.berGundeckKeyspace - -- other services do not have a DB - _ -> pure - - setEsIndex :: Service -> Value -> App Value - setEsIndex = \case - Brig -> setField "elasticsearch.index" resource.berElasticsearchIndex - -- other services do not have an ES index - _ -> pure - - setLogLevel :: Service -> Value -> App Value - setLogLevel = \case - Spar -> setField "saml.logLevel" ("Warn" :: String) - _ -> setField "logLevel" ("Warn" :: String) - -setFederatorPorts :: BackendResource -> ServiceMap -> ServiceMap -setFederatorPorts resource sm = - sm - { federatorInternal = sm.federatorInternal {host = "127.0.0.1", port = resource.berFederatorInternal}, - federatorExternal = sm.federatorExternal {host = "127.0.0.1", port = resource.berFederatorExternal} - } - -withModifiedServices :: Map.Map Service (Value -> App Value) -> Codensity App String -withModifiedServices services = do - domain <- lift $ asks (.domain1) - void $ - startBackend domain mempty Nothing Nothing services (\ports -> Map.adjust (updateServiceMap ports) domain) - pure domain - -updateServiceMap :: Map.Map Service Word16 -> ServiceMap -> ServiceMap -updateServiceMap ports serviceMap = - Map.foldrWithKey - ( \srv newPort sm -> - case srv of - Brig -> sm {brig = sm.brig {host = "127.0.0.1", port = newPort}} - Galley -> sm {galley = sm.galley {host = "127.0.0.1", port = newPort}} - Cannon -> sm {cannon = sm.cannon {host = "127.0.0.1", port = newPort}} - Gundeck -> sm {gundeck = sm.gundeck {host = "127.0.0.1", port = newPort}} - Cargohold -> sm {cargohold = sm.cargohold {host = "127.0.0.1", port = newPort}} - Nginz -> sm {nginz = sm.nginz {host = "127.0.0.1", port = newPort}} - Spar -> sm {spar = sm.spar {host = "127.0.0.1", port = newPort}} - BackgroundWorker -> sm {backgroundWorker = sm.backgroundWorker {host = "127.0.0.1", port = newPort}} - Stern -> sm {stern = sm.stern {host = "127.0.0.1", port = newPort}} - FederatorInternal -> sm {federatorInternal = sm.federatorInternal {host = "127.0.0.1", port = newPort}} - ) - serviceMap - ports + def + { brigCfg = + setField "optSettings.setFederationDomain" resource.berDomain + >=> setField "optSettings.setFederationDomainConfigs" ([] :: [Value]) + >=> setField "federatorInternal.port" resource.berFederatorInternal + >=> setField "federatorInternal.host" ("127.0.0.1" :: String) + >=> setField "rabbitmq.vHost" resource.berVHost, + cargoholdCfg = + setField "settings.federationDomain" resource.berDomain + >=> setField "federator.host" ("127.0.0.1" :: String) + >=> setField "federator.port" resource.berFederatorInternal, + galleyCfg = + setField "settings.federationDomain" resource.berDomain + >=> setField "settings.featureFlags.classifiedDomains.config.domains" [resource.berDomain] + >=> setField "federator.host" ("127.0.0.1" :: String) + >=> setField "federator.port" resource.berFederatorInternal + >=> setField "rabbitmq.vHost" resource.berVHost, + gundeckCfg = setField "settings.federationDomain" resource.berDomain, + backgroundWorkerCfg = + setField "federatorInternal.port" resource.berFederatorInternal + >=> setField "federatorInternal.host" ("127.0.0.1" :: String) + >=> setField "rabbitmq.vHost" resource.berVHost, + federatorInternalCfg = + setField "federatorInternal.port" resource.berFederatorInternal + >=> setField "federatorExternal.port" resource.berFederatorExternal + >=> setField "optSettings.setFederationDomain" resource.berDomain + } + + setKeyspace :: ServiceOverrides + setKeyspace = + def + { galleyCfg = setField "cassandra.keyspace" resource.berGalleyKeyspace, + brigCfg = setField "cassandra.keyspace" resource.berBrigKeyspace, + sparCfg = setField "cassandra.keyspace" resource.berSparKeyspace, + gundeckCfg = setField "cassandra.keyspace" resource.berGundeckKeyspace + } + + setEsIndex :: ServiceOverrides + setEsIndex = + def + { brigCfg = setField "elasticsearch.index" resource.berElasticsearchIndex + } + + setLogLevel :: ServiceOverrides + setLogLevel = + def + { sparCfg = setField "saml.logLevel" ("Warn" :: String), + brigCfg = setField "logLevel" ("Warn" :: String), + cannonCfg = setField "logLevel" ("Warn" :: String), + cargoholdCfg = setField "logLevel" ("Warn" :: String), + galleyCfg = setField "logLevel" ("Warn" :: String), + gundeckCfg = setField "logLevel" ("Warn" :: String), + nginzCfg = setField "logLevel" ("Warn" :: String), + backgroundWorkerCfg = setField "logLevel" ("Warn" :: String), + sternCfg = setField "logLevel" ("Warn" :: String), + federatorInternalCfg = setField "logLevel" ("Warn" :: String) + } startBackend :: HasCallStack => - String -> - Map.Map Service Word16 -> - Maybe Word16 -> - Maybe (Value -> App Value) -> - Map.Map Service (Value -> App Value) -> - (Map.Map Service Word16 -> Map.Map String ServiceMap -> Map.Map String ServiceMap) -> + BackendResource -> + ServiceOverrides -> + [Service] -> Codensity App (Env -> Env) -startBackend domain staticPorts nginzSslPort mFederatorOverrides services modifyBackends = do - -- We already close sockets before starting any services that want to bind to - -- it, because if done later some services might already connect to the - -- dummy sockets (e.g. federator connecting to nginz) and blocking the ports - -- from being bindable - ports <- - Map.traverseWithKey - ( \srv _ -> - case Map.lookup srv staticPorts of - Just port -> pure port - Nothing -> do - (port, sock) <- liftIO openFreePort - liftIO $ N.close sock - pure (fromIntegral port) - ) - services - nginzHttp2Port <- liftIO $ do - (port, sock) <- openFreePort - N.close sock - pure (fromIntegral port) +startBackend resource overrides services = do + let domain = resource.berDomain - let updateServiceMapInConfig :: Maybe Service -> Value -> App Value + let updateServiceMapInConfig :: Service -> Value -> App Value updateServiceMapInConfig forSrv config = foldlM ( \c (srv, port) -> do @@ -323,7 +254,7 @@ startBackend domain staticPorts nginzSslPort mFederatorOverrides services modify ) ) case (srv, forSrv) of - (Spar, Just Spar) -> do + (Spar, Spar) -> do overridden -- FUTUREWORK: override "saml.spAppUri" and "saml.spSsoUri" with correct port, too? & setField "saml.spHost" ("127.0.0.1" :: String) @@ -331,63 +262,63 @@ startBackend domain staticPorts nginzSslPort mFederatorOverrides services modify _ -> pure overridden ) config - (Map.assocs ports) - - -- close all sockets before starting the services - stopInstances <- lift $ do - fedInstance <- - case mFederatorOverrides of - Nothing -> pure [] - Just override -> - readServiceConfig' "federator" - >>= updateServiceMapInConfig Nothing - >>= override - >>= startProcess' domain "federator" - <&> (: []) - - otherInstances <- for (Map.assocs $ Map.filterWithKey (\s _ -> s /= FederatorInternal) services) $ \case - (Nginz, _) -> do + [(srv, berInternalServicePorts resource srv :: Int) | srv <- services] + + let serviceMap = + let g srv = HostPort "127.0.0.1" (berInternalServicePorts resource srv) + in ServiceMap + { brig = g Brig, + backgroundWorker = g BackgroundWorker, + cannon = g Cannon, + cargohold = g Cargohold, + federatorInternal = g FederatorInternal, + federatorExternal = HostPort "127.0.0.1" resource.berFederatorExternal, + galley = g Galley, + gundeck = g Gundeck, + nginz = g Nginz, + spar = g Spar, + -- FUTUREWORK: Set to g Proxy, when we add Proxy to spawned services + proxy = HostPort "127.0.0.1" 9087, + stern = g Stern + } + + instances <- lift $ do + for services $ \case + Nginz -> do env <- ask - sm <- maybe (failApp "the impossible in withServices happened") pure (Map.lookup domain (modifyBackends (fromIntegral <$> ports) env.serviceMap)) - port <- maybe (failApp "the impossible in withServices happened") (pure . fromIntegral) (Map.lookup Nginz ports) case env.servicesCwdBase of - Nothing -> startNginzK8s domain sm - Just _ -> startNginzLocal domain port nginzHttp2Port nginzSslPort sm - (srv, modifyConfig) -> do + Nothing -> startNginzK8s domain serviceMap + Just _ -> startNginzLocal domain resource.berNginzSslPort resource.berNginzSslPort serviceMap + srv -> do readServiceConfig srv - >>= updateServiceMapInConfig (Just srv) - >>= modifyConfig + >>= updateServiceMapInConfig srv + >>= lookupConfigOverride overrides srv >>= startProcess domain srv - let instances = fedInstance <> otherInstances - - let stopInstances = liftIO $ do - -- Running waitForProcess would hang for 30 seconds when the test suite - -- is run from within ghci, so we don't wait here. - for_ instances $ \(ph, path) -> do - terminateProcess ph - timeout 50000 (waitForProcess ph) >>= \case - Just _ -> pure () - Nothing -> do - timeout 100000 (waitForProcess ph) >>= \case - Just _ -> pure () - Nothing -> do - mPid <- getPid ph - for_ mPid (signalProcess killProcess) - void $ waitForProcess ph - whenM (doesFileExist path) $ removeFile path - whenM (doesDirectoryExist path) $ removeDirectoryRecursive path - - pure stopInstances - - let modifyEnv env = - env {serviceMap = modifyBackends (fromIntegral <$> ports) env.serviceMap} + let stopInstances = liftIO $ do + -- Running waitForProcess would hang for 30 seconds when the test suite + -- is run from within ghci, so we don't wait here. + for_ instances $ \(ph, path) -> do + terminateProcess ph + timeout 50000 (waitForProcess ph) >>= \case + Just _ -> pure () + Nothing -> do + timeout 100000 (waitForProcess ph) >>= \case + Just _ -> pure () + Nothing -> do + mPid <- getPid ph + for_ mPid (signalProcess killProcess) + void $ waitForProcess ph + whenM (doesFileExist path) $ removeFile path + whenM (doesDirectoryExist path) $ removeDirectoryRecursive path + + let modifyEnv env = env {serviceMap = Map.insert resource.berDomain serviceMap env.serviceMap} Codensity $ \action -> local modifyEnv $ do waitForService <- appToIOKleisli (waitUntilServiceUp domain) ioAction <- appToIO (action ()) liftIO $ - (mapConcurrently_ waitForService (Map.keys ports) >> ioAction) + (mapConcurrently_ waitForService services >> ioAction) `finally` stopInstances pure modifyEnv @@ -455,29 +386,6 @@ waitUntilServiceUp domain = \case unless isUp $ failApp ("Time out for service " <> show srv <> " to come up") --- | Open a TCP socket on a random free port. This is like 'warp''s --- openFreePort. --- --- Since 0.0.0.1 -openFreePort :: IO (Int, N.Socket) -openFreePort = - E.bracketOnError (N.socket N.AF_INET N.Stream N.defaultProtocol) N.close $ - \sock -> do - N.bind sock $ N.SockAddrInet 0 $ N.tupleToHostAddress (127, 0, 0, 1) - N.getSocketName sock >>= \case - N.SockAddrInet port _ -> do - pure (fromIntegral port, sock) - addr -> - E.throwIO $ - Error.mkIOError - Error.userErrorType - ( "openFreePort was unable to create socket with a SockAddrInet. " - <> "Got " - <> show addr - ) - Nothing - Nothing - startNginzK8s :: String -> ServiceMap -> App (ProcessHandle, FilePath) startNginzK8s domain sm = do tmpDir <- liftIO $ createTempDirectory "/tmp" ("nginz" <> "-" <> domain) @@ -501,8 +409,8 @@ startNginzK8s domain sm = do ph <- startNginz domain nginxConfFile "/" pure (ph, tmpDir) -startNginzLocal :: String -> Word16 -> Word16 -> Maybe Word16 -> ServiceMap -> App (ProcessHandle, FilePath) -startNginzLocal domain localPort http2Port mSslPort sm = do +startNginzLocal :: String -> Word16 -> Word16 -> ServiceMap -> App (ProcessHandle, FilePath) +startNginzLocal domain http2Port sslPort sm = do -- Create a whole temporary directory and copy all nginx's config files. -- This is necessary because nginx assumes local imports are relative to -- the location of the main configuration file. @@ -527,25 +435,6 @@ startNginzLocal domain localPort http2Port mSslPort sm = do & Text.replace "access_log /dev/stdout" "access_log /dev/null" ) - conf <- Prelude.lines <$> liftIO (readFile integrationConfFile) - let sslPortParser = do - _ <- string "listen" - _ <- many1 space - p <- many1 digit - _ <- many1 space - _ <- string "ssl" - _ <- many1 space - _ <- string "http2" - _ <- many1 space - _ <- char ';' - pure (read p :: Word16) - - let mParsedPort = - mapMaybe (eitherToMaybe . parseOnly sslPortParser . cs) conf - & (\case [] -> Nothing; (p : _) -> Just p) - - sslPort <- maybe (failApp "could not determine nginz's ssl port") pure (mSslPort <|> mParsedPort) - -- override port configuration let portConfigTemplate = [r|listen {localPort}; @@ -555,7 +444,7 @@ listen [::]:{ssl_port} ssl http2; |] let portConfig = portConfigTemplate - & Text.replace "{localPort}" (cs $ show localPort) + & Text.replace "{localPort}" (cs $ show (sm.nginz.port)) & Text.replace "{http2_port}" (cs $ show http2Port) & Text.replace "{ssl_port}" (cs $ show sslPort) diff --git a/integration/test/Testlib/Ports.hs b/integration/test/Testlib/Ports.hs new file mode 100644 index 00000000000..05f48483c2b --- /dev/null +++ b/integration/test/Testlib/Ports.hs @@ -0,0 +1,39 @@ +module Testlib.Ports where + +import Testlib.Service qualified as Service +import Prelude + +data PortNamespace + = NginzSSL + | NginzHttp2 + | FederatorExternal + | ServiceInternal Service.Service + +port :: Num a => PortNamespace -> Service.BackendName -> a +port NginzSSL bn = mkPort 8443 bn +port NginzHttp2 bn = mkPort 8099 bn +port FederatorExternal bn = mkPort 8098 bn +port (ServiceInternal Service.BackgroundWorker) bn = mkPort 8089 bn +port (ServiceInternal Service.Brig) bn = mkPort 8082 bn +port (ServiceInternal Service.Cannon) bn = mkPort 8083 bn +port (ServiceInternal Service.Cargohold) bn = mkPort 8084 bn +port (ServiceInternal Service.FederatorInternal) bn = mkPort 8097 bn +port (ServiceInternal Service.Galley) bn = mkPort 8085 bn +port (ServiceInternal Service.Gundeck) bn = mkPort 8086 bn +port (ServiceInternal Service.Nginz) bn = mkPort 8080 bn +port (ServiceInternal Service.Spar) bn = mkPort 8088 bn +port (ServiceInternal Service.Stern) bn = mkPort 8091 bn + +portForDyn :: Num a => PortNamespace -> Int -> a +portForDyn ns i = port ns (Service.DynamicBackend i) + +mkPort :: Num a => Int -> Service.BackendName -> a +mkPort basePort bn = + let i = case bn of + Service.BackendA -> 0 + Service.BackendB -> 1 + (Service.DynamicBackend k) -> 1 + k + in fromIntegral basePort + (fromIntegral i) * 1000 + +internalServicePorts :: Num a => Service.BackendName -> Service.Service -> a +internalServicePorts backend service = port (ServiceInternal service) backend diff --git a/integration/test/Testlib/Prelude.hs b/integration/test/Testlib/Prelude.hs index 05a04f366a3..ce21e7ac6f0 100644 --- a/integration/test/Testlib/Prelude.hs +++ b/integration/test/Testlib/Prelude.hs @@ -8,6 +8,7 @@ module Testlib.Prelude module Testlib.HTTP, module Testlib.JSON, module Testlib.PTest, + module Testlib.Service, module Data.Aeson, module Prelude, module Control.Applicative, @@ -120,6 +121,7 @@ import Testlib.HTTP import Testlib.JSON import Testlib.ModService import Testlib.PTest +import Testlib.Service import Testlib.Types import UnliftIO.Exception import Prelude diff --git a/integration/test/Testlib/ResourcePool.hs b/integration/test/Testlib/ResourcePool.hs index ae498c4eabf..06b05f17904 100644 --- a/integration/test/Testlib/ResourcePool.hs +++ b/integration/test/Testlib/ResourcePool.hs @@ -5,6 +5,8 @@ module Testlib.ResourcePool backendResources, createBackendResourcePool, acquireResources, + backendA, + backendB, ) where @@ -23,6 +25,8 @@ import Data.Word import GHC.Generics import GHC.Stack (HasCallStack) import System.IO +import Testlib.Ports qualified as Ports +import Testlib.Service import Prelude data ResourcePool a = ResourcePool @@ -52,7 +56,8 @@ createBackendResourcePool dynConfs = <*> newIORef resources data BackendResource = BackendResource - { berBrigKeyspace :: String, + { berName :: BackendName, + berBrigKeyspace :: String, berGalleyKeyspace :: String, berSparKeyspace :: String, berGundeckKeyspace :: String, @@ -69,9 +74,16 @@ data BackendResource = BackendResource berEmailSMSEmailSender :: String, berGalleyJournal :: String, berVHost :: String, - berNginzSslPort :: Word16 + berNginzSslPort :: Word16, + berNginzHttp2Port :: Word16, + berInternalServicePorts :: forall a. Num a => Service -> a } - deriving (Show, Eq, Ord) + +instance Eq BackendResource where + a == b = a.berName == b.berName + +instance Ord BackendResource where + a `compare` b = a.berName `compare` b.berName data DynamicBackendConfig = DynamicBackendConfig { domain :: String, @@ -85,35 +97,87 @@ backendResources :: [DynamicBackendConfig] -> Set.Set BackendResource backendResources dynConfs = (zip dynConfs [1 ..]) <&> ( \(dynConf, i) -> - BackendResource - { berBrigKeyspace = "brig_test_dyn_" <> show i, - berGalleyKeyspace = "galley_test_dyn_" <> show i, - berSparKeyspace = "spar_test_dyn_" <> show i, - berGundeckKeyspace = "gundeck_test_dyn_" <> show i, - berElasticsearchIndex = "directory_dyn_" <> show i <> "_test", - berFederatorInternal = federatorInternalPort i, - berFederatorExternal = dynConf.federatorExternalPort, - berDomain = dynConf.domain, - berAwsUserJournalQueue = "integration-user-events.fifo" <> suffix i, - berAwsPrekeyTable = "integration-brig-prekeys" <> suffix i, - berAwsS3Bucket = "dummy-bucket" <> suffix i, - berAwsQueueName = "integration-gundeck-events" <> suffix i, - berBrigInternalEvents = "integration-brig-events-internal" <> suffix i, - berEmailSMSSesQueue = "integration-brig-events" <> suffix i, - berEmailSMSEmailSender = "backend-integration" <> suffix i <> "@wire.com", - berGalleyJournal = "integration-team-events.fifo" <> suffix i, - berVHost = dynConf.domain, - berNginzSslPort = mkNginzSslPort i - } + let name = DynamicBackend i + in BackendResource + { berName = name, + berBrigKeyspace = "brig_test_dyn_" <> show i, + berGalleyKeyspace = "galley_test_dyn_" <> show i, + berSparKeyspace = "spar_test_dyn_" <> show i, + berGundeckKeyspace = "gundeck_test_dyn_" <> show i, + berElasticsearchIndex = "directory_dyn_" <> show i <> "_test", + berFederatorInternal = Ports.portForDyn (Ports.ServiceInternal FederatorInternal) i, + berFederatorExternal = dynConf.federatorExternalPort, + berDomain = dynConf.domain, + berAwsUserJournalQueue = "integration-user-events.fifo" <> suffix i, + berAwsPrekeyTable = "integration-brig-prekeys" <> suffix i, + berAwsS3Bucket = "dummy-bucket" <> suffix i, + berAwsQueueName = "integration-gundeck-events" <> suffix i, + berBrigInternalEvents = "integration-brig-events-internal" <> suffix i, + berEmailSMSSesQueue = "integration-brig-events" <> suffix i, + berEmailSMSEmailSender = "backend-integration" <> suffix i <> "@wire.com", + berGalleyJournal = "integration-team-events.fifo" <> suffix i, + berVHost = dynConf.domain, + berNginzSslPort = Ports.portForDyn Ports.NginzSSL i, + berNginzHttp2Port = Ports.portForDyn Ports.NginzHttp2 i, + berInternalServicePorts = Ports.internalServicePorts name + } ) & Set.fromList where - suffix :: Word16 -> String + suffix :: (Show a, Num a) => a -> String suffix i = show $ i + 2 - mkNginzSslPort :: Word16 -> Word16 - mkNginzSslPort i = 8443 + ((1 + i) * 1000) +backendA :: BackendResource +backendA = + BackendResource + { berName = BackendA, + berBrigKeyspace = "brig_test", + berGalleyKeyspace = "galley_test", + berSparKeyspace = "spar_test", + berGundeckKeyspace = "gundeck_test", + berElasticsearchIndex = "directory_test", + berFederatorInternal = Ports.port (Ports.ServiceInternal FederatorInternal) BackendA, + berFederatorExternal = Ports.port Ports.FederatorExternal BackendA, + berDomain = "example.com", + berAwsUserJournalQueue = "integration-user-events.fifo", + berAwsPrekeyTable = "integration-brig-prekeys", + berAwsS3Bucket = "dummy-bucket", + berAwsQueueName = "integration-gundeck-events", + berBrigInternalEvents = "integration-brig-events-internal", + berEmailSMSSesQueue = "integration-brig-events", + berEmailSMSEmailSender = "backend-integration@wire.com", + berGalleyJournal = "integration-team-events.fifo", + berVHost = "backendA", + berNginzSslPort = Ports.port Ports.NginzSSL BackendA, + berInternalServicePorts = Ports.internalServicePorts BackendA, + berNginzHttp2Port = Ports.port Ports.NginzHttp2 BackendA + } - -- Fixed internal port for federator, e.g. for dynamic backends: 1 -> 10097, 2 -> 11097, etc. - federatorInternalPort :: Num a => a -> a - federatorInternalPort i = 8097 + ((1 + i) * 1000) +backendB :: BackendResource +backendB = + BackendResource + { berName = BackendB, + berBrigKeyspace = "brig_test2", + berGalleyKeyspace = "galley_test2", + berSparKeyspace = "spar_test2", + berGundeckKeyspace = "gundeck_test2", + berElasticsearchIndex = "directory2_test", + berFederatorInternal = Ports.port (Ports.ServiceInternal FederatorInternal) BackendB, + berFederatorExternal = Ports.port Ports.FederatorExternal BackendB, + berDomain = "b.example.com", + berAwsUserJournalQueue = "integration-user-events.fifo2", + berAwsPrekeyTable = "integration-brig-prekeys2", + berAwsS3Bucket = "dummy-bucket2", + berAwsQueueName = "integration-gundeck-events2", + berBrigInternalEvents = "integration-brig-events-internal2", + berEmailSMSSesQueue = "integration-brig-events2", + berEmailSMSEmailSender = "backend-integration2@wire.com", + berGalleyJournal = "integration-team-events.fifo2", + -- FUTUREWORK: set up vhosts in dev/ci for example.com and b.example.com + -- in case we want backendA and backendB to federate with a third backend + -- (because otherwise both queues will overlap) + berVHost = "backendB", + berNginzSslPort = Ports.port Ports.NginzSSL BackendB, + berInternalServicePorts = Ports.internalServicePorts BackendB, + berNginzHttp2Port = Ports.port Ports.NginzHttp2 BackendB + } diff --git a/integration/test/Testlib/Run.hs b/integration/test/Testlib/Run.hs index b53ec16cab6..ee6b76f531c 100644 --- a/integration/test/Testlib/Run.hs +++ b/integration/test/Testlib/Run.hs @@ -23,6 +23,7 @@ import Testlib.Env import Testlib.JSON import Testlib.Options import Testlib.Printing +import Testlib.Service import Testlib.Types import Text.Printf import UnliftIO.Async diff --git a/integration/test/Testlib/RunServices.hs b/integration/test/Testlib/RunServices.hs index 3991a607b59..5f2b5a3eb34 100644 --- a/integration/test/Testlib/RunServices.hs +++ b/integration/test/Testlib/RunServices.hs @@ -4,7 +4,6 @@ module Testlib.RunServices where import Control.Concurrent import Control.Monad.Codensity (lowerCodensity) -import Data.Map qualified as Map import SetupHelpers import System.Directory import System.Environment (getArgs) @@ -16,83 +15,6 @@ import Testlib.Prelude import Testlib.ResourcePool import Testlib.Run (createGlobalEnv) -backendA :: BackendResource -backendA = - BackendResource - { berBrigKeyspace = "brig_test", - berGalleyKeyspace = "galley_test", - berSparKeyspace = "spar_test", - berGundeckKeyspace = "gundeck_test", - berElasticsearchIndex = "directory_test", - berFederatorInternal = 8097, - berFederatorExternal = 8098, - berDomain = "example.com", - berAwsUserJournalQueue = "integration-user-events.fifo", - berAwsPrekeyTable = "integration-brig-prekeys", - berAwsS3Bucket = "dummy-bucket", - berAwsQueueName = "integration-gundeck-events", - berBrigInternalEvents = "integration-brig-events-internal", - berEmailSMSSesQueue = "integration-brig-events", - berEmailSMSEmailSender = "backend-integration@wire.com", - berGalleyJournal = "integration-team-events.fifo", - berVHost = "backendA", - berNginzSslPort = 8443 - } - -staticPortsA :: Map.Map Service Word16 -staticPortsA = - Map.fromList - [ (Brig, 8082), - (Galley, 8085), - (Gundeck, 8086), - (Cannon, 8083), - (Cargohold, 8084), - (Spar, 8088), - (BackgroundWorker, 8089), - (Nginz, 8080), - (Stern, 8091) - ] - -backendB :: BackendResource -backendB = - BackendResource - { berBrigKeyspace = "brig_test2", - berGalleyKeyspace = "galley_test2", - berSparKeyspace = "spar_test2", - berGundeckKeyspace = "gundeck_test2", - berElasticsearchIndex = "directory2_test", - berFederatorInternal = 9097, - berFederatorExternal = 9098, - berDomain = "b.example.com", - berAwsUserJournalQueue = "integration-user-events.fifo2", - berAwsPrekeyTable = "integration-brig-prekeys2", - berAwsS3Bucket = "dummy-bucket2", - berAwsQueueName = "integration-gundeck-events2", - berBrigInternalEvents = "integration-brig-events-internal2", - berEmailSMSSesQueue = "integration-brig-events2", - berEmailSMSEmailSender = "backend-integration2@wire.com", - berGalleyJournal = "integration-team-events.fifo2", - -- FUTUREWORK: set up vhosts in dev/ci for example.com and b.example.com - -- in case we want backendA and backendB to federate with a third backend - -- (because otherwise both queues will overlap) - berVHost = "backendB", - berNginzSslPort = 9443 - } - -staticPortsB :: Map.Map Service Word16 -staticPortsB = - Map.fromList - [ (Brig, 9082), - (Galley, 9085), - (Gundeck, 9086), - (Cannon, 9083), - (Cargohold, 9084), - (Spar, 9088), - (BackgroundWorker, 9089), - (Nginz, 9080), - (Stern, 9091) - ] - parentDir :: FilePath -> Maybe FilePath parentDir path = let dirs = splitPath path @@ -140,10 +62,10 @@ main = do lowerCodensity $ do _modifyEnv <- traverseConcurrentlyCodensity - ( \(res, staticPorts) -> + ( \resource -> -- We add the 'fullSerachWithAll' overrrides is a hack to get -- around https://wearezeta.atlassian.net/browse/WPB-3796 - startDynamicBackend res staticPorts fullSearchWithAll + startDynamicBackend resource fullSearchWithAll ) - [(backendA, staticPortsA), (backendB, staticPortsB)] + [backendA, backendB] liftIO run diff --git a/integration/test/Testlib/Service.hs b/integration/test/Testlib/Service.hs new file mode 100644 index 00000000000..a921858051d --- /dev/null +++ b/integration/test/Testlib/Service.hs @@ -0,0 +1,49 @@ +module Testlib.Service where + +import Prelude + +data Service = Brig | Galley | Cannon | Gundeck | Cargohold | Nginz | Spar | BackgroundWorker | Stern | FederatorInternal + deriving + ( Show, + Eq, + Ord, + Enum, + Bounded + ) + +serviceName :: Service -> String +serviceName = \case + Brig -> "brig" + Galley -> "galley" + Cannon -> "cannon" + Gundeck -> "gundeck" + Cargohold -> "cargohold" + Nginz -> "nginz" + Spar -> "spar" + BackgroundWorker -> "backgroundWorker" + Stern -> "stern" + FederatorInternal -> "federator" + +-- | Converts the service name to kebab-case. +configName :: Service -> String +configName = \case + Brig -> "brig" + Galley -> "galley" + Cannon -> "cannon" + Gundeck -> "gundeck" + Cargohold -> "cargohold" + Nginz -> "nginz" + Spar -> "spar" + BackgroundWorker -> "background-worker" + Stern -> "stern" + FederatorInternal -> "federator" + +data BackendName + = BackendA + | BackendB + | -- | The index of dynamic backends begin with 1 + DynamicBackend Int + deriving (Show, Eq, Ord) + +allServices :: [Service] +allServices = [minBound .. maxBound] diff --git a/integration/test/Testlib/Types.hs b/integration/test/Testlib/Types.hs index 25ed9c640ed..d6a2f590f1f 100644 --- a/integration/test/Testlib/Types.hs +++ b/integration/test/Testlib/Types.hs @@ -14,7 +14,6 @@ import Data.ByteString.Char8 qualified as C8 import Data.ByteString.Lazy qualified as L import Data.CaseInsensitive qualified as CI import Data.Default -import Data.Function ((&)) import Data.Functor import Data.Hex import Data.IORef @@ -29,6 +28,7 @@ import Network.HTTP.Types qualified as HTTP import Network.URI import Testlib.Env import Testlib.Printing +import Testlib.Service import UnliftIO (MonadUnliftIO) import Prelude @@ -127,7 +127,7 @@ appToIOKleisli k = do env <- ask pure $ \a -> runAppWithEnv env (k a) -getServiceMap :: String -> App ServiceMap +getServiceMap :: HasCallStack => String -> App ServiceMap getServiceMap fedDomain = do env <- ask assertJust ("Could not find service map for federation domain: " <> fedDomain) (Map.lookup fedDomain (env.serviceMap)) @@ -192,15 +192,16 @@ modifyFailure modifyAssertion action = do ) data ServiceOverrides = ServiceOverrides - { dbBrig :: Value -> App Value, - dbCannon :: Value -> App Value, - dbCargohold :: Value -> App Value, - dbGalley :: Value -> App Value, - dbGundeck :: Value -> App Value, - dbNginz :: Value -> App Value, - dbSpar :: Value -> App Value, - dbBackgroundWorker :: Value -> App Value, - dbStern :: Value -> App Value + { brigCfg :: Value -> App Value, + cannonCfg :: Value -> App Value, + cargoholdCfg :: Value -> App Value, + galleyCfg :: Value -> App Value, + gundeckCfg :: Value -> App Value, + nginzCfg :: Value -> App Value, + sparCfg :: Value -> App Value, + backgroundWorkerCfg :: Value -> App Value, + sternCfg :: Value -> App Value, + federatorInternalCfg :: Value -> App Value } instance Default ServiceOverrides where @@ -209,15 +210,16 @@ instance Default ServiceOverrides where instance Semigroup ServiceOverrides where a <> b = ServiceOverrides - { dbBrig = dbBrig a >=> dbBrig b, - dbCannon = dbCannon a >=> dbCannon b, - dbCargohold = dbCargohold a >=> dbCargohold b, - dbGalley = dbGalley a >=> dbGalley b, - dbGundeck = dbGundeck a >=> dbGundeck b, - dbNginz = dbNginz a >=> dbNginz b, - dbSpar = dbSpar a >=> dbSpar b, - dbBackgroundWorker = dbBackgroundWorker a >=> dbBackgroundWorker b, - dbStern = dbStern a >=> dbStern b + { brigCfg = brigCfg a >=> brigCfg b, + cannonCfg = cannonCfg a >=> cannonCfg b, + cargoholdCfg = cargoholdCfg a >=> cargoholdCfg b, + galleyCfg = galleyCfg a >=> galleyCfg b, + gundeckCfg = gundeckCfg a >=> gundeckCfg b, + nginzCfg = nginzCfg a >=> nginzCfg b, + sparCfg = sparCfg a >=> sparCfg b, + backgroundWorkerCfg = backgroundWorkerCfg a >=> backgroundWorkerCfg b, + sternCfg = sternCfg a >=> sternCfg b, + federatorInternalCfg = federatorInternalCfg a >=> federatorInternalCfg b } instance Monoid ServiceOverrides where @@ -226,42 +228,27 @@ instance Monoid ServiceOverrides where defaultServiceOverrides :: ServiceOverrides defaultServiceOverrides = ServiceOverrides - { dbBrig = pure, - dbCannon = pure, - dbCargohold = pure, - dbGalley = pure, - dbGundeck = pure, - dbNginz = pure, - dbSpar = pure, - dbBackgroundWorker = pure, - dbStern = pure + { brigCfg = pure, + cannonCfg = pure, + cargoholdCfg = pure, + galleyCfg = pure, + gundeckCfg = pure, + nginzCfg = pure, + sparCfg = pure, + backgroundWorkerCfg = pure, + sternCfg = pure, + federatorInternalCfg = pure } -defaultServiceOverridesToMap :: Map.Map Service (Value -> App Value) -defaultServiceOverridesToMap = ([minBound .. maxBound] <&> (,pure)) & Map.fromList - --- | Overrides the service configurations with the given overrides. --- e.g. --- `let overrides = --- def --- { dbBrig = --- setField "optSettings.setFederationStrategy" "allowDynamic" --- >=> removeField "optSettings.setFederationDomainConfigs" --- } --- withOverrides overrides defaultServiceOverridesToMap` -withOverrides :: ServiceOverrides -> Map.Map Service (Value -> App Value) -> Map.Map Service (Value -> App Value) -withOverrides overrides = - Map.mapWithKey - ( \svr f -> - case svr of - Brig -> f >=> overrides.dbBrig - Cannon -> f >=> overrides.dbCannon - Cargohold -> f >=> overrides.dbCargohold - Galley -> f >=> overrides.dbGalley - Gundeck -> f >=> overrides.dbGundeck - Nginz -> f >=> overrides.dbNginz - Spar -> f >=> overrides.dbSpar - BackgroundWorker -> f >=> overrides.dbBackgroundWorker - Stern -> f >=> overrides.dbStern - FederatorInternal -> f - ) +lookupConfigOverride :: ServiceOverrides -> Service -> (Value -> App Value) +lookupConfigOverride overrides = \case + Brig -> overrides.brigCfg + Cannon -> overrides.cannonCfg + Cargohold -> overrides.cargoholdCfg + Galley -> overrides.galleyCfg + Gundeck -> overrides.gundeckCfg + Nginz -> overrides.nginzCfg + Spar -> overrides.sparCfg + BackgroundWorker -> overrides.backgroundWorkerCfg + Stern -> overrides.sternCfg + FederatorInternal -> overrides.federatorInternalCfg