1#!/usr/local/bin/python3.8 2# 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17# 18 19from __future__ import (absolute_import, division, print_function) 20__metaclass__ = type 21 22DOCUMENTATION = ''' 23--- 24module: ce_info_center_log 25short_description: Manages information center log configuration on HUAWEI CloudEngine switches. 26description: 27 - Setting the Timestamp Format of Logs. 28 Configuring the Device to Output Logs to the Log Buffer. 29author: QijunPan (@QijunPan) 30notes: 31 - This module requires the netconf system service be enabled on the remote device being managed. 32 - Recommended connection is C(netconf). 33 - This module also works with C(local) connections for legacy playbooks. 34options: 35 log_time_stamp: 36 description: 37 - Sets the timestamp format of logs. 38 choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', 39 'shortdate_second', 'shortdate_tenthsecond', 'shortdate_millisecond', 40 'formatdate_second', 'formatdate_tenthsecond', 'formatdate_millisecond'] 41 log_buff_enable: 42 description: 43 - Enables the Switch to send logs to the log buffer. 44 default: no_use 45 choices: ['no_use','true', 'false'] 46 log_buff_size: 47 description: 48 - Specifies the maximum number of logs in the log buffer. 49 The value is an integer that ranges from 0 to 10240. If logbuffer-size is 0, logs are not displayed. 50 module_name: 51 description: 52 - Specifies the name of a module. 53 The value is a module name in registration logs. 54 channel_id: 55 description: 56 - Specifies a channel ID. 57 The value is an integer ranging from 0 to 9. 58 log_enable: 59 description: 60 - Indicates whether log filtering is enabled. 61 default: no_use 62 choices: ['no_use','true', 'false'] 63 log_level: 64 description: 65 - Specifies a log severity. 66 choices: ['emergencies', 'alert', 'critical', 'error', 67 'warning', 'notification', 'informational', 'debugging'] 68 state: 69 description: 70 - Determines whether the config should be present or not 71 on the device. 72 default: present 73 choices: ['present', 'absent'] 74''' 75 76EXAMPLES = ''' 77 78- name: CloudEngine info center log test 79 hosts: cloudengine 80 connection: local 81 gather_facts: no 82 vars: 83 cli: 84 host: "{{ inventory_hostname }}" 85 port: "{{ ansible_ssh_port }}" 86 username: "{{ username }}" 87 password: "{{ password }}" 88 transport: cli 89 90 tasks: 91 92 - name: "Setting the timestamp format of logs" 93 community.network.ce_info_center_log: 94 log_time_stamp: date_tenthsecond 95 provider: "{{ cli }}" 96 97 - name: "Enabled to output information to the log buffer" 98 community.network.ce_info_center_log: 99 log_buff_enable: true 100 provider: "{{ cli }}" 101 102 - name: "Set the maximum number of logs in the log buffer" 103 community.network.ce_info_center_log: 104 log_buff_size: 100 105 provider: "{{ cli }}" 106 107 - name: "Set a rule for outputting logs to a channel" 108 community.network.ce_info_center_log: 109 module_name: aaa 110 channel_id: 1 111 log_enable: true 112 log_level: critical 113 provider: "{{ cli }}" 114''' 115 116RETURN = ''' 117proposed: 118 description: k/v pairs of parameters passed into module 119 returned: verbose mode 120 type: dict 121 sample: {"log_time_stamp": "date_tenthsecond", "state": "present"} 122existing: 123 description: k/v pairs of existing configuration 124 returned: verbose mode 125 type: dict 126 sample: {"log_time_stamp": "date_second"} 127end_state: 128 description: k/v pairs of configuration after module execution 129 returned: verbose mode 130 type: dict 131 sample: {"log_time_stamp": "date_tenthsecond"} 132updates: 133 description: commands sent to the device 134 returned: always 135 type: list 136 sample: ["info-center timestamp log date precision-time tenth-second"] 137changed: 138 description: check to see if a change was made on the device 139 returned: always 140 type: bool 141 sample: true 142''' 143 144from xml.etree import ElementTree 145from ansible.module_utils.basic import AnsibleModule 146from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec 147 148 149CE_NC_GET_LOG = """ 150<filter type="subtree"> 151 <syslog xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 152 <globalParam> 153 <bufferSize></bufferSize> 154 <logTimeStamp></logTimeStamp> 155 <icLogBuffEn></icLogBuffEn> 156 </globalParam> 157 <icSources> 158 <icSource> 159 <moduleName>%s</moduleName> 160 <icChannelId>%s</icChannelId> 161 <icChannelName></icChannelName> 162 <logEnFlg></logEnFlg> 163 <logEnLevel></logEnLevel> 164 </icSource> 165 </icSources> 166 </syslog> 167</filter> 168""" 169 170CE_NC_GET_LOG_GLOBAL = """ 171<filter type="subtree"> 172 <syslog xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 173 <globalParam> 174 <bufferSize></bufferSize> 175 <logTimeStamp></logTimeStamp> 176 <icLogBuffEn></icLogBuffEn> 177 </globalParam> 178 </syslog> 179</filter> 180""" 181 182TIME_STAMP_DICT = {"date_boot": "boot", 183 "date_second": "date precision-time second", 184 "date_tenthsecond": "date precision-time tenth-second", 185 "date_millisecond": "date precision-time millisecond", 186 "shortdate_second": "short-date precision-time second", 187 "shortdate_tenthsecond": "short-date precision-time tenth-second", 188 "shortdate_millisecond": "short-date precision-time millisecond", 189 "formatdate_second": "format-date precision-time second", 190 "formatdate_tenthsecond": "format-date precision-time tenth-second", 191 "formatdate_millisecond": "format-date precision-time millisecond"} 192 193CHANNEL_DEFAULT_LOG_STATE = {"0": "true", 194 "1": "true", 195 "2": "true", 196 "3": "false", 197 "4": "true", 198 "5": "false", 199 "6": "true", 200 "7": "true", 201 "8": "true", 202 "9": "true"} 203 204CHANNEL_DEFAULT_LOG_LEVEL = {"0": "warning", 205 "1": "warning", 206 "2": "informational", 207 "3": "informational", 208 "4": "warning", 209 "5": "debugging", 210 "6": "debugging", 211 "7": "warning", 212 "8": "debugging", 213 "9": "debugging"} 214 215 216class InfoCenterLog(object): 217 """ 218 Manages information center log configuration 219 """ 220 221 def __init__(self, argument_spec): 222 self.spec = argument_spec 223 self.module = None 224 self.init_module() 225 226 # module input info 227 self.log_time_stamp = self.module.params['log_time_stamp'] 228 self.log_buff_enable = self.module.params['log_buff_enable'] 229 self.log_buff_size = self.module.params['log_buff_size'] 230 self.module_name = self.module.params['module_name'] 231 self.channel_id = self.module.params['channel_id'] 232 self.log_enable = self.module.params['log_enable'] 233 self.log_level = self.module.params['log_level'] 234 self.state = self.module.params['state'] 235 236 # state 237 self.log_dict = dict() 238 self.changed = False 239 self.updates_cmd = list() 240 self.commands = list() 241 self.results = dict() 242 self.proposed = dict() 243 self.existing = dict() 244 self.end_state = dict() 245 246 def init_module(self): 247 """init module""" 248 249 self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) 250 251 def check_response(self, xml_str, xml_name): 252 """Check if response message is already succeed""" 253 254 if "<ok/>" not in xml_str: 255 self.module.fail_json(msg='Error: %s failed.' % xml_name) 256 257 def get_log_dict(self): 258 """ log config dict""" 259 260 log_dict = dict() 261 if self.module_name: 262 if self.module_name.lower() == "default": 263 conf_str = CE_NC_GET_LOG % (self.module_name.lower(), self.channel_id) 264 else: 265 conf_str = CE_NC_GET_LOG % (self.module_name.upper(), self.channel_id) 266 else: 267 conf_str = CE_NC_GET_LOG_GLOBAL 268 269 xml_str = get_nc_config(self.module, conf_str) 270 if "<data/>" in xml_str: 271 return log_dict 272 273 xml_str = xml_str.replace('\r', '').replace('\n', '').\ 274 replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ 275 replace('xmlns="http://www.huawei.com/netconf/vrp"', "") 276 root = ElementTree.fromstring(xml_str) 277 278 # get global param info 279 glb = root.find("syslog/globalParam") 280 if glb: 281 for attr in glb: 282 if attr.tag in ["bufferSize", "logTimeStamp", "icLogBuffEn"]: 283 log_dict[attr.tag] = attr.text 284 285 # get info-center source info 286 log_dict["source"] = dict() 287 src = root.find("syslog/icSources/icSource") 288 if src: 289 for attr in src: 290 if attr.tag in ["moduleName", "icChannelId", "icChannelName", "logEnFlg", "logEnLevel"]: 291 log_dict["source"][attr.tag] = attr.text 292 293 return log_dict 294 295 def config_log_global(self): 296 """config log global param""" 297 298 xml_str = '<globalParam operation="merge">' 299 if self.log_time_stamp: 300 if self.state == "present" and self.log_time_stamp.upper() != self.log_dict.get("logTimeStamp"): 301 xml_str += '<logTimeStamp>%s</logTimeStamp>' % self.log_time_stamp.upper() 302 self.updates_cmd.append( 303 "info-center timestamp log %s" % TIME_STAMP_DICT.get(self.log_time_stamp)) 304 elif self.state == "absent" and self.log_time_stamp.upper() == self.log_dict.get("logTimeStamp"): 305 xml_str += '<logTimeStamp>DATE_SECOND</logTimeStamp>' # set default 306 self.updates_cmd.append("undo info-center timestamp log") 307 else: 308 pass 309 310 if self.log_buff_enable != 'no_use': 311 if self.log_dict.get("icLogBuffEn") != self.log_buff_enable: 312 xml_str += '<icLogBuffEn>%s</icLogBuffEn>' % self.log_buff_enable 313 if self.log_buff_enable == "true": 314 self.updates_cmd.append("info-center logbuffer") 315 else: 316 self.updates_cmd.append("undo info-center logbuffer") 317 318 if self.log_buff_size: 319 if self.state == "present" and self.log_dict.get("bufferSize") != self.log_buff_size: 320 xml_str += '<bufferSize>%s</bufferSize>' % self.log_buff_size 321 self.updates_cmd.append( 322 "info-center logbuffer size %s" % self.log_buff_size) 323 elif self.state == "absent" and self.log_dict.get("bufferSize") == self.log_buff_size: 324 xml_str += '<bufferSize>512</bufferSize>' 325 self.updates_cmd.append("undo info-center logbuffer size") 326 327 if xml_str == '<globalParam operation="merge">': 328 return "" 329 else: 330 xml_str += '</globalParam>' 331 return xml_str 332 333 def config_log_soruce(self): 334 """config info-center sources""" 335 336 xml_str = '' 337 if not self.module_name or not self.channel_id: 338 return xml_str 339 340 source = self.log_dict["source"] 341 if self.state == "present": 342 xml_str = '<icSources><icSource operation="merge">' 343 cmd = 'info-center source %s channel %s log' % ( 344 self.module_name, self.channel_id) 345 else: 346 if not source or self.module_name != source.get("moduleName").lower() or \ 347 self.channel_id != source.get("icChannelId"): 348 return '' 349 350 if self.log_enable == 'no_use' and not self.log_level: 351 xml_str = '<icSources><icSource operation="delete">' 352 else: 353 xml_str = '<icSources><icSource operation="merge">' 354 cmd = 'undo info-center source %s channel %s log' % ( 355 self.module_name, self.channel_id) 356 357 xml_str += '<moduleName>%s</moduleName><icChannelId>%s</icChannelId>' % ( 358 self.module_name, self.channel_id) 359 360 # log_enable 361 if self.log_enable != 'no_use': 362 if self.state == "present" and (not source or self.log_enable != source.get("logEnFlg")): 363 xml_str += '<logEnFlg>%s</logEnFlg>' % self.log_enable 364 if self.log_enable == "true": 365 cmd += ' state on' 366 else: 367 cmd += ' state off' 368 elif self.state == "absent" and source and self.log_level == source.get("logEnLevel"): 369 xml_str += '<logEnFlg>%s</logEnFlg>' % CHANNEL_DEFAULT_LOG_STATE.get(self.channel_id) 370 cmd += ' state' 371 372 # log_level 373 if self.log_level: 374 if self.state == "present" and (not source or self.log_level != source.get("logEnLevel")): 375 xml_str += '<logEnLevel>%s</logEnLevel>' % self.log_level 376 cmd += ' level %s' % self.log_level 377 elif self.state == "absent" and source and self.log_level == source.get("logEnLevel"): 378 xml_str += '<logEnLevel>%s</logEnLevel>' % CHANNEL_DEFAULT_LOG_LEVEL.get(self.channel_id) 379 cmd += ' level' 380 381 if xml_str.endswith("</icChannelId>"): 382 if self.log_enable == 'no_use' and not self.log_level and self.state == "absent": 383 xml_str += '</icSource></icSources>' 384 self.updates_cmd.append(cmd) 385 return xml_str 386 else: 387 return '' 388 else: 389 xml_str += '</icSource></icSources>' 390 self.updates_cmd.append(cmd) 391 return xml_str 392 393 def netconf_load_config(self, xml_str): 394 """load log config by netconf""" 395 396 if not xml_str: 397 return 398 399 xml_cfg = """ 400 <config> 401 <syslog xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0"> 402 %s 403 </syslog> 404 </config>""" % xml_str 405 406 recv_xml = set_nc_config(self.module, xml_cfg) 407 self.check_response(recv_xml, "SET_LOG") 408 self.changed = True 409 410 def check_params(self): 411 """Check all input params""" 412 413 # check log_buff_size ranges from 0 to 10240 414 if self.log_buff_size: 415 if not self.log_buff_size.isdigit(): 416 self.module.fail_json( 417 msg="Error: log_buff_size is not digit.") 418 if int(self.log_buff_size) < 0 or int(self.log_buff_size) > 10240: 419 self.module.fail_json( 420 msg="Error: log_buff_size is not ranges from 0 to 10240.") 421 422 # check channel_id ranging from 0 to 9 423 if self.channel_id: 424 if not self.channel_id.isdigit(): 425 self.module.fail_json(msg="Error: channel_id is not digit.") 426 if int(self.channel_id) < 0 or int(self.channel_id) > 9: 427 self.module.fail_json( 428 msg="Error: channel_id is not ranges from 0 to 9.") 429 430 # module_name and channel_id must be set at the same time 431 if bool(self.module_name) != bool(self.channel_id): 432 self.module.fail_json( 433 msg="Error: module_name and channel_id must be set at the same time.") 434 435 def get_proposed(self): 436 """get proposed info""" 437 438 if self.log_time_stamp: 439 self.proposed["log_time_stamp"] = self.log_time_stamp 440 if self.log_buff_enable != 'no_use': 441 self.proposed["log_buff_enable"] = self.log_buff_enable 442 if self.log_buff_size: 443 self.proposed["log_buff_size"] = self.log_buff_size 444 if self.module_name: 445 self.proposed["module_name"] = self.module_name 446 if self.channel_id: 447 self.proposed["channel_id"] = self.channel_id 448 if self.log_enable != 'no_use': 449 self.proposed["log_enable"] = self.log_enable 450 if self.log_level: 451 self.proposed["log_level"] = self.log_level 452 self.proposed["state"] = self.state 453 454 def get_existing(self): 455 """get existing info""" 456 457 if not self.log_dict: 458 return 459 460 if self.log_time_stamp: 461 self.existing["log_time_stamp"] = self.log_dict.get("logTimeStamp").lower() 462 if self.log_buff_enable != 'no_use': 463 self.existing["log_buff_enable"] = self.log_dict.get("icLogBuffEn") 464 if self.log_buff_size: 465 self.existing["log_buff_size"] = self.log_dict.get("bufferSize") 466 if self.module_name: 467 self.existing["source"] = self.log_dict.get("source") 468 469 def get_end_state(self): 470 """get end state info""" 471 472 log_dict = self.get_log_dict() 473 if not log_dict: 474 return 475 476 if self.log_time_stamp: 477 self.end_state["log_time_stamp"] = log_dict.get("logTimeStamp").lower() 478 if self.log_buff_enable != 'no_use': 479 self.end_state["log_buff_enable"] = log_dict.get("icLogBuffEn") 480 if self.log_buff_size: 481 self.end_state["log_buff_size"] = log_dict.get("bufferSize") 482 if self.module_name: 483 self.end_state["source"] = log_dict.get("source") 484 485 def work(self): 486 """worker""" 487 488 self.check_params() 489 self.log_dict = self.get_log_dict() 490 self.get_existing() 491 self.get_proposed() 492 493 # deal present or absent 494 xml_str = '' 495 if self.log_time_stamp or self.log_buff_enable != 'no_use' or self.log_buff_size: 496 xml_str += self.config_log_global() 497 498 if self.module_name: 499 xml_str += self.config_log_soruce() 500 501 if xml_str: 502 self.netconf_load_config(xml_str) 503 self.changed = True 504 505 self.get_end_state() 506 self.results['changed'] = self.changed 507 self.results['proposed'] = self.proposed 508 self.results['existing'] = self.existing 509 self.results['end_state'] = self.end_state 510 if self.changed: 511 self.results['updates'] = self.updates_cmd 512 else: 513 self.results['updates'] = list() 514 515 self.module.exit_json(**self.results) 516 517 518def main(): 519 """Module main""" 520 521 argument_spec = dict( 522 log_time_stamp=dict(required=False, type='str', 523 choices=['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', 524 'shortdate_second', 'shortdate_tenthsecond', 'shortdate_millisecond', 525 'formatdate_second', 'formatdate_tenthsecond', 'formatdate_millisecond']), 526 log_buff_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), 527 log_buff_size=dict(required=False, type='str'), 528 module_name=dict(required=False, type='str'), 529 channel_id=dict(required=False, type='str'), 530 log_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), 531 log_level=dict(required=False, type='str', 532 choices=['emergencies', 'alert', 'critical', 'error', 533 'warning', 'notification', 'informational', 'debugging']), 534 state=dict(required=False, default='present', 535 choices=['present', 'absent']) 536 ) 537 538 argument_spec.update(ce_argument_spec) 539 module = InfoCenterLog(argument_spec) 540 module.work() 541 542 543if __name__ == '__main__': 544 main() 545