1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2022 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import os 17import json 18import copy 19import sa_info_config_errors as json_err 20 21 22class RearrangementPolicy(object): 23 BOOT_START_PHASE = "BootStartPhase" 24 CORE_START_PHASE = "CoreStartPhase" 25 DEFAULT_START_PHASE = "OthersStartPhase" 26 27 rearrange_category_order = (BOOT_START_PHASE, CORE_START_PHASE, 28 DEFAULT_START_PHASE) 29 30 bootphase_priority_table = { 31 BOOT_START_PHASE: 3, 32 CORE_START_PHASE: 2, 33 DEFAULT_START_PHASE: 1 34 } 35 36 def __init__(self): 37 self.bootphase_categories = { 38 RearrangementPolicy.BOOT_START_PHASE: [], 39 RearrangementPolicy.CORE_START_PHASE: [], 40 RearrangementPolicy.DEFAULT_START_PHASE: [] 41 } 42 43 44class SARearrangement(object): 45 def __init__(self): 46 self.rearranged_systemabilities = [] 47 self.ordered_systemability_names = [] 48 self.name_node_dict = {} 49 self.systemability_deps_dict = {} 50 self.bootphase_dict = {} 51 self.creation_dict = {} 52 self.policy = RearrangementPolicy() 53 self.sanode_start_idx = 0 54 self.systemability_nodes = [] 55 self.sa_nodes_count = 0 56 57 def __parse_json_file(self, source_file): 58 with open(source_file, "r", encoding="utf-8") as file: 59 data = json.load(file) 60 self.systemability_nodes = data['systemability'] 61 try: 62 first_sa_node = self.systemability_nodes[0] 63 self.sa_nodes_count = len(self.systemability_nodes) 64 except IndexError: 65 pass 66 67 def __rearrange_systemability_node_strict(self, source_file, dest_file): 68 rearranged_name = self.rearranged_systemabilities 69 final_systemability = [] 70 for name in rearranged_name: 71 temp = self.name_node_dict.get(name) 72 final_systemability.append(temp) 73 with open(source_file, "r", encoding="utf-8") as file: 74 data = json.load(file) 75 data['systemability'] = final_systemability 76 file_node = os.open(dest_file, os.O_RDWR | os.O_CREAT, 0o640) 77 with os.fdopen(file_node, 'w') as json_files: 78 json.dump(data, json_files, indent=4, ensure_ascii=False) 79 80 @classmethod 81 def __detect_invalid_dependency(self, dependency_checkers, ordered_sa_names, 82 sa_deps_dict): 83 """ 84 Iterate over the dependency tree, to detect whether there 85 exists circular dependency and other kinds bad dependency 86 """ 87 deps_visit_cnt = {} 88 ordered_systemability_names = ordered_sa_names 89 systemability_deps_dict = sa_deps_dict 90 91 def init_dependencies_visit_counter(): 92 for name in ordered_systemability_names: 93 deps_visit_cnt[name] = 0 94 95 def do_check(systemability, dependency): 96 """ 97 Check other kind dependency problem 98 """ 99 for checker in dependency_checkers: 100 checker(systemability, dependency) 101 102 def check_depend(cur_systemability, deps_count, dependencies, depend_path): 103 if deps_visit_cnt.get(cur_systemability) < deps_count: 104 index = deps_visit_cnt.get(cur_systemability) 105 cur_dependency = dependencies[index] 106 # execute other kind dependency checkers right here 107 do_check(cur_systemability, cur_dependency) 108 try: 109 depend_path.index(cur_dependency) 110 depend_path.append(cur_dependency) 111 _format = "A circular dependency found: {}" 112 route = "->".join(map(str, depend_path)) 113 raise json_err.CircularDependencyError(_format.format(route)) 114 except ValueError: 115 depend_path.append(cur_dependency) 116 deps_visit_cnt[cur_systemability] += 1 117 else: 118 # pop the systemability in process if it's all 119 # dependencies have been visited 120 depend_path.pop() 121 122 init_dependencies_visit_counter() 123 for systemability in ordered_systemability_names: 124 depend_path = [] 125 depend_path.append(systemability) 126 while len(depend_path) != 0: 127 cur_systemability = depend_path[-1] 128 # the cur_systemability may be in a different process, 129 # thus can't find it's dependency info 130 dependencies = systemability_deps_dict.get(cur_systemability) 131 if dependencies is None: 132 dependencies = [] 133 deps_count = len(dependencies) 134 if deps_count == 0: 135 depend_path.pop() 136 else: 137 check_depend(cur_systemability, deps_count, dependencies, depend_path) 138 139 def __extract_info_from_systemability_nodes(self): 140 """ 141 Extract info like dependencies and bootphase from a systemability node 142 """ 143 def validate_creation(creation): 144 _format = ("In tag {} only a boolean value is expected, " + 145 "but actually is '{}'") 146 if str(creation) not in {"true", "false", "True", "False"}: 147 raise json_err.BadFormatJsonError(_format.format("run-on-create", creation), 148 self.file_in_process) 149 150 def validate_bootphase(bootphase, nodename): 151 _format = ("In systemability: {}, The bootphase '{}' is not supported " + 152 "please check yourself") 153 if self.policy.bootphase_categories.get(bootphase) is None: 154 raise json_err.NotSupportedBootphaseError(_format.format(nodename, bootphase)) 155 156 def validate_systemability_name(nodename): 157 if nodename < 1 or nodename > 16777215: 158 _format = ("name's value should be [1-16777215], but actually is {}") 159 raise json_err.BadFormatJsonError(_format.format(nodename), 160 self.file_in_process) 161 162 def check_nodes_constraints_one(systemability_node, tag): 163 _format = ("The tag {} should exist, but it does not exist") 164 if tag not in systemability_node: 165 raise json_err.BadFormatJsonError(_format.format(tag), self.file_in_process) 166 tags_nodes = systemability_node[tag] 167 return tags_nodes 168 169 def check_nodes_constraints_two(systemability_node, tag): 170 if tag not in systemability_node or systemability_node[tag] == '': 171 return "" 172 tags_nodes = systemability_node[tag] 173 return tags_nodes 174 175 default_bootphase = RearrangementPolicy.DEFAULT_START_PHASE 176 for systemability_node in self.systemability_nodes: 177 # Required <name> one and only one is expected 178 name_node = check_nodes_constraints_one(systemability_node, "name") 179 validate_systemability_name(name_node) 180 try: 181 self.ordered_systemability_names.index(name_node) 182 raise json_err.SystemAbilityNameConflictError(name_node) 183 except ValueError: 184 self.ordered_systemability_names.append(name_node) 185 self.name_node_dict[name_node] = systemability_node 186 self.systemability_deps_dict[name_node] = [] 187 self.bootphase_dict[name_node] = default_bootphase 188 # Optional bootphase: zero or one are both accepted 189 bootphase_nodes = check_nodes_constraints_two(systemability_node, 190 "bootphase") 191 if bootphase_nodes != '': 192 validate_bootphase(bootphase_nodes, name_node) 193 self.bootphase_dict[name_node] = bootphase_nodes 194 # Required run-on-create one and only one is expected 195 runoncreate_node = check_nodes_constraints_one(systemability_node, 196 "run-on-create") 197 validate_creation(runoncreate_node) 198 self.creation_dict[name_node] = runoncreate_node 199 # Optional depend: 200 depend_nodes = check_nodes_constraints_two(systemability_node, 201 "depend") 202 for depend_node in depend_nodes: 203 deps = self.systemability_deps_dict.get(name_node) 204 deps.append(depend_node) 205 206 def __sort_systemability_by_bootphase_priority(self): 207 def check_index(systemabilities, dependency, idx_self): 208 try: 209 idx_dep = systemabilities.index(dependency) 210 # if the dependency is behind, then exchange the order 211 if idx_self < idx_dep: 212 tmp = systemabilities[idx_dep] 213 systemabilities[idx_dep] = systemabilities[idx_self] 214 systemabilities[idx_self] = tmp 215 except ValueError: 216 pass # ignore different category of dependencies 217 218 def inner_category_sort(systemabilities): 219 """ 220 Sort dependencies with same bootphase category, preserve the 221 original order in source file 222 """ 223 systemabilities_ = systemabilities[:] 224 for systemability in systemabilities_: 225 dependencies = self.systemability_deps_dict.get(systemability) 226 for dependency in dependencies: 227 # should update idx_self each iteration 228 idx_self = systemabilities.index(systemability) 229 check_index(systemabilities, dependency, idx_self) 230 231 # put the systemability nodes into different categories 232 for systemability_name in self.ordered_systemability_names: 233 bootphase = self.bootphase_dict.get(systemability_name) 234 salist = self.policy.bootphase_categories.get(bootphase) 235 salist.append(systemability_name) 236 237 # sort the systemability nodes according to RearrangementPolicy 238 for category in RearrangementPolicy.rearrange_category_order: 239 salist = self.policy.bootphase_categories.get(category) 240 inner_category_sort(salist) 241 self.rearranged_systemabilities += salist 242 243 def __detect_invert_dependency(self, systemability, depend): 244 """ 245 Detect invert dependency: systemability with high boot priority depends 246 on systemability with low ones, e.g. a systemability named 'sa1' with 247 BootStartPhase priority depends on a systemability named 'sa2' with 248 CoreStartPhase 249 """ 250 _format = ("Bad dependency found: the {} with high priority " + 251 "depends on a {} with low one") 252 self_idx = self.bootphase_dict.get(systemability) 253 # The depend may be in other process 254 dep_idx = self.bootphase_dict.get(depend) 255 if dep_idx is None: 256 return 257 self_priority = RearrangementPolicy.bootphase_priority_table.get( 258 self_idx) 259 depend_priority = RearrangementPolicy.bootphase_priority_table.get( 260 dep_idx) 261 if self_priority > depend_priority: 262 raise json_err.InvertDependencyError( 263 _format.format(systemability, depend)) 264 265 def __detect_creation_dependency(self, systemability, depend): 266 """ 267 Detect dependency related to configuration on <run-on-create>: 268 if a sa with <run-on-create> set to 'true' depending on a sa 269 with 'false', then a RunOnCreateDependencyError will be thrown 270 """ 271 _format = ("Bad dependency found: the {} with run-on-create " + 272 "depends on a {} with run-on-demand") 273 self_creation = self.creation_dict.get(systemability) 274 dep_creation = self.creation_dict.get(depend) 275 if self_creation is True and dep_creation is False: 276 raise json_err.RunOnCreateDependencyError(_format.format(systemability, depend)) 277 278 def sort(self, source_file, dest_file): 279 self.file_in_process = source_file 280 dependency_checkers = [] 281 dependency_checkers.append(self.__detect_invert_dependency) 282 dependency_checkers.append(self.__detect_creation_dependency) 283 284 self.__parse_json_file(source_file) 285 self.__extract_info_from_systemability_nodes() 286 self.__detect_invalid_dependency(dependency_checkers, 287 self.ordered_systemability_names, 288 self.systemability_deps_dict) 289 self.__sort_systemability_by_bootphase_priority() 290 self.__rearrange_systemability_node_strict(source_file, dest_file) 291 292 @classmethod 293 def detect_invalid_dependency_globally(clazz, 294 global_ordered_systemability_names, 295 global_systemability_deps_dict): 296 dependency_checkers = [] 297 clazz.__detect_invalid_dependency(dependency_checkers, 298 global_ordered_systemability_names, 299 global_systemability_deps_dict) 300 301 def get_deps_info(self): 302 """ 303 Returns systemabilities and their dependencies for later detecting 304 possible globally circular dependency problem 305 """ 306 return [self.ordered_systemability_names, self.systemability_deps_dict] 307