DPP-745 Add Oracle data continuity tests (#12961)

* Add Oracle compatibility tests

changelog_begin
changelog_end

* Clean up

* Remove debug change

* Skip Oracle tests if there is nothing to do

* Add missing line warp

* Use ORACLE_PORT

Co-authored-by: mziolekda <marcin.ziolek@digitalasset.com>

Co-authored-by: mziolekda <marcin.ziolek@digitalasset.com>
This commit is contained in:
Robert Autenrieth 2022-02-16 23:37:50 +01:00 committed by GitHub
parent fceb9c1118
commit 18b681f521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 234 additions and 1 deletions

View File

@ -34,3 +34,6 @@ steps:
set -eou pipefail
./compatibility/test.sh ${{ parameters.test_flags }}
displayName: 'Run tests'
env:
DOCKER_LOGIN: $(DOCKER_LOGIN)
DOCKER_PASSWORD: $(DOCKER_PASSWORD)

View File

@ -0,0 +1,33 @@
load("@daml//bazel_tools:haskell.bzl", "da_haskell_binary", "da_haskell_library")
da_haskell_library(
name = "with-oracle",
srcs = ["lib/WithOracle.hs"],
data = [],
hackage_deps = [
"base",
"directory",
"extra",
"filepath",
"network",
"process",
"safe-exceptions",
"text",
"uuid",
],
visibility = ["//visibility:public"],
)
da_haskell_binary(
name = "with-oracle-exe",
srcs = ["exe/Main.hs"],
hackage_deps = [
"base",
"process",
"text",
],
visibility = ["//visibility:public"],
deps = [
":with-oracle",
],
)

View File

@ -0,0 +1,16 @@
-- Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module Main (main) where
import qualified Data.Text as T
import System.Environment
import System.Process
import WithOracle
main :: IO ()
main = do
(arg : args) <- getArgs
withOracle $ \jdbcUrl ->
callProcess arg (map (T.unpack . T.replace "__jdbcurl__" jdbcUrl . T.pack) args)

View File

@ -0,0 +1,90 @@
-- Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module WithOracle (withOracle) where
import Control.Exception.Safe
import qualified Data.UUID as UUID
import Data.UUID.V4
import Data.Text (Text)
import Data.Maybe
import qualified Data.Text as T
import Network.Socket
import System.Environment.Blank
import System.IO.Extra
import System.Process
-- This is loosely modelled after com.daml.testing.oracle.OracleAround.
-- We make this a separate executable since it should only
-- depend on released artifacts so we cannot easily use sandbox as a library.
data OracleConnection = OracleConnection
{ port :: PortNumber
, adminUsername :: Text
, adminPassword :: Text
, dockerPath :: Text
}
withOracle :: (Text -> IO a) -> IO a
withOracle f = do
connection <- getOracleConnection
-- Note: we do not check whether the generated user name already exists
username <- randomUserName
bracket_ (createUser connection username) (dropUser connection username) $ f (jdbcUrl connection username)
-- Takes a UUID and modifies it so that it conforms to Oracle constraints for usernames and passwords
randomUserName :: IO Text
randomUserName = do fmap (T.take 30 . T.cons 'u' . T.replace "-" "" . T.pack . UUID.toString) nextRandom
-- Note: currently we only start Oracle databases through docker.
-- It's easier to shell out to sqlplus than dealing with Oracle-compatible SQL libraries.
executeStatement :: OracleConnection -> Text -> IO ()
executeStatement OracleConnection { .. } str = do
-- Note: need to login as 'sys', not as the user provided through the ORACLE_USERNAME environment variable
let cmd = "echo '" <> str <> "' | " <> "sqlplus sys/" <> adminPassword <> "@ORCLPDB1 as sysdba"
-- hPutStrLn stderr $ T.unpack $ "Executing command " <> cmd <> " through " <> dockerPath
callProcess (T.unpack dockerPath)
[ "exec"
, "oracle"
, "bash"
, "-c"
, T.unpack cmd
]
jdbcUrl :: OracleConnection -> Text -> Text
jdbcUrl OracleConnection { .. } username =
"jdbc:oracle:thin:" <>
username <>
"/" <>
username <>
"@localhost:" <>
T.pack (show port) <>
"/ORCLPDB1"
getOracleConnection :: IO OracleConnection
getOracleConnection = do
mbPort <- getEnv "ORACLE_PORT"
mbUser <- getEnv "ORACLE_USERNAME"
mbPwd <- getEnv "ORACLE_PWD"
mbDocker <- getEnv "ORACLE_DOCKER_PATH"
let port = read $ fromMaybe (error "ORACLE_PORT environment variable not set") mbPort
let username = T.pack $ fromMaybe (error "ORACLE_USERNAME environment variable not set") mbUser
let password = T.pack $ fromMaybe (error "ORACLE_PWD environment variable not set") mbPwd
let docker = T.pack $ fromMaybe (error "ORACLE_DOCKER_PATH environment variable not set") mbDocker
pure $ OracleConnection port username password docker
createUser :: OracleConnection -> Text -> IO ()
createUser connection name = do
hPutStrLn stderr $ T.unpack $ "Creating Oracle user " <> name
executeStatement connection $ "create user " <> name <> " identified by " <> name <> ";"
executeStatement connection $ "grant connect, resource to " <> name <> ";"
executeStatement connection $ "grant create table, create materialized view, create view, create procedure, create sequence, create type to " <> name <> ";"
executeStatement connection $ "alter user " <> name <> " quota unlimited on users;"
executeStatement connection $ "GRANT EXECUTE ON SYS.DBMS_LOCK TO " <> name <> ";"
executeStatement connection $ "GRANT SELECT ON V_$MYSTAT TO " <> name <> ";"
executeStatement connection $ "GRANT SELECT ON V_$LOCK TO " <> name <> ";"
dropUser :: OracleConnection -> Text -> IO ()
dropUser connection name = do
hPutStrLn stderr $ T.unpack $ "Dropping Oracle user " <> name
executeStatement connection ("drop user " <> name <> " cascade;")

View File

@ -86,6 +86,7 @@ da_haskell_binary(
main_function = "Migration.Runner.main",
visibility = ["//visibility:public"],
deps = [
"//bazel_tools/client_server/with-oracle",
"//bazel_tools/client_server/with-postgres",
"//bazel_tools/daml_ledger:sandbox-helper",
"@rules_haskell//tools/runfiles",

View File

@ -19,6 +19,7 @@ import Control.Lens
import Control.Monad
import Data.Either
import Data.List
import Data.List.Extra (lower)
import Data.Maybe
import qualified Data.SemVer as SemVer
import qualified Data.Text as T
@ -37,6 +38,7 @@ import System.FilePath
import System.IO.Extra
import System.Process
import WithPostgres (withPostgres)
import WithOracle (withOracle)
import qualified Bazel.Runfiles
import qualified Migration.Divulgence as Divulgence
@ -50,15 +52,26 @@ data Options = Options
-- ^ Ordered list of assistant binaries that will be used to run sandbox.
-- We run through migrations in the order of the list
, appendOnly :: AppendOnly
, databaseType :: DatabaseType
}
newtype AppendOnly = AppendOnly Bool
data DatabaseType = Postgres | Oracle
deriving (Eq, Ord, Show)
optsParser :: Parser Options
optsParser = Options
<$> strOption (long "model-dar")
<*> many (strArgument mempty)
<*> fmap AppendOnly (switch (long "append-only"))
<*> option (eitherReader databaseTypeReader)(long "database" <> value Postgres)
databaseTypeReader :: String -> Either String DatabaseType
databaseTypeReader str =
case lower str of
"postgres" -> Right Postgres
"oracle" -> Right Oracle
_ -> Left "Unknown database type. Expected postgres or oracle."
main :: IO ()
main = do
@ -69,7 +82,11 @@ main = do
let step = Bazel.Runfiles.rlocation
runfiles
("compatibility" </> "sandbox-migration" </> "migration-step")
withPostgres $ \jdbcUrl -> do
let withDatabase = case databaseType of
Postgres -> withPostgres
Oracle -> withOracle
withDatabase $ \jdbcUrl -> do
hPutStrLn stderr $ T.unpack $ "Using database " <> jdbcUrl
initialPlatform : _ <- pure platformAssistants
hPutStrLn stderr "--> Uploading model DAR"
withSandbox appendOnly initialPlatform jdbcUrl $ \p ->

View File

@ -22,6 +22,10 @@ if [[ $1 == "--append-only" ]]; then
EXTRA_ARGS="--append-only"
VERSIONS="${@:2}"
fi
if [[ $1 == "--oracle" ]]; then
EXTRA_ARGS="--database=oracle"
VERSIONS="${@:2}"
fi
SANDBOX_ARGS=""
for PLATFORM in $VERSIONS; do
SANDBOX_ARGS="$SANDBOX_ARGS $(rlocation daml-sdk-$PLATFORM/daml)"

View File

@ -7,6 +7,9 @@ load("//bazel_tools:versions.bzl", "versions")
def runfiles(ver):
return ["@daml-sdk-{}//:daml".format(ver)] + (["@daml-sdk-{}//:sandbox-on-x".format(ver)] if versions.is_at_least("2.0.0", ver) else [])
def oracle_versions(vers):
return [v for v in vers if versions.is_at_least("1.18.0", v)]
def migration_test(name, versions, tags, quick_tags, **kwargs):
native.sh_test(
name = name,
@ -36,3 +39,18 @@ def migration_test(name, versions, tags, quick_tags, **kwargs):
args = ["--append-only"] + versions,
**kwargs
)
# Oracle was introduced after the switch to the append-only schema
native.sh_test(
name = "{}-oracle".format(name),
srcs = ["//sandbox-migration:test.sh"],
deps = ["@bazel_tools//tools/bash/runfiles"],
tags = tags,
data = [
"//sandbox-migration:sandbox-migration-runner",
"//sandbox-migration:migration-model.dar",
"//sandbox-migration:migration-step",
] + [dep for ver in oracle_versions(versions) for dep in runfiles(ver)],
args = ["--oracle"] + oracle_versions(versions),
**kwargs
) if len(oracle_versions(versions)) > 1 else None

View File

@ -58,6 +58,53 @@ trap stop_postgresql EXIT
stop_postgresql # in case it's running from a previous build
start_postgresql
# Set up a shared Oracle instance
# Note: this is code duplicated from ../ci/build.yml
function start_oracle() {
# Only log in automatically if DOCKER_LOGIN is set (as is in our CI).
# When running this script locally, the developer is expected to have docker running and authenticated.
if [ -z "${DOCKER_LOGIN:-}" ]; then
echo "DOCKER_LOGIN is not set, skipping docker login"
else
docker login --username "$DOCKER_LOGIN" --password "$DOCKER_PASSWORD"
fi
IMAGE=$(cat ../ci/oracle_image)
docker pull $IMAGE
# Cleanup stray containers that might still be running from
# another build that didnt get shut down cleanly.
docker rm -f oracle || true
if [ "${OSTYPE:0:6}" = "darwin" ]; then
# macOS: Oracle does not like if you use the host network to connect to it if its running in the container.
echo "Starting oracle docker with port mapping"
docker run -d --rm --name oracle -p $ORACLE_PORT:$ORACLE_PORT -e ORACLE_PWD=$ORACLE_PWD $IMAGE
else
# Unix: Oracle does not like if you connect to it via localhost if its running in the container.
# Interestingly it works if you use the external IP of the host so the issue is
# not the host it is listening on (it claims for that to be 0.0.0.0).
# --network host is a cheap escape hatch for this.
echo "Starting oracle docker with host network"
docker run -d --rm --name oracle --network host -e ORACLE_PWD=$ORACLE_PWD $IMAGE
fi
}
function stop_oracle() {
docker rm -f oracle
}
function test_oracle_connection() {
docker exec oracle bash -c 'sqlplus -L '"$ORACLE_USERNAME"'/'"$ORACLE_PWD"'@//localhost:'"$ORACLE_PORT"'/ORCLPDB1 <<< "select * from dba_users;"; exit $?' >/dev/null
}
trap stop_oracle EXIT
start_oracle
until test_oracle_connection
do
echo "Could not connect to Oracle, trying again..."
sleep 1
done
# Pass the path to the docker executable to the tests.
# This is because the tests need to interact with the docker container started above.
ORACLE_DOCKER_PATH=$(which docker)
bazel build //...
BAZEL_ARGS=""
@ -69,4 +116,8 @@ bazel test //... \
--test_env "POSTGRESQL_HOST=${POSTGRESQL_HOST}" \
--test_env "POSTGRESQL_PORT=${POSTGRESQL_PORT}" \
--test_env "POSTGRESQL_USERNAME=${POSTGRESQL_USERNAME}" \
--test_env "ORACLE_PORT=${ORACLE_PORT}" \
--test_env "ORACLE_USERNAME=${ORACLE_USERNAME}" \
--test_env "ORACLE_PWD=${ORACLE_PWD}" \
--test_env "ORACLE_DOCKER_PATH=${ORACLE_DOCKER_PATH}" \
$BAZEL_ARGS