summaryrefslogtreecommitdiff
path: root/reactive/dns_auto.py
blob: 82c17448335bea6bd87ff8b892d7a64e733b2a97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python3
#
# Copyright:    (c) 2016 Canonical
# License:      GPLv3 <http://www.gnu.org/licenses/gpl.html>

import ipaddress
import os
import pprint
import socket
import sys
import yaml

from warnings import warn

# Augment system path.
if 'JUJU_CHARM_DIR' in os.environ:
    dirs = ['lib', 'reactive']
    for d in dirs:
        reactivedir = os.path.join(os.environ['JUJU_CHARM_DIR'], d)
        abspath = os.path.abspath(reactivedir)
        sys.path.insert(0, abspath)

from charmhelpers.core import (                 # NOQA: E402
    hookenv,
    unitdata,
)

from charms.reactive import (                   # NOQA: E402
    main,
    remove_state,
    set_state,
)

from charms.reactive.decorators import (        # NOQA: E402
    hook,
    when,
)

from dns_common import (                        # NOQA: E402
    active,
    blocked,
    maint,
)


#
# Utility methods
#
def isipaddress(i):
    """
    Return the IP address object if the passed string is an IP address,
    otherwise return False.
    """
    try:
        ip = ipaddress.ip_address(i)
        return ip
    except:
        return False


def gethostbyname(name):
    """
    Resolve hostname to IP(s)
    """
    sockets = socket.getaddrinfo(name, None)
    socketips = [i[4] for i in sockets if i[4] is not None]
    ips = []
    for s in socketips:
        try:
            ip = s[0]
            if ip not in ips:
                ips.append(ip)
        except Exception as e:
            # ignore any missing elements
            warn('Caught exception: ' + e)
            pass

    return ips


def getpublicaddresses():
    """
    Return all IP addresses associated with the unit's public-address,
    or None if they cannot be determined.
    """
    # get unit's public-address value
    pubaddr = hookenv.unit_get('public-address')
    if pubaddr is None:
        return None

    # Check if it's an IP address or hostname
    if isipaddress(pubaddr):
        return [pubaddr]

    ips = gethostbyname(pubaddr)
    if len(ips) > 0:
        return ips
    else:
        return None


def addunit(units, unit):
    """
    This could be eliminated if units were a set, but we want the
    maintenance message anyway, so a set doesn't gain us anything.
    """
    if unit not in units:
        maint('public address for peer: %s' % unit)
        units.append(unit)


#
# Hooks
#

@hook(
    'peer-relation-broken',
    'peer-relation-changed',
    'peer-relation-departed',
    'peer-relation-joined',
)
def update_peer_relation_data():
    # get our local unit addresses
    thisunit = getpublicaddresses()
    if thisunit is None:
        remove_state('dns-auto.peers-available')
        blocked('No public-address available for this unit')
        return

    maint('public addresses for local unit: %s' % thisunit)
    units = thisunit

    # get each peer's address, and set our address on each relation
    for relid in hookenv.relation_ids('peer'):

        # get a list of remote units and retrieve their public addresses
        for unit in hookenv.related_units(relid):
            unitaddrs = hookenv.relation_get('public-address', unit, relid)
            if unitaddrs is not None:
                hookenv.log(pprint.pformat(unitaddrs))
                unitaddrs = yaml.safe_load(unitaddrs)
                for u in unitaddrs:
                    addunit(units, u)

        # send our addresses on the relation
        hookenv.relation_set(relid, {'public-address': thisunit})

    # display the result
    units.sort()
    active('peer list: %s' % units)

    # store unit data for use by other layers
    db = unitdata.kv()
    db.set('peers', units)

    set_state('dns-auto.peers-available')


@when('config.changed.provider')
def provider_changed():
    hookenv.log('provider changed to %s' % hookenv.config('provider'))


if __name__ == '__main__':
    main()