Skip to content

Commit 4a39c3c

Browse files
anrsanrs
anrs
authored andcommitted
feat: check calico weps which are dangling
1 parent 7624030 commit 4a39c3c

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

scripts/check_calico.py

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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())

0 commit comments

Comments
 (0)