|
| 1 | +// Copyright (c) 2021, Oracle and/or its affiliates. |
| 2 | +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. |
| 3 | + |
| 4 | +package oracle.weblogic.kubernetes; |
| 5 | + |
| 6 | +import java.io.File; |
| 7 | +import java.io.FileOutputStream; |
| 8 | +import java.nio.file.Path; |
| 9 | +import java.nio.file.Paths; |
| 10 | +import java.util.ArrayList; |
| 11 | +import java.util.Arrays; |
| 12 | +import java.util.List; |
| 13 | +import java.util.Properties; |
| 14 | + |
| 15 | +import io.kubernetes.client.openapi.models.V1Container; |
| 16 | +import io.kubernetes.client.openapi.models.V1EnvVar; |
| 17 | +import io.kubernetes.client.openapi.models.V1LocalObjectReference; |
| 18 | +import io.kubernetes.client.openapi.models.V1ObjectMeta; |
| 19 | +import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource; |
| 20 | +import io.kubernetes.client.openapi.models.V1SecretReference; |
| 21 | +import io.kubernetes.client.openapi.models.V1Volume; |
| 22 | +import io.kubernetes.client.openapi.models.V1VolumeMount; |
| 23 | +import oracle.weblogic.domain.AdminServer; |
| 24 | +import oracle.weblogic.domain.AdminService; |
| 25 | +import oracle.weblogic.domain.Channel; |
| 26 | +import oracle.weblogic.domain.Cluster; |
| 27 | +import oracle.weblogic.domain.Domain; |
| 28 | +import oracle.weblogic.domain.DomainSpec; |
| 29 | +import oracle.weblogic.domain.ServerPod; |
| 30 | +import oracle.weblogic.kubernetes.annotations.IntegrationTest; |
| 31 | +import oracle.weblogic.kubernetes.annotations.Namespaces; |
| 32 | +import oracle.weblogic.kubernetes.logging.LoggingFacade; |
| 33 | +import oracle.weblogic.kubernetes.utils.CommonTestUtils; |
| 34 | +import org.awaitility.core.ConditionFactory; |
| 35 | +import org.junit.jupiter.api.BeforeAll; |
| 36 | +import org.junit.jupiter.api.DisplayName; |
| 37 | +import org.junit.jupiter.api.Test; |
| 38 | + |
| 39 | +import static java.util.concurrent.TimeUnit.MINUTES; |
| 40 | +import static java.util.concurrent.TimeUnit.SECONDS; |
| 41 | +import static oracle.weblogic.kubernetes.TestConstants.ADMIN_PASSWORD_DEFAULT; |
| 42 | +import static oracle.weblogic.kubernetes.TestConstants.ADMIN_USERNAME_DEFAULT; |
| 43 | +import static oracle.weblogic.kubernetes.TestConstants.BASE_IMAGES_REPO_SECRET; |
| 44 | +import static oracle.weblogic.kubernetes.TestConstants.DB_IMAGE_TO_USE_IN_SPEC; |
| 45 | +import static oracle.weblogic.kubernetes.TestConstants.DOMAIN_API_VERSION; |
| 46 | +import static oracle.weblogic.kubernetes.TestConstants.FMWINFRA_IMAGE_TO_USE_IN_SPEC; |
| 47 | +import static oracle.weblogic.kubernetes.TestConstants.K8S_NODEPORT_HOST; |
| 48 | +import static oracle.weblogic.kubernetes.actions.ActionConstants.RESOURCE_DIR; |
| 49 | +import static oracle.weblogic.kubernetes.actions.TestActions.getServiceNodePort; |
| 50 | +import static oracle.weblogic.kubernetes.actions.impl.primitive.Docker.getImageEnvVar; |
| 51 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.checkPodReadyAndServiceExists; |
| 52 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.createDomainAndVerify; |
| 53 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.createSecretForBaseImages; |
| 54 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.createSecretWithUsernamePassword; |
| 55 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.getExternalServicePodName; |
| 56 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.installAndVerifyOperator; |
| 57 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.setPodAntiAffinity; |
| 58 | +import static oracle.weblogic.kubernetes.utils.DbUtils.setupDBandRCUschema; |
| 59 | +import static oracle.weblogic.kubernetes.utils.TestUtils.callWebAppAndWaitTillReady; |
| 60 | +import static oracle.weblogic.kubernetes.utils.TestUtils.getNextFreePort; |
| 61 | +import static oracle.weblogic.kubernetes.utils.ThreadSafeLogger.getLogger; |
| 62 | +import static org.awaitility.Awaitility.with; |
| 63 | +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; |
| 64 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 65 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 66 | + |
| 67 | +/** |
| 68 | + * Test to creat a FMW dynamic domain in persistent volume using WLST. |
| 69 | + */ |
| 70 | +@DisplayName("Test to creat a FMW dynamic domain in persistent volume using WLST") |
| 71 | +@IntegrationTest |
| 72 | +public class ItFmwDynamicDomainInPV { |
| 73 | + |
| 74 | + private static ConditionFactory withStandardRetryPolicy; |
| 75 | + |
| 76 | + private static String opNamespace = null; |
| 77 | + private static String domainNamespace = null; |
| 78 | + private static String dbNamespace = null; |
| 79 | + private static String oracle_home = null; |
| 80 | + private static String java_home = null; |
| 81 | + |
| 82 | + private static final String RCUSCHEMAPREFIX = "fmwdomainpv"; |
| 83 | + private static final String ORACLEDBURLPREFIX = "oracledb."; |
| 84 | + private static final String ORACLEDBSUFFIX = ".svc.cluster.local:1521/devpdb.k8s"; |
| 85 | + private static final String RCUSYSUSERNAME = "sys"; |
| 86 | + private static final String RCUSYSPASSWORD = "Oradoc_db1"; |
| 87 | + private static final String RCUSCHEMAUSERNAME = "myrcuuser"; |
| 88 | + private static final String RCUSCHEMAPASSWORD = "Oradoc_db1"; |
| 89 | + |
| 90 | + private static String dbUrl = null; |
| 91 | + private static LoggingFacade logger = null; |
| 92 | + |
| 93 | + private static final String domainUid = "fmw-dynamicdomain-inpv"; |
| 94 | + private static final String clusterName = "cluster-dynamicdomain-inpv"; |
| 95 | + private static final String adminServerName = "admin-server"; |
| 96 | + private static final String managedServerNameBase = "managed-server"; |
| 97 | + private static final String adminServerPodName = domainUid + "-" + adminServerName; |
| 98 | + private static final String managedServerPodNamePrefix = domainUid + "-" + managedServerNameBase; |
| 99 | + private final int managedServerPort = 8001; |
| 100 | + private final String wlSecretName = domainUid + "-weblogic-credentials"; |
| 101 | + private final String rcuSecretName = domainUid + "-rcu-credentials"; |
| 102 | + private static final int replicaCount = 2; |
| 103 | + |
| 104 | + /** |
| 105 | + * Assigns unique namespaces for DB, operator and domains. |
| 106 | + * Start DB service and create RCU schema. |
| 107 | + * Pull FMW image and Oracle DB image if running tests in Kind cluster. |
| 108 | + */ |
| 109 | + @BeforeAll |
| 110 | + public static void initAll(@Namespaces(3) List<String> namespaces) { |
| 111 | + logger = getLogger(); |
| 112 | + // create standard, reusable retry/backoff policy |
| 113 | + withStandardRetryPolicy = with().pollDelay(10, SECONDS) |
| 114 | + .and().with().pollInterval(10, SECONDS) |
| 115 | + .atMost(5, MINUTES).await(); |
| 116 | + |
| 117 | + logger.info("Assign a unique namespace for DB and RCU"); |
| 118 | + assertNotNull(namespaces.get(0), "Namespace is null"); |
| 119 | + dbNamespace = namespaces.get(0); |
| 120 | + dbUrl = ORACLEDBURLPREFIX + dbNamespace + ORACLEDBSUFFIX; |
| 121 | + |
| 122 | + logger.info("Assign a unique namespace for operator1"); |
| 123 | + assertNotNull(namespaces.get(1), "Namespace is null"); |
| 124 | + opNamespace = namespaces.get(1); |
| 125 | + |
| 126 | + logger.info("Assign a unique namespace for FMW domain"); |
| 127 | + assertNotNull(namespaces.get(2), "Namespace is null"); |
| 128 | + domainNamespace = namespaces.get(2); |
| 129 | + |
| 130 | + logger.info("Start DB and create RCU schema for namespace: {0}, RCU prefix: {1}, " |
| 131 | + + "dbUrl: {2}, dbImage: {3}, fmwImage: {4} ", dbNamespace, RCUSCHEMAPREFIX, dbUrl, |
| 132 | + DB_IMAGE_TO_USE_IN_SPEC, FMWINFRA_IMAGE_TO_USE_IN_SPEC); |
| 133 | + assertDoesNotThrow(() -> setupDBandRCUschema(DB_IMAGE_TO_USE_IN_SPEC, FMWINFRA_IMAGE_TO_USE_IN_SPEC, |
| 134 | + RCUSCHEMAPREFIX, dbNamespace, 0, dbUrl), |
| 135 | + String.format("Failed to create RCU schema for prefix %s in the namespace %s with " |
| 136 | + + "dbUrl %s", RCUSCHEMAPREFIX, dbNamespace, dbUrl)); |
| 137 | + |
| 138 | + logger.info("DB image: {0}, FMW image {1} used in the test", |
| 139 | + DB_IMAGE_TO_USE_IN_SPEC, FMWINFRA_IMAGE_TO_USE_IN_SPEC); |
| 140 | + |
| 141 | + // install operator and verify its running in ready state |
| 142 | + installAndVerifyOperator(opNamespace, domainNamespace); |
| 143 | + } |
| 144 | + |
| 145 | + /** |
| 146 | + * Create a basic FMW dynamic cluster model in image domain. |
| 147 | + * Verify Pod is ready and service exists for both admin server and managed servers. |
| 148 | + * Verify EM console is accessible. |
| 149 | + */ |
| 150 | + @Test |
| 151 | + @DisplayName("Create FMW Dynamic Domain in PV") |
| 152 | + public void testFmwDynamicDomainInPV() { |
| 153 | + // create FMW dynamic domain and verify |
| 154 | + createFmwDomainAndVerify(); |
| 155 | + verifyDomainReady(); |
| 156 | + } |
| 157 | + |
| 158 | + private void createFmwDomainAndVerify() { |
| 159 | + final String pvName = domainUid + "-" + domainNamespace + "-pv"; |
| 160 | + final String pvcName = domainUid + "-" + domainNamespace + "-pvc"; |
| 161 | + final int t3ChannelPort = getNextFreePort(30000, 32767); |
| 162 | + |
| 163 | + // create pull secrets for domainNamespace when running in non Kind Kubernetes cluster |
| 164 | + // this secret is used only for non-kind cluster |
| 165 | + createSecretForBaseImages(domainNamespace); |
| 166 | + |
| 167 | + // create FMW domain credential secret |
| 168 | + createSecretWithUsernamePassword(wlSecretName, domainNamespace, |
| 169 | + ADMIN_USERNAME_DEFAULT, ADMIN_PASSWORD_DEFAULT); |
| 170 | + |
| 171 | + // create RCU credential secret |
| 172 | + CommonTestUtils.createRcuSecretWithUsernamePassword(rcuSecretName, domainNamespace, |
| 173 | + RCUSCHEMAUSERNAME, RCUSCHEMAPASSWORD, RCUSYSUSERNAME, RCUSYSPASSWORD); |
| 174 | + |
| 175 | + // create persistent volume and persistent volume claim for domain |
| 176 | + CommonTestUtils.createPV(pvName, domainUid, this.getClass().getSimpleName()); |
| 177 | + CommonTestUtils.createPVC(pvName, pvcName, domainUid, domainNamespace); |
| 178 | + |
| 179 | + File domainPropertiesFile = createWlstPropertyFile(t3ChannelPort); |
| 180 | + |
| 181 | + // WLST script for creating domain |
| 182 | + Path wlstScript = Paths.get(RESOURCE_DIR, "python-scripts", "fmw-create-dynamic-domain.py"); |
| 183 | + |
| 184 | + // create configmap and domain on persistent volume using the WLST script and property file |
| 185 | + createDomainOnPvUsingWlst(wlstScript, domainPropertiesFile.toPath(), pvName, pvcName); |
| 186 | + |
| 187 | + // create domain and verify |
| 188 | + createDomainCrAndVerify(pvName, pvcName, t3ChannelPort); |
| 189 | + } |
| 190 | + |
| 191 | + private void createDomainCrAndVerify(String pvName, |
| 192 | + String pvcName, |
| 193 | + int t3ChannelPort) { |
| 194 | + // create a domain custom resource configuration object |
| 195 | + logger.info("Creating domain custom resource"); |
| 196 | + Domain domain = new Domain() |
| 197 | + .apiVersion(DOMAIN_API_VERSION) |
| 198 | + .kind("Domain") |
| 199 | + .metadata(new V1ObjectMeta() |
| 200 | + .name(domainUid) |
| 201 | + .namespace(domainNamespace)) |
| 202 | + .spec(new DomainSpec() |
| 203 | + .domainUid(domainUid) |
| 204 | + .domainHome("/shared/domains/" + domainUid) // point to domain home in pv |
| 205 | + .domainHomeSourceType("PersistentVolume") // set the domain home source type as pv |
| 206 | + .image(FMWINFRA_IMAGE_TO_USE_IN_SPEC) |
| 207 | + .imagePullPolicy("IfNotPresent") |
| 208 | + .imagePullSecrets(Arrays.asList( |
| 209 | + new V1LocalObjectReference() |
| 210 | + .name(BASE_IMAGES_REPO_SECRET))) |
| 211 | + .webLogicCredentialsSecret(new V1SecretReference() |
| 212 | + .name(wlSecretName) |
| 213 | + .namespace(domainNamespace)) |
| 214 | + .includeServerOutInPodLog(true) |
| 215 | + .logHomeEnabled(Boolean.TRUE) |
| 216 | + .logHome("/shared/logs/" + domainUid) |
| 217 | + .dataHome("") |
| 218 | + .serverStartPolicy("IF_NEEDED") |
| 219 | + .serverPod(new ServerPod() //serverpod |
| 220 | + .addEnvItem(new V1EnvVar() |
| 221 | + .name("JAVA_OPTIONS") |
| 222 | + .value("-Dweblogic.StdoutDebugEnabled=false")) |
| 223 | + .addEnvItem(new V1EnvVar() |
| 224 | + .name("USER_MEM_ARGS") |
| 225 | + .value("-Djava.security.egd=file:/dev/./urandom")) |
| 226 | + .addEnvItem(new V1EnvVar() |
| 227 | + .name("ALLOW_DYNAMIC_CLUSTER_IN_FMW") |
| 228 | + .value("true")) |
| 229 | + .addVolumesItem(new V1Volume() |
| 230 | + .name(pvName) |
| 231 | + .persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource() |
| 232 | + .claimName(pvcName))) |
| 233 | + .addVolumeMountsItem(new V1VolumeMount() |
| 234 | + .mountPath("/shared") |
| 235 | + .name(pvName))) |
| 236 | + .adminServer(new AdminServer() //admin server |
| 237 | + .serverStartState("RUNNING") |
| 238 | + .adminService(new AdminService() |
| 239 | + .addChannelsItem(new Channel() |
| 240 | + .channelName("default") |
| 241 | + .nodePort(0)) |
| 242 | + .addChannelsItem(new Channel() |
| 243 | + .channelName("T3Channel") |
| 244 | + .nodePort(t3ChannelPort)))) |
| 245 | + .addClustersItem(new Cluster() //cluster |
| 246 | + .clusterName(clusterName) |
| 247 | + .replicas(replicaCount) |
| 248 | + .serverStartState("RUNNING") |
| 249 | + )); |
| 250 | + setPodAntiAffinity(domain); |
| 251 | + |
| 252 | + // verify the domain custom resource is created |
| 253 | + createDomainAndVerify(domain, domainNamespace); |
| 254 | + } |
| 255 | + |
| 256 | + private void createDomainOnPvUsingWlst(Path wlstScriptFile, |
| 257 | + Path domainPropertiesFile, |
| 258 | + String pvName, |
| 259 | + String pvcName) { |
| 260 | + |
| 261 | + logger.info("Preparing to run create domain job using WLST"); |
| 262 | + |
| 263 | + List<Path> domainScriptFiles = new ArrayList<>(); |
| 264 | + domainScriptFiles.add(wlstScriptFile); |
| 265 | + domainScriptFiles.add(domainPropertiesFile); |
| 266 | + |
| 267 | + logger.info("Creating a config map to hold domain creation scripts"); |
| 268 | + String domainScriptConfigMapName = "create-domain-scripts-cm"; |
| 269 | + assertDoesNotThrow( |
| 270 | + () -> CommonTestUtils.createConfigMapForDomainCreation(domainScriptConfigMapName, domainScriptFiles, |
| 271 | + domainNamespace, this.getClass().getSimpleName()), |
| 272 | + "Create configmap for domain creation failed"); |
| 273 | + |
| 274 | + // create a V1Container with specific scripts and properties for creating domain |
| 275 | + V1Container jobCreationContainer = new V1Container() |
| 276 | + .addCommandItem("/bin/sh") |
| 277 | + .addArgsItem("/u01/oracle/oracle_common/common/bin/wlst.sh") |
| 278 | + .addArgsItem("/u01/weblogic/" + wlstScriptFile.getFileName()) //wlst.sh script |
| 279 | + .addArgsItem("-skipWLSModuleScanning") |
| 280 | + .addArgsItem("-loadProperties") |
| 281 | + .addArgsItem("/u01/weblogic/" + domainPropertiesFile.getFileName()); //domain property file |
| 282 | + |
| 283 | + logger.info("Running a Kubernetes job to create the domain"); |
| 284 | + CommonTestUtils.createDomainJob(FMWINFRA_IMAGE_TO_USE_IN_SPEC, pvName, pvcName, domainScriptConfigMapName, |
| 285 | + domainNamespace, jobCreationContainer); |
| 286 | + } |
| 287 | + |
| 288 | + private File createWlstPropertyFile(int t3ChannelPort) { |
| 289 | + //get ENV variable from the image |
| 290 | + assertNotNull(getImageEnvVar(FMWINFRA_IMAGE_TO_USE_IN_SPEC, "ORACLE_HOME"), |
| 291 | + "envVar ORACLE_HOME from image is null"); |
| 292 | + oracle_home = getImageEnvVar(FMWINFRA_IMAGE_TO_USE_IN_SPEC, "ORACLE_HOME"); |
| 293 | + logger.info("ORACLE_HOME in image {0} is: {1}", FMWINFRA_IMAGE_TO_USE_IN_SPEC, oracle_home); |
| 294 | + assertNotNull(getImageEnvVar(FMWINFRA_IMAGE_TO_USE_IN_SPEC, "JAVA_HOME"), |
| 295 | + "envVar JAVA_HOME from image is null"); |
| 296 | + java_home = getImageEnvVar(FMWINFRA_IMAGE_TO_USE_IN_SPEC, "JAVA_HOME"); |
| 297 | + logger.info("JAVA_HOME in image {0} is: {1}", FMWINFRA_IMAGE_TO_USE_IN_SPEC, java_home); |
| 298 | + |
| 299 | + // create wlst property file object |
| 300 | + Properties p = new Properties(); |
| 301 | + p.setProperty("oracleHome", oracle_home); //default $ORACLE_HOME |
| 302 | + p.setProperty("javaHome", java_home); //default $JAVA_HOME |
| 303 | + p.setProperty("domainParentDir", "/shared/domains/"); |
| 304 | + p.setProperty("domainName", domainUid); |
| 305 | + p.setProperty("domainUser", ADMIN_USERNAME_DEFAULT); |
| 306 | + p.setProperty("domainPassword", ADMIN_PASSWORD_DEFAULT); |
| 307 | + p.setProperty("rcuDb", dbUrl); |
| 308 | + p.setProperty("rcuSchemaPrefix", RCUSCHEMAPREFIX); |
| 309 | + p.setProperty("rcuSchemaPassword", RCUSCHEMAPASSWORD); |
| 310 | + p.setProperty("adminListenPort", "7001"); |
| 311 | + p.setProperty("adminName", adminServerName); |
| 312 | + p.setProperty("adminPodName", adminServerPodName); |
| 313 | + p.setProperty("adminUsername", ADMIN_USERNAME_DEFAULT); |
| 314 | + p.setProperty("adminPassword", ADMIN_PASSWORD_DEFAULT); |
| 315 | + p.setProperty("managedNameBase", managedServerNameBase); |
| 316 | + p.setProperty("managedServerPort", Integer.toString(managedServerPort)); |
| 317 | + p.setProperty("prodMode", "true"); |
| 318 | + p.setProperty("managedCount", "4"); |
| 319 | + p.setProperty("clusterName", clusterName); |
| 320 | + p.setProperty("t3ChannelPublicAddress", K8S_NODEPORT_HOST); |
| 321 | + p.setProperty("t3ChannelPort", Integer.toString(t3ChannelPort)); |
| 322 | + p.setProperty("exposeAdminT3Channel", "true"); |
| 323 | + |
| 324 | + // create a temporary WebLogic domain property file |
| 325 | + File domainPropertiesFile = assertDoesNotThrow(() -> |
| 326 | + File.createTempFile("domain", "properties"), |
| 327 | + "Failed to create domain properties file"); |
| 328 | + |
| 329 | + // create the property file |
| 330 | + assertDoesNotThrow(() -> |
| 331 | + p.store(new FileOutputStream(domainPropertiesFile), "FMW wlst properties file"), |
| 332 | + "Failed to write domain properties file"); |
| 333 | + |
| 334 | + return domainPropertiesFile; |
| 335 | + } |
| 336 | + |
| 337 | + /** |
| 338 | + * Verify Pod is ready and service exists for both admin server and managed servers. |
| 339 | + * Verify EM console is accessible. |
| 340 | + */ |
| 341 | + private void verifyDomainReady() { |
| 342 | + checkPodReadyAndServiceExists(adminServerPodName, domainUid, domainNamespace); |
| 343 | + for (int i = 1; i <= replicaCount; i++) { |
| 344 | + logger.info("Checking managed server service {0} is created in namespace {1}", |
| 345 | + managedServerPodNamePrefix + i, domainNamespace); |
| 346 | + checkPodReadyAndServiceExists(managedServerPodNamePrefix + i, domainUid, domainNamespace); |
| 347 | + } |
| 348 | + |
| 349 | + //check access to the em console: http://hostname:port/em |
| 350 | + int nodePort = getServiceNodePort( |
| 351 | + domainNamespace, getExternalServicePodName(adminServerPodName), "default"); |
| 352 | + assertTrue(nodePort != -1, |
| 353 | + "Could not get the default external service node port"); |
| 354 | + logger.info("Found the default service nodePort {0}", nodePort); |
| 355 | + String curlCmd1 = "curl -s -L --show-error --noproxy '*' " |
| 356 | + + " http://" + K8S_NODEPORT_HOST + ":" + nodePort |
| 357 | + + "/em --write-out %{http_code} -o /dev/null"; |
| 358 | + logger.info("Executing default nodeport curl command {0}", curlCmd1); |
| 359 | + assertTrue(callWebAppAndWaitTillReady(curlCmd1, 5), "Calling web app failed"); |
| 360 | + logger.info("EM console is accessible thru default service"); |
| 361 | + } |
| 362 | +} |
| 363 | + |
0 commit comments