Merge "Implement algorithm for reapply evaluation priority"
This commit is contained in:
commit
9e6cb84816
|
@ -1600,6 +1600,7 @@ APP_METADATA_PLATFORM_MANAGED_APPS = 'platform_managed_apps_list'
|
||||||
APP_METADATA_DESIRED_STATE = 'desired_state'
|
APP_METADATA_DESIRED_STATE = 'desired_state'
|
||||||
APP_METADATA_DESIRED_STATES = 'desired_states'
|
APP_METADATA_DESIRED_STATES = 'desired_states'
|
||||||
APP_METADATA_FORBIDDEN_MANUAL_OPERATIONS = 'forbidden_manual_operations'
|
APP_METADATA_FORBIDDEN_MANUAL_OPERATIONS = 'forbidden_manual_operations'
|
||||||
|
APP_METADATA_ORDERED_APPS = 'ordered_apps'
|
||||||
|
|
||||||
APP_EVALUATE_REAPPLY_TYPE_HOST_ADD = 'host-add'
|
APP_EVALUATE_REAPPLY_TYPE_HOST_ADD = 'host-add'
|
||||||
APP_EVALUATE_REAPPLY_TYPE_HOST_DELETE = 'host-delete'
|
APP_EVALUATE_REAPPLY_TYPE_HOST_DELETE = 'host-delete'
|
||||||
|
|
|
@ -82,6 +82,7 @@ ARMADA_LOCK_PLURAL = 'locks'
|
||||||
ARMADA_LOCK_NAME = 'lock'
|
ARMADA_LOCK_NAME = 'lock'
|
||||||
|
|
||||||
LOCK_NAME_APP_REAPPLY = 'app_reapply'
|
LOCK_NAME_APP_REAPPLY = 'app_reapply'
|
||||||
|
LOCK_NAME_PROCESS_APP_METADATA = 'process_app_metadata'
|
||||||
|
|
||||||
|
|
||||||
# Helper functions
|
# Helper functions
|
||||||
|
@ -1888,6 +1889,200 @@ class AppOperator(object):
|
||||||
lifecycle_op.app_lifecycle_actions(context, conductor_obj, self, app, hook_info)
|
lifecycle_op.app_lifecycle_actions(context, conductor_obj, self, app, hook_info)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
def recompute_app_evaluation_order(apps_metadata_dict):
|
||||||
|
""" Get the order of app reapplies based on dependencies
|
||||||
|
|
||||||
|
The following algorithm uses these concepts:
|
||||||
|
Root apps are apps that have no dependency.
|
||||||
|
Chain depth for an app is the number of apps that form the longest
|
||||||
|
chain ending in the current app.
|
||||||
|
|
||||||
|
Main logic:
|
||||||
|
Compute reverse graph (after_apps).
|
||||||
|
Determine root apps.
|
||||||
|
Detect cycles and abort.
|
||||||
|
Compute the longest dependency chain.
|
||||||
|
Traverse again to populate ordered list.
|
||||||
|
|
||||||
|
Assumptions:
|
||||||
|
In theory there is one or few root apps that are dominant vertices.
|
||||||
|
Other than the dominant vertices, there are very sparse vertices with
|
||||||
|
a degree more than one, most of the vertices are either leaves or
|
||||||
|
isolated.
|
||||||
|
Chain depth is usually 0 or 1, few apps have a chain depth of 2, 3, 4
|
||||||
|
The structure is a sparse digraph, or multiple separate sparse digraphs
|
||||||
|
with a total number of vertices equal to the number of apps.
|
||||||
|
|
||||||
|
Complexity analysis:
|
||||||
|
Spatial complexity O(V+E)
|
||||||
|
Cycle detection: O(V+E)
|
||||||
|
|
||||||
|
After cycle detection the graph is a DAG.
|
||||||
|
For computing the chain depth and final traversal a subgraph may be
|
||||||
|
revisited. Complexity would be O(V*E).
|
||||||
|
|
||||||
|
Let k = number of apps with a vertex that have the in degree > 1 and
|
||||||
|
that are not leaf apps. We can bind k to be 0<=k<=10000, shall we reach
|
||||||
|
that app number.
|
||||||
|
|
||||||
|
Each node and each vertex will be visited once O(V+E) (root apps
|
||||||
|
+ vertex to leaf).
|
||||||
|
Only k nodes will trigger a revisit of a subset of vertices (k * O(E)).
|
||||||
|
|
||||||
|
Complexity now becomes O(V+(k+1)*E) = O(V+E)
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
If an app(current) depends only on non-existing apps, then
|
||||||
|
current app will not be properly ordered. It will not be present in
|
||||||
|
the ordered list before other apps based on it.
|
||||||
|
If an app(current) depends only on non platform managed apps, then
|
||||||
|
current app will not be properly ordered. It will not be present in
|
||||||
|
the ordered list before other apps based on it.
|
||||||
|
|
||||||
|
:param: apps_metadata_dict dictionary containing parsed and processed
|
||||||
|
metadata collection
|
||||||
|
|
||||||
|
:return: Sorted list containing the app reapply order.
|
||||||
|
"""
|
||||||
|
# Apps directly after current
|
||||||
|
after_apps = {}
|
||||||
|
|
||||||
|
# Remember the maximum depth
|
||||||
|
chain_depth = {}
|
||||||
|
|
||||||
|
# Used to detect cycles
|
||||||
|
cycle_depth = {}
|
||||||
|
|
||||||
|
# Used for second traversal when populating ordered list
|
||||||
|
traverse_depth = {}
|
||||||
|
|
||||||
|
# Final result
|
||||||
|
ordered_apps = []
|
||||||
|
apps_metadata_dict[constants.APP_METADATA_ORDERED_APPS] = ordered_apps
|
||||||
|
|
||||||
|
# Initialize structures
|
||||||
|
for app_name in apps_metadata_dict[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
after_apps[app_name] = []
|
||||||
|
chain_depth[app_name] = 0
|
||||||
|
cycle_depth[app_name] = 0
|
||||||
|
traverse_depth[app_name] = 0
|
||||||
|
|
||||||
|
# For each app remember which apps are directly after
|
||||||
|
for app_name in apps_metadata_dict[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
app_metadata = apps_metadata_dict[constants.APP_METADATA_APPS][app_name]
|
||||||
|
metadata_after = app_metadata.get(constants.APP_METADATA_BEHAVIOR, None)
|
||||||
|
|
||||||
|
if metadata_after is not None:
|
||||||
|
metadata_after = metadata_after.get(constants.APP_METADATA_EVALUATE_REAPPLY, None)
|
||||||
|
if metadata_after is not None:
|
||||||
|
metadata_after = metadata_after.get(constants.APP_METADATA_AFTER, None)
|
||||||
|
if metadata_after is not None:
|
||||||
|
for before_app in metadata_after:
|
||||||
|
# This one may be a non-existing app, need to initialize
|
||||||
|
if after_apps.get(before_app, None) is None:
|
||||||
|
after_apps[before_app] = []
|
||||||
|
|
||||||
|
# Store information
|
||||||
|
after_apps[before_app].append(app_name)
|
||||||
|
|
||||||
|
# Remember that current app is before at least one
|
||||||
|
chain_depth[app_name] = 1
|
||||||
|
traverse_depth[app_name] = 1
|
||||||
|
|
||||||
|
# Identify root apps
|
||||||
|
root_apps = []
|
||||||
|
for app_name in apps_metadata_dict[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
if chain_depth.get(app_name, None) == 0:
|
||||||
|
root_apps.append(app_name)
|
||||||
|
|
||||||
|
# Used for cycle detection
|
||||||
|
stack_ = queue.LifoQueue()
|
||||||
|
cycle_checked = {}
|
||||||
|
max_depth = len(apps_metadata_dict[constants.APP_METADATA_PLATFORM_MANAGED_APPS])
|
||||||
|
|
||||||
|
# Detect cycles and abort
|
||||||
|
for app_name in apps_metadata_dict[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
# Skip already checked app
|
||||||
|
if cycle_checked.get(app_name, False) is True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Start from this
|
||||||
|
stack_.put(app_name)
|
||||||
|
|
||||||
|
# Reinitialize temporary visited
|
||||||
|
visited = {}
|
||||||
|
|
||||||
|
# Traverse DFS to detect cycles
|
||||||
|
while not stack_.empty():
|
||||||
|
app_name = stack_.get_nowait()
|
||||||
|
visited[app_name] = True
|
||||||
|
|
||||||
|
# Skip already checked app
|
||||||
|
if cycle_checked.get(app_name, False) is True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for after in after_apps[app_name]:
|
||||||
|
cycle_depth[after] = max(cycle_depth[app_name] + 1, cycle_depth[after])
|
||||||
|
# Detected cycle
|
||||||
|
if cycle_depth[after] > max_depth:
|
||||||
|
return ordered_apps
|
||||||
|
|
||||||
|
stack_.put(after)
|
||||||
|
|
||||||
|
# Remember the temporary visited apps to skip them in the future
|
||||||
|
for r in visited.keys():
|
||||||
|
cycle_checked[r] = True
|
||||||
|
|
||||||
|
# Used for traversal
|
||||||
|
queue_ = queue.Queue()
|
||||||
|
|
||||||
|
# Compute the longest dependency chain starting from root apps
|
||||||
|
for app_name in root_apps:
|
||||||
|
queue_.put(app_name)
|
||||||
|
|
||||||
|
# Traverse similar to BFS to compute the longest dependency chain
|
||||||
|
while not queue_.empty():
|
||||||
|
app_name = queue_.get_nowait()
|
||||||
|
for after in after_apps[app_name]:
|
||||||
|
chain_depth[after] = max(chain_depth[app_name] + 1, chain_depth[after])
|
||||||
|
queue_.put(after)
|
||||||
|
|
||||||
|
# Traverse graph again similar to BFS
|
||||||
|
# Add to ordered list when the correct chain depth is reached
|
||||||
|
found = {}
|
||||||
|
for app_name in root_apps:
|
||||||
|
queue_.put(app_name)
|
||||||
|
found[app_name] = True
|
||||||
|
ordered_apps.append(app_name)
|
||||||
|
|
||||||
|
while not queue_.empty():
|
||||||
|
app_name = queue_.get_nowait()
|
||||||
|
|
||||||
|
for after in after_apps[app_name]:
|
||||||
|
traverse_depth[after] = max(traverse_depth[app_name] + 1, traverse_depth[after])
|
||||||
|
|
||||||
|
# This is the correct depth, add to ordered list
|
||||||
|
if traverse_depth[after] == chain_depth[after]:
|
||||||
|
# Skip if already added
|
||||||
|
if found.get(after, False) is True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
found[after] = True
|
||||||
|
ordered_apps.append(after)
|
||||||
|
|
||||||
|
queue_.put(after)
|
||||||
|
|
||||||
|
# Add apps that have dependencies on non-existing apps
|
||||||
|
for app_name in apps_metadata_dict[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
if found.get(app_name, False) is True:
|
||||||
|
continue
|
||||||
|
ordered_apps.append(app_name)
|
||||||
|
|
||||||
|
LOG.info("Applications reapply order: {}".format(ordered_apps))
|
||||||
|
apps_metadata_dict[constants.APP_METADATA_ORDERED_APPS] = ordered_apps
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@cutils.synchronized(LOCK_NAME_PROCESS_APP_METADATA, external=False)
|
||||||
def update_and_process_app_metadata(apps_metadata_dict, app_name, metadata, overwrite=True):
|
def update_and_process_app_metadata(apps_metadata_dict, app_name, metadata, overwrite=True):
|
||||||
""" Update the cached metadata for an app
|
""" Update the cached metadata for an app
|
||||||
|
|
||||||
|
@ -1920,6 +2115,9 @@ class AppOperator(object):
|
||||||
LOG.info("App {} requested to be platform managed"
|
LOG.info("App {} requested to be platform managed"
|
||||||
"".format(app_name))
|
"".format(app_name))
|
||||||
|
|
||||||
|
# Recompute app reapply order
|
||||||
|
AppOperator.recompute_app_evaluation_order(apps_metadata_dict)
|
||||||
|
|
||||||
# Remember the desired state the app should achieve
|
# Remember the desired state the app should achieve
|
||||||
if desired_state is not None:
|
if desired_state is not None:
|
||||||
apps_metadata_dict[
|
apps_metadata_dict[
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2013-2020 Wind River Systems, Inc.
|
# Copyright (c) 2013-2021 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""Conduct all activity related system inventory.
|
"""Conduct all activity related system inventory.
|
||||||
|
@ -225,7 +225,8 @@ class ConductorManager(service.PeriodicService):
|
||||||
|
|
||||||
self.apps_metadata = {constants.APP_METADATA_APPS: {},
|
self.apps_metadata = {constants.APP_METADATA_APPS: {},
|
||||||
constants.APP_METADATA_PLATFORM_MANAGED_APPS: {},
|
constants.APP_METADATA_PLATFORM_MANAGED_APPS: {},
|
||||||
constants.APP_METADATA_DESIRED_STATES: {}}
|
constants.APP_METADATA_DESIRED_STATES: {},
|
||||||
|
constants.APP_METADATA_ORDERED_APPS: []}
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self._start()
|
self._start()
|
||||||
|
@ -11979,22 +11980,30 @@ class ConductorManager(service.PeriodicService):
|
||||||
|
|
||||||
:returns: list of apps or app names
|
:returns: list of apps or app names
|
||||||
"""
|
"""
|
||||||
# TODO(dvoicule) reorder apps based on dependencies between them
|
|
||||||
# now make HELM_APP_PLATFORM first to keep backward compatibility
|
|
||||||
ordered_apps = []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if filter_active:
|
# Cached entry: precomputed order of reapply evaluation
|
||||||
apps = list(filter(lambda app: app.active, self.dbapi.kube_app_get_all()))
|
if name_only and not filter_active:
|
||||||
else:
|
return self.apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
apps = self.dbapi.kube_app_get_all()
|
|
||||||
|
ordered_apps = []
|
||||||
|
# Start from already ordered list
|
||||||
|
for app_name in self.apps_metadata[constants.APP_METADATA_ORDERED_APPS]:
|
||||||
|
try:
|
||||||
|
app = self.dbapi.kube_app_get(app_name)
|
||||||
|
except exception.KubeAppNotFound:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if filter_active and app.active:
|
||||||
|
ordered_apps.append(app)
|
||||||
|
elif not filter_active:
|
||||||
|
ordered_apps.append(app)
|
||||||
|
|
||||||
|
LOG.info("Apps reapply order: {}".format([app.name for app in ordered_apps]))
|
||||||
|
|
||||||
ordered_apps = sorted(apps, key=lambda x: x.get('name', 'placeholder')
|
|
||||||
if x.get('name', 'placeholder') != constants.HELM_APP_PLATFORM else '')
|
|
||||||
if name_only:
|
if name_only:
|
||||||
ordered_apps = [app.name for app in ordered_apps]
|
ordered_apps = [app.name for app in ordered_apps]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Error while ordering apps for reapply {}".format(e))
|
LOG.error("Error while ordering apps for reapply {}".format(str(e)))
|
||||||
ordered_apps = []
|
ordered_apps = []
|
||||||
|
|
||||||
return ordered_apps
|
return ordered_apps
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
app_name: app0
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app0
|
||||||
|
- app1
|
||||||
|
- app2
|
||||||
|
- app4
|
||||||
|
---
|
||||||
|
app_name: app4
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
triggers:
|
||||||
|
- mock: mock
|
||||||
|
---
|
||||||
|
app_name: app5
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app4
|
||||||
|
triggers:
|
||||||
|
- mock: mock
|
||||||
|
---
|
||||||
|
app_name: app20
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app_2_0
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
triggers:
|
||||||
|
- mock: mock
|
||||||
|
---
|
||||||
|
app_name: app_2_1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_2_0
|
||||||
|
---
|
||||||
|
app_name: app_2_2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_2_1
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
app_name: app_last_in_sorted_list
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
triggers:
|
||||||
|
- mock: mock
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_last_in_sorted_list
|
||||||
|
- app3
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
- app2
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app3
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
- app2
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
app_name: app0
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_non_existing_0
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_non_existing_2
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app0
|
||||||
|
- app2
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
app_name: app0
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_non_existing_0
|
||||||
|
- app4
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app_non_existing_2
|
||||||
|
- app4
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app0
|
||||||
|
- app2
|
||||||
|
---
|
||||||
|
app_name: app4
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
app_name: app0
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app0
|
||||||
|
- app2
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app3
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
- app2
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
app_name: app0
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
---
|
||||||
|
app_name: app1
|
||||||
|
behavior:
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app3
|
||||||
|
---
|
||||||
|
app_name: app2
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
- app0
|
||||||
|
---
|
||||||
|
app_name: app3
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- app1
|
||||||
|
- app2
|
|
@ -0,0 +1,314 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021 Wind River Systems, Inc.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Test class for Sysinv Kube App Metadata operations."""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import ruamel.yaml as yaml
|
||||||
|
|
||||||
|
from sysinv.common import constants
|
||||||
|
from sysinv.conductor import kube_app
|
||||||
|
from sysinv.conductor import manager
|
||||||
|
from sysinv.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestKubeAppMetadata(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestKubeAppMetadata, self).setUp()
|
||||||
|
|
||||||
|
# Manager holds apps_metadata dict
|
||||||
|
self.service = manager.ConductorManager('test-host', 'test-topic')
|
||||||
|
|
||||||
|
def test_reapply_order_computation(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_1.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
before_apps = {}
|
||||||
|
self._compute_before_apps(mock_apps_metadata, before_apps)
|
||||||
|
|
||||||
|
# All apps are present in the ordered list
|
||||||
|
for app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(app in ordered_list)
|
||||||
|
|
||||||
|
# All apps are present only once
|
||||||
|
self.assertEqual(len(ordered_list),
|
||||||
|
len(mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]))
|
||||||
|
|
||||||
|
# All apps have the constrains satisfied
|
||||||
|
for app in before_apps.keys():
|
||||||
|
for before in before_apps[app]:
|
||||||
|
self.assertTrue(ordered_list.index(before) < ordered_list.index(app))
|
||||||
|
|
||||||
|
def test_reapply_non_existing_1(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_non_existing_1.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
before_apps = {}
|
||||||
|
self._compute_before_apps(mock_apps_metadata, before_apps)
|
||||||
|
|
||||||
|
# All apps are present in the ordered list
|
||||||
|
for app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(app in ordered_list)
|
||||||
|
|
||||||
|
# All apps are present only once
|
||||||
|
self.assertEqual(len(ordered_list),
|
||||||
|
len(mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]))
|
||||||
|
|
||||||
|
self.skipTest("Corner case not implemented -> "
|
||||||
|
"app based only on non-existing")
|
||||||
|
|
||||||
|
# All apps have the constrains satisfied
|
||||||
|
for app in before_apps.keys():
|
||||||
|
for before in before_apps[app]:
|
||||||
|
# Skip non-existing
|
||||||
|
if app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS] and \
|
||||||
|
before in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(ordered_list.index(before) < ordered_list.index(app))
|
||||||
|
|
||||||
|
def test_reapply_non_existing_2(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_non_existing_2.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
before_apps = {}
|
||||||
|
self._compute_before_apps(mock_apps_metadata, before_apps)
|
||||||
|
|
||||||
|
# All apps are present in the ordered list
|
||||||
|
for app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(app in ordered_list)
|
||||||
|
|
||||||
|
# All apps are present only once
|
||||||
|
self.assertEqual(len(ordered_list),
|
||||||
|
len(mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]))
|
||||||
|
|
||||||
|
# All apps have the constrains satisfied
|
||||||
|
for app in before_apps.keys():
|
||||||
|
for before in before_apps[app]:
|
||||||
|
# Skip non-existing
|
||||||
|
if app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS] and \
|
||||||
|
before in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(ordered_list.index(before) < ordered_list.index(app))
|
||||||
|
|
||||||
|
def test_reapply_non_managed_1(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_non_managed_1.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
before_apps = {}
|
||||||
|
self._compute_before_apps(mock_apps_metadata, before_apps)
|
||||||
|
|
||||||
|
# All apps are present in the ordered list
|
||||||
|
for app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(app in ordered_list)
|
||||||
|
|
||||||
|
# All apps are present only once
|
||||||
|
self.assertEqual(len(ordered_list),
|
||||||
|
len(mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]))
|
||||||
|
|
||||||
|
self.skipTest("Corner case not implemented -> "
|
||||||
|
"app based only on non platform managed")
|
||||||
|
|
||||||
|
# All apps have the constrains satisfied
|
||||||
|
for app in before_apps.keys():
|
||||||
|
for before in before_apps[app]:
|
||||||
|
# Skip non-existing
|
||||||
|
if app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS] and \
|
||||||
|
before in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(ordered_list.index(before) < ordered_list.index(app))
|
||||||
|
|
||||||
|
def test_reapply_not_cycle_1(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_not_cycle_1_non_managed.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
before_apps = {}
|
||||||
|
self._compute_before_apps(mock_apps_metadata, before_apps)
|
||||||
|
|
||||||
|
# All apps are present in the ordered list
|
||||||
|
for app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(app in ordered_list)
|
||||||
|
|
||||||
|
# All apps are present only once
|
||||||
|
self.assertEqual(len(ordered_list),
|
||||||
|
len(mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]))
|
||||||
|
|
||||||
|
self.skipTest("Corner case not implemented -> "
|
||||||
|
"app based only on non platform managed")
|
||||||
|
|
||||||
|
# All apps have the constrains satisfied
|
||||||
|
for app in before_apps.keys():
|
||||||
|
for before in before_apps[app]:
|
||||||
|
# Skip non-existing
|
||||||
|
if app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS] and \
|
||||||
|
before in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(ordered_list.index(before) < ordered_list.index(app))
|
||||||
|
|
||||||
|
def test_reapply_not_cycle_2(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_not_cycle_2.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
before_apps = {}
|
||||||
|
self._compute_before_apps(mock_apps_metadata, before_apps)
|
||||||
|
|
||||||
|
# All apps are present in the ordered list
|
||||||
|
for app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(app in ordered_list)
|
||||||
|
|
||||||
|
# All apps are present only once
|
||||||
|
self.assertEqual(len(ordered_list),
|
||||||
|
len(mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]))
|
||||||
|
|
||||||
|
# All apps have the constrains satisfied
|
||||||
|
for app in before_apps.keys():
|
||||||
|
for before in before_apps[app]:
|
||||||
|
# Skip non-existing
|
||||||
|
if app in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS] and \
|
||||||
|
before in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
self.assertTrue(ordered_list.index(before) < ordered_list.index(app))
|
||||||
|
|
||||||
|
def _compute_before_apps(self, mock_apps_metadata, before_apps):
|
||||||
|
# Initialize structures
|
||||||
|
for app_name in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
before_apps[app_name] = []
|
||||||
|
|
||||||
|
# For each app remember which apps are before
|
||||||
|
for app_name in mock_apps_metadata[constants.APP_METADATA_PLATFORM_MANAGED_APPS]:
|
||||||
|
app_metadata = mock_apps_metadata[constants.APP_METADATA_APPS][app_name]
|
||||||
|
metadata_after = app_metadata.get(constants.APP_METADATA_BEHAVIOR, None)
|
||||||
|
|
||||||
|
if metadata_after is not None:
|
||||||
|
metadata_after = metadata_after.get(constants.APP_METADATA_EVALUATE_REAPPLY, None)
|
||||||
|
if metadata_after is not None:
|
||||||
|
metadata_after = metadata_after.get('after', None)
|
||||||
|
if metadata_after is not None:
|
||||||
|
for before_app in metadata_after:
|
||||||
|
# Append to apps that are before
|
||||||
|
before_apps[app_name].append(before_app)
|
||||||
|
|
||||||
|
def test_reapply_order_cycle_detection_1(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_cycle_1.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
self.assertEqual(ordered_list, [])
|
||||||
|
|
||||||
|
def test_reapply_order_cycle_detection_2(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_cycle_2.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
self.assertEqual(ordered_list, [])
|
||||||
|
|
||||||
|
def test_reapply_order_cycle_detection_3(self):
|
||||||
|
# Temporary copy
|
||||||
|
mock_apps_metadata = copy.deepcopy(self.service.apps_metadata)
|
||||||
|
|
||||||
|
yaml_file = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", "metadata_app_reapply_cycle_3.yaml")
|
||||||
|
with open(yaml_file, 'r') as f:
|
||||||
|
metadata_collection = yaml.safe_load_all(f)
|
||||||
|
|
||||||
|
for metadata in metadata_collection:
|
||||||
|
kube_app.AppOperator.update_and_process_app_metadata(mock_apps_metadata,
|
||||||
|
metadata['app_name'],
|
||||||
|
metadata)
|
||||||
|
|
||||||
|
ordered_list = mock_apps_metadata[constants.APP_METADATA_ORDERED_APPS]
|
||||||
|
|
||||||
|
self.assertEqual(ordered_list, [])
|
Loading…
Reference in New Issue