-
Notifications
You must be signed in to change notification settings - Fork 0
/
fabfile_pre.py
222 lines (169 loc) · 6.05 KB
/
fabfile_pre.py
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python
# encoding: utf-8
"""Orchestration template
The following tasks must be implemented:
- start
- stop
- restart
- status
An instance endpoint has to be provided using the CLUSTERDN environment variable.
For example:
CLUSTERDN="instances/test/reference/1.0.0/1"
A fabric roledef is created for each service defined in the registry.
It can be used with the decorator: @roles('servicename1')
WARN: The hosts are accesed using the IP address of the second network device,
usually eth1.
The properties of a given service can be accessed through:
SERVICES['servicename'].propertyname
for example:
SERVICES['namenode'].heap
# If the property has dots we can use
SERVICES['datanode'].get('dfs.blocksize')
# Or even define a default value in case it does not exist
SERVICES['datanode'].get('dfs.blocksize', '134217728')
Details about a given node can be obtained through each Node object returned by service.nodes
The fabfile can be tested running it in NOOP mode (testing mode) exporting a NOOP env variable.
Required roles: initiator, responders, peerback
"""
from __future__ import print_function
import os
import sys
from fabric.api import *
from fabric.colors import red, green, yellow, blue
from fabric.contrib.files import exists, append, sed, comment, uncomment
# FIXME: Installing configuration-registry with pip and importing registry directly does not work
# inside the fabfile. Temporarily it is copied manually in the utils directory
#from utils import registry
# In the big data nodes configuration-registry is installed globally
import registry
import time
from pprint import pprint
from StringIO import StringIO
import jinja2
# Maximum number of retries to wait for a node to change to status running
MAX_RETRIES = 100
# Seconds between retries
DELAY = 5
def eprint(*args, **kwargs):
"""Print to stderr"""
print(*args, file=sys.stderr, **kwargs)
if os.environ.get('CLUSTERDN'):
CLUSTERDN = os.environ.get('CLUSTERDN')
else:
eprint(red('An instance endpoint has to be provided using the CLUSTERDN environment variable'))
sys.exit(2)
if os.environ.get('REGISTRY'):
REGISTRY = os.environ.get('REGISTRY')
else:
REGISTRY = 'http://consul.service.int.cesga.es:8500/v1/kv'
# Retrieve info from the registry
registry.connect(REGISTRY)
cluster = registry.Cluster(CLUSTERDN)
nodes = cluster.nodes
services = cluster.services
def wait_until_node_is_running(node):
"""Wait until node is in status running: i.e. docker-executor finished"""
name = node.name
retry = 0
while not node.status == 'running':
retry += 1
if retry > MAX_RETRIES: sys.exit(3)
print('Waiting for node {}: {}/{}'.format(name, retry, MAX_RETRIES))
time.sleep(DELAY)
def external_address(node):
"""Return the network address to be used by fabric to connect to the node
By convention the address used is the address of its **second** network interface
"""
return node.networks[1].address
def internal_address(node):
"""Return the network address to be used internally by the cluster
By convention the address used is the address of its **first** network interface
"""
return node.networks[0].address
def put_template(tmpl_string, dest, context=None):
"""Upload a template contained in tmpl_string to the dest path
The context is passed as p
"""
t = jinja2.Template(tmpl_string)
rendered = t.render(p=context)
put(StringIO(rendered), dest)
# Expose the relevant information
NODES = {node.name: node for node in nodes}
SERVICES = {service.name: service for service in services}
NODE = {}
for node in nodes:
wait_until_node_is_running(node)
properties = {'hostname': node.name}
for dev in node.networks:
properties[dev.name] = dev.address
for disk in node.disks:
properties[disk.name] = disk.destination
properties['address_int'] = internal_address(node)
properties['address_ext'] = external_address(node)
# The node is labeled with the network address that will be used by fabric
# to connect to the node, this allows to retrieve the node using NODE[env.host]
label = external_address(node)
NODE[label] = properties
env.user = 'root'
env.hosts = NODE.keys()
# Allow known hosts with changed keys
env.disable_known_hosts = True
# Retry 30 times each 10 seconds -> (30-1)*10 = 290 seconds
env.connection_attempts = 30
env.timeout = 10
# Enable ssh client keep alive messages
env.keepalive = 15
# Define the fabric roles according to the cluster services
for service in services:
env.roledefs[service.name] = [external_address(n) for n in service.nodes]
# Define also a global var ROLE to be used for internal cluster configuration
ROLE = {}
for service in services:
ROLE[service.name] = [internal_address(n) for n in service.nodes]
print(blue('= Summary of cluster information ='))
print('== NODE ==')
pprint(NODE)
print('== Fabric roles ==')
pprint(env.roledefs)
print('== ROLE ==')
pprint(ROLE)
print(blue('= End of summary ='))
#
# Debugging mode
#
# To enable it use: export NOOP=1
if os.environ.get('NOOP'):
print(yellow('\n\n== Running in NOOP mode ==\n\n'))
def run(name):
print('[{0}] run: {1}'.format(env.host, name))
def put(source, destination):
print('[{0}] put: {1} {2}'.format(env.host, source, destination))
@task
@parallel
def hostname():
"""Print the hostnames: mainly used for testing purposes"""
run('/bin/hostname')
#
# CONFIGURATION FILE TEMPLATES
# /etc/postgresql/X.X/main/postgresql.conf
POSTGRESQL_CONF = """{{ include_file_contents('files/my.cnf.jinja') }}"""
@task
@runs_once
def start():
"""Start the service"""
print('No orchestration needed.')
cluster.status = 'running'
@task
def status():
"""Check the status of the service"""
print(red('Not supported.'))
@task
@runs_once
def stop():
"""Stop the service and all the containers that provide it"""
print(red('Not supported.'))
@task
@runs_once
def restart():
"""Restart all the services of the cluster"""
print(red('Restart is not supported'))