~ajkavanagh/charms/trusty/memcached/add-spaces-support

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/network/ip.py

  • Committer: Jorge Niedbalski
  • Date: 2016-09-20 18:01:20 UTC
  • mfrom: (72.2.3 memcached.py3)
  • Revision ID: jorge.niedbalski@canonical.com-20160920180120-apb4ut3cugwxcrer
[freyes, r=niedbalski] Fixes bug LP: #1576458

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright 2014-2015 Canonical Limited.
2
2
#
3
 
# This file is part of charm-helpers.
4
 
#
5
 
# charm-helpers is free software: you can redistribute it and/or modify
6
 
# it under the terms of the GNU Lesser General Public License version 3 as
7
 
# published by the Free Software Foundation.
8
 
#
9
 
# charm-helpers is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU Lesser General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU Lesser General Public License
15
 
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
 
3
# Licensed under the Apache License, Version 2.0 (the "License");
 
4
# you may not use this file except in compliance with the License.
 
5
# You may obtain a copy of the License at
 
6
#
 
7
#  http://www.apache.org/licenses/LICENSE-2.0
 
8
#
 
9
# Unless required by applicable law or agreed to in writing, software
 
10
# distributed under the License is distributed on an "AS IS" BASIS,
 
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
# See the License for the specific language governing permissions and
 
13
# limitations under the License.
16
14
 
17
15
import glob
18
16
import re
23
21
from functools import partial
24
22
 
25
23
from charmhelpers.core.hookenv import unit_get
26
 
from charmhelpers.fetch import apt_install
 
24
from charmhelpers.fetch import apt_install, apt_update
27
25
from charmhelpers.core.hookenv import (
28
26
    log,
29
27
    WARNING,
32
30
try:
33
31
    import netifaces
34
32
except ImportError:
35
 
    apt_install('python-netifaces')
 
33
    apt_update(fatal=True)
 
34
    apt_install('python-netifaces', fatal=True)
36
35
    import netifaces
37
36
 
38
37
try:
39
38
    import netaddr
40
39
except ImportError:
41
 
    apt_install('python-netaddr')
 
40
    apt_update(fatal=True)
 
41
    apt_install('python-netaddr', fatal=True)
42
42
    import netaddr
43
43
 
44
44
 
51
51
 
52
52
 
53
53
def no_ip_found_error_out(network):
54
 
    errmsg = ("No IP address found in network: %s" % network)
 
54
    errmsg = ("No IP address found in network(s): %s" % network)
55
55
    raise ValueError(errmsg)
56
56
 
57
57
 
59
59
    """Get an IPv4 or IPv6 address within the network from the host.
60
60
 
61
61
    :param network (str): CIDR presentation format. For example,
62
 
        '192.168.1.0/24'.
 
62
        '192.168.1.0/24'. Supports multiple networks as a space-delimited list.
63
63
    :param fallback (str): If no address is found, return fallback.
64
64
    :param fatal (boolean): If no address is found, fallback is not
65
65
        set and fatal is True then exit(1).
73
73
        else:
74
74
            return None
75
75
 
76
 
    _validate_cidr(network)
77
 
    network = netaddr.IPNetwork(network)
78
 
    for iface in netifaces.interfaces():
79
 
        addresses = netifaces.ifaddresses(iface)
80
 
        if network.version == 4 and netifaces.AF_INET in addresses:
81
 
            addr = addresses[netifaces.AF_INET][0]['addr']
82
 
            netmask = addresses[netifaces.AF_INET][0]['netmask']
83
 
            cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
84
 
            if cidr in network:
85
 
                return str(cidr.ip)
 
76
    networks = network.split() or [network]
 
77
    for network in networks:
 
78
        _validate_cidr(network)
 
79
        network = netaddr.IPNetwork(network)
 
80
        for iface in netifaces.interfaces():
 
81
            addresses = netifaces.ifaddresses(iface)
 
82
            if network.version == 4 and netifaces.AF_INET in addresses:
 
83
                addr = addresses[netifaces.AF_INET][0]['addr']
 
84
                netmask = addresses[netifaces.AF_INET][0]['netmask']
 
85
                cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
 
86
                if cidr in network:
 
87
                    return str(cidr.ip)
86
88
 
87
 
        if network.version == 6 and netifaces.AF_INET6 in addresses:
88
 
            for addr in addresses[netifaces.AF_INET6]:
89
 
                if not addr['addr'].startswith('fe80'):
90
 
                    cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
91
 
                                                        addr['netmask']))
92
 
                    if cidr in network:
93
 
                        return str(cidr.ip)
 
89
            if network.version == 6 and netifaces.AF_INET6 in addresses:
 
90
                for addr in addresses[netifaces.AF_INET6]:
 
91
                    if not addr['addr'].startswith('fe80'):
 
92
                        cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
 
93
                                                            addr['netmask']))
 
94
                        if cidr in network:
 
95
                            return str(cidr.ip)
94
96
 
95
97
    if fallback is not None:
96
98
        return fallback
187
189
get_netmask_for_address = partial(_get_for_address, key='netmask')
188
190
 
189
191
 
 
192
def resolve_network_cidr(ip_address):
 
193
    '''
 
194
    Resolves the full address cidr of an ip_address based on
 
195
    configured network interfaces
 
196
    '''
 
197
    netmask = get_netmask_for_address(ip_address)
 
198
    return str(netaddr.IPNetwork("%s/%s" % (ip_address, netmask)).cidr)
 
199
 
 
200
 
190
201
def format_ipv6_addr(address):
191
202
    """If address is IPv6, wrap it in '[]' otherwise return None.
192
203
 
201
212
 
202
213
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
203
214
                   fatal=True, exc_list=None):
