| #!/usr/bin/python | |
| import sys, signal, time, os | |
| import docker | |
| import re | |
| import subprocess | |
| import json | |
| import hashlib | |
| ipv4match = re.compile( | |
| r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' + | |
| r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' + | |
| r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]).' + | |
| r'(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])' | |
| ) | |
| def which(name, defaultPath=""): | |
| if defaultPath and os.path.exists(defaultPath): | |
| return defaultPath | |
| for path in os.getenv("PATH").split(os.path.pathsep): | |
| fullPath = path + os.sep + name | |
| if os.path.exists(fullPath): | |
| return fullPath | |
| def check_iptables(name, plist): | |
| replace = (':', ',') | |
| ports = [] | |
| for port in plist: | |
| for r in replace: | |
| port = port.replace(r, ' ') | |
| p = port.split() | |
| ports.append((p[1], p[3])) | |
| # get the ingress sandbox's docker_gwbridge network IP. | |
| # published ports get DNAT'ed to this IP. | |
| 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']) | |
| ip = ip.rstrip() | |
| for p in ports: | |
| 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]) | |
| try: | |
| subprocess.check_output([which("bash", "/bin/bash"), "-c", rule]) | |
| except subprocess.CalledProcessError as e: | |
| print "Service {0}: host iptables DNAT rule for port {1} -> ingress sandbox {2}:{3} missing".format(name, p[1], ip, p[1]) | |
| def get_namespaces(data, ingress=False): | |
| if ingress is True: | |
| return {"Ingress":"/var/run/docker/netns/ingress_sbox"} | |
| else: | |
| spaces =[] | |
| for c in data["Containers"]: | |
| sandboxes = {str(c) for c in data["Containers"]} | |
| containers = {} | |
| for s in sandboxes: | |
| spaces.append(str(cli.inspect_container(s)["NetworkSettings"]["SandboxKey"])) | |
| inspect = cli.inspect_container(s) | |
| containers[str(inspect["Name"])] = str(inspect["NetworkSettings"]["SandboxKey"]) | |
| return containers | |
| def check_network(nw_name, ingress=False): | |
| print "Verifying LB programming for containers on network %s" % nw_name | |
| data = cli.inspect_network(nw_name, verbose=True) | |
| if "Services" in data.keys(): | |
| services = data["Services"] | |
| else: | |
| print "Network %s has no services. Skipping check" % nw_name | |
| return | |
| fwmarks = {str(service): str(svalue["LocalLBIndex"]) for service, svalue in services.items()} | |
| stasks = {} | |
| for service, svalue in services.items(): | |
| if service == "": | |
| continue | |
| tasks = [] | |
| for task in svalue["Tasks"]: | |
| tasks.append(str(task["EndpointIP"])) | |
| stasks[fwmarks[str(service)]] = tasks | |
| # for services in ingress network verify the iptables rules | |
| # that direct ingress (published port) to backend (target port) | |
| if ingress is True: | |
| check_iptables(service, svalue["Ports"]) | |
| containers = get_namespaces(data, ingress) | |
| for container, namespace in containers.items(): | |
| print "Verifying container %s..." % container | |
| ipvs = subprocess.check_output([which("nsenter","/usr/bin/nsenter"), '--net=%s' % namespace, which("ipvsadm","/usr/sbin/ipvsadm"), '-ln']) | |
| mark = "" | |
| realmark = {} | |
| for line in ipvs.splitlines(): | |
| if "FWM" in line: | |
| mark = re.findall("[0-9]+", line)[0] | |
| realmark[str(mark)] = [] | |
| elif "->" in line: | |
| if mark == "": | |
| continue | |
| ip = ipv4match.search(line) | |
| if ip is not None: | |
| realmark[mark].append(format(ip.group(0))) | |
| else: | |
| mark = "" | |
| for key in realmark.keys(): | |
| if key not in stasks: | |
| print "LB Index %s" % key, "present in IPVS but missing in docker daemon" | |
| del realmark[key] | |
| for key in stasks.keys(): | |
| if key not in realmark: | |
| print "LB Index %s" % key, "present in docker daemon but missing in IPVS" | |
| del stasks[key] | |
| for key in realmark: | |
| service = "--Invalid--" | |
| for sname, idx in fwmarks.items(): | |
| if key == idx: | |
| service = sname | |
| if len(set(realmark[key])) != len(set(stasks[key])): | |
| print "Incorrect LB Programming for service %s" % service | |
| print "control-plane backend tasks:" | |
| for task in stasks[key]: | |
| print task | |
| print "kernel IPVS backend tasks:" | |
| for task in realmark[key]: | |
| print task | |
| else: | |
| print "service %s... OK" % service | |
| if __name__ == '__main__': | |
| if len(sys.argv) < 2: | |
| print 'Usage: ssd.py network-name [gossip-consistency]' | |
| sys.exit() | |
| cli = docker.APIClient(base_url='unix://var/run/docker.sock', version='auto') | |
| if len(sys.argv) == 3: | |
| command = sys.argv[2] | |
| else: | |
| command = 'default' | |
| if command == 'gossip-consistency': | |
| cspec = docker.types.ContainerSpec( | |
| image='docker/ssd', | |
| args=[sys.argv[1], 'gossip-hash'], | |
| mounts=[docker.types.Mount('/var/run/docker.sock', '/var/run/docker.sock', type='bind')] | |
| ) | |
| mode = docker.types.ServiceMode( | |
| mode='global' | |
| ) | |
| task_template = docker.types.TaskTemplate(cspec) | |
| cli.create_service(task_template, name='gossip-hash', mode=mode) | |
| #TODO change to a deterministic way to check if the service is up. | |
| time.sleep(5) | |
| output = cli.service_logs('gossip-hash', stdout=True, stderr=True, details=True) | |
| for line in output: | |
| print("Node id: %s gossip hash %s" % (line[line.find("=")+1:line.find(",")], line[line.find(" ")+1:])) | |
| if cli.remove_service('gossip-hash') is not True: | |
| print("Deleting gossip-hash service failed") | |
| elif command == 'gossip-hash': | |
| data = cli.inspect_network(sys.argv[1], verbose=True) | |
| services = data["Services"] | |
| md5 = hashlib.md5() | |
| entries = [] | |
| for service, value in services.items(): | |
| entries.append(service) | |
| entries.append(value["VIP"]) | |
| for task in value["Tasks"]: | |
| for key, val in task.items(): | |
| if isinstance(val, dict): | |
| for k, v in val.items(): | |
| entries.append(v) | |
| else: | |
| entries.append(val) | |
| entries.sort() | |
| for e in entries: | |
| md5.update(e) | |
| print(md5.hexdigest()) | |
| sys.stdout.flush() | |
| while True: | |
| signal.pause() | |
| elif command == 'default': | |
| if sys.argv[1] == "ingress": | |
| check_network("ingress", ingress=True) | |
| else: | |
| check_network(sys.argv[1]) | |
| check_network("ingress", ingress=True) |