Bumps OVS version to 2.8 for OVN
[apex.git] / lib / python / apex / ip_utils.py
1 ##############################################################################
2 # Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10
11 import ipaddress
12 import subprocess
13 import re
14 import logging
15
16
17 def get_ip_range(start_offset=None, count=None, end_offset=None,
18                  cidr=None, interface=None):
19     """
20     Generate IP range for a network (cidr) or an interface.
21
22     If CIDR is provided, it will take precedence over interface. In this case,
23     The entire CIDR IP address space is considered usable. start_offset will be
24     calculated from the network address, and end_offset will be calculated from
25     the last address in subnet.
26
27     If interface is provided, the interface IP will be used to calculate
28     offsets:
29         - If the interface IP is in the first half of the address space,
30         start_offset will be calculated from the interface IP, and end_offset
31         will be calculated from end of address space.
32         - If the interface IP is in the second half of the address space,
33         start_offset will be calculated from the network address in the address
34         space, and end_offset will be calculated from the interface IP.
35
36     2 of start_offset, end_offset and count options must be provided:
37         - If start_offset and end_offset are provided, a range from
38         start_offset to end_offset will be returned.
39         - If count is provided, a range from either start_offset to
40         (start_offset+count) or (end_offset-count) to end_offset will be
41         returned. The IP range returned will be of size <count>.
42     Both start_offset and end_offset must be greater than 0.
43
44     Returns IP range in the format of "first_addr,second_addr" or exception
45     is raised.
46     """
47     if cidr:
48         if count and start_offset and not end_offset:
49             start_index = start_offset
50             end_index = start_offset + count - 1
51         elif count and end_offset and not start_offset:
52             end_index = -1 - end_offset
53             start_index = -1 - end_index - count + 1
54         elif start_offset and end_offset and not count:
55             start_index = start_offset
56             end_index = -1 - end_offset
57         else:
58             raise IPUtilsException("Argument error: must pass in exactly 2 of"
59                                    " start_offset, end_offset and count")
60
61         start_ip = cidr[start_index]
62         end_ip = cidr[end_index]
63         network = cidr
64     elif interface:
65         network = interface.network
66         number_of_addr = network.num_addresses
67         if interface.ip < network[int(number_of_addr / 2)]:
68             if count and start_offset and not end_offset:
69                 start_ip = interface.ip + start_offset
70                 end_ip = start_ip + count - 1
71             elif count and end_offset and not start_offset:
72                 end_ip = network[-1 - end_offset]
73                 start_ip = end_ip - count + 1
74             elif start_offset and end_offset and not count:
75                 start_ip = interface.ip + start_offset
76                 end_ip = network[-1 - end_offset]
77             else:
78                 raise IPUtilsException(
79                     "Argument error: must pass in exactly 2 of"
80                     " start_offset, end_offset and count")
81         else:
82             if count and start_offset and not end_offset:
83                 start_ip = network[start_offset]
84                 end_ip = start_ip + count - 1
85             elif count and end_offset and not start_offset:
86                 end_ip = interface.ip - end_offset
87                 start_ip = end_ip - count + 1
88             elif start_offset and end_offset and not count:
89                 start_ip = network[start_offset]
90                 end_ip = interface.ip - end_offset
91             else:
92                 raise IPUtilsException(
93                     "Argument error: must pass in exactly 2 of"
94                     " start_offset, end_offset and count")
95
96     else:
97         raise IPUtilsException("Must pass in cidr or interface to generate"
98                                "ip range")
99
100     range_result = _validate_ip_range(start_ip, end_ip, network)
101     if range_result:
102         ip_range = "{},{}".format(start_ip, end_ip)
103         return ip_range
104     else:
105         raise IPUtilsException("Invalid IP range: {},{} for network {}"
106                                .format(start_ip, end_ip, network))
107
108
109 def get_ip(offset, cidr=None, interface=None):
110     """
111     Returns an IP in a network given an offset.
112
113     Either cidr or interface must be provided, cidr takes precedence.
114
115     If cidr is provided, offset is calculated from network address.
116     If interface is provided, offset is calculated from interface IP.
117
118     offset can be positive or negative, but the resulting IP address must also
119     be contained in the same subnet, otherwise an exception will be raised.
120
121     returns a IP address object.
122     """
123     if cidr:
124         ip = cidr[0 + offset]
125         network = cidr
126     elif interface:
127         ip = interface.ip + offset
128         network = interface.network
129     else:
130         raise IPUtilsException("Must pass in cidr or interface to generate IP")
131
132     if ip not in network:
133         raise IPUtilsException("IP {} not in network {}".format(ip, network))
134     else:
135         return str(ip)
136
137
138 def get_interface(nic, address_family=4):
139     """
140     Returns interface object for a given NIC name in the system
141
142     Only global address will be returned at the moment.
143
144     Returns interface object if an address is found for the given nic,
145     otherwise returns None.
146     """
147     if not nic.strip():
148         logging.error("empty nic name specified")
149         return None
150     output = subprocess.getoutput("/usr/sbin/ip -{} addr show {} scope global"
151                                   .format(address_family, nic))
152     if address_family == 4:
153         pattern = re.compile("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}")
154     elif address_family == 6:
155         pattern = re.compile("([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}")
156     else:
157         raise IPUtilsException("Invalid address family: {}"
158                                .format(address_family))
159     match = re.search(pattern, output)
160     if match:
161         logging.info("found interface {} ip: {}".format(nic, match.group()))
162         return ipaddress.ip_interface(match.group())
163     else:
164         logging.info("interface ip not found! ip address output:\n{}"
165                      .format(output))
166         return None
167
168
169 def find_gateway(interface):
170     """
171     Validate gateway on the system
172
173     Ensures that the provided interface object is in fact configured as default
174     route on the system.
175
176     Returns gateway IP (reachable from interface) if default route is found,
177     otherwise returns None.
178     """
179
180     address_family = interface.version
181     output = subprocess.getoutput("/usr/sbin/ip -{} route".format(
182         address_family))
183
184     pattern = re.compile("default\s+via\s+(\S+)\s+")
185     match = re.search(pattern, output)
186
187     if match:
188         gateway_ip = match.group(1)
189         reverse_route_output = subprocess.getoutput("/usr/sbin/ip route get {}"
190                                                     .format(gateway_ip))
191         pattern = re.compile("{}.+src\s+{}".format(gateway_ip, interface.ip))
192         if not re.search(pattern, reverse_route_output):
193             logging.warning("Default route doesn't match interface specified: "
194                             "{}".format(reverse_route_output))
195             return None
196         else:
197             return gateway_ip
198     else:
199         logging.warning("Can't find gateway address on system")
200         return None
201
202
203 def _validate_ip_range(start_ip, end_ip, cidr):
204     """
205     Validates an IP range is in good order and the range is part of cidr.
206
207     Returns True if validation succeeds, False otherwise.
208     """
209     ip_range = "{},{}".format(start_ip, end_ip)
210     if end_ip <= start_ip:
211         logging.warning("IP range {} is invalid: end_ip should be greater "
212                         "than starting ip".format(ip_range))
213         return False
214     if start_ip not in ipaddress.ip_network(cidr):
215         logging.warning('start_ip {} is not in network {}'
216                         .format(start_ip, cidr))
217         return False
218     if end_ip not in ipaddress.ip_network(cidr):
219         logging.warning('end_ip {} is not in network {}'.format(end_ip, cidr))
220         return False
221
222     return True
223
224
225 class IPUtilsException(Exception):
226     def __init__(self, value):
227         self.value = value
228
229     def __str__(self):
230         return self.value