Skip to content

Commit 6c07628

Browse files
authored
Update Verrazzano CRD ingress traits with application information (#1380)
* Update Verrazzano CRD ingress traits with application information * Update unit test to verify ingress trait rule merging for Verrazzano
1 parent e01581e commit 6c07628

File tree

7 files changed

+239
-6
lines changed

7 files changed

+239
-6
lines changed

core/src/main/python/wlsdeploy/tool/util/targets/additional_output_helper.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
Copyright (c) 2020, 2023, Oracle and/or its affiliates.
33
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
44
55
Methods for creating Kubernetes resource configuration files for Verrazzano.
@@ -8,12 +8,18 @@
88

99
from java.io import File
1010

11+
from wlsdeploy.aliases import alias_utils
1112
from wlsdeploy.aliases.location_context import LocationContext
1213
from wlsdeploy.aliases.model_constants import APPLICATION
1314
from wlsdeploy.aliases.model_constants import CLUSTER
15+
from wlsdeploy.aliases.model_constants import DYNAMIC_SERVERS
1416
from wlsdeploy.aliases.model_constants import JDBC_DRIVER_PARAMS
1517
from wlsdeploy.aliases.model_constants import JDBC_RESOURCE
1618
from wlsdeploy.aliases.model_constants import JDBC_SYSTEM_RESOURCE
19+
from wlsdeploy.aliases.model_constants import LISTEN_PORT
20+
from wlsdeploy.aliases.model_constants import SERVER
21+
from wlsdeploy.aliases.model_constants import SERVER_TEMPLATE
22+
from wlsdeploy.aliases.model_constants import TARGET
1723
from wlsdeploy.aliases.model_constants import URL
1824
from wlsdeploy.logging.platform_logger import PlatformLogger
1925
from wlsdeploy.tool.util import k8s_helper
@@ -52,14 +58,21 @@
5258
HAS_APPLICATIONS = 'hasApplications'
5359
HAS_CLUSTERS = 'hasClusters'
5460
HAS_DATASOURCES = 'hasDatasources'
61+
HAS_HOST_APPLICATIONS = 'hasHostApplications'
5562
HAS_MODEL = 'hasModel'
63+
HOST_APPLICATION_APPLICATIONS = 'applications'
64+
HOST_APPLICATION_HOST = 'host'
65+
HOST_APPLICATION_PORT = 'port'
66+
HOST_APPLICATIONS = 'hostApplications'
5667
NAMESPACE = 'namespace'
5768
REPLICAS = 'replicas'
5869
RUNTIME_ENCRYPTION_SECRET = "runtimeEncryptionSecret"
5970
SET_CLUSTER_REPLICAS = "setClusterReplicas"
6071
USE_PERSISTENT_VOLUME = "usePersistentVolume"
6172
WEBLOGIC_CREDENTIALS_SECRET = 'webLogicCredentialsSecret'
6273

74+
DEFAULT_LISTEN_PORT = 7001
75+
6376

6477
def create_additional_output(model, model_context, aliases, credential_injector, exception_type,
6578
domain_home_override=None):
@@ -255,6 +268,54 @@ def _build_template_hash(model, model_context, aliases, credential_injector, dom
255268
template_hash[APPLICATIONS] = apps
256269
template_hash[HAS_APPLICATIONS] = len(apps) != 0
257270

271+
# host applications - applications organized by host, for Verrazzano IngressTrait
272+
273+
app_map = {}
274+
applications = dictionary_utils.get_dictionary_element(model.get_model_app_deployments(), APPLICATION)
275+
for app_name in applications:
276+
app_hash = dict()
277+
app_hash[APPLICATION_NAME] = app_name
278+
# this text is matched in crd_file_updater, be careful if changing
279+
app_hash[APPLICATION_PREFIX] = '(path for ' + app_name + ')'
280+
281+
app_folder = dictionary_utils.get_dictionary_element(applications, app_name)
282+
targets_value = dictionary_utils.get_dictionary_element(app_folder, TARGET)
283+
targets = alias_utils.create_list(targets_value, 'WLSDPLY-01682')
284+
for target in targets:
285+
if target not in app_map:
286+
app_map[target] = []
287+
app_map[target].append(app_hash)
288+
289+
host_apps = []
290+
target_keys = app_map.keys()
291+
target_keys.sort()
292+
for target_key in target_keys:
293+
listen_port = DEFAULT_LISTEN_PORT
294+
target_cluster = _find_cluster(model, target_key)
295+
if target_cluster is not None:
296+
full_host_name = k8s_helper.get_dns_name(domain_uid + '-cluster-' + target_key)
297+
dynamic_servers = dictionary_utils.get_dictionary_element(target_cluster, DYNAMIC_SERVERS)
298+
template_name = dictionary_utils.get_element(dynamic_servers, SERVER_TEMPLATE)
299+
if template_name:
300+
server_template = _find_server_template(model, template_name)
301+
if server_template:
302+
listen_port = server_template[LISTEN_PORT] or listen_port
303+
else:
304+
full_host_name = k8s_helper.get_dns_name(domain_uid + '-' + target_key)
305+
target_server = _find_server(model, target_key)
306+
if target_server is not None:
307+
listen_port = target_server[LISTEN_PORT] or listen_port
308+
309+
host_app = {
310+
HOST_APPLICATION_HOST: full_host_name,
311+
HOST_APPLICATION_PORT: str_helper.to_string(listen_port),
312+
HOST_APPLICATION_APPLICATIONS: app_map[target_key]
313+
}
314+
host_apps.append(host_app)
315+
316+
template_hash[HOST_APPLICATIONS] = host_apps
317+
template_hash[HAS_HOST_APPLICATIONS] = len(host_apps) != 0
318+
258319
# additional secrets - exclude admin
259320

260321
additional_secrets = []
@@ -280,3 +341,27 @@ def _build_template_hash(model, model_context, aliases, credential_injector, dom
280341
template_hash[HAS_ADDITIONAL_SECRETS] = len(additional_secrets) != 0
281342

282343
return template_hash
344+
345+
346+
def _find_cluster(model, name):
347+
cluster_map = dictionary_utils.get_dictionary_element(model.get_model_topology(), CLUSTER)
348+
for cluster_name in cluster_map:
349+
if name == cluster_name:
350+
return cluster_map[cluster_name]
351+
return None
352+
353+
354+
def _find_server(model, name):
355+
server_map = dictionary_utils.get_dictionary_element(model.get_model_topology(), SERVER)
356+
for server_name in server_map:
357+
if name == server_name:
358+
return server_map[server_name]
359+
return None
360+
361+
362+
def _find_server_template(model, name):
363+
template_map = dictionary_utils.get_dictionary_element(model.get_model_topology(), SERVER_TEMPLATE)
364+
for template_name in template_map:
365+
if name == template_name:
366+
return template_map[template_name]
367+
return None

core/src/main/python/wlsdeploy/tool/util/targets/crd_file_updater.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
Methods to update an output file with information from the kubernetes section of the model.
66
"""
7+
import re
8+
79
from oracle.weblogic.deploy.util import PyOrderedDict
810
from oracle.weblogic.deploy.util import PyRealBoolean
911
from oracle.weblogic.deploy.yaml import YamlException
@@ -42,6 +44,19 @@
4244
VERRAZZANO_APPLICATION_KIND = 'ApplicationConfiguration'
4345
WORKLOAD = 'workload'
4446

47+
# specific to Verrazzano application document
48+
COMPONENTS = "components"
49+
DESTINATION = "destination"
50+
INGRESS_TRAIT = "IngressTrait"
51+
PATH = "path"
52+
PATH_TYPE = "pathType"
53+
PATHS = "paths"
54+
RULES = "rules"
55+
TRAIT = "trait"
56+
TRAITS = "traits"
57+
58+
PATH_SAMPLE_PATTERN = '^\\(path for.*\\)$'
59+
4560

4661
def update_from_model(crd_file, model, crd_helper):
4762
"""
@@ -112,7 +127,7 @@ def _update_documents(crd_documents, model_content, crd_helper, output_file_path
112127
found = True
113128

114129
elif kind == VERRAZZANO_APPLICATION_KIND:
115-
_update_crd(crd_document, model_content, 'application', crd_helper, output_file_path)
130+
_update_crd_application(crd_document, model_content, crd_helper, output_file_path)
116131
found = True
117132

118133
if not found:
@@ -158,6 +173,20 @@ def _update_crd_cluster(crd_dictionary, model_dictionary, crd_helper, output_fil
158173
_update_dictionary(crd_dictionary, model_cluster, schema, None, cluster_crd_folder, output_file_path)
159174

160175

176+
def _update_crd_application(crd_dictionary, model_dictionary, crd_helper, output_file_path):
177+
"""
178+
Update the CRD application dictionary from the model.
179+
:param crd_dictionary: the CRD dictionary to be updated
180+
:param model_dictionary: the model content to use for update
181+
:param crd_helper: used to get CRD folder information
182+
:param output_file_path: used for logging
183+
"""
184+
_method_name = '_update_crd_application'
185+
186+
_update_crd(crd_dictionary, model_dictionary, 'application', crd_helper, output_file_path)
187+
_add_application_comments(crd_dictionary)
188+
189+
161190
def _update_crd_component(crd_dictionary, model_dictionary, crd_helper, output_file_path):
162191
"""
163192
Update the CRD component dictionary from the model.
@@ -418,6 +447,69 @@ def _add_weblogic_workload_comments(vz_dictionary):
418447
_add_cluster_spec_comments(cluster_spec)
419448

420449

450+
def _add_application_comments(vz_dictionary):
451+
"""
452+
Add relevant comments to the Verrazzano application CRD dictionary for additional information.
453+
:param vz_dictionary: the Verrazzano dictionary
454+
"""
455+
spec_folder = dictionary_utils.get_dictionary_element(vz_dictionary, SPEC)
456+
components = dictionary_utils.get_dictionary_element(spec_folder, COMPONENTS)
457+
for component in components:
458+
traits = _get_list_element(component, TRAITS)
459+
for trait in traits:
460+
trait_dictionary = dictionary_utils.get_dictionary_element(trait, TRAIT)
461+
trait_kind = dictionary_utils.get_element(trait_dictionary, KIND)
462+
if trait_kind == INGRESS_TRAIT:
463+
_add_ingress_trait_comments(trait_dictionary)
464+
465+
466+
def _add_ingress_trait_comments(trait_dictionary):
467+
"""
468+
Add relevant comments to the IngressTrait CRD dictionary for additional information.
469+
Convert sample rule paths to comments if none were added from WDT model.
470+
Remove sample rule paths if any paths were added from WDT model.
471+
:param trait_dictionary: the IngressTrait dictionary
472+
"""
473+
trait_spec = dictionary_utils.get_dictionary_element(trait_dictionary, SPEC)
474+
rules = _get_list_element(trait_spec, RULES)
475+
for rule in rules:
476+
sample_paths = []
477+
has_defined_paths = False
478+
paths = _get_list_element(rule, PATHS)
479+
for path in paths:
480+
path_path = dictionary_utils.get_dictionary_element(path, PATH)
481+
is_sample_path = re.search(PATH_SAMPLE_PATTERN, str_helper.to_string(path_path))
482+
if is_sample_path:
483+
sample_paths.append(path)
484+
else:
485+
has_defined_paths = True
486+
487+
if has_defined_paths:
488+
for sample_path in sample_paths:
489+
paths.remove(sample_path)
490+
else:
491+
rule.addComment(DESTINATION, PATHS + ':')
492+
for sample_path in sample_paths:
493+
rule.addComment(DESTINATION, ' - ' + PATH + ': ' + str_helper.to_string(sample_path[PATH]))
494+
rule.addComment(DESTINATION, ' ' + PATH_TYPE + ': ' + str_helper.to_string(sample_path[PATH_TYPE]))
495+
del rule[PATHS]
496+
497+
498+
def _get_list_element(dictionary, element_name):
499+
"""
500+
Retrieve the value for the provided element name from the dictionary.
501+
Return empty list if name is not in the dictionary.
502+
:param dictionary: to find the element name
503+
:param element_name: for which to retrieve the value
504+
:return: value from the dictionary or empty list
505+
"""
506+
if element_name in dictionary:
507+
result = dictionary[element_name]
508+
else:
509+
result = []
510+
return result
511+
512+
421513
def _get_or_create_dictionary(dictionary, key):
422514
if key not in dictionary:
423515
dictionary[key] = PyOrderedDict()

core/src/main/python/wlsdeploy/tool/util/targets/model_crd_helper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def get_product_helper(product_key, product_version, exception_type=ExceptionTyp
105105
application_folder = ModelCrdFolder("application", application_schema, False)
106106
application_folder.add_object_list_key('spec/components', 'componentName')
107107
application_folder.add_object_list_key('spec/components/traits', 'trait/kind')
108+
application_folder.add_object_list_key('spec/components/traits/trait/spec/rules', 'destination/host')
108109
helper.add_crd_folder(application_folder)
109110

110111
weblogic_schema_name = VZ_1_WEBLOGIC_SCHEMA_NAME

core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ WLSDPLY-01678=Expected a list value for {0} in the target output file {1}, skipp
365365
WLSDPLY-01679=Add any credential secrets that are required to pull the image
366366
WLSDPLY-01680=Set a specific replica count for this cluster
367367
WLSDPLY-01681=Unable to create results file "{0}": {1}
368+
WLSDPLY-01682=Unable to convert target value of type {0} to a list
368369

369370
# wlsdeploy/util/enum.py
370371
WLSDPLY-01700=The value {0} is not a valid value of the Enum type {1}

core/src/main/targetconfigs/templates/vz-application-v1.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ spec:
2323
kind: IngressTrait
2424
spec:
2525
rules:
26-
{{#hasApplications}}
27-
- paths:
28-
{{/hasApplications}}
26+
{{#hostApplications}}
27+
- destination:
28+
host: {{{host}}}
29+
port: {{{port}}}
30+
paths:
2931
{{#applications}}
30-
# application {{{applicationName}}}
3132
- path: "{{{applicationPrefix}}}"
3233
pathType: Prefix
3334
{{/applications}}
35+
{{/hostApplications}}
3436
- componentName: {{{domainPrefix}}}-configmap
3537
---
3638
apiVersion: core.oam.dev/v1alpha2

core/src/test/python/wlsdeploy/tool/extract/extract_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ def testVerrazzanoModel(self):
105105
trait_list = self._traverse(component_list[0], 'traits')
106106
self._match_values("Application trait count", len(trait_list), 3)
107107

108+
ingress_trait = self._traverse(trait_list[1], 'trait')
109+
rule_list = self._traverse(ingress_trait, 'spec', 'rules')
110+
self._match_values("Ingress trait rule count", len(rule_list), 3)
111+
112+
# m1 has paths added from the verrazzano section
113+
m1_rule = rule_list[0]
114+
m1_path_list = self._traverse(m1_rule, 'paths')
115+
self._match_values("Server 1 rule path count", len(m1_path_list), 2)
116+
117+
# m2 has no rules, only sample comments
118+
m2_rule = rule_list[1]
119+
self._match_values("Server 2 has no paths", 'paths' in m2_rule, False)
120+
108121
configmap_resource = documents[2]
109122

110123
# one entry was added to config map

core/src/test/resources/extract/model-3.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
# This will test that fields from the verrazzano section of the model
66
# are transferred to the resulting domain resource file
77

8+
topology:
9+
Cluster:
10+
mycluster:
11+
DynamicServers:
12+
ServerTemplate: template1
13+
Server:
14+
m1:
15+
# no ListenPort, use default
16+
m2:
17+
ListenPort: 9005
18+
ServerTemplate:
19+
template1:
20+
ListenPort: 9008
21+
822
resources:
923
# template will create a configmap entry for this JDBC resource
1024
JDBCSystemResource:
@@ -13,6 +27,19 @@ resources:
1327
JDBCDriverParams:
1428
URL: 'jdbc:oracle:thin:@dbhost:1521/pdborcl'
1529

30+
appDeployments:
31+
# these apps will go into application IngressTrait rules
32+
Application:
33+
oneApp:
34+
SourcePath: wlsdeploy/apps/oneApp.ear
35+
Target: mycluster,m1
36+
twoApp :
37+
SourcePath: wlsdeploy/apps/twoApp.ear
38+
Target: m2,mycluster
39+
threeApp :
40+
SourcePath: wlsdeploy/apps/threeApp.ear
41+
Target: m2,m1
42+
1643
verrazzano:
1744
application:
1845
spec:
@@ -24,6 +51,18 @@ verrazzano:
2451
kind: MetricsTrait
2552
spec:
2653
scraper: verrazzano-system/my-model-scraper
54+
- trait: # should merge with template trait
55+
kind: IngressTrait
56+
spec:
57+
rules:
58+
# assign specific paths to this destination
59+
- destination:
60+
host: base-domain-m1
61+
paths:
62+
- path: '/simple-ear-path'
63+
pathType: Prefix
64+
- path: '/simple-ear3-path'
65+
pathType: Prefix
2766
- trait: # should add to template traits
2867
apiVersion: oam.verrazzano.io/v1alpha1
2968
kind: LoggingTrait

0 commit comments

Comments
 (0)