204
 
    """Return the assigned IP address for a given interface, if any."""
 
215
    """Return the assigned IP address for a given interface, if any.
 
216
 
 
217
    :param iface: network interface on which address(es) are expected to
 
218
                  be found.
 
219
    :param inet_type: inet address family
 
220
    :param inc_aliases: include alias interfaces in search
 
221
    :param fatal: if True, raise exception if address not found
 
222
    :param exc_list: list of addresses to ignore
 
223
    :return: list of ip addresses
 
224
    """
205
225
    # Extract nic if passed /dev/ethX
206
226
    if '/' in iface:
207
227
        iface = iface.split('/')[-1]
302
322
    We currently only support scope global IPv6 addresses i.e. non-temporary
303
323
    addresses. If no global IPv6 address is found, return the first one found
304
324
    in the ipv6 address list.
 
325
 
 
326
    :param iface: network interface on which ipv6 address(es) are expected to
 
327
                  be found.
 
328
    :param inc_aliases: include alias interfaces in search
 
329
    :param fatal: if True, raise exception if address not found
 
330
    :param exc_list: list of addresses to ignore
 
331
    :param dynamic_only: only recognise dynamic addresses
 
332
    :return: list of ipv6 addresses
305
333
    """
306
334
    addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
307
335
                               inc_aliases=inc_aliases, fatal=fatal,
323
351
            cmd = ['ip', 'addr', 'show', iface]
324
352
            out = subprocess.check_output(cmd).decode('UTF-8')
325
353
            if dynamic_only:
326
 
                key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
 
354
                key = re.compile("inet6 (.+)/[0-9]+ scope global.* dynamic.*")
327
355
            else:
328
356
                key = re.compile("inet6 (.+)/[0-9]+ scope global.*")
329
357
 
375
403
    Returns True if address is a valid IP address.
376
404
    """
377
405
    try:
378
 
        # Test to see if already an IPv4 address
379
 
        socket.inet_aton(address)
 
406
        # Test to see if already an IPv4/IPv6 address
 
407
        address = netaddr.IPAddress(address)
380
408
        return True
381
 
    except socket.error:
 
409
    except netaddr.AddrFormatError:
382
410
        return False
383
411
 
384
412
 
386
414
    try:
387
415
        import dns.resolver
388
416
    except ImportError:
389
 
        apt_install('python-dnspython')
 
417
        apt_install('python-dnspython', fatal=True)
390
418
        import dns.resolver
391
419
 
392
420
    if isinstance(address, dns.name.Name):
430
458
        try:
431
459
            import dns.reversename
432
460
        except ImportError:
433
 
            apt_install("python-dnspython")
 
461
            apt_install("python-dnspython", fatal=True)
434
462
            import dns.reversename
435
463
 
436
464
        rev = dns.reversename.from_address(address)
437
465
        result = ns_query(rev)
 
466
 
438
467
        if not result:
439
 
            return None
 
468
            try:
 
469
                result = socket.gethostbyaddr(address)[0]
 
470
            except:
 
471
                return None
440
472
    else:
441
473
        result = address
442
474
 
448
480
            return result
449
481
    else:
450
482
        return result.split('.')[0]
 
483
 
 
484
 
 
485
def port_has_listener(address, port):
 
486
    """
 
487
    Returns True if the address:port is open and being listened to,
 
488
    else False.
 
489
 
 
490
    @param address: an IP address or hostname
 
491
    @param port: integer port
 
492
 
 
493
    Note calls 'zc' via a subprocess shell
 
494
    """
 
495
    cmd = ['nc', '-z', address, str(port)]
 
496
    result = subprocess.call(cmd)
 
497
    return not(bool(result))