ssd.py 7.0 KB

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