diff --git a/documentation/staging/content/userguide/istio/istio.md b/documentation/staging/content/userguide/istio/istio.md index 14e43d4dcbd..99adc3d7a02 100644 --- a/documentation/staging/content/userguide/istio/istio.md +++ b/documentation/staging/content/userguide/istio/istio.md @@ -142,6 +142,38 @@ If the WebLogic administration port is enabled on the Administration Server: Additionally, when Istio support is enabled for a domain, the operator ensures that the Istio sidecar is not injected into the introspector job's pods. +#### Support for network changes in Istio v1.10 and later + +Starting with Istio 1.10, the networking behavior was changed in that the proxy no longer redirects +the traffic to the localhost interface, but instead forwards it to the network interface associated +with the pod's IP. + +To learn more about changes to Istio networking beginning with Istio 1.10, see [Upcoming networking changes in Istio 1.10](https://istio.io/latest/blog/2021/upcoming-networking-changes/). + +In order to support Istio v1.10 and later, as well as previous releases, the +operator will: + +* Add an additional WebLogic HTTP protocol network channel for the readiness probe that is bound to the localhost network interface. +* Add additional WebLogic network channels, bound to the localhost network interface, for each defined custom network channel. +* Continue to automatically add the network channels described above in [How Istio-enabled domains differ from regular domains](#how-istio-enabled-domains-differ-from-regular-domains) + +When adding additional WebLogic network channels for the readiness probe and any defined custom channels, +the name of the additional channel will be appended with '-lhNN', where NN represents +a two digit value for uniqueness. + +For example, the additional WebLogic HTTP protocol network channel for the readiness probe would be +defined as follows: + +|Name|Port|Listening address|Protocol|Exposed as a container port| +|----|----|----|--------|-----| +|`http-probe-lh01`|From configuration Istio `readinessPort` | `127.0.0.1` | `http`| No | + +As another example, for a custom WebLogic network channel defined as `T3Channel` with port `5556` +and protocol `t3`, the additional channel would be defined as follows: + +|Name|Port|Listening address|Protocol|Exposed as a container port| +|----|----|----|--------|-----| +|`T3Channel-lh01`| `5556` | `127.0.0.1` | `t3`| Yes | ### Apply the Domain YAML file diff --git a/operator/src/main/resources/scripts/introspectDomain.py b/operator/src/main/resources/scripts/introspectDomain.py index 1098a053014..9e08834e96e 100644 --- a/operator/src/main/resources/scripts/introspectDomain.py +++ b/operator/src/main/resources/scripts/introspectDomain.py @@ -99,6 +99,8 @@ from utils import * from weblogic.management.configuration import LegalHelper +ISTIO_NAP_NAMES = ['tcp-cbt', 'tcp-ldap', 'tcp-iiop', 'tcp-snmp', 'http-default', 'tcp-default', 'https-secure', 'tls-ldaps', 'tls-default', 'tls-cbts', 'tls-iiops', 'https-admin'] +WLS_LOCALHOST_IDENTIFIER = '-lh' class OfflineWlstEnv(object): @@ -751,7 +753,8 @@ def addNetworkAccessPoint(self, server, nap, is_server_template): if istio_enabled == 'true': name = nap.getName() - if name.startswith('http-') or name.startswith('tcp-') or name.startswith('tls-') or name.startswith('https-'): + if name.startswith('http-') or name.startswith('tcp-') or name.startswith('tls-') \ + or name.startswith('https-') or nameContainsLocalHostIdentifier(name): # skip istio ports already defined by WDT filtering for MII return http_protocol = [ 'http' ] @@ -1056,6 +1059,222 @@ def writeListenAddress(self, originalValue, newValue): repVerb="\"add\"" self.writeln("" + newValue + "") + def writeName(self, name): + self.writeln("" + name + "") + + def writeProtocol(self, protocol): + self.writeln("" + protocol + "") + + def writeListenPort(self, listen_port): + # offline WLST initializes int values to 0 + if listen_port > 0: + self.writeln("" + str(listen_port) + "") + + def writePublicAddress(self, public_address): + if public_address is not None and len(public_address) > 0: + self.writeln("" + public_address + "") + + def writePublicPort(self, public_port): + # offline WLST initializes int values to 0 + if public_port > 0: + self.writeln("" + str(public_port) + "") + + def writeAcceptBacklog(self, accept_backlog): + # offline WLST initializes int values to 0 + if accept_backlog > 0: + self.writeln("" + str(accept_backlog) + "") + + def writeMaxBackoffBetweenFailures(self, maxBackoffBetweenFailures): + if maxBackoffBetweenFailures > 0: + self.writeln("" + str(maxBackoffBetweenFailures) + "") + + def writeLoginTimeoutMillis(self, loginTimeoutMillis): + # offline WLST initializes int values to 0 + if loginTimeoutMillis > 0: + self.writeln("" + str(loginTimeoutMillis) + "") + + def writeCompleteMessageTimeout(self, completeMessageTimeout): + # offline WLST initializes int values to 0 + if completeMessageTimeout > 0: + self.writeln("" + str(completeMessageTimeout) + "") + + def writeIdleConnectionTimeout(self, idleConnectionTimeout): + # offline WLST initializes int values to 0 + if idleConnectionTimeout > 0: + self.writeln("" + str(idleConnectionTimeout) + "") + + def writeConnectTimeout(self, connectTimeout): + # offline WLST initializes int values to 0 + if connectTimeout > 0: + self.writeln("" + str(connectTimeout) + "") + + def writeTunnelingClientPingSecs(self, tunnelingClientPingSecs): + # offline WLST initializes int values to 0 + if tunnelingClientPingSecs > 0: + self.writeln("" + str(tunnelingClientPingSecs) + "") + + def writeTunnelingClientTimeoutSecs(self, tunnelingClientTimeoutSecs): + # offline WLST initializes int values to 0 + if tunnelingClientTimeoutSecs > 0: + self.writeln("" + str(tunnelingClientTimeoutSecs) + "") + + def writeMaxMessageSize(self, maxMessageSize): + # offline WLST initializes int values to 0 + # Legal minimum max message size is 4096 + # Allow WLS to check legal range + if maxMessageSize > 0: + self.writeln("" + str(maxMessageSize) + "") + + def writeChannelWeight(self, channelWeight): + # default is 50 + if channelWeight != 50: + self.writeln("" + str(channelWeight) + "") + + def writeMaxConnectedClients(self, maxConnectedClients): + # default = java.lang.Integer.MAX_VALUE + if maxConnectedClients != 2147483647: + self.writeln("" + str(maxConnectedClients) + "") + + def writeEnabled(self, nap): + # default enabled = 'true' + if nap.isEnabled() == false: + enabled = 'false' + self.writeln("" + enabled + "") + + def writeTunnelingEnabled(self, nap): + # default tunnelingEnabled = 'false' + if nap.isTunnelingEnabled() == true: + tunnelingEnabled = 'true' + self.writeln("" + tunnelingEnabled + "") + + def writeOutboundEnabled(self, nap): + # default outboundEnabled = 'false' + if nap.isOutboundEnabled() == true: + outboundEnabled = 'true' + self.writeln("" + outboundEnabled + "") + + def writeUseFastSerialization(self, nap): + # default useFastSerialization = 'false' + if nap.getUseFastSerialization() == true: + useFastSerialization = 'true' + self.writeln("" + useFastSerialization + "") + + def writeHttpEnabledForThisProtocol(self, nap): + # default httpEnabledForThisProtocol = 'true' + if nap.isHttpEnabledForThisProtocol() == false: + httpEnabledForThisProtocol = 'false' + self.writeln("" + httpEnabledForThisProtocol + "") + + def writeTimeoutConnectionWithPendingResponses(self, nap): + # default timeoutConnectionWithPendingResponses = 'false' + if nap.getTimeoutConnectionWithPendingResponses() == true: + timeoutConnectionWithPendingResponses = 'true' + self.writeln("" + timeoutConnectionWithPendingResponses + "") + + def writeSDPEnabled(self, nap): + # default sdpEnabled = 'false' + if nap.isSDPEnabled() == true: + sdpEnabled = 'true' + self.writeln("" + sdpEnabled + "") + + def writeTwoWaySSLEnabled(self, nap): + # default twoWaySSLEnabled = 'false' + if nap.isTwoWaySSLEnabled() == true: + twoWaySSLEnabled = 'true' + self.writeln("" + twoWaySSLEnabled + "") + + def writeClientCertificateEnforced(self, nap): + # default clientCertificateEnforced = 'false' + if nap.isClientCertificateEnforced() == true: + clientCertificateEnforced = 'true' + self.writeln("" + clientCertificateEnforced + "") + + def writeChannelIdentityCustomized(self, nap): + # default channelIdentityCustomized = 'false' + if nap.isChannelIdentityCustomized() == true: + channelIdentityCustomized = 'true' + self.writeln("" + channelIdentityCustomized + "") + + def writeCustomPrivateKeyAlias(self, nap): + customPrivateKeyAlias = nap.getCustomPrivateKeyAlias() + if customPrivateKeyAlias is not None: + self.writeln("" + customPrivateKeyAlias + "") + + def writeCustomPrivateKeyPassPhraseEncrypted(self, nap): + customPriveKeyPassPhraseEncrypted = nap.getCustomPrivateKeyPassPhraseEncrypted() + if customPriveKeyPassPhraseEncrypted is not None and len(customPriveKeyPassPhraseEncrypted) > 0: + self.writeln("" + customPriveKeyPassPhraseEncrypted + "") + + def writeCustomIdentityKeyStoreType(self, nap): + customIdentityKeyStoreType = nap.getCustomIdentityKeyStoreType() + if customIdentityKeyStoreType is not None and len(customIdentityKeyStoreType) > 0: + self.writeln("" + customIdentityKeyStoreType + "") + + def writeCustomIdentityKeyStorePassPhraseEncrypted(self, nap): + customIdentityKeyStorePassPhraseEncrypted = nap.getCustomIdentityKeyStorePassPhraseEncrypted() + if customIdentityKeyStorePassPhraseEncrypted is not None and len(customIdentityKeyStorePassPhraseEncrypted) > 0: + self.writeln("" + customIdentityKeyStorePassPhraseEncrypted + "") + + def writeHostnameVerificationIgnored(self, nap): + # default hostnameVerificationIgnored = 'false' + if nap.isHostnameVerificationIgnored() == true: + hostnameVerificationIgnored = 'true' + self.writeln("" + hostnameVerificationIgnored + "") + + def writeHostnameVerifier(self, nap): + hostnameVerifier = nap.getHostnameVerifier() + if hostnameVerifier is not None and len(hostnameVerifier) > 0: + self.writeln("" + hostnameVerifier + "") + + def writeCiphersuites(self, nap): + ciphersuites = nap.getCiphersuites() + if ciphersuites is not None: + for cipher in ciphersuites: + self.writeln("" + cipher + "") + + def writeAllowUnencryptedNullCipher(self, nap): + # default allowUnencryptedNullCipher = 'false' + if nap.isAllowUnencryptedNullCipher() == true: + allowUnencryptedNullCipher = 'true' + self.writeln("" + allowUnencryptedNullCipher + "") + + def writeInboundCertificateValidation(self, nap): + inboundCertificateValidation = nap.getInboundCertificateValidation() + if inboundCertificateValidation is not None and len(inboundCertificateValidation) > 0: + self.writeln("" + inboundCertificateValidation + "") + + def writeOutboundCertificateValidation(self, nap): + outboundCertificateValidation = nap.getOutboundCertificateValidation() + if outboundCertificateValidation is not None and len(outboundCertificateValidation) > 0: + self.writeln("" + outboundCertificateValidation + "") + + def createNameForLocalHostNetworkAccessPoint(self, nap_name, nap_name_dict): + # NAP names can be a maximum of 15 characters in length + key = nap_name + idx = 1 + if len(nap_name) >= 10: + # Slice out the first 10 characters to use since there is a 15 character + # limit to NAP names + key = nap_name[:10] + if key not in nap_name_dict: + # Add the first nap name with index=1 into the Dictionary + nap_name_dict[key] = idx + else: + # Found a nap with the same first 10 characters so increment and + # save the index + idx = nap_name_dict[key] + 1 + nap_name_dict[key] = idx + + # zero fill to the left for single digit index (e.g. 01) + idx_str = str(idx) + if idx < 10: + idx_str = '0' + idx_str + + # NAP name of for localhost binding will be of the form 'xxxxxxxxxx-lh01' + return key + WLS_LOCALHOST_IDENTIFIER + idx_str + + + def customizeServer(self, server): name=server.getName() listen_address=self.env.toDNS1123Legal(self.env.getDomainUID() + "-" + name) @@ -1097,8 +1316,10 @@ def customizeServerTemplate(self, template): self.writeln("") def customizeNetworkAccessPoints(self, server, listen_address): + nap_name_dict = {} for nap in server.getNetworkAccessPoints(): self.customizeNetworkAccessPoint(nap,listen_address) + self.createLocalHostNetworkAccessPoint(nap, '127.0.0.1', listen_address, nap_name_dict) def customizeNetworkAccessPoint(self, nap, listen_address): # Don't bother 'add' a nap listen-address, only do a 'replace'. @@ -1110,18 +1331,86 @@ def customizeNetworkAccessPoint(self, nap, listen_address): istio_enabled = self.env.getEnvOrDef("ISTIO_ENABLED", "false") nap_name=nap.getName() + if nap_name in ISTIO_NAP_NAMES or nameContainsLocalHostIdentifier(nap_name): + # skip customizing internal channels that were generated + return + + # replace listen address to bind to server pod IP if not (nap.getListenAddress() is None) and len(nap.getListenAddress()) > 0: self.writeln("") self.indent() self.writeln("" + nap_name + "") - if istio_enabled == 'true': - self.writeListenAddress("force a replace", '127.0.0.1') - else: - self.writeListenAddress("force a replace",listen_address) - + self.writeListenAddress("force a replace",listen_address) self.undent() self.writeln("") + # Create copy of custom NAP for binding to localhost for handling k8s 'port-forward' + # feature and Istio versions < 1.10.x + def createLocalHostNetworkAccessPoint(self, nap, listen_address, public_listen_address, nap_name_dict): + # Don't bother 'add' a nap listen-address, only do a 'replace'. + # If we try 'add' this appears to mess up an attempt to + # 'add' PublicAddress/Port via custom sit-cfg. + # FWIW there's theoretically no need to 'add' or 'replace' when empty + # since the runtime default is the server listen-address. + + istio_enabled = self.env.getEnvOrDef("ISTIO_ENABLED", "false") + if istio_enabled == 'true': + nap_name=nap.getName() + if nap_name in ISTIO_NAP_NAMES or nameContainsLocalHostIdentifier(nap_name): + # skip creating ISTIO channels + return + + self.writeln('') + self.indent() + self.writeName(self.createNameForLocalHostNetworkAccessPoint(nap_name, nap_name_dict)) + self.writeGeneralNetworkAccessPointConfiguration(nap, listen_address, public_listen_address) + self.writeSecureNetworkAccessPointConfiguration(nap) + self.undent() + self.writeln("") + + def writeGeneralNetworkAccessPointConfiguration(self, nap, listen_address, public_listen_address): + self.writeProtocol(nap.getProtocol()) + self.writeListenAddress("", listen_address) + self.writeListenPort(nap.getListenPort()) + self.writePublicAddress(public_listen_address) + self.writePublicPort(nap.getPublicPort()) + self.writeEnabled(nap) + self.writeOutboundEnabled(nap) + self.writeAcceptBacklog(nap.getAcceptBacklog()) + self.writeMaxBackoffBetweenFailures(nap.getMaxBackoffBetweenFailures()) + self.writeHttpEnabledForThisProtocol(nap) + self.writeLoginTimeoutMillis(nap.getLoginTimeoutMillis()) + self.writeCompleteMessageTimeout(nap.getCompleteMessageTimeout()) + self.writeIdleConnectionTimeout(nap.getIdleConnectionTimeout()) + self.writeConnectTimeout(nap.getConnectTimeout()) + self.writeTimeoutConnectionWithPendingResponses(nap) + self.writeTunnelingEnabled(nap) + self.writeTunnelingClientPingSecs(nap.getTunnelingClientPingSecs()) + self.writeTunnelingClientTimeoutSecs(nap.getTunnelingClientTimeoutSecs()) + self.writeMaxMessageSize(nap.getMaxMessageSize()) + self.writeChannelWeight(nap.getChannelWeight()) + self.writeMaxConnectedClients(nap.getMaxConnectedClients()) + self.writeUseFastSerialization(nap) + self.writeSDPEnabled(nap) + + def writeSecureNetworkAccessPointConfiguration(self, nap): + protocol = nap.getProtocol() + if protocol.endswith('s') or protocol == 'admin': + self.writeTwoWaySSLEnabled(nap) + self.writeClientCertificateEnforced(nap) + self.writeChannelIdentityCustomized(nap) + self.writeCustomPrivateKeyAlias(nap) + self.writeCustomPrivateKeyPassPhraseEncrypted(nap) + self.writeCustomIdentityKeyStoreType(nap) + self.writeCustomIdentityKeyStorePassPhraseEncrypted(nap) + self.writeHostnameVerificationIgnored(nap) + self.writeHostnameVerifier(nap) + self.writeCiphersuites(nap) + self.writeAllowUnencryptedNullCipher(nap) + self.writeInboundCertificateValidation(nap) + self.writeOutboundCertificateValidation(nap) + + def _getNapConfigOverrideAction(self, svr, testname): replace_action = 'f:combine-mode="replace"' add_action = 'f:combine-mode="add"' @@ -1155,7 +1444,11 @@ def _writeIstioNAP(self, name, server, listen_address, listen_port, protocol, ht self.writeln('%s' % name) self.writeln('%s' % (action, protocol)) - self.writeln('127.0.0.1' % action) + if name == 'http-probe': + self.writeln('%s.%s' % (action, listen_address, + self.env.getEnvOrDef("ISTIO_POD_NAMESPACE", "default"))) + else: + self.writeln('127.0.0.1' % action) self.writeln('%s.%s' % (action, listen_address, self.env.getEnvOrDef("ISTIO_POD_NAMESPACE", "default"))) self.writeln('%s' % (action, listen_port)) @@ -1210,6 +1503,9 @@ def customizeServerIstioNetworkAccessPoint(self, listen_address, server): self._writeIstioNAP(name='http-probe', server=server, listen_address=listen_address, listen_port=istio_readiness_port, protocol='http', http_enabled="true") + self._writeIstioNAP(name=self.createNameForLocalHostNetworkAccessPoint('http-probe', {}), server=server, listen_address=listen_address, + listen_port=istio_readiness_port, protocol='http', http_enabled="true") + # Generate NAP for each protocols self._writeIstioNAP(name='tcp-ldap', server=server, listen_address=listen_address, listen_port=admin_server_port, protocol='ldap') @@ -1264,6 +1560,9 @@ def customizeManagedIstioNetworkAccessPoint(self, listen_address, template): self._writeIstioNAP(name='http-probe', server=template, listen_address=listen_address, listen_port=istio_readiness_port, protocol='http') + self._writeIstioNAP(name=self.createNameForLocalHostNetworkAccessPoint('http-probe', {}), server=template, listen_address=listen_address, + listen_port=istio_readiness_port, protocol='http') + self._writeIstioNAP(name='tcp-default', server=template, listen_address=listen_address, listen_port=listen_port, protocol='t3', http_enabled='false') @@ -1876,6 +2175,9 @@ def get_server_template_listening_ports_from_configxml(config_xml): return server_template_ssls, server_template_ports +def nameContainsLocalHostIdentifier(name): + return name.find(WLS_LOCALHOST_IDENTIFIER) > -1 + def main(env): try: # Needs to build the domain first diff --git a/operator/src/main/resources/scripts/model_wdt_mii_filter.py b/operator/src/main/resources/scripts/model_wdt_mii_filter.py index 87326c94238..28a604e5062 100644 --- a/operator/src/main/resources/scripts/model_wdt_mii_filter.py +++ b/operator/src/main/resources/scripts/model_wdt_mii_filter.py @@ -44,7 +44,7 @@ # configuration. # - +import copy import inspect import os import sys @@ -55,12 +55,20 @@ sys.path.append(tmp_scriptdir) env = None +ISTIO_NAP_NAMES = ['tcp-cbt', 'tcp-ldap', 'tcp-iiop', 'tcp-snmp', 'http-default', 'tcp-default', 'https-secure', 'tls-ldaps', 'tls-default', 'tls-cbts', 'tls-iiops', 'https-admin'] +WLS_LOCALHOST_IDENTIFIER = '-lh' + class OfflineWlstEnv(object): def open(self, model): self.model = model + + # Dictionary to track the count of naps that have names > 15 characters + # key = 10 char name, value = index count + #self.nap_name_dict = {} + # before doing anything, get each env var and verify it exists self.DOMAIN_UID = self.getEnv('DOMAIN_UID') @@ -370,7 +378,7 @@ def getSSLOrNone(server): return server['SSL'] -def _writeIstioNAP(name, server, listen_address, listen_port, protocol, http_enabled="true"): +def _writeIstioNAP(name, server, listen_address, listen_port, protocol, http_enabled="true", bind_to_localhost="true"): if 'NetworkAccessPoint' not in server: server['NetworkAccessPoint'] = {} @@ -381,7 +389,10 @@ def _writeIstioNAP(name, server, listen_address, listen_port, protocol, http_ena nap = naps[name] nap['Protocol'] = protocol - nap['ListenAddress'] = '127.0.0.1' + if bind_to_localhost == 'true': + nap['ListenAddress'] = '127.0.0.1' + else: + nap['ListenAddress'] = '%s.%s' % (listen_address, env.getEnvOrDef("ISTIO_POD_NAMESPACE", "default")) nap['PublicAddress'] = '%s.%s' % (listen_address, env.getEnvOrDef("ISTIO_POD_NAMESPACE", "default")) nap['ListenPort'] = listen_port nap['HttpEnabledForThisProtocol'] = http_enabled @@ -405,7 +416,12 @@ def customizeServerIstioNetworkAccessPoint(server, listen_address): # readiness probe _writeIstioNAP(name='http-probe', server=server, listen_address=listen_address, - listen_port=istio_readiness_port, protocol='http', http_enabled="true") + listen_port=istio_readiness_port, protocol='http', http_enabled="true", + bind_to_localhost="false") + + # readiness probe NAP binding to localhost + _writeIstioNAP(name=createNameForLocalHostNetworkAccessPoint('http-probe', {}), server=server, listen_address=listen_address, + listen_port=istio_readiness_port, protocol='http', http_enabled="true") # Generate NAP for each protocols _writeIstioNAP(name='tcp-ldap', server=server, listen_address=listen_address, @@ -471,6 +487,11 @@ def customizeManagedIstioNetworkAccessPoint(template, listen_address): listen_port = 7001 # readiness probe _writeIstioNAP(name='http-probe', server=template, listen_address=listen_address, + listen_port=istio_readiness_port, protocol='http', http_enabled="true", + bind_to_localhost="false") + + # readiness probe NAP binding to localhost address for Istio versions < 1.10.x + _writeIstioNAP(name=createNameForLocalHostNetworkAccessPoint('http-probe', {}), server=template, listen_address=listen_address, listen_port=istio_readiness_port, protocol='http', http_enabled="true") # Generate NAP for each protocols @@ -525,22 +546,88 @@ def customizeNetworkAccessPoints(server, listen_address): naps = server['NetworkAccessPoint'] nap_names = naps.keys() + # Dictionary to track the count of naps that have names > 15 characters + # key = 10 char name, value = index count + nap_name_dict = {} + # Dictionary of LocalHost NAP's created + local_nap_dict = {} for nap_name in nap_names: nap = naps[nap_name] - customizeNetworkAccessPoint(nap, listen_address) + customizeNetworkAccessPoint(nap_name, nap, listen_address) + createLocalHostNetworkAccessPoint(nap_name, nap, nap_name_dict, local_nap_dict) + # Iterate through the Dictionary of cloned local NAP's and add to the NetworkAccesPoint + # list of the model + local_nap_names = local_nap_dict.keys() + for local_nap_name in local_nap_names: + server['NetworkAccessPoint'][local_nap_name] = local_nap_dict[local_nap_name] -def customizeNetworkAccessPoint(nap, listen_address): - istio_enabled = env.getEnvOrDef("ISTIO_ENABLED", "false") +def customizeNetworkAccessPoint(nap_name, nap, listen_address): + if nap_name in ISTIO_NAP_NAMES or nameContainsLocalHostIdentifier(nap_name): + # skip creating ISTIO channels + return + + # fix NAP listen address if 'ListenAddress' in nap: original_listen_address = nap['ListenAddress'] if len(original_listen_address) > 0: - if istio_enabled == 'true': - nap['ListenAddress'] = '127.0.0.1' - else: nap['ListenAddress'] = listen_address +# Create copy of custom NAP for binding to localhost for handling k8s 'port-forward' +# feature and Istio versions < 1.10.x +def createLocalHostNetworkAccessPoint(nap_name, nap, nap_name_dict, local_nap_dict): + istio_enabled = env.getEnvOrDef("ISTIO_ENABLED", "false") + if istio_enabled == 'true': + if nap_name in ISTIO_NAP_NAMES or nameContainsLocalHostIdentifier(nap_name): + # skip creating ISTIO channels + return + + wls_local_nap = copy.deepcopy(nap) + wls_local_nap['ListenAddress'] = '127.0.0.1' + local_nap_name = createNameForLocalHostNetworkAccessPoint(nap_name, nap_name_dict) + local_nap_dict[local_nap_name] = wls_local_nap + +def createNameForLocalHostNetworkAccessPoint(nap_name, nap_name_dict): + # NAP names can be a maximum of 15 characters in length + key = nap_name + idx = 1 + if len(nap_name) >= 10: + # Slice out the first 10 characters to use since there is a 15 character + # limit to NAP names + key = nap_name[:10] + if key not in nap_name_dict: + # Add the first nap name with index=1 into the Dictionary + nap_name_dict[key] = idx + else: + # Found a nap with the same first 10 characters so increment and + # save the index + idx = nap_name_dict[key] + 1 + nap_name_dict[key] = idx + + # zero fill to the left for single digit index (e.g. 01) + idx_str = str(idx) + if idx < 10: + idx_str = '0' + idx_str + + # NAP name of for localhost binding will be of the form 'xxxxxxxxxx-lh01' + return key + WLS_LOCALHOST_IDENTIFIER + idx_str + +def nameContainsLocalHostIdentifier(name): + # look for '-lh' + identifierIdx = name.find(WLS_LOCALHOST_IDENTIFIER) + # check if there is a localhost identifier + if identifierIdx > -1: + endIdentifierIdx = identifierIdx + len(WLS_LOCALHOST_IDENTIFIER) + # get substring from localhost identifier to end + subStr = name[endIdentifierIdx:] + # should be only 2 digits 'NN' from '-lhNN' format + if len(subStr) == 2: + # verify the last two chars are digits + if subStr.isdigit(): + return True + + return False def setServerListenAddress(serverOrTemplate, listen_address): serverOrTemplate['ListenAddress'] = listen_address diff --git a/operator/src/test/python/test_wdt_mii_filter.py b/operator/src/test/python/test_wdt_mii_filter.py index ccca77c2355..bcc9e697617 100644 --- a/operator/src/test/python/test_wdt_mii_filter.py +++ b/operator/src/test/python/test_wdt_mii_filter.py @@ -8,7 +8,7 @@ class WdtUpdateFilterCase(unittest.TestCase): - ISTIO_NAP_NAMES = ['tcp-cbt', 'tcp-ldap', 'tcp-iiop', 'tcp-snmp', 'http-probe', 'http-default', 'tcp-default'] + ISTIO_NAP_NAMES = ['tcp-cbt', 'tcp-ldap', 'tcp-iiop', 'tcp-snmp', 'http-probe', 'http-probe' + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01', 'http-default', 'tcp-default'] def setUp(self): self.initialize_environment_variables() @@ -147,7 +147,17 @@ def test_customize_istio_enabled_network_access_points_in_server_template(self): server_template = self.getServerTemplate(model) model_wdt_mii_filter.customizeNetworkAccessPoints(server_template, 'sample-domain1-managed-server${id}') nap_listen_address = model['topology']['ServerTemplate']['cluster-1-template']['NetworkAccessPoint']['T3Channel']['ListenAddress'] - self.assertEqual('127.0.0.1', nap_listen_address, "Expected nap listen address to be \'127.0.0.1\'") + self.assertEqual('sample-domain1-managed-server${id}', nap_listen_address, "Expected nap listen address to be \'sample-domain1-managed-server${id}\'") + + # Assert LocalHost channel listen address is '127.0.0.1' + nap_local_host = model['topology']['ServerTemplate']['cluster-1-template']['NetworkAccessPoint']['T3Channel' + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01']['ListenAddress'] + self.assertEqual('127.0.0.1', nap_local_host, "Expected nap listen address to be \'127.0.0.1\'") + + # Verify we don't duplicate channels more than once + model_wdt_mii_filter.customizeNetworkAccessPoints(server_template, 'sample-domain1-managed-server${id}') + naps = server_template['NetworkAccessPoint'] + nap_names = naps.keys() + self.assertEqual(2, len(nap_names), "Expected only two NAPS") finally: del os.environ['ISTIO_ENABLED'] @@ -236,6 +246,86 @@ def test_readDomainNameFromTopologyYaml(self): domain_name = model_wdt_mii_filter.env.getDomainName() self.assertEqual('wls-domain1', domain_name, "Expected domain name to be \'wls-domain1\'") + def test_createNameForLocalHostNetworkAccessPoint(self): + model = self.getModel() + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint('http-probe', {}) + self.assertEqual('http-probe' + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01', nap_name_copy, "Expected name to be http-probe" + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01') + + def test_createNameForLocalHostNetworkAccessPoint_with_long_name(self): + nap_name_10_chars = 'abcdefghij' + nap_name_15_chars = nap_name_10_chars + 'klmno' + nap_name_20_chars = nap_name_15_chars + 'pqrst' + + model = self.getModel() + nap_name_dict = {} + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_10_chars, nap_name_dict) + self.assertEqual(nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01', nap_name_copy, "Expected name to be " + nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01') + + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_15_chars, nap_name_dict) + self.assertEqual(nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '02', nap_name_copy, "Expected 15 character name to be " + nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '02') + + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + self.assertEqual(nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '03', nap_name_copy, "Expected name to be " + nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '03') + + def test_createNameForLocalHostNetworkAccessPoint_with_double_digit_idx(self): + nap_name_10_chars = 'abcdefghij' + nap_name_20_chars = nap_name_10_chars + 'klmnopqrst' + nap_name_dict = {} + + # 01 + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + self.assertEqual(nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01', nap_name_copy, "Expected name to be " + nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01') + + # 02 + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + self.assertEqual(nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '02', nap_name_copy, "Expected name to be " + nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01') + + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + # 10 + nap_name_copy = model_wdt_mii_filter.createNameForLocalHostNetworkAccessPoint(nap_name_20_chars, nap_name_dict) + self.assertEqual(nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '10', nap_name_copy, "Expected name to be " + nap_name_10_chars + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '10') + + def test_createLocalHostNetworkAccessPoint(self): + try: + os.environ['ISTIO_ENABLED'] = 'true' + model = self.getModel() + server_template = self.getServerTemplate(model) + naps = server_template['NetworkAccessPoint'] + + nap_name_dict = {} + local_nap_dict = {} + nap = naps['T3Channel'] + model_wdt_mii_filter.createLocalHostNetworkAccessPoint('T3Channel', + nap, nap_name_dict, local_nap_dict) + self.assertEqual(1, len(local_nap_dict), "Expected only one dictionary entry") + nap = local_nap_dict['T3Channel' + + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01'] + self.assertEqual('127.0.0.1', nap['ListenAddress'], + "Expected ListenAddress to be '\127.0.0.1\'") + finally: + del os.environ['ISTIO_ENABLED'] + + def test_nameContainsLocalHostIdentifier(self): + channelName = 'abced' + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + '01' + self.assertTrue(model_wdt_mii_filter.nameContainsLocalHostIdentifier(channelName), + "Expected name to contain " + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER) + + def test_nameDoesNotContainsLocalHostIdentifier(self): + channelName = 'abcedefg-l01' + self.assertFalse(model_wdt_mii_filter.nameContainsLocalHostIdentifier(channelName), + "Expected name to not contain " + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER) + + def test_localHostIdentifierDoesNotContainDigits(self): + channelName = 'abced' + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER + 'o1' + self.assertFalse(model_wdt_mii_filter.nameContainsLocalHostIdentifier(channelName), + "Expected name to not contain " + model_wdt_mii_filter.WLS_LOCALHOST_IDENTIFIER) + class MockOfflineWlstEnv(model_wdt_mii_filter.OfflineWlstEnv): WLS_CRED_USERNAME = 'weblogic'