|
| 1 | +// Copyright (c) 2023, 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.verrazzano.weblogic.kubernetes; |
| 5 | + |
| 6 | +import java.util.ArrayList; |
| 7 | +import java.util.Arrays; |
| 8 | +import java.util.Collections; |
| 9 | +import java.util.HashMap; |
| 10 | +import java.util.List; |
| 11 | +import java.util.Map; |
| 12 | +import java.util.Optional; |
| 13 | + |
| 14 | +import io.kubernetes.client.custom.Quantity; |
| 15 | +import io.kubernetes.client.openapi.models.V1ObjectMeta; |
| 16 | +import io.kubernetes.client.util.Yaml; |
| 17 | +import oracle.verrazzano.weblogic.ApplicationConfiguration; |
| 18 | +import oracle.verrazzano.weblogic.ApplicationConfigurationSpec; |
| 19 | +import oracle.verrazzano.weblogic.Component; |
| 20 | +import oracle.verrazzano.weblogic.ComponentSpec; |
| 21 | +import oracle.verrazzano.weblogic.Components; |
| 22 | +import oracle.verrazzano.weblogic.Destination; |
| 23 | +import oracle.verrazzano.weblogic.IngressRule; |
| 24 | +import oracle.verrazzano.weblogic.IngressTrait; |
| 25 | +import oracle.verrazzano.weblogic.IngressTraitSpec; |
| 26 | +import oracle.verrazzano.weblogic.IngressTraits; |
| 27 | +import oracle.verrazzano.weblogic.Path; |
| 28 | +import oracle.verrazzano.weblogic.Workload; |
| 29 | +import oracle.verrazzano.weblogic.WorkloadSpec; |
| 30 | +import oracle.verrazzano.weblogic.kubernetes.annotations.VzIntegrationTest; |
| 31 | +import oracle.weblogic.domain.DomainResource; |
| 32 | +import oracle.weblogic.kubernetes.annotations.Namespaces; |
| 33 | +import oracle.weblogic.kubernetes.logging.LoggingFacade; |
| 34 | +import org.junit.jupiter.api.BeforeAll; |
| 35 | +import org.junit.jupiter.api.DisplayName; |
| 36 | +import org.junit.jupiter.api.Tag; |
| 37 | +import org.junit.jupiter.api.Test; |
| 38 | + |
| 39 | +import static oracle.weblogic.kubernetes.TestConstants.ADMIN_PASSWORD_DEFAULT; |
| 40 | +import static oracle.weblogic.kubernetes.TestConstants.ADMIN_SERVER_NAME_BASE; |
| 41 | +import static oracle.weblogic.kubernetes.TestConstants.ADMIN_USERNAME_DEFAULT; |
| 42 | +import static oracle.weblogic.kubernetes.TestConstants.MANAGED_SERVER_NAME_BASE; |
| 43 | +import static oracle.weblogic.kubernetes.TestConstants.TEST_IMAGES_REPO_SECRET_NAME; |
| 44 | +import static oracle.weblogic.kubernetes.actions.impl.primitive.Kubernetes.createApplication; |
| 45 | +import static oracle.weblogic.kubernetes.actions.impl.primitive.Kubernetes.createComponent; |
| 46 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.checkPodReadyAndServiceExists; |
| 47 | +import static oracle.weblogic.kubernetes.utils.CommonTestUtils.generateNewModelFileWithUpdatedDomainUid; |
| 48 | +import static oracle.weblogic.kubernetes.utils.ConfigMapUtils.createConfigMapAndVerify; |
| 49 | +import static oracle.weblogic.kubernetes.utils.ImageUtils.createMiiImageAndVerify; |
| 50 | +import static oracle.weblogic.kubernetes.utils.ImageUtils.createTestRepoSecret; |
| 51 | +import static oracle.weblogic.kubernetes.utils.ImageUtils.imageRepoLoginAndPushImageToRegistry; |
| 52 | +import static oracle.weblogic.kubernetes.utils.IstioUtils.createIstioDomainResource; |
| 53 | +import static oracle.weblogic.kubernetes.utils.SecretUtils.createSecretWithUsernamePassword; |
| 54 | +import static oracle.weblogic.kubernetes.utils.SessionMigrationUtil.getOrigModelFile; |
| 55 | +import static oracle.weblogic.kubernetes.utils.SessionMigrationUtil.getServerAndSessionInfoAndVerify; |
| 56 | +import static oracle.weblogic.kubernetes.utils.SessionMigrationUtil.shutdownServerAndVerify; |
| 57 | +import static oracle.weblogic.kubernetes.utils.ThreadSafeLogger.getLogger; |
| 58 | +import static oracle.weblogic.kubernetes.utils.VerrazzanoUtils.getIstioHost; |
| 59 | +import static oracle.weblogic.kubernetes.utils.VerrazzanoUtils.getLoadbalancerAddress; |
| 60 | +import static oracle.weblogic.kubernetes.utils.VerrazzanoUtils.setLabelToNamespace; |
| 61 | +import static oracle.weblogic.kubernetes.utils.VerrazzanoUtils.verifyVzApplicationAccess; |
| 62 | +import static org.junit.jupiter.api.Assertions.assertAll; |
| 63 | +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; |
| 64 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 65 | +import static org.junit.jupiter.api.Assertions.assertNotEquals; |
| 66 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 67 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 68 | + |
| 69 | +@DisplayName("Test WLS Session Migration when istio is enabled") |
| 70 | +@VzIntegrationTest |
| 71 | +@Tag("v8o") |
| 72 | +class ItVzIstioSessionMigration { |
| 73 | + |
| 74 | + private static String domainNamespace = null; |
| 75 | + |
| 76 | + // constants for creating domain image using model in image |
| 77 | + private static final String SESSMIGR_IMAGE_NAME = "istio-sessmigr-mii-image"; |
| 78 | + |
| 79 | + // constants for web service |
| 80 | + private static final String SESSMIGR_APP_NAME = "sessmigr-app"; |
| 81 | + private static final String SESSMIGR_APP_WAR_NAME = "sessmigr-war"; |
| 82 | + private static final int SESSION_STATE = 4; |
| 83 | + private static Map<String, String> httpAttrMap; |
| 84 | + |
| 85 | + // constants for operator and WebLogic domain |
| 86 | + private static String domainUid = "istio-sessmigr-domain"; |
| 87 | + private static String clusterName = "cluster-1"; |
| 88 | + private static String adminServerPodName = domainUid + "-" + ADMIN_SERVER_NAME_BASE; |
| 89 | + private static String managedServerPrefix = domainUid + "-" + MANAGED_SERVER_NAME_BASE; |
| 90 | + private static int managedServerPort = 7100; |
| 91 | + private static String finalPrimaryServerName = null; |
| 92 | + private static String configMapName = "istio-configmap"; |
| 93 | + private static int replicaCount = 2; |
| 94 | + private static DomainResource domain; |
| 95 | + |
| 96 | + |
| 97 | + private static LoggingFacade logger = null; |
| 98 | + |
| 99 | + private static Map<String, Quantity> resourceRequest = new HashMap<>(); |
| 100 | + private static Map<String, Quantity> resourceLimit = new HashMap<>(); |
| 101 | + |
| 102 | + /** |
| 103 | + * Build custom image using model in image with model files |
| 104 | + * and create a verrazzano application with a dynamic cluster. |
| 105 | + * |
| 106 | + * @param namespaces list of namespaces created by the IntegrationTestWatcher by the |
| 107 | + * JUnit engine parameter resolution mechanism |
| 108 | + */ |
| 109 | + @BeforeAll |
| 110 | + public static void initAll(@Namespaces(1) List<String> namespaces) { |
| 111 | + logger = getLogger(); |
| 112 | + |
| 113 | + logger.info("Assign unique namespace for Domain"); |
| 114 | + assertNotNull(namespaces.get(0), "Namespace list is null"); |
| 115 | + domainNamespace = namespaces.get(0); |
| 116 | + assertDoesNotThrow(() -> setLabelToNamespace(Arrays.asList(domainNamespace))); |
| 117 | + |
| 118 | + // Generate the model.sessmigr.yaml file at RESULTS_ROOT |
| 119 | + String destSessionMigrYamlFile = |
| 120 | + generateNewModelFileWithUpdatedDomainUid(domainUid, "ItVzIstioSessionMigration", getOrigModelFile()); |
| 121 | + |
| 122 | + List<String> appList = new ArrayList<>(); |
| 123 | + appList.add(SESSMIGR_APP_NAME); |
| 124 | + |
| 125 | + // build the model file list |
| 126 | + final List<String> modelList = Collections.singletonList(destSessionMigrYamlFile); |
| 127 | + |
| 128 | + // create image with model files |
| 129 | + logger.info("Create image with model file and verify"); |
| 130 | + String miiImage = createMiiImageAndVerify(SESSMIGR_IMAGE_NAME, modelList, appList); |
| 131 | + |
| 132 | + // repo login and push image to registry if necessary |
| 133 | + imageRepoLoginAndPushImageToRegistry(miiImage); |
| 134 | + |
| 135 | + // set resource request and limit |
| 136 | + resourceRequest.put("cpu", new Quantity("250m")); |
| 137 | + resourceRequest.put("memory", new Quantity("768Mi")); |
| 138 | + resourceLimit.put("cpu", new Quantity("2")); |
| 139 | + resourceLimit.put("memory", new Quantity("2Gi")); |
| 140 | + |
| 141 | + // create secret for admin credentials |
| 142 | + logger.info("Create secret for admin credentials"); |
| 143 | + String adminSecretName = "weblogic-credentials"; |
| 144 | + assertDoesNotThrow(() -> createSecretWithUsernamePassword( |
| 145 | + adminSecretName, |
| 146 | + domainNamespace, |
| 147 | + ADMIN_USERNAME_DEFAULT, |
| 148 | + ADMIN_PASSWORD_DEFAULT), |
| 149 | + String.format("createSecret failed for %s", adminSecretName)); |
| 150 | + |
| 151 | + // create encryption secret |
| 152 | + logger.info("Create encryption secret"); |
| 153 | + String encryptionSecretName = "encryptionsecret"; |
| 154 | + assertDoesNotThrow(() -> createSecretWithUsernamePassword( |
| 155 | + encryptionSecretName, |
| 156 | + domainNamespace, |
| 157 | + "weblogicenc", |
| 158 | + "weblogicenc"), |
| 159 | + String.format("createSecret failed for %s", encryptionSecretName)); |
| 160 | + |
| 161 | + domain = createDomainCrAndVerify(adminSecretName, encryptionSecretName, miiImage); |
| 162 | + createVzApplication(); |
| 163 | + |
| 164 | + // map to save HTTP response data |
| 165 | + httpAttrMap = new HashMap<String, String>(); |
| 166 | + httpAttrMap.put("sessioncreatetime", "(.*)sessioncreatetime>(.*)</sessioncreatetime(.*)"); |
| 167 | + httpAttrMap.put("sessionid", "(.*)sessionid>(.*)</sessionid(.*)"); |
| 168 | + httpAttrMap.put("primary", "(.*)primary>(.*)</primary(.*)"); |
| 169 | + httpAttrMap.put("secondary", "(.*)secondary>(.*)</secondary(.*)"); |
| 170 | + httpAttrMap.put("count", "(.*)countattribute>(.*)</countattribute(.*)"); |
| 171 | + } |
| 172 | + |
| 173 | + /** |
| 174 | + * In an istio enabled Environment, test sends a HTTP request to set http session state(count number), |
| 175 | + * get the primary and secondary server name, session create time and session state and from the util method |
| 176 | + * and save HTTP session info, then stop the primary server by changing ServerStartPolicy to Never and |
| 177 | + * patching domain. Send another HTTP request to get http session state (count number), primary server |
| 178 | + * and session create time. Verify that a new primary server is selected and HTTP session state is migrated. |
| 179 | + */ |
| 180 | + @Test |
| 181 | + @DisplayName("Verify session migration in an istio enabled environment") |
| 182 | + void testSessionMigrationIstioEnabled() { |
| 183 | + final String primaryServerAttr = "primary"; |
| 184 | + final String secondaryServerAttr = "secondary"; |
| 185 | + final String sessionCreateTimeAttr = "sessioncreatetime"; |
| 186 | + final String countAttr = "count"; |
| 187 | + final String webServiceSetUrl = SESSMIGR_APP_WAR_NAME + "/?setCounter=" + SESSION_STATE; |
| 188 | + final String webServiceGetUrl = SESSMIGR_APP_WAR_NAME + "/?getCounter"; |
| 189 | + final String clusterAddress = domainUid + "-cluster-" + clusterName; |
| 190 | + String serverName = managedServerPrefix + "1"; |
| 191 | + |
| 192 | + // send a HTTP request to set http session state(count number) and save HTTP session info |
| 193 | + // before shutting down the primary server |
| 194 | + Map<String, String> httpDataInfo = getServerAndSessionInfoAndVerify(domainNamespace, |
| 195 | + adminServerPodName, serverName, clusterAddress, managedServerPort, webServiceSetUrl, " -c "); |
| 196 | + |
| 197 | + // get server and session info from web service deployed on the cluster |
| 198 | + String origPrimaryServerName = httpDataInfo.get(primaryServerAttr); |
| 199 | + String origSecondaryServerName = httpDataInfo.get(secondaryServerAttr); |
| 200 | + String origSessionCreateTime = httpDataInfo.get(sessionCreateTimeAttr); |
| 201 | + logger.info("Got the primary server {0}, the secondary server {1} " |
| 202 | + + "and session create time {2} before shutting down the primary server.", |
| 203 | + origPrimaryServerName, origSecondaryServerName, origSessionCreateTime); |
| 204 | + |
| 205 | + // stop the primary server by changing ServerStartPolicy to Never and patching domain |
| 206 | + logger.info("Shut down the primary server {0}", origPrimaryServerName); |
| 207 | + shutdownServerAndVerify(domainUid, domainNamespace, origPrimaryServerName); |
| 208 | + |
| 209 | + // send a HTTP request to get server and session info after shutting down the primary server |
| 210 | + serverName = domainUid + "-" + origSecondaryServerName; |
| 211 | + httpDataInfo = getServerAndSessionInfoAndVerify(domainNamespace, adminServerPodName, |
| 212 | + serverName, clusterAddress, managedServerPort, webServiceGetUrl, " -b "); |
| 213 | + |
| 214 | + // get server and session info from web service deployed on the cluster |
| 215 | + String primaryServerName = httpDataInfo.get(primaryServerAttr); |
| 216 | + String sessionCreateTime = httpDataInfo.get(sessionCreateTimeAttr); |
| 217 | + String countStr = httpDataInfo.get(countAttr); |
| 218 | + int count; |
| 219 | + if (countStr.equalsIgnoreCase("null")) { |
| 220 | + count = managedServerPort; |
| 221 | + } else { |
| 222 | + count = Optional.ofNullable(countStr).map(Integer::valueOf).orElse(managedServerPort); |
| 223 | + } |
| 224 | + logger.info("After patching the domain, the primary server changes to {0} " |
| 225 | + + ", session create time {1} and session state {2}", |
| 226 | + primaryServerName, sessionCreateTime, countStr); |
| 227 | + |
| 228 | + // verify that a new primary server is picked and HTTP session state is migrated |
| 229 | + assertAll("Check that WebLogic server and session vars is not null or empty", |
| 230 | + () -> assertNotEquals(origPrimaryServerName, primaryServerName, |
| 231 | + "After the primary server stopped, another server should become the new primary server"), |
| 232 | + () -> assertEquals(origSessionCreateTime, sessionCreateTime, |
| 233 | + "After the primary server stopped, HTTP session state should be migrated to the new primary server"), |
| 234 | + () -> assertEquals(SESSION_STATE, count, |
| 235 | + "After the primary server stopped, HTTP session state should be migrated to the new primary server") |
| 236 | + ); |
| 237 | + |
| 238 | + finalPrimaryServerName = primaryServerName; |
| 239 | + |
| 240 | + logger.info("Done testSessionMigration \nThe new primary server is {0}, it was {1}. " |
| 241 | + + "\nThe session state was set to {2}, it is migrated to the new primary server.", |
| 242 | + primaryServerName, origPrimaryServerName, SESSION_STATE); |
| 243 | + } |
| 244 | + |
| 245 | + private static void createVzApplication() { |
| 246 | + |
| 247 | + Component component = new Component() |
| 248 | + .apiVersion("core.oam.dev/v1alpha2") |
| 249 | + .kind("Component") |
| 250 | + .metadata(new V1ObjectMeta() |
| 251 | + .name(domainUid) |
| 252 | + .namespace(domainNamespace)) |
| 253 | + .spec(new ComponentSpec() |
| 254 | + .workLoad(new Workload() |
| 255 | + .apiVersion("oam.verrazzano.io/v1alpha1") |
| 256 | + .kind("VerrazzanoWebLogicWorkload") |
| 257 | + .spec(new WorkloadSpec() |
| 258 | + .template(domain)))); |
| 259 | + |
| 260 | + Map<String, String> keyValueMap = new HashMap<>(); |
| 261 | + keyValueMap.put("version", "v1.0.0"); |
| 262 | + keyValueMap.put("description", "My vz wls application"); |
| 263 | + |
| 264 | + ApplicationConfiguration application = new ApplicationConfiguration() |
| 265 | + .apiVersion("core.oam.dev/v1alpha2") |
| 266 | + .kind("ApplicationConfiguration") |
| 267 | + .metadata(new V1ObjectMeta() |
| 268 | + .name("myvzsessiondomain") |
| 269 | + .namespace(domainNamespace) |
| 270 | + .annotations(keyValueMap)) |
| 271 | + .spec(new ApplicationConfigurationSpec() |
| 272 | + .components(Arrays.asList(new Components() |
| 273 | + .componentName(domainUid) |
| 274 | + .traits(Arrays.asList(new IngressTraits() |
| 275 | + .trait(new IngressTrait() |
| 276 | + .apiVersion("oam.verrazzano.io/v1alpha1") |
| 277 | + .kind("IngressTrait") |
| 278 | + .metadata(new V1ObjectMeta() |
| 279 | + .name("mydomain-ingress") |
| 280 | + .namespace(domainNamespace)) |
| 281 | + .spec(new IngressTraitSpec() |
| 282 | + .ingressRules(Arrays.asList( |
| 283 | + new IngressRule() |
| 284 | + .paths(Arrays.asList(new Path() |
| 285 | + .path("/console") |
| 286 | + .pathType("Prefix"))) |
| 287 | + .destination(new Destination() |
| 288 | + .host(adminServerPodName) |
| 289 | + .port(7001)), |
| 290 | + new IngressRule() |
| 291 | + .paths(Arrays.asList(new Path() |
| 292 | + .path("/sessmigr-app") |
| 293 | + .pathType("Prefix"))) |
| 294 | + .destination(new Destination() |
| 295 | + .host(domainUid + "-cluster-" + clusterName) |
| 296 | + .port(managedServerPort))))))))))); |
| 297 | + |
| 298 | + logger.info(Yaml.dump(component)); |
| 299 | + logger.info(Yaml.dump(application)); |
| 300 | + |
| 301 | + logger.info("Deploying components"); |
| 302 | + assertDoesNotThrow(() -> createComponent(component)); |
| 303 | + logger.info("Deploying application"); |
| 304 | + assertDoesNotThrow(() -> createApplication(application)); |
| 305 | + |
| 306 | + // check admin server pod is ready |
| 307 | + logger.info("Wait for admin server pod {0} to be ready in namespace {1}", |
| 308 | + adminServerPodName, domainNamespace); |
| 309 | + checkPodReadyAndServiceExists(adminServerPodName, domainUid, domainNamespace); |
| 310 | + // check managed server pods are ready |
| 311 | + for (int i = 1; i <= replicaCount; i++) { |
| 312 | + logger.info("Wait for managed server pod {0} to be ready in namespace {1}", |
| 313 | + managedServerPrefix + i, domainNamespace); |
| 314 | + checkPodReadyAndServiceExists(managedServerPrefix + i, domainUid, domainNamespace); |
| 315 | + } |
| 316 | + |
| 317 | + // get istio gateway host and loadbalancer address |
| 318 | + String host = getIstioHost(domainNamespace); |
| 319 | + String address = getLoadbalancerAddress(); |
| 320 | + |
| 321 | + // verify WebLogic console page is accessible through istio/loadbalancer |
| 322 | + String message = "Oracle WebLogic Server Administration Console"; |
| 323 | + String consoleUrl = "https://" + host + "/console/login/LoginForm.jsp --resolve " + host + ":443:" + address; |
| 324 | + assertTrue(verifyVzApplicationAccess(consoleUrl, message), "Failed to get WebLogic administration console"); |
| 325 | + } |
| 326 | + |
| 327 | + private static DomainResource createDomainCrAndVerify(String adminSecretName, |
| 328 | + String encryptionSecretName, |
| 329 | + String miiImage) { |
| 330 | + |
| 331 | + // Create the repo secret to pull the image |
| 332 | + // this secret is used only for non-kind cluster |
| 333 | + createTestRepoSecret(domainNamespace); |
| 334 | + |
| 335 | + // create WDT config map without any files |
| 336 | + createConfigMapAndVerify(configMapName, domainUid, domainNamespace, Collections.emptyList()); |
| 337 | + |
| 338 | + // create the domain object |
| 339 | + DomainResource domain = createIstioDomainResource(domainUid, |
| 340 | + domainNamespace, |
| 341 | + adminSecretName, |
| 342 | + TEST_IMAGES_REPO_SECRET_NAME, |
| 343 | + encryptionSecretName, |
| 344 | + replicaCount, |
| 345 | + miiImage, |
| 346 | + configMapName, |
| 347 | + clusterName); |
| 348 | + return domain; |
| 349 | + } |
| 350 | + |
| 351 | +} |
0 commit comments