Initial commit
[ta/config-manager.git] / cmframework / src / cmframework / lib / cmupdateimpl.py
1 # Copyright 2019 Nokia
2
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import logging
16 import time
17
18 from cmframework.apis.cmerror import CMError
19 from cmframework.apis.cmmanage import CMManage
20 from cmframework.utils.cmpluginloader import CMPluginLoader
21 from cmframework.utils.cmdependencysort import CMDependencySort
22
23 from cmdatahandlers.api.configmanager import ConfigManager
24 from cmdatahandlers.api import utils
25
26
27 class CMUpdateImpl(object):
28     def __init__(self, plugins_path, server_ip='config-manager', server_port=61100,
29                  client_lib_impl_module='cmframework.lib.CMClientImpl', verbose_logger=None):
30         logging.info('CMUpdateImpl constructor, plugins_path is %s', plugins_path)
31
32         self._plugins_path = plugins_path
33         self._handlers = {}
34         self._sorted_handlers = []
35
36         self._load_handlers()
37         self._load_dependencies()
38
39         self.uuid_value = None
40
41         self.api = CMManage(server_ip, server_port, client_lib_impl_module, verbose_logger)
42
43     def _load_handlers(self):
44         loader = CMPluginLoader(self._plugins_path)
45         handler_modules, _ = loader.load()
46         logging.info('Handler module(s): %r', handler_modules)
47
48         for handler_class_name, module in handler_modules.iteritems():
49             handler_class = getattr(module, handler_class_name)
50             handler = handler_class()
51             self._handlers[handler_class_name] = handler
52
53     def update(self, confman=None):
54         logging.info('Taking snapshot of the original configuration just in-case')
55         now = int(round(time.time()*1000))
56         snapshot_name = 'cmupdate-' + str(now)
57         self.api.create_snapshot(snapshot_name)
58         if not confman:
59             properties = self.api.get_properties('.*')
60             propsjson = utils.unflatten_config_data(properties)
61             confman = ConfigManager(propsjson)
62
63         for handler in self._sorted_handlers:
64             try:
65                 logging.debug('Calling update for %s', handler)
66                 self._handlers[handler].update(confman)
67                 logging.debug('update for %s done', handler)
68             except Exception as ex:  # pylint: disable=broad-except
69                 logging.warning('Update handler %s failed: %s', handler, str(ex))
70                 raise
71
72         properties = confman.get_config()
73         flatprops = utils.flatten_config_data(properties)
74
75         try:
76             self.uuid_value = self.api.set_properties(flatprops, True)
77         except Exception as exp:  # pylint: disable=broad-except
78             for handler in self._sorted_handlers:
79                 try:
80                     logging.debug('Calling validation_failed for %s', handler)
81                     self._handlers[handler].validation_failed(confman)
82                 except Exception as ex2:  # pylint: disable=broad-except
83                     logging.warning('Update handler %s validation_failed raised exception: %s',
84                                     handler, str(ex2))
85             raise exp
86
87     def wait_activation(self):
88         if self.uuid_value:
89             self.api.wait_activation(self.uuid_value)
90
91     @staticmethod
92     def _read_dependency_file(file_name):
93         before_list = []
94         after_list = []
95
96         try:
97             with open(file_name, 'r') as deps_file:
98                 while True:
99                     line = deps_file.readline()
100                     if not line:
101                         break
102                     if line.startswith('Before:'):
103                         before_list = line[7:].strip().replace(' ', '').split(',')
104                     if line.startswith('After:'):
105                         after_list = line[6:].strip().replace(' ', '').split(',')
106         except IOError:
107             logging.debug('Dependency file %s not found.', file_name)
108
109         return (before_list, after_list)
110
111     def _load_dependencies(self):
112         before_graph = {}
113         after_graph = {}
114
115         for handler in self._handlers.values():
116             deps_filename = '{}/{}.deps'.format(self._plugins_path, handler)
117             before_list, after_list = self._read_dependency_file(deps_filename)
118
119             for before_dep in before_list:
120                 if not self._handlers.get(before_dep, None):
121                     raise CMError(
122                         'Unexisting handler {} referred in handler {}\'s "Before" dependencies'
123                         .format(before_dep, handler))
124             for after_dep in after_list:
125                 if not self._handlers.get(after_dep, None):
126                     raise CMError(
127                         'Unexisting handler {} referred in handler {}\'s "After" dependencies'
128                         .format(after_dep, handler))
129
130             before_graph[str(handler)] = before_list
131             after_graph[str(handler)] = after_list
132
133         sorter = CMDependencySort(after_graph, before_graph)
134         self._sorted_handlers = sorter.sort()