• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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