2525# Author: Komal Thareja (kthare10@renci.org)
2626import argparse
2727import logging
28+ import os
29+ import re
2830import traceback
2931from datetime import datetime , timezone , timedelta
3032from logging .handlers import RotatingFileHandler
@@ -50,10 +52,15 @@ class MainClass:
5052 - Remove/Delete slices older than specified number of days
5153 - Remove/Delete dangling network services which connect the ports to deleted/closed VMs
5254 """
53- def __init__ (self , config_file : str ):
55+ def __init__ (self , config_file : str , am_config_file : str ):
56+ self .am_config_dict = None
5457 with open (config_file ) as f :
5558 config_dict = yaml .safe_load (f )
5659
60+ if am_config_file is not None and os .path .exists (am_config_file ):
61+ with open (am_config_file ) as f :
62+ self .am_config_dict = yaml .safe_load (f )
63+
5764 # Load the config file
5865 self .log_config = config_dict [Constants .CONFIG_LOGGING_SECTION ]
5966
@@ -215,6 +222,132 @@ def delete_dead_closing_slice(self, *, days: int):
215222 self .logger .error (f"Failed to delete slice: { s .get_slice_id ()} : e: { e } " )
216223 self .logger .error (traceback .format_exc ())
217224
225+ def execute_ansible (self , * , inventory_path : str , playbook_path : str , extra_vars : dict ,
226+ ansible_python_interpreter : str , sources : str = None , private_key_file : str = None ,
227+ host_vars : dict = None , host : str = None , user : str = None ):
228+ from fabric_am .util .ansible_helper import AnsibleHelper
229+ ansible_helper = AnsibleHelper (inventory_path = inventory_path , logger = self .logger ,
230+ ansible_python_interpreter = ansible_python_interpreter ,
231+ sources = sources )
232+
233+ ansible_helper .set_extra_vars (extra_vars = extra_vars )
234+
235+ if host is not None and host_vars is not None and len (host_vars ) > 0 :
236+ for key , value in host_vars .items ():
237+ ansible_helper .add_vars (host = host , var_name = key , value = value )
238+
239+ self .logger .info (f"Executing playbook { playbook_path } extra_vars: { extra_vars } host_vars: { host_vars } " )
240+ ansible_helper .run_playbook (playbook_path = playbook_path , private_key_file = private_key_file , user = user )
241+ return ansible_helper .get_result_callback ()
242+
243+ def clean_sliver_inconsistencies (self ):
244+ try :
245+ actor_type = self .actor_config [Constants .TYPE ]
246+ if actor_type .lower () != ActorType .Authority .name .lower () or self .am_config_dict is None :
247+ return
248+
249+ from fabric_am .util .am_constants import AmConstants
250+ pb_section = self .am_config_dict .get (AmConstants .PLAYBOOK_SECTION )
251+ if pb_section is None :
252+ return
253+ inventory_location = pb_section .get (AmConstants .PB_INVENTORY )
254+ pb_dir = pb_section .get (AmConstants .PB_LOCATION )
255+ vm_playbook_name = pb_section .get ("VM" )
256+ if inventory_location is None or pb_dir is None or vm_playbook_name is None :
257+ return
258+
259+ vm_playbook_path = f"{ pb_dir } /{ vm_playbook_name } "
260+
261+ ansible_python_interpreter = None
262+ ansible_section = self .am_config_dict .get (AmConstants .ANSIBLE_SECTION )
263+ if ansible_section :
264+ ansible_python_interpreter = ansible_section .get (AmConstants .ANSIBLE_PYTHON_INTERPRETER )
265+
266+ actor_db = ActorDatabase (user = self .database_config [Constants .PROPERTY_CONF_DB_USER ],
267+ password = self .database_config [Constants .PROPERTY_CONF_DB_PASSWORD ],
268+ database = self .database_config [Constants .PROPERTY_CONF_DB_NAME ],
269+ db_host = self .database_config [Constants .PROPERTY_CONF_DB_HOST ],
270+ logger = self .logger )
271+
272+ states = [ReservationStates .Active .value ,
273+ ReservationStates .ActiveTicketed .value ,
274+ ReservationStates .Ticketed .value ,
275+ ReservationStates .Nascent .value ]
276+
277+ resource_type = ["VM" ]
278+
279+ # Get the Active Slivers from CF
280+ slivers = actor_db .get_reservations (states = states , rsv_type = resource_type )
281+ cf_active_sliver_ids = []
282+ if slivers :
283+ for s in slivers :
284+ cf_active_sliver_ids .append (str (s .get_reservation_id ()))
285+
286+ self .logger .info (f"Active Slivers: { cf_active_sliver_ids } " )
287+
288+ # Get the VMs from Openstack
289+ result_callback_1 = self .execute_ansible (inventory_path = inventory_location ,
290+ playbook_path = vm_playbook_path ,
291+ extra_vars = {"operation" : "list" },
292+ ansible_python_interpreter = ansible_python_interpreter )
293+ result_1 = result_callback_1 .get_json_result_ok ()
294+
295+ os_vms = {}
296+ if result_1 and result_1 .get ('openstack_servers' ):
297+ servers = result_1 .get ('openstack_servers' )
298+ for s in servers :
299+ if s .get ('OS-EXT-SRV-ATTR:instance_name' ) and s .get ('name' ):
300+ os_vms [s .get ('OS-EXT-SRV-ATTR:instance_name' )] = s .get ('name' )
301+
302+ uuid_pattern = r'([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})'
303+
304+ # Cleanup inconsistencies between CF and Open Stack
305+ if len (cf_active_sliver_ids ):
306+ for instance , vm_name in os_vms .items ():
307+ try :
308+ # Search for UUID in the input string
309+ match = re .search (uuid_pattern , vm_name )
310+
311+ # Extract UUID if found
312+ if match :
313+ sliver_id = match .group (1 )
314+ if sliver_id not in cf_active_sliver_ids :
315+ result_2 = self .execute_ansible (inventory_path = inventory_location ,
316+ playbook_path = vm_playbook_path ,
317+ extra_vars = {"operation" : "delete" , "vmname" : vm_name },
318+ ansible_python_interpreter = ansible_python_interpreter )
319+ self .logger .info (f"Deleted instance: { vm_name } ; result: { result_2 .get_json_result_ok ()} " )
320+ else :
321+ self .logger .error (f"Sliver Id not found in the input string: { vm_name } " )
322+ except Exception as e :
323+ self .logger .error (f"Failed to cleanup CF and openstack inconsistencies instance: { instance } vm: { vm_name } : { e } " )
324+ self .logger .error (traceback .format_exc ())
325+
326+ # Cleanup inconsistencies between Open Stack and Virsh
327+ result_3 = self .execute_ansible (inventory_path = inventory_location ,
328+ playbook_path = f"{ pb_dir } /worker_libvirt_operations.yml" ,
329+ extra_vars = {"operation" : "listall" },
330+ ansible_python_interpreter = ansible_python_interpreter )
331+
332+ for host , ok_result in result_3 .host_ok .items ():
333+ try :
334+ if ok_result and ok_result ._result :
335+ virsh_vms = ok_result ._result .get ('stdout_lines' , [])
336+ self .logger .info (f"Host: { host } has VMs: { virsh_vms } " )
337+ for instance in virsh_vms :
338+ if instance not in os_vms :
339+ results_4 = self .execute_ansible (inventory_path = inventory_location ,
340+ playbook_path = vm_playbook_path ,
341+ extra_vars = {"operation" : "delete" , "host" : str (host )},
342+ ansible_python_interpreter = ansible_python_interpreter )
343+ self .logger .info (f"Deleted instance: { instance } ; result: { results_4 .get_json_result_ok ()} " )
344+ except Exception as e :
345+ self .logger .error (f"Failed to cleanup openstack and virsh inconsistencies on { host } : { e } " )
346+ self .logger .error (traceback .format_exc ())
347+ except Exception as e :
348+ self .logger .error (f"Failed to cleanup inconsistencies: { e } " )
349+ self .logger .error (traceback .format_exc ())
350+
218351 def handle_command (self , args ):
219352 """
220353 Command Handler
@@ -230,8 +363,15 @@ def handle_command(self, args):
230363 # Slivers
231364 elif args .command == "slivers" :
232365 # Close operation
233- if args .operation is not None and args .operation == "close" :
234- self .delete_dangling_network_slivers ()
366+ if args .operation is not None and args .operation == "cleanup" :
367+ self .clean_sliver_inconsistencies ()
368+ else :
369+ print (f"Unsupported operation: { args .operation } " )
370+ elif args .command == "audit" :
371+ # Close operation
372+ if args .operation is not None and args .operation == "audit" :
373+ self .delete_dead_closing_slice (days = args .days )
374+ self .clean_sliver_inconsistencies ()
235375 else :
236376 print (f"Unsupported operation: { args .operation } " )
237377 else :
@@ -240,12 +380,13 @@ def handle_command(self, args):
240380
241381if __name__ == '__main__' :
242382 parser = argparse .ArgumentParser ()
383+ parser .add_argument ("-a" , dest = 'amconfig' , required = True , type = str )
243384 parser .add_argument ("-f" , dest = 'config' , required = True , type = str )
244385 parser .add_argument ("-d" , dest = 'days' , required = False , type = int , default = 30 )
245386 parser .add_argument ("-c" , dest = 'command' , required = True , type = str )
246387 parser .add_argument ("-o" , dest = 'operation' , required = True , type = str )
247388 args = parser .parse_args ()
248389
249- mc = MainClass (config_file = args .config )
390+ mc = MainClass (config_file = args .config , am_config_file = args . amconfig )
250391 mc .handle_command (args )
251392
0 commit comments