From eb082e22b8431ff9e2b6ac3c776a1f83e7150453 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Wed, 26 Jun 2013 22:59:23 -0400 Subject: [PATCH] initial arista module import --- library/network/arista_interface | 253 +++++++++++++++++++++++ library/network/arista_l2interface | 317 +++++++++++++++++++++++++++++ library/network/arista_lag | 311 ++++++++++++++++++++++++++++ library/network/arista_vlan | 298 +++++++++++++++++++++++++++ 4 files changed, 1179 insertions(+) create mode 100644 library/network/arista_interface create mode 100644 library/network/arista_l2interface create mode 100644 library/network/arista_lag create mode 100644 library/network/arista_vlan diff --git a/library/network/arista_interface b/library/network/arista_interface new file mode 100644 index 0000000000..77d0a71b08 --- /dev/null +++ b/library/network/arista_interface @@ -0,0 +1,253 @@ +#!/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 + default: null + aliases: [] + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + aliases: [] + admin: + description: + - controls the operational state of the interface + required: false + default: null + choices: [ 'up', 'down' ] + aliases: [] + description: + description: + - a single line text string describing the interface + required: false + default: null + aliases: [] + mtu: + description: + - configureds the maximum transmission unit for the interface + required: false + default: 1500 + aliases: [] + speed: + description: + - sets the interface speed setting + required: false + default: 'auto' + choices: [ 'auto', '100m', '1g', '10g' ] + aliases: [] + duplex: + description: + - sets the interface duplex setting + required: false + default: 'auto' + choices: [ 'auto', 'half', 'full' ] + aliases: [] +examples: + - code: 'arista_interface: interface_id=Ethernet1 admin="up"' + description: "Administratively enable the Ethernet1 interface" + - code: 'arista_interface: itnerface_id=Ethernet4 state="default"' + descripton: "Default interface Ethernet4" +''' +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, choices=BOOLEANS) + ), + 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/network/arista_l2interface b/library/network/arista_l2interface new file mode 100644 index 0000000000..64ce8b0d2a --- /dev/null +++ b/library/network/arista_l2interface @@ -0,0 +1,317 @@ +#!/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 + default: null + aliases: [] + state: + description: + - describe the desired state of the interface related to the config + required: false + default: 'present' + choices: [ 'present', 'absent' ] + aliases: [] + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + aliases: [] + vlan_tagging: + description: + - specifies whether or not vlan tagging should be enabled for + this interface + required: false + default: true + choices: [ 'enable', 'disable' ] + aliases: [] + tagged_vlans: + description: + - specifies the list of vlans that should be allowed to transit + this interface + required: false + default: null + aliases: [] + untagged_vlan: + description: + - specifies the vlan that untagged traffic should be placed in for + transit across a vlan tagged link + required: false + default: 'default' + aliases: [] +examples: + - code: 'arista_l2interface: interface_id=Ethernet1 vlan_tagging="enable"' + description: "Enable vlan tagging for interface Ethernet1" + - code: 'arista_l2interface: interface_id=Ethernet4 tagged_vlans=Blue,Red' + descripton: "Specifies vlans Blue & Red should be allowed across this interface" +''' +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, choices=BOOLEANS) + ), + 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/network/arista_lag b/library/network/arista_lag new file mode 100644 index 0000000000..2523215f61 --- /dev/null +++ b/library/network/arista_lag @@ -0,0 +1,311 @@ +#!/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 (lag) interfaceresources on Arista EOS network devices +options: + interface_id: + description: + - the full name of the interface + required: true + default: null + aliases: [] + state: + description: + - describe the desired state of the interface related to the config + required: false + default: 'present' + choices: [ 'present', 'absent' ] + aliases: [] + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + aliases: [] + links: + description: + - array of physical interface links to include in this lag + required: false + default: null + aliases: [] + minimum_links: + description: + - the minimum number of physical interaces that must be operationally up to consider the lag operationally up + required: false + default: null + aliases: [] + lacp: + description: + - enables the use of the LACP protocol for managing link bundles + required: false + default: 'active', + choices: [ 'active', 'passive', 'off' ] + aliases: [] +examples: + - code: 'arista_lag: interface_id=Port-Channel10 links=Ethernet1,Ethernet2' + description: "Configure Port-Channel 10 with physical interfaces Ethernet1 and Ethernet2 as members" +''' +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, choices=BOOLEANS) + ), + 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/network/arista_vlan b/library/network/arista_vlan new file mode 100644 index 0000000000..9c64cded02 --- /dev/null +++ b/library/network/arista_vlan @@ -0,0 +1,298 @@ +#!/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 +options: + vlan_id: + description: + - the vlan id + required: true + default: null + aliases: [] + state: + description: + - describe the desired state of the vlan related to the config + required: false + default: 'present' + choices: [ 'present', 'absent' ] + aliases: [] + logging: + description: + - enables or disables the syslog facility for this module + required: false + default: false + choices: [ 'true', 'false', 'yes', 'no' ] + aliases: [] + name: + description: + - a descriptive name for the vlan + required: false + default: null + aliases: [] +examples: + - code: 'arista_vlan: vlan_id=100 name="Blue"' + description: "Creates vlan 100 in the running config" + - code: 'arista_vlan: vlan_id=200 state="absent"' + descripton: "Ensures vlan 200 is not in the config" +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) +''' +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, choices=BOOLEANS) + ), + 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()