AnsibleのJUNOS設定モジュール書いてみた(VLAN編)
Ansibleのお勉強のためにJUNOS向けにNETCONFでVLANを作成・削除するモジュールを書いてみた。
動いてるけど、VC環境だとcommit syncするときにタイムアウトするかも。
Ex3300とEx2200で動作確認した。意外にちゃんとうごいて冪等性も担保できてる。
引数
- state: 作成する時(present),削除する時(absent)
- node: 操作対象ネットワーク機器のノード名またはIPアドレス
- port: 操作対象ネットワーク機器がNETCONF用に口をあけているポート(デフォルト:830)
- user: 操作対象ネットワーク機器のログインアカウント
- password: 操作対象ネットワーク機器のパスワード
- vlan_name: VLAN名
- vlan_id: VLANID
- vlan_desc: VLANのDescription
テストしてみる
VLANを作成する
$ ~/ansible/hacking/test-module -m ./junos_vlan -a "node='switch00' port=830 user='foo' password='bar' state='present' vlan_name='VLAN3000' vlan_id=3000 vlan_desc='test vlan'"
VLANを削除する
もちろん対象VLANが存在しなければ何もしない。
$ ~/ansible/hacking/test-module -m ./junos_vlan -a "node='switch00' port=830 user='foo' password='bar' state='absent' vlan_name='VLAN3000' vlan_id=3000 vlan_desc='test vlan'"
モジュール
#!/usr/bin/env python # -*- coding: utf-8 -*- DOCUMENTATION = ''' --- module: junos_vlan author: Hideki Saito short_description: Manage VLAN resources requirements: - ncclient v0.3.2 description: - Manage VLAN resources on JUNOS devices. This module requires the ncclient. [link] http://ncclient.grnet.gr/ options: node: desctiption - the hostname or IP address to connect requred: true port: description - the port number to connect user: description - junos user id required: true password: description - junos user password requre: true vlan_name: description: - name of vlan required: false vlan_id: description: - the vlan id required: true vlan_desc: description: - a descriptive name for the vlan required: false state: description: - describe the desired state of the vlan related to the config required: false default: 'present' choices: [ 'present', 'absent' ] logging: description: - enables or disables the syslog facility for this module required: false choices: [ 'true', 'false', 'yes', 'no' ] notes: - The ncclient module must be installed. - See http://ncclient.grnet.gr/ for details ''' import json from jinja2 import Template from ncclient import manager from ncclient.operations.errors import TimeoutExpiredError from xml.etree import ElementTree create_vlan = """ <config> <configuration> <vlans> <vlan operation="create"> <name>{{ vlan_name }}</name> <description>{{ vlan_desc }}</description> <vlan-id>{{ vlan_id }}</vlan-id> </vlan> </vlans> </configuration> </config> """ delete_vlan = """ <config> <configuration> <vlans> <vlan operation="delete"> <name>{{ vlan_name }}</name> <vlan-id>{{ vlan_id }}</vlan-id> </vlan> </vlans> </configuration> </config> """ set_description = """ <config> <configuration> <vlans> <vlan> <name>{{ vlan_name }}</name> <description>{{ vlan_desc }}</description> </vlan> </vlans> </configuration> </config> """ class JunosVlan(object): def __init__(self, module): self.module = module self.node = module.params['node'] self.port = module.params['port'] self.user = module.params['user'] self.password = module.params['password'] self.vlan_name = module.params['vlan_name'] self.vlan_id = module.params['vlan_id'] self.vlan_desc = module.params['vlan_desc'] self.state = module.params['state'] with manager.connect(host=self.node, port=self.port, username=self.user, password=self.password, hostkey_verify=False) as m: self.config = m.get_config(source='candidate').data_xml def push(self, config): try: with manager.connect(host=self.node, port=self.port, username=self.user, password=self.password, unknown_host_cb=_always_unknown_true) as mgr: mgr.edit_config(target="candidate", config=config, test_option="test-then-set") mgr.commit() rc = 0 out = 'commit succeeded' err = '' except TimeoutExpiredError as e: rc = 1 out = 'commit failure' err = 'operation timeout' return (rc, out, err) def vlans(self): elm = ElementTree.fromstring(self.config) elm_vlans = elm.find('.//vlans') vlan_info = dict() for elm_vlan in elm_vlans.findall('.//vlan'): vlan_id = elm_vlan.findtext('.//vlan-id') name = elm_vlan.findtext('.//name') description = elm_vlan.findtext('.//description') vlan_info[name] = dict(vlan_id=vlan_id, description=description) return vlan_info.keys() def _always_unknown_true(host, fingerprint): return True def main(): module = AnsibleModule( argument_spec = dict( node=dict(default=None, requred=True, type='str'), port=dict(default=830, requred=True, type='int'), user=dict(default=None, requred=True, type='str'), password=dict(default=None, requred=True, type='str'), vlan_name=dict(default=None, required=True, type='str'), vlan_id=dict(default=None, type='int'), vlan_desc=dict(default=None, type='str'), state=dict(default='present', choices=['present', 'absent']), ), supports_check_mode = True ) obj = JunosVlan(module) rc = None result = dict() changed = False if obj.state == 'absent': if obj.vlan_name in obj.vlans(): template = Template(delete_vlan) config = template.render(vlan_name=obj.vlan_name, vlan_id=obj.vlan_id) if module.check_mode: module.exit_json(changed=True) (rc, out, err) = obj.push(config) result['results'] = out elif obj.state == 'present': if obj.vlan_name in obj.vlans(): template = Template(set_description) config = template.render(vlan_name=obj.vlan_name, vlan_desc=obj.vlan_desc) else: template = Template(create_vlan) config = template.render(vlan_name=obj.vlan_name, vlan_id=obj.vlan_id, vlan_desc=obj.vlan_desc) if module.check_mode: module.exit_json(changed=True) (rc, out, err) = obj.push(config) result['results'] = out if rc is not None and rc != 0: module.fail_json(msg=err, rc=rc) if rc is None: result['changed'] = False else: result['changed'] = True module.exit_json(**result) # include magic from lib/ansible/module_common.py #<<INCLUDE_ANSIBLE_MODULE_COMMON>> main() ## ## [EOF] ##