123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- #!/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)
|