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())