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()
|