summaryrefslogtreecommitdiffstats
path: root/clover/servicemesh/route_rules.py
blob: 935940e819e726ba8b9db5819191a7d939243192 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python

# Copyright (c) Authors of Clover
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
import os
import redis
import subprocess
import sys
import yaml

from clover.orchestration.kube_client import KubeClient

#istioctl='$HOME/istio-0.6.0/bin/istioctl'
# The assumption is that istioctl is already in the user's path
ISTIOCTL='istioctl'

def cmd_exists(cmd):
    return any(
        os.access(os.path.join(path, cmd), os.X_OK)
        for path in os.environ["PATH"].split(os.pathsep)
    )

def load_route_rules(rr_yaml_path):
    if not cmd_exists(ISTIOCTL):
        print('%s does not exist in PATH, please export istioctl to PATH' % istioctl)
        return False

    # TODO(s3wong): load yaml and verify it does indeed contain route rule
    cmd = ISTIOCTL + ' create -f ' + rr_yaml_path
    output = subprocess.check_output(cmd, shell=True)
    if not output:
        print('Route rule creation failed: %s' % output)
        return False
    return True

def delete_route_rules(rr_yaml_path, namespace):
    if not cmd_exists(ISTIOCTL):
        print('%s does not exist in PATH, please export istioctl to PATH' % istioctl)
        return False

    # TODO(s3wong): load yaml and verify it does indeed contain route rule
    cmd = ISTIOCTL + ' delete -f ' + rr_yaml_path + ' -n ' + namespace
    output = subprocess.check_output(cmd, shell=True)
    if not output or not 'Deleted' in output:
        print('Route rule deletion failed: %s' % output)
        return False
    return True

def get_route_rules():
    if not cmd_exists(ISTIOCTL):
        print('%s does not exist in PATH, please export istioctl to PATH' % istioctl)
        return None
    cmd = ISTIOCTL + ' get routerules -o yaml'
    output = subprocess.check_output(cmd, shell=True)
    if not output:
        print('No route rule configured')
        return None
    docs = []
    for raw_doc in output.split('\n---'):
        try:
            docs.append(yaml.load(raw_doc))
        except SyntaxError:
            print('syntax error: %s' % raw_doc)
    return docs

def parse_route_rules(routerules):
    ret_list = []
    if not routerules:
        print('No routerules')
        return ret_list
    for routerule in routerules:
        if not routerule or routerule == 'None':  continue
        print('routerule is %s' % routerule)
        if routerule.get('kind') != 'RouteRule': continue
        ret_rr_dict = {}
        spec = routerule.get('spec')
        if not spec: continue
        ret_rr_dict['service'] = spec.get('destination').get('name')
        ret_rr_dict['rules'] = spec.get('route')
        ret_list.append(ret_rr_dict)
    return ret_list

def _derive_key_from_test_id(test_id):
    return 'route-rules-' + str(test_id)

def _get_redis_ip():
    k8s_client = KubeClient()
    redis_pod = k8s_client.find_pod_by_name('redis')
    redis_ip = redis_pod.get('pod_ip')
    return redis_ip

def set_route_rules(test_id):
    redis_ip = _get_redis_ip()
    r = redis.StrictRedis(host=redis_ip, port=6379, db=0)
    key = _derive_key_from_test_id(test_id)
    rr = get_route_rules()
    r.set(key, rr)

def fetch_route_rules(test_id):
    redis_ip = _get_redis_ip()
    r = redis.StrictRedis(host=redis_ip, port=6379, db=0)
    key = _derive_key_from_test_id(test_id)
    rr = r.get(key)
    return yaml.load(rr)

'''
    The format of result_dict is expected to be:
    {
        'service':  <service name>,
        <version string 1>: <integer representation of version string 1 occurrances during test>,
        <version string 2>: <integer representation of version string 2 occurrances during test>,
        ...
    }
'''
def validate_weighted_route_rules(result_dict, test_id=None):
    print('validate_weighted_route_rules: test id %s' % test_id)
    svc_name = result_dict.get('service')
    if not test_id:
        rr_list = parse_route_rules(get_route_rules())
    else:
        rr_list = parse_route_rules(fetch_route_rules(test_id))
    errors = []
    ret = True
    for rr in rr_list:
        route_rules = rr.get('rules')
        if not route_rules:
            break
        for rule in route_rules:
            version = rule.get('labels').get('version')
            weight = rule.get('weight')
            if not weight: weight = 1
            if abs(weight - result_dict[version]) > 10:
                err = 'svc %s version %s expected to get %d, but got %d' % (svc_name, version, weight, result_dict[version])
                ret = False
            else:
                err = 'svc %s version %s expected to get %d, got %d. Validation succeeded' % (svc_name, version, weight, result_dict[version])
            errors.append(err)
    return ret, errors