github.com/moby/docker@v26.1.3+incompatible/libnetwork/cmd/ssd/ssd.py (about)

     1  #!/usr/bin/python
     2  
     3  import sys, signal, time, os
     4  import docker
     5  import re
     6  import subprocess
     7  import json
     8  import hashlib
     9  
    10  ipv4match = re.compile(
    11      r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' +
    12      r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' +
    13      r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' +
    14      r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])'
    15  )
    16  
    17  def which(name, defaultPath=""):
    18      if defaultPath and os.path.exists(defaultPath):
    19        return defaultPath
    20      for path in os.getenv("PATH").split(os.path.pathsep):
    21          fullPath = path + os.sep + name
    22          if os.path.exists(fullPath):
    23              return fullPath
    24          
    25  def check_iptables(name, plist):
    26      replace = (':', ',')
    27      ports = []
    28      for port in plist:
    29          for r in replace:
    30              port = port.replace(r, ' ')
    31  
    32          p = port.split()
    33          ports.append((p[1], p[3]))
    34  
    35      # get the ingress sandbox's docker_gwbridge network IP.
    36      # published ports get DNAT'ed to this IP.
    37      ip = subprocess.check_output([ which("nsenter","/usr/bin/nsenter"), '--net=/var/run/docker/netns/ingress_sbox', which("bash", "/bin/bash"), '-c', 'ifconfig eth1 | grep \"inet\\ addr\" | cut -d: -f2 | cut -d\" \" -f1'])
    38      ip = ip.rstrip()
    39  
    40      for p in ports:
    41          rule = which("iptables", "/sbin/iptables") + '-t nat -C DOCKER-INGRESS -p tcp --dport {0} -j DNAT --to {1}:{2}'.format(p[1], ip, p[1])
    42          try:
    43              subprocess.check_output([which("bash", "/bin/bash"), "-c", rule])
    44          except subprocess.CalledProcessError as e:
    45              print "Service {0}: host iptables DNAT rule for port {1} -> ingress sandbox {2}:{3} missing".format(name, p[1], ip, p[1])
    46  
    47  def get_namespaces(data, ingress=False):
    48      if ingress is True:
    49          return {"Ingress":"/var/run/docker/netns/ingress_sbox"}
    50      else:
    51          spaces =[]
    52          for c in data["Containers"]:
    53              sandboxes = {str(c) for c in data["Containers"]}
    54  
    55          containers = {}
    56          for s in sandboxes:
    57              spaces.append(str(cli.inspect_container(s)["NetworkSettings"]["SandboxKey"]))
    58              inspect = cli.inspect_container(s)
    59              containers[str(inspect["Name"])] = str(inspect["NetworkSettings"]["SandboxKey"])
    60          return containers
    61  
    62  
    63  def check_network(nw_name, ingress=False):
    64  
    65      print "Verifying LB programming for containers on network %s" % nw_name
    66  
    67      data = cli.inspect_network(nw_name, verbose=True)
    68  
    69      if "Services" in data.keys():
    70          services = data["Services"]
    71      else:
    72          print "Network %s has no services. Skipping check" % nw_name
    73          return
    74  
    75      fwmarks = {str(service): str(svalue["LocalLBIndex"]) for service, svalue in services.items()}
    76  
    77      stasks = {}
    78      for service, svalue in services.items():
    79          if service == "":
    80              continue
    81          tasks = []
    82          for task in svalue["Tasks"]:
    83              tasks.append(str(task["EndpointIP"]))
    84          stasks[fwmarks[str(service)]] = tasks
    85  
    86          # for services in ingress network verify the iptables rules
    87          # that direct ingress (published port) to backend (target port)
    88          if ingress is True:
    89              check_iptables(service, svalue["Ports"])
    90  
    91      containers = get_namespaces(data, ingress)
    92      for container, namespace in containers.items():
    93          print "Verifying container %s..." % container
    94          ipvs = subprocess.check_output([which("nsenter","/usr/bin/nsenter"), '--net=%s' % namespace, which("ipvsadm","/usr/sbin/ipvsadm"), '-ln'])
    95  
    96          mark = ""
    97          realmark = {}
    98          for line in ipvs.splitlines():
    99              if "FWM" in line:
   100                  mark = re.findall("[0-9]+", line)[0]
   101                  realmark[str(mark)] = []
   102              elif "->" in line:
   103                  if mark == "":
   104                      continue
   105                  ip = ipv4match.search(line)
   106                  if ip is not None:
   107                      realmark[mark].append(format(ip.group(0)))
   108              else:
   109                  mark = ""
   110          for key in realmark.keys():
   111              if key not in stasks:
   112                  print "LB Index %s" % key, "present in IPVS but missing in docker daemon"
   113                  del realmark[key]
   114  
   115          for key in stasks.keys():
   116              if key not in realmark:
   117                  print "LB Index %s" % key, "present in docker daemon but missing in IPVS"
   118                  del stasks[key]
   119  
   120          for key in realmark:
   121              service = "--Invalid--"
   122              for sname, idx in fwmarks.items():
   123                  if key == idx:
   124                      service = sname
   125              if len(set(realmark[key])) != len(set(stasks[key])):
   126                  print "Incorrect LB Programming for service %s" % service
   127                  print "control-plane backend tasks:"
   128                  for task in stasks[key]:
   129                      print task
   130                  print "kernel IPVS backend tasks:"
   131                  for task in realmark[key]:
   132                      print task
   133              else:
   134                  print "service %s... OK" % service
   135  
   136  if __name__ == '__main__':
   137      if len(sys.argv) < 2:
   138          print 'Usage: ssd.py network-name [gossip-consistency]'
   139          sys.exit()
   140  
   141      cli = docker.APIClient(base_url='unix://var/run/docker.sock', version='auto')
   142      if len(sys.argv) == 3:
   143          command = sys.argv[2]
   144      else:
   145          command = 'default'
   146  
   147      if command == 'gossip-consistency':
   148          cspec = docker.types.ContainerSpec(
   149              image='docker/ssd',
   150              args=[sys.argv[1], 'gossip-hash'],
   151              mounts=[docker.types.Mount('/var/run/docker.sock', '/var/run/docker.sock', type='bind')]
   152          )
   153          mode = docker.types.ServiceMode(
   154              mode='global'
   155          )
   156          task_template = docker.types.TaskTemplate(cspec)
   157  
   158          cli.create_service(task_template, name='gossip-hash', mode=mode)
   159          #TODO change to a deterministic way to check if the service is up.
   160          time.sleep(5)
   161          output = cli.service_logs('gossip-hash', stdout=True, stderr=True, details=True)
   162          for line in output:
   163              print("Node id: %s gossip hash %s" % (line[line.find("=")+1:line.find(",")], line[line.find(" ")+1:]))
   164          if cli.remove_service('gossip-hash') is not True:
   165              print("Deleting gossip-hash service failed")
   166      elif command == 'gossip-hash':
   167          data = cli.inspect_network(sys.argv[1], verbose=True)
   168          services = data["Services"]
   169          md5 = hashlib.md5()
   170          entries = []
   171          for service, value in services.items():
   172              entries.append(service)
   173              entries.append(value["VIP"])
   174              for task in value["Tasks"]:
   175                  for key, val in task.items():
   176                      if isinstance(val, dict):
   177                          for k, v in val.items():
   178                              entries.append(v)
   179                      else:
   180                          entries.append(val)
   181          entries.sort()
   182          for e in entries:
   183              md5.update(e)
   184          print(md5.hexdigest())
   185          sys.stdout.flush()
   186          while True:
   187             signal.pause()
   188      elif command == 'default':
   189          if sys.argv[1] == "ingress":
   190              check_network("ingress", ingress=True)
   191          else:
   192              check_network(sys.argv[1])
   193              check_network("ingress", ingress=True)