github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/scripts/check_calico.py (about) 1 #!/usr/bin/env python3 2 import argparse 3 import json 4 import os 5 import re 6 import subprocess 7 import sys 8 9 import etcd3 10 11 def sh(prog, *args): 12 p = subprocess.Popen((prog,)+args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 13 so, se = p.communicate() 14 return p.returncode, so, se 15 16 17 class Calico(object): 18 19 @classmethod 20 def get_weps(cls): 21 items = cls.get('wep', '--all-namespaces') 22 return WorkloadEndpoint.parse(items) 23 24 @classmethod 25 def get(cls, resource_type, *args): 26 rc, so, se = cls.ctl('get', resource_type, '-o', 'json', *args) 27 if rc: 28 raise ValueError('get %s failed: %s; %s; %s' % (resource_type, rc, so, se)) 29 return json.loads(so.decode('utf-8'))['items'] 30 31 @classmethod 32 def ctl(cls, subcommand, *args): 33 return sh('calicoctl', subcommand, *args) 34 35 36 class WorkloadEndpoint(object): 37 38 def __init__(self, raw_dict): 39 self.dict = raw_dict 40 41 @classmethod 42 def parse(cls, items): 43 weps = [] 44 for elem in items: 45 weps.append(WorkloadEndpoint(elem)) 46 return weps 47 48 def has_belong_eru(self, eru): 49 return any(eru.has_ip(ip) for ip in self.ips) 50 51 @property 52 def name(self): 53 return self.dict['metadata']['name'] 54 55 @property 56 def namespace(self): 57 return self.dict['metadata']['namespace'] 58 59 @property 60 def node(self): 61 return self.dict['spec']['node'] 62 63 @property 64 def interface(self): 65 return self.dict['spec']['interfaceName'] 66 67 @property 68 def ips(self): 69 return [cidr.split('/')[0] for cidr in self.dict['spec']['ipNetworks']] 70 71 72 class Eru(object): 73 74 def __init__(self, etcd, root_prefix): 75 self.etcd = etcd 76 self.root_prefix = os.path.join('/', root_prefix.lstrip('/')) 77 self.ips = [] 78 79 def watch_ips(self): 80 81 ipre = re.compile(r'^(\d+\.\d+\.\d+\.\d+)') 82 83 def parse(key, value): 84 for _, ip in json.loads(value).get('networks', {}).items(): 85 match = ipre.search(ip) 86 if not match: 87 continue 88 self.ips.append(match.group(1)) 89 90 self.etcd.get_prefix(self.workload_status_prefix, parse) 91 92 @property 93 def workload_status_prefix(self): 94 # the ended with '/' is necessary to avoid 'status:node' 95 return os.path.join(self.root_prefix, 'status/') 96 97 def has_ip(self, ip): 98 return ip in self.ips 99 100 101 class ETCD(object): 102 103 def __init__(self, cli): 104 self.cli = cli 105 106 @classmethod 107 def connect(cls, host, port): 108 cli = etcd3.client(host=host, port=port) 109 return ETCD(cli) 110 111 def get_prefix(self, prefix, fn): 112 start = prefix 113 end = etcd3.utils.increment_last_byte(etcd3.utils.to_bytes(start)) 114 115 while 1: 116 req = etcd3.etcdrpc.RangeRequest() 117 req.key = etcd3.utils.to_bytes(start) 118 req.keys_only = False 119 req.range_end = etcd3.utils.to_bytes(end) 120 req.sort_order = etcd3.etcdrpc.RangeRequest.ASCEND 121 req.sort_target = etcd3.etcdrpc.RangeRequest.KEY 122 req.serializable = True 123 req.limit = 1000 124 125 resp = self.cli.kvstub.Range( 126 req, 127 self.cli.timeout, 128 credentials=self.cli.call_credentials, 129 metadata=self.cli.metadata, 130 ) 131 132 for kv in resp.kvs: 133 key = kv.key.decode('utf-8') 134 fn(kv.key.decode('utf-8'), kv.value.decode('utf-8')) 135 136 if not resp.more: 137 return 138 139 start = etcd3.utils.increment_last_byte(kv.key) 140 141 142 def print_dangling(wep): 143 print('%s/%s is dangling' % (wep.namespace, wep.name)) 144 145 def get_args(): 146 ap = argparse.ArgumentParser() 147 ap.add_argument('-e', '--eru-etcd-endpoints', help='the ERU ETCD endpoints', default='127.0.0.1:2379') 148 ap.add_argument('-p', '--eru-etcd-prefix', help='the ERU ETCD root prefix', required=True) 149 return ap.parse_args() 150 151 def main(): 152 args = get_args() 153 154 host, _, port = args.eru_etcd_endpoints.split(',')[0].partition(':') 155 port = int(port) if port else 2379 156 etcd = ETCD.connect(host, port) 157 158 global eru 159 eru = Eru(etcd, args.eru_etcd_prefix) 160 eru.watch_ips() 161 162 for wep in Calico.get_weps(): 163 if 'yavirt-cali-gw' in wep.interface or wep.has_belong_eru(eru): 164 continue 165 166 print_dangling(wep) 167 168 return 0 169 170 if __name__ == '__main__': 171 sys.exit(main())