1#
2# Copyright (c) 2015, Arista Networks, Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9#   Redistributions of source code must retain the above copyright notice,
10#   this list of conditions and the following disclaimer.
11#
12#   Redistributions in binary form must reproduce the above copyright
13#   notice, this list of conditions and the following disclaimer in the
14#   documentation and/or other materials provided with the distribution.
15#
16#   Neither the name of Arista Networks nor the names of its
17#   contributors may be used to endorse or promote products derived from
18#   this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#
32"""Module for managing the VARP configuration in EOS
33
34This module provides an API for configuring VARP resources using
35EOS and eAPI.
36
37Arguments:
38    name (string): The interface name the configuration is in reference
39        to.  The interface name is the full interface identifier
40
41    address (string): The interface IP address in the form of
42        address/len.
43
44    mtu (integer): The interface MTU value.  The MTU value accepts
45        integers in the range of 68 to 65535 bytes
46"""
47
48import re
49
50from pyeapi.api import EntityCollection
51
52
53class Varp(EntityCollection):
54
55    def __init__(self, *args, **kwargs):
56        super(Varp, self).__init__(*args, **kwargs)
57        self._interfaces = None
58
59    @property
60    def interfaces(self):
61        if self._interfaces is not None:
62            return self._interfaces
63        self._interfaces = VarpInterfaces(self.node)
64        return self._interfaces
65
66    def get(self):
67        """Returns the current VARP configuration
68
69        The Varp resource returns the following:
70
71            * mac_address (str): The virtual-router mac address
72            * interfaces (dict): A list of the interfaces that have a
73                                 virtual-router address configured.
74
75        Return:
76            A Python dictionary object of key/value pairs that represents
77            the current configuration of the node.  If the specified
78            interface does not exist then None is returned::
79
80                {
81                    "mac_address": "aa:bb:cc:dd:ee:ff",
82                    "interfaces": {
83                        "Vlan100": {
84                            "addresses": [ "1.1.1.1", "2.2.2.2"]
85                        },
86                        "Vlan200": [...]
87                    }
88                }
89        """
90        resource = dict()
91        resource.update(self._parse_mac_address())
92        resource.update(self._parse_interfaces())
93        return resource
94
95    def _parse_mac_address(self):
96        mac_address_re = re.compile(r'^ip\svirtual-router\smac-address\s'
97                                    r'((?:[a-f0-9]{2}:){5}[a-f0-9]{2})$', re.M)
98        mac = mac_address_re.search(self.config)
99        mac = mac.group(1) if mac else None
100        return dict(mac_address=mac)
101
102    def _parse_interfaces(self):
103        interfaces = VarpInterfaces(self.node).getall()
104        return dict(interfaces=interfaces)
105
106    def set_mac_address(self, mac_address=None, default=False, disable=False):
107        """ Sets the virtual-router mac address
108
109        This method will set the switch virtual-router mac address. If a
110        virtual-router mac address already exists it will be overwritten.
111
112        Args:
113            mac_address (string): The mac address that will be assigned as
114                the virtual-router mac address. This should be in the format,
115                aa:bb:cc:dd:ee:ff.
116            default (bool): Sets the virtual-router mac address to the system
117                default (which is to remove the configuration line).
118            disable (bool): Negates the virtual-router mac address using
119                the system no configuration command
120
121        Returns:
122            True if the set operation succeeds otherwise False.
123        """
124        base_command = 'ip virtual-router mac-address'
125        if not default and not disable:
126            if mac_address is not None:
127                # Check to see if mac_address matches expected format
128                if not re.match(r'(?:[a-f0-9]{2}:){5}[a-f0-9]{2}',
129                                mac_address):
130                    raise ValueError('mac_address must be formatted like:'
131                                     'aa:bb:cc:dd:ee:ff')
132            else:
133                raise ValueError('mac_address must be a properly formatted '
134                                 'address string')
135        if default or disable and not mac_address:
136            current_mac = self._parse_mac_address()
137            if current_mac['mac_address']:
138                base_command = base_command + ' ' + current_mac['mac_address']
139        commands = self.command_builder(base_command, value=mac_address,
140                                        default=default, disable=disable)
141        return self.configure(commands)
142
143
144class VarpInterfaces(EntityCollection):
145    """The VarpInterfaces class helps manage interfaces with
146    virtual-router configuration.
147    """
148    def get(self, name):
149        interface_re = r'interface\s%s' % name
150        config = self.get_block(interface_re)
151
152        if not config:
153            return None
154
155        resource = dict(addresses=dict())
156        resource.update(self._parse_virtual_addresses(config))
157        return resource
158
159    def getall(self):
160        resources = dict()
161        interfaces_re = re.compile(r'^interface\s(Vlan\d+)$', re.M)
162        for name in interfaces_re.findall(self.config):
163            interface_detail = self.get(name)
164            if interface_detail:
165                resources[name] = interface_detail
166        return resources
167
168    def set_addresses(self, name, addresses=None, default=False,
169                      disable=False):
170
171        commands = list()
172        commands.append('interface %s' % name)
173
174        if default:
175            commands.append('default ip virtual-router address')
176        elif disable:
177            commands.append('no ip virtual-router address')
178        elif addresses is not None:
179            try:
180                current_addresses = self.get(name)['addresses']
181            except Exception:
182                current_addresses = []
183
184            # remove virtual-router addresses not present in addresses list
185            for entry in set(current_addresses).difference(addresses):
186                commands.append('no ip virtual-router address %s' % entry)
187
188            # add new set virtual-router addresses that werent present
189            for entry in set(addresses).difference(current_addresses):
190                commands.append('ip virtual-router address %s' % entry)
191        else:
192            commands.append('no ip virtual-router address')
193
194        return self.configure(commands) if commands else True
195
196    def _parse_virtual_addresses(self, config):
197        virt_ip_re = re.compile(r'^\s+ip\svirtual-router\saddress\s(\S+)$',
198                                re.M)
199        return dict(addresses=virt_ip_re.findall(config))
200
201
202def instance(node):
203    """Returns an instance of Ipinterfaces
204
205    This method will create and return an instance of the Varp object
206    passing the value of node to the instance.  This function is required
207    for the resource to be autoloaded by the Node object
208
209    Args:
210        node (Node): The node argument provides an instance of Node to
211            the Varp instance
212    """
213    return Varp(node)
214