Bug-180: Floating IP reuse
[osm/SO.git] / rwcal / plugins / vala / rwcal_openstack / rift / rwcal / openstack / prepare_vm.py
1 #!/usr/bin/env python3
2
3 #
4 # Copyright 2016 RIFT.IO Inc
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18
19 import rift.rwcal.openstack as openstack_drv
20 import logging
21 import argparse
22 import sys, os, time
23 import rwlogger
24 import yaml
25 import random
26 import fcntl
27
28
29 logging.basicConfig(level=logging.DEBUG)
30 logger = logging.getLogger()
31
32 rwlog_handler = rwlogger.RwLogger(category="rw-cal-log",
33 subcategory="openstack",)
34 logger.addHandler(rwlog_handler)
35 #logger.setLevel(logging.DEBUG)
36
37 class FileLock:
38 FILE_LOCK = '/tmp/_openstack_prepare_vm.lock'
39 def __init__(self):
40 # This will create it if it does not exist already
41 self.filename = FileLock.FILE_LOCK
42 self.handle = None
43
44 # Bitwise OR fcntl.LOCK_NB if you need a non-blocking lock
45 def acquire(self):
46 logger.info("<PID: %d> Attempting to acquire log." %os.getpid())
47 self.handle = open(self.filename, 'w')
48 fcntl.flock(self.handle, fcntl.LOCK_EX)
49 logger.info("<PID: %d> Lock successfully acquired." %os.getpid())
50
51 def release(self):
52 fcntl.flock(self.handle, fcntl.LOCK_UN)
53 self.handle.close()
54 logger.info("<PID: %d> Released lock." %os.getpid())
55
56 def __del__(self):
57 if self.handle and self.handle.closed == False:
58 self.handle.close()
59
60
61 def allocate_floating_ip(drv, argument):
62 #### Allocate a floating_ip
63 available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
64
65 if argument.pool_name:
66 ### Filter further based on IP address
67 available_ip = [ ip for ip in available_ip if ip.pool == argument.pool_name ]
68
69 if not available_ip:
70 logger.info("<PID: %d> No free floating_ips available. Allocating fresh from pool: %s" %(os.getpid(), argument.pool_name))
71 pool_name = argument.pool_name if argument.pool_name is not None else None
72 floating_ip = drv.nova_floating_ip_create(pool_name)
73 else:
74 floating_ip = random.choice(available_ip)
75 logger.info("<PID: %d> Selected floating_ip: %s from available free pool" %(os.getpid(), floating_ip))
76
77 return floating_ip
78
79
80 def handle_floating_ip_assignment(drv, server, argument, management_ip):
81 lock = FileLock()
82 ### Try 3 time (<<<magic number>>>)
83 RETRY = 3
84 for attempt in range(RETRY):
85 try:
86 lock.acquire()
87 floating_ip = allocate_floating_ip(drv, argument)
88 logger.info("Assigning the floating_ip: %s to VM: %s" %(floating_ip, server['name']))
89 drv.nova_floating_ip_assign(argument.server_id,
90 floating_ip,
91 management_ip)
92 logger.info("Assigned floating_ip: %s to management_ip: %s" %(floating_ip, management_ip))
93 except Exception as e:
94 logger.error("Could not assign floating_ip: %s to VM: %s. Exception: %s" %(floating_ip, server['name'], str(e)))
95 lock.release()
96 if attempt == (RETRY -1):
97 logger.error("Max attempts %d reached for floating_ip allocation. Giving up" %attempt)
98 raise
99 else:
100 logger.error("Retrying floating ip allocation. Current retry count: %d" %attempt)
101 else:
102 lock.release()
103 return
104
105
106 def assign_floating_ip_address(drv, argument):
107 if not argument.floating_ip:
108 return
109
110 server = drv.nova_server_get(argument.server_id)
111
112 for i in range(120):
113 server = drv.nova_server_get(argument.server_id)
114 for network_name,network_info in server['addresses'].items():
115 if network_info and network_name == argument.mgmt_network:
116 for n_info in network_info:
117 if 'OS-EXT-IPS:type' in n_info and n_info['OS-EXT-IPS:type'] == 'fixed':
118 management_ip = n_info['addr']
119 handle_floating_ip_assignment(drv, server, argument, management_ip)
120 return
121 else:
122 logger.info("Waiting for management_ip to be assigned to server: %s" %(server['name']))
123 time.sleep(1)
124 else:
125 logger.info("No management_ip IP available to associate floating_ip for server: %s" %(server['name']))
126 return
127
128
129 def create_port_metadata(drv, argument):
130 if argument.port_metadata == False:
131 return
132
133 ### Get Management Network ID
134 network_list = drv.neutron_network_list()
135 mgmt_network_id = [net['id'] for net in network_list if net['name'] == argument.mgmt_network][0]
136 port_list = [ port for port in drv.neutron_port_list(**{'device_id': argument.server_id})
137 if port['network_id'] != mgmt_network_id ]
138 meta_data = {}
139
140 meta_data['rift-meta-ports'] = str(len(port_list))
141 port_id = 0
142 for port in port_list:
143 info = []
144 info.append('"port_name":"'+port['name']+'"')
145 if 'mac_address' in port:
146 info.append('"hw_addr":"'+port['mac_address']+'"')
147 if 'network_id' in port:
148 #info.append('"network_id":"'+port['network_id']+'"')
149 net_name = [net['name'] for net in network_list if net['id'] == port['network_id']]
150 if net_name:
151 info.append('"network_name":"'+net_name[0]+'"')
152 if 'fixed_ips' in port:
153 ip_address = port['fixed_ips'][0]['ip_address']
154 info.append('"ip":"'+ip_address+'"')
155
156 meta_data['rift-meta-port-'+str(port_id)] = '{' + ','.join(info) + '}'
157 port_id += 1
158
159 nvconn = drv.nova_drv._get_nova_connection()
160 nvconn.servers.set_meta(argument.server_id, meta_data)
161
162 def get_volume_id(server_vol_list, name):
163 if server_vol_list is None:
164 return
165
166 for os_volume in server_vol_list:
167 try:
168 " Device name is of format /dev/vda"
169 vol_name = (os_volume['device']).split('/')[2]
170 except:
171 continue
172 if name == vol_name:
173 return os_volume['volumeId']
174
175 def create_volume_metadata(drv, argument):
176 if argument.vol_metadata is None:
177 return
178
179 yaml_vol_str = argument.vol_metadata.read()
180 yaml_vol_cfg = yaml.load(yaml_vol_str)
181
182 srv_volume_list = drv.nova_volume_list(argument.server_id)
183 for volume in yaml_vol_cfg:
184 if 'custom_meta_data' not in volume:
185 continue
186 vmd = dict()
187 for vol_md_item in volume['custom_meta_data']:
188 if 'value' not in vol_md_item:
189 continue
190 vmd[vol_md_item['name']] = vol_md_item['value']
191
192 # Get volume id
193 vol_id = get_volume_id(srv_volume_list, volume['name'])
194 if vol_id is None:
195 logger.error("Server %s Could not find volume %s" %(argument.server_id, volume['name']))
196 sys.exit(3)
197 drv.cinder_volume_set_metadata(vol_id, vmd)
198
199
200 def prepare_vm_after_boot(drv,argument):
201 ### Important to call create_port_metadata before assign_floating_ip_address
202 ### since assign_floating_ip_address can wait thus delaying port_metadata creation
203
204 ### Wait for a max of 5 minute for server to come up -- Needs fine tuning
205 wait_time = 500
206 sleep_time = 2
207 for i in range(int(wait_time/sleep_time)):
208 server = drv.nova_server_get(argument.server_id)
209 if server['status'] == 'ACTIVE':
210 logger.info("Server %s to reached active state" %(server['name']))
211 break
212 elif server['status'] == 'BUILD':
213 logger.info("Waiting for server: %s to build. Current state: %s" %(server['name'], server['status']))
214 time.sleep(sleep_time)
215 else:
216 logger.info("Server %s reached state: %s" %(server['name'], server['status']))
217 sys.exit(3)
218 else:
219 logger.error("Server %s did not reach active state in %d seconds. Current state: %s" %(server['name'], wait_time, server['status']))
220 sys.exit(4)
221
222 #create_port_metadata(drv, argument)
223 create_volume_metadata(drv, argument)
224 assign_floating_ip_address(drv, argument)
225
226
227 def main():
228 """
229 Main routine
230 """
231 parser = argparse.ArgumentParser(description='Script to create openstack resources')
232 parser.add_argument('--auth_url',
233 action = "store",
234 dest = "auth_url",
235 type = str,
236 help='Keystone Auth URL')
237
238 parser.add_argument('--username',
239 action = "store",
240 dest = "username",
241 type = str,
242 help = "Username for openstack installation")
243
244 parser.add_argument('--password',
245 action = "store",
246 dest = "password",
247 type = str,
248 help = "Password for openstack installation")
249
250 parser.add_argument('--tenant_name',
251 action = "store",
252 dest = "tenant_name",
253 type = str,
254 help = "Tenant name openstack installation")
255
256 parser.add_argument('--user_domain',
257 action = "store",
258 dest = "user_domain",
259 default = None,
260 type = str,
261 help = "User domain name for openstack installation")
262
263 parser.add_argument('--project_domain',
264 action = "store",
265 dest = "project_domain",
266 default = None,
267 type = str,
268 help = "Project domain name for openstack installation")
269
270 parser.add_argument('--region',
271 action = "store",
272 dest = "region",
273 default = "RegionOne",
274 type = str,
275 help = "Region name for openstack installation")
276
277 parser.add_argument('--mgmt_network',
278 action = "store",
279 dest = "mgmt_network",
280 type = str,
281 help = "mgmt_network")
282
283 parser.add_argument('--server_id',
284 action = "store",
285 dest = "server_id",
286 type = str,
287 help = "Server ID on which boot operations needs to be performed")
288
289 parser.add_argument('--floating_ip',
290 action = "store_true",
291 dest = "floating_ip",
292 default = False,
293 help = "Floating IP assignment required")
294
295 parser.add_argument('--pool_name',
296 action = "store",
297 dest = "pool_name",
298 type = str,
299 help = "Floating IP pool name")
300
301
302 parser.add_argument('--port_metadata',
303 action = "store_true",
304 dest = "port_metadata",
305 default = False,
306 help = "Create Port Metadata")
307
308 parser.add_argument("--vol_metadata", type=argparse.FileType('r'))
309
310 argument = parser.parse_args()
311
312 if not argument.auth_url:
313 logger.error("ERROR: AuthURL is not configured")
314 sys.exit(1)
315 else:
316 logger.info("Using AuthURL: %s" %(argument.auth_url))
317
318 if not argument.username:
319 logger.error("ERROR: Username is not configured")
320 sys.exit(1)
321 else:
322 logger.info("Using Username: %s" %(argument.username))
323
324 if not argument.password:
325 logger.error("ERROR: Password is not configured")
326 sys.exit(1)
327 else:
328 logger.info("Using Password: %s" %(argument.password))
329
330 if not argument.tenant_name:
331 logger.error("ERROR: Tenant Name is not configured")
332 sys.exit(1)
333 else:
334 logger.info("Using Tenant Name: %s" %(argument.tenant_name))
335
336 if not argument.mgmt_network:
337 logger.error("ERROR: Management Network Name is not configured")
338 sys.exit(1)
339 else:
340 logger.info("Using Management Network: %s" %(argument.mgmt_network))
341
342 if not argument.server_id:
343 logger.error("ERROR: Server ID is not configured")
344 sys.exit(1)
345 else:
346 logger.info("Using Server ID : %s" %(argument.server_id))
347
348 try:
349 pid = os.fork()
350 if pid > 0:
351 # exit for parent
352 sys.exit(0)
353 except OSError as e:
354 logger.error("fork failed: %d (%s)\n" % (e.errno, e.strerror))
355 sys.exit(2)
356
357
358 drv = openstack_drv.OpenstackDriver(username = argument.username,
359 password = argument.password,
360 auth_url = argument.auth_url,
361 tenant_name = argument.tenant_name,
362 mgmt_network = argument.mgmt_network,
363 user_domain_name = argument.user_domain,
364 project_domain_name = argument.project_domain,
365 region = argument.region)
366
367 prepare_vm_after_boot(drv, argument)
368 sys.exit(0)
369
370 if __name__ == "__main__":
371 main()
372
373