|  | 
|  | 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