diff --git a/library/net_infrastructure/arista_interface b/library/net_infrastructure/arista_interface new file mode 100644 index 0000000000..c5ab653bb8 --- /dev/null +++ b/library/net_infrastructure/arista_interface @@ -0,0 +1,258 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013, Arista Networks +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +DOCUMENTATION = ''' +--- +module: arista_interface +author: Peter Sprygada +short_description: Manage physical Ethernet interfaces +requirements: + - Arista EOS 4.10 + - Netdev extension for EOS +description: + - Manage physical Ethernet interface resources on Arista EOS network devices +options: + interface_id: + description: + - the full name of the interface + required: true + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + admin: + description: + - controls the operational state of the interface + required: false + choices: [ 'up', 'down' ] + description: + description: + - a single line text string describing the interface + required: false + mtu: + description: + - configureds the maximum transmission unit for the interface + required: false + default: 1500 + speed: + description: + - sets the interface speed setting + required: false + default: 'auto' + choices: [ 'auto', '100m', '1g', '10g' ] + duplex: + description: + - sets the interface duplex setting + required: false + default: 'auto' + choices: [ 'auto', 'half', 'full' ] +notes: + - Requires EOS 4.10 or later + - The Netdev extension for EOS must be installed and active in the + available extensions (show extensions from the EOS CLI) + - See http://eos.aristanetworks.com for details +''' +EXAMPLES = ''' +Example playbook entries using the arista_interface module to manage resource +state. Note that interface names must be the full interface name not shortcut +names (ie Ethernet, not Et1) + + tasks: + - name: enable interface Ethernet 1 + action: arista_interface interface_id=Ethernet1 admin=up speed=10g duplex=full logging=true + + - name: set mtu on Ethernet 1 + action: arista_interface interface_id=Ethernet1 mtu=1600 speed=10g duplex=full logging=true + + - name: reset changes to Ethernet 1 + action: arista_interface interface_id=Ethernet1 admin=down mtu=1500 speed=10g duplex=full logging=true +''' +import syslog +import json + +class AristaInterface(object): + """ This is the base class for managing physcial Ethernet interface + resources in EOS network devices. This class acts as a wrapper around + the netdev extension in EOS. You must have the netdev extension + installed in order for this module to work properly. + + The following commands are implemented in this module: + * netdev interface list + * netdev interface show + * netdev interface edit + * netdev interface delete + + This module only allows for the management of physical Ethernet + interfaces. + """ + + attributes = ['interface_id', 'admin', 'description', 'mtu', 'speed', 'duplex'] + + def __init__(self, module): + self.module = module + self.interface_id = module.params['interface_id'] + self.admin = module.params['admin'] + self.description = module.params['description'] + self.mtu = module.params['mtu'] + self.speed = module.params['speed'] + self.duplex = module.params['duplex'] + self.logging = module.params['logging'] + + @property + def changed(self): + """ The changed property provides a boolean response if the currently + loaded resouces has changed from the resource running in EOS. + + Returns True if the object is not in sync + Returns False if the object is in sync. + """ + return len(self.updates()) > 0 + + def log(self, entry): + """ This method is responsible for sending log messages to the local + syslog. + """ + if self.logging: + syslog.openlog('ansible-%s' % os.path.basename(__file__)) + syslog.syslog(syslog.LOG_NOTICE, entry) + + def run_command(self, cmd): + """ Calls the Ansible module run_command method. This method will + directly return the results of the run_command method + """ + self.log(cmd) + return self.module.run_command(cmd.split()) + + def get(self): + """ This method will return a dictionary with the attributes of the + physical ethernet interface resource specified in interface_id. + The physcial ethernet interface resource has the following + stucture: + + { + "interface_id": , + "description": , + "admin": [up | down], + "mtu": , + "speed": [auto | 100m | 1g | 10g] + "duplex": [auto | half | full] + } + + If the physical ethernet interface specified by interface_id does + not exist in the system, this method will return None. + """ + cmd = "netdev interface show %s" % self.interface_id + (rc, out, err) = self.run_command(cmd) + obj = json.loads(out) + if obj.get('status') != 200: + return None + return obj['result'] + + def update(self): + """ Updates an existing physical ethernet resource in the current + running configuration. If the physical ethernet resource does + not exist, this method will return an error. + + This method implements the following commands: + * netdev interface edit {interface_id} [attributes] + + Returns an updated physical ethernet interafce resoure if the + update method was successful + """ + attribs = list() + for attrib in self.updates(): + attribs.append("--%s" % attrib) + attribs.append(str(getattr(self, attrib))) + + if attribs: + cmd = "netdev interface edit %s " % self.interface_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = resp['result'] + return (rc, out, err) + + return (0, None, "No attributes have been modified") + + def updates(self): + """ This method will check the current phy resource in the running + configuration and return a list of attribute that are not in sync + with the current resource from the running configuration. + """ + obj = self.get() + update = lambda a, z: a != z + + updates = list() + for attrib in self.attributes: + value = getattr(self, attrib) + if update(obj[attrib], value) and value is not None: + updates.append(attrib) + + self.log("updates: %s" % updates) + return updates + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + interface_id=dict(default=None, type='str'), + admin=dict(default=None, choices=['up', 'down'], type='str'), + description=dict(default=None, type='str'), + mtu=dict(default=None, type='int'), + speed=dict(default=None, choices=['auto', '100m', '1g', '10g']), + duplex=dict(default=None, choices=['auto', 'half', 'full']), + logging=dict(default=False, type='bool') + ), + supports_check_mode = True + ) + + obj = AristaInterface(module) + + rc = None + result = dict() + + if module.check_mode: + module.exit_json(changed=obj.changed) + + else: + if obj.changed: + (rc, out, err) = obj.update() + 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 +#<> +main() diff --git a/library/net_infrastructure/arista_l2interface b/library/net_infrastructure/arista_l2interface new file mode 100644 index 0000000000..fa33aff3bc --- /dev/null +++ b/library/net_infrastructure/arista_l2interface @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013, Arista Networks +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +DOCUMENTATION = ''' +--- +module: arista_l2interface +author: Peter Sprygada +short_description: Manage layer 2 interfaces +requirements: + - Arista EOS 4.10 + - Netdev extension for EOS +description: + - Manage layer 2 interface resources on Arista EOS network devices +options: + interface_id: + description: + - the full name of the interface + required: true + state: + description: + - describe the desired state of the interface related to the config + required: false + default: 'present' + choices: [ 'present', 'absent' ] + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + vlan_tagging: + description: + - specifies whether or not vlan tagging should be enabled for + this interface + required: false + default: true + choices: [ 'enable', 'disable' ] + tagged_vlans: + description: + - specifies the list of vlans that should be allowed to transit + this interface + required: false + untagged_vlan: + description: + - specifies the vlan that untagged traffic should be placed in for + transit across a vlan tagged link + required: false + default: 'default' +notes: + - Requires EOS 4.10 or later + - The Netdev extension for EOS must be installed and active in the + available extensions (show extensions from the EOS CLI) + - See http://eos.aristanetworks.com for details +''' +EXAMPLES = ''' +Example playbook entries using the arista_l2interface module to manage resource +state. Note that interface names must be the full interface name not shortcut +names (ie Ethernet, not Et1) + + tasks: + - name: create switchport ethernet1 access port + action: arista_l2interface interface_id=Ethernet1 logging=true + + - name: create switchport ethernet2 trunk port + action: arista_l2interface interface_id=Ethernet2 vlan_tagging=enable logging=true + + - name: add vlans to red and blue switchport ethernet2 + action: arista_l2interface interface_id=Ethernet2 tagged_vlans=red,blue logging=true + + - name: set untagged vlan for Ethernet1 + action: arista_l2interface interface_id=Ethernet1 untagged_vlan=red logging=true + + - name: convert access to trunk + action: arista_l2interface interface_id=Ethernet1 vlan_tagging=enable tagged_vlans=red,blue logging=true + + - name: convert trunk to access + action: arista_l2interface interface_id=Ethernet2 vlan_tagging=disable untagged_vlan=blue logging=true + + - name: delete switchport ethernet1 + action: arista_l2interface interface_id=Ethernet1 state=absent logging=true +''' +import syslog +import json + +class AristaL2Interface(object): + """ This is the base class managing layer 2 interfaces (switchport) + resources in Arista EOS network devices. This class provides an + implementation for creating, updating and deleting layer 2 interfaces. + + Note: The netdev extension for EOS must be installed in order of this + module to work properly. + + The following commands are implemented in this module: + * netdev l2interface list + * netdev l2interface show + * netdev l2interface edit + * netdev l2interface delete + + """ + + attributes= ['vlan_tagging', 'tagged_vlans', 'untagged_vlan'] + + def __init__(self, module): + self.module = module + self.interface_id = module.params['interface_id'] + self.state = module.params['state'] + self.vlan_tagging = module.params['vlan_tagging'] + self.tagged_vlans = module.params['tagged_vlans'] + self.untagged_vlan = module.params['untagged_vlan'] + self.logging = module.params['logging'] + + @property + def changed(self): + """ The changed property provides a boolean response if the currently + loaded resouces has changed from the resource running in EOS. + + Returns True if the object is not in sync + Returns False if the object is in sync. + """ + return len(self.updates()) > 0 + + def log(self, entry): + """ This method is responsible for sending log messages to the local + syslog. + """ + if self.logging: + syslog.openlog('ansible-%s' % os.path.basename(__file__)) + syslog.syslog(syslog.LOG_NOTICE, entry) + + def run_command(self, cmd): + """ Calls the Ansible module run_command method. This method will + directly return the results of the run_command method + """ + self.log("Command: %s" % cmd) + return self.module.run_command(cmd.split()) + + + def get(self): + """ This method will return a dictionary with the attributes of the + layer 2 interface resource specified in interface_id. The layer + 2 interface resource has the following stucture: + + { + "interface_id": , + "vlan_tagging": [enable* | disable], + "tagged_vlans": , + "untagged_vlan": + } + + If the layer 2 interface specified by interface_id does not + exist in the system, this method will return None. + """ + cmd = "netdev l2interface show %s" % self.interface_id + (rc, out, err) = self.run_command(cmd) + obj = json.loads(out) + if obj.get('status') != 200: + return None + return obj['result'] + + def create(self): + """ Creates a layer 2 interface resource in the current running + configuration. If the layer 2 interface already exists, the + function will return successfully. + + This function implements the following commands: + * netdev l2interface create {interface_id} [attributes] + + Returns the layer 2 interface resource if the create method was + successful + Returns an error message if there as a problem creating the layer + 2 interface + """ + attribs = [] + for attrib in self.attributes: + if getattr(self, attrib): + attribs.append("--%s" % attrib) + attribs.append(getattr(self, attrib)) + + cmd = "netdev l2interface create %s " % self.interface_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 201: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = resp['result'] + return (rc, out, err) + + def update(self): + """ Updates an existing VLAN resource in the current running + configuration. If the VLAN resource does not exist, this method + will return an error. + + This method implements the following commands: + * netdev l2interface edit {interface_id} [attributes] + + Returns an updated layer 2 interafce resoure if the update method + was successful + """ + attribs = list() + for attrib in self.updates(): + attribs.append("--%s" % attrib) + attribs.append(getattr(self, attrib)) + + cmd = "netdev l2interface edit %s " % self.interface_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = resp['result'] + return (rc, out, err) + + return (0, None, "No attributes have been modified") + + def delete(self): + """ Deletes an existing layer 2 interface resource from the current + running configuration. A nonexistent layer 2 interface will + return successful for this operation. + + This method implements the following commands: + * netdev l2interface delete {interface_id} + + Returns nothing if the delete was successful + Returns error message if there was a problem deleting the resource + """ + cmd = "netdev l2interface delete %s" % self.interface_id + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = resp['status'] + err = resp['message'] + out = None + return (rc, out, err) + + def updates(self): + """ This method will check the current layer 2 interface resource in + the running configuration and return a list of attributes that are + not in sync with the current resource. + """ + obj = self.get() + update = lambda a, z: a != z + + updates = list() + for attrib in self.attributes: + value = getattr(self, attrib) + if update(obj[attrib], value) and value is not None: + updates.append(attrib) + self.log("Updates: %s" % updates) + return updates + + def exists(self): + """ Returns True if the current layer 2 interface resource exists and + returns False if it does not. This method only checks for the + existence of the interface as specified in interface_id. + """ + (rc, out, err) = self.run_command("netdev l2interface list") + collection = json.loads(out) + return collection.get('result').has_key(self.interface_id) + + +def main(): + module = AnsibleModule( + argument_spec = dict( + interface_id=dict(default=None, type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + vlan_tagging=dict(default=None, choices=['enable', 'disable']), + tagged_vlans=dict(default=None, type='str'), + untagged_vlan=dict(default=None, type='str'), + logging=dict(default=False, type='bool') + ), + supports_check_mode = True + ) + + obj = AristaL2Interface(module) + + rc = None + result = dict() + + if obj.state == 'absent': + if obj.exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = obj.delete() + if rc !=0: + module.fail_json(msg=err, rc=rc) + + elif obj.state == 'present': + if not obj.exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = obj.create() + result['results'] = out + else: + if obj.changed: + if module.check_mode: + module.exit_json(changed=obj.changed) + (rc, out, err) = obj.update() + 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 +#<> +main() diff --git a/library/net_infrastructure/arista_lag b/library/net_infrastructure/arista_lag new file mode 100644 index 0000000000..c96d0dee58 --- /dev/null +++ b/library/net_infrastructure/arista_lag @@ -0,0 +1,324 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013, Arista Networks +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +DOCUMENTATION = ''' +--- +module: arista_lag +author: Peter Sprygada +short_description: Manage port channel (lag) interfaces +requirements: + - Arista EOS 4.10 + - Netdev extension for EOS +description: + - Manage port channel interface resources on Arista EOS network + devices +options: + interface_id: + description: + - the full name of the interface + required: true + state: + description: + - describe the desired state of the interface related to the config + required: false + default: 'present' + choices: [ 'present', 'absent' ] + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + links: + description: + - array of physical interface links to include in this lag + required: false + minimum_links: + description: + - the minimum number of physical interaces that must be operationally up to consider the lag operationally up + required: false + lacp: + description: + - enables the use of the LACP protocol for managing link bundles + required: false + default: 'active', + choices: [ 'active', 'passive', 'off' ] +notes: + - Requires EOS 4.10 or later + - The Netdev extension for EOS must be installed and active in the + available extensions (show extensions from the EOS CLI) + - See http://eos.aristanetworks.com for details +''' + +EXAMPLES = ''' +Example playbook entries using the arista_lag module to manage resource +state. Note that interface names must be the full interface name not shortcut +names (ie Ethernet, not Et1) + + tasks: + - name: create lag interface + action: arista_lag interface_id=Port-Channel1 links=Ethernet1,Ethernet2 logging=true + + - name: add member links + action: arista_lag interface_id=Port-Channel1 links=Ethernet1,Ethernet2,Ethernet3 logging=true + + - name: remove member links + action: arista_lag interface_id=Port-Channel1 links=Ethernet2,Ethernet3 logging=true + + - name: remove lag interface + action: arista_lag interface_id=Port-Channel1 state=absent logging=true +''' +import syslog +import json + +class AristaLag(object): + """ This is the base class managing port-channel (lag) interfaces + resources in Arista EOS network devices. This class provides an + implementation for creating, updating and deleting port-channel + interfaces. + + Note: The netdev extension for EOS must be installed in order of this + module to work properly. + + The following commands are implemented in this module: + * netdev lag list + * netdev lag show + * netdev lag edit + * netdev lag delete + + """ + + attributes = ['links', 'minimum_links', 'lacp'] + + def __init__(self, module): + self.module = module + self.interface_id = module.params['interface_id'] + self.state = module.params['state'] + self.links = module.params['links'] + self.minimum_links = module.params['minimum_links'] + self.lacp = module.params['lacp'] + self.logging = module.params['logging'] + + @property + def changed(self): + """ The changed property provides a boolean response if the currently + loaded resouces has changed from the resource running in EOS. + + Returns True if the object is not in sync + Returns False if the object is in sync. + """ + return len(self.updates()) > 0 + + def log(self, entry): + """ This method is responsible for sending log messages to the local + syslog. + """ + if self.logging: + syslog.openlog('ansible-%s' % os.path.basename(__file__)) + syslog.syslog(syslog.LOG_NOTICE, entry) + + def run_command(self, cmd): + """ Calls the Ansible module run_command method. This method will + directly return the results of the run_command method + """ + self.log("Command: %s" % cmd) + return self.module.run_command(cmd.split()) + + + def get(self): + """ This method will return a dictionary with the attributes of the + lag interface resource specified in interface_id. The lag + interface resource has the following stucture: + + { + "interface_id": , + "links": , + "minimum_links": , + "lacp": [active* | passive | off] + } + + If the lag interface specified by interface_id does not + exist in the system, this method will return None. + """ + cmd = "netdev lag show %s" % self.interface_id + (rc, out, err) = self.run_command(cmd) + obj = json.loads(out) + if obj.get('status') != 200: + return None + return obj['result'] + + def create(self): + """ Creates a lag interface resource in the current running + configuration. If the lag interface already exists, the + function will return successfully. + + This function implements the following commands: + * netdev lag create {interface_id} [attributes] + + Returns the lag interface resource if the create method was + successful + Returns an error message if there as a problem creating the lag + interface + """ + attribs = [] + for attrib in self.attributes: + if getattr(self, attrib): + attribs.append("--%s" % attrib) + attribs.append(getattr(self, attrib)) + + cmd = "netdev lag create %s " % self.interface_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 201: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = self.get() + return (rc, out, err) + + def update(self): + """ Updates an existing lag resource in the current running + configuration. If the lag resource does not exist, this method + will return an error. + + This method implements the following commands: + * netdev lag edit {interface_id} [attributes] + + Returns an updated lag interafce resoure if the update method + was successful + """ + attribs = list() + for attrib in self.updates(): + attribs.append("--%s" % attrib) + attribs.append(getattr(self, attrib)) + + cmd = "netdev lag edit %s " % self.interface_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = resp['result'] + return (rc, out, err) + + return (2, None, "No attributes have been modified") + + def delete(self): + """ Deletes an existing lag interface resource from the current + running configuration. A nonexistent lag interface will + return successful for this operation. + + This method implements the following commands: + * netdev lag delete {interface_id} + + Returns nothing if the delete was successful + Returns error message if there was a problem deleting the resource + """ + cmd = "netdev lag delete %s" % self.interface_id + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = resp['status'] + err = resp['message'] + out = None + return (rc, out, err) + + def updates(self): + """ This method will check the current lag interface resource in the + running configuration and return a list of attributes that are + not in sync with the current resource. + """ + obj = self.get() + update = lambda a, z: a != z + + updates = list() + for attrib in self.attributes: + if update(obj[attrib], getattr(self, attrib)): + updates.append(attrib) + + return updates + + def exists(self): + """ Returns True if the current lag interface resource exists and + returns False if it does not. This method only checks for the + existence of the interface as specified in interface_id. + """ + (rc, out, err) = self.run_command("netdev lag list") + collection = json.loads(out) + return collection.get('result').has_key(self.interface_id) + + +def main(): + module = AnsibleModule( + argument_spec = dict( + interface_id=dict(default=None, type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + links=dict(default=None, type='str'), + lacp=dict(default=None, choices=['active', 'passive', 'off'], type='str'), + minimum_links=dict(default=None, type='int'), + logging=dict(default=False, type='bool') + ), + supports_check_mode = True + ) + + obj = AristaLag(module) + + rc = None + result = dict() + + if obj.state == 'absent': + if obj.exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = obj.delete() + if rc !=0: + module.fail_json(msg=err, rc=rc) + + elif obj.state == 'present': + if not obj.exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = obj.create() + result['results'] = out + else: + if module.check_mode: + module.exit_json(changed=obj.changed) + (rc, out, err) = obj.update() + 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 +#<> +main() diff --git a/library/net_infrastructure/arista_vlan b/library/net_infrastructure/arista_vlan new file mode 100644 index 0000000000..14d429a0af --- /dev/null +++ b/library/net_infrastructure/arista_vlan @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013, Arista Networks +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +DOCUMENTATION = ''' +--- +module: arista_vlan +author: Peter Sprygada +short_description: Manage VLAN resources +requirements: + - Arista EOS 4.10 + - Netdev extension for EOS +description: + - Manage VLAN resources on Arista EOS network devices. This module + requires the Netdev EOS extension to be installed in EOS. For detailed + instructions for installing and using the Netdev module please see + [link] +options: + vlan_id: + description: + - the vlan id + required: true + 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' ] + name: + description: + - a descriptive name for the vlan + required: false +notes: + - Requires EOS 4.10 or later + - The Netdev extension for EOS must be installed and active in the + available extensions (show extensions from the EOS CLI) + - See http://eos.aristanetworks.com for details +''' + +EXAMPLES = ''' +Example playbook entries using the arista_vlan module to manage resource +state. + + tasks: + - name: create vlan 999 + action: arista_vlan vlan_id=999 logging=true + + - name: create / edit vlan 999 + action: arista_vlan vlan_id=999 name=test logging=true + + - name: remove vlan 999 + action: arista_vlan vlan_id=999 state=absent logging=true + +''' +import syslog +import json + +class AristaVlan(object): + """ This is the base class for managing VLAN resources in EOS network + devices. This class provides basic CRUD functions for VLAN + resources. This class acts as a wrapper around the netdev extension + in EOS. You must have the netdev extension installed in order for + this module to work properly. + + The following commands are implemented in this module: + * netdev vlan create + * netdev vlan list + * netdev vlan show + * netdev vlan edit + * netdev vlan delete + """ + + attributes = ['name'] + + def __init__(self, module): + self.module = module + self.vlan_id = module.params['vlan_id'] + self.name = module.params['name'] + self.state = module.params['state'] + self.logging = module.boolean(module.params['logging']) + + + @property + def changed(self): + """ The changed property provides a boolean response if the currently + loaded resouces has changed from the resource running in EOS. + + Returns True if the object is not in sync + Returns False if the object is in sync. + """ + return len(self.updates()) > 0 + + def log(self, entry): + """ This method is responsible for sending log messages to the local + syslog. + """ + if self.logging: + syslog.openlog('ansible-%s' % os.path.basename(__file__)) + syslog.syslog(syslog.LOG_INFO, entry) + + def run_command(self, cmd): + """ Calls the Ansible module run_command method. This method will + also send a message to syslog with the command name + """ + self.log("Command: %s" % cmd) + return self.module.run_command(cmd.split()) + + + def delete(self): + """ Deletes an existing VLAN resource from the current running + configuration. A nonexistent VLAN will return successful for this + operation. + + This method implements the following commands: + * netdev vlan delete {vlan_id} + + Returns nothing if the delete was successful + Returns error message if there was a problem deleting the vlan + """ + cmd = "netdev vlan delete %s" % self.vlan_id + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = resp['status'] + err = resp['message'] + out = None + return (rc, out, err) + + def create(self): + """ Creates a VLAN resource in the current running configuration. If + the VLAN already exists, the function will return successfully. + + This function implements the following commands: + * netdev vlan create {vlan_id} [--name ] + + Returns the VLAN resource if the create function was successful + Returns an error message if there as a problem creating the vlan + """ + attribs = [] + for attrib in self.attributes: + if getattr(self, attrib): + attribs.append("--%s" % attrib) + attribs.append(getattr(self, attrib)) + + cmd = "netdev vlan create %s " % self.vlan_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 201: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = resp['result'] + return (rc, out, err) + + def update(self): + """ Updates an existing VLAN resource in the current running + configuration. If the VLAN resource does not exist, this method + will return an error. + + This method implements the following commands: + * netdev vlan edit {vlan_id} [--name ] + + Returns an updated VLAN resoure if the create method was successful + """ + attribs = list() + for attrib in self.updates(): + attribs.append("--%s" % attrib) + attribs.append(getattr(self, attrib)) + + if attribs: + cmd = "netdev vlan edit %s " % self.vlan_id + cmd += " ".join(attribs) + + (rc, out, err) = self.run_command(cmd) + resp = json.loads(out) + if resp.get('status') != 200: + rc = int(resp['status']) + err = resp['message'] + out = None + else: + out = resp['result'] + return (rc, out, err) + + return (0, None, "No attributes have been modified") + + def updates(self): + """ This method will check the current VLAN resource in the running + configuration and return a list of attributes that are not in sync + with the current resource from the running configuration. + """ + obj = self.get() + update = lambda a, z: a != z + + updates = list() + for attrib in self.attributes: + value = getattr(self, attrib) + if update(obj[attrib], update) and value is not None: + updates.append(attrib) + self.log("updates: %s" % updates) + return updates + + def exists(self): + """ Returns True if the current VLAN resource exists and returns False + if it does not. This method only checks for the existence of the + VLAN ID. + """ + (rc, out, err) = self.run_command("netdev vlan list") + collection = json.loads(out) + return collection.get('result').has_key(str(self.vlan_id)) + + def get(self): + """ This method will return a dictionary with the attributes of the + VLAN resource identified in vlan_id. The VLAN resource has the + following stucture: + + { + "vlan_id": , + "name": + } + + If the VLAN ID specified by vlan_id does not exist in the system, + this method will return None + """ + cmd = "netdev vlan show %s" % self.vlan_id + (rc, out, err) = self.run_command(cmd) + obj = json.loads(out) + if obj.get('status') != 200: + return None + return obj['result'] + + + +def main(): + + module = AnsibleModule( + argument_spec = dict( + vlan_id=dict(default=None, required=True, type='int'), + name=dict(default=None, type='str'), + state=dict(default='present', choices=['present', 'absent']), + logging=dict(default=False, type='bool') + ), + supports_check_mode = True + ) + + obj = AristaVlan(module) + + rc = None + result = dict() + + if obj.state == 'absent': + if obj.exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = obj.delete() + if rc !=0: + module.fail_json(msg=err, rc=rc) + + elif obj.state == 'present': + if not obj.exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = obj.create() + result['results'] = out + else: + if obj.changed: + if module.check_mode: + module.exit_json(changed=obj.changed) + (rc, out, err) = obj.update() + 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 +#<> +main()