~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to assess_spaces_subnets.py

  • Committer: Aaron Bentley
  • Date: 2015-09-02 17:46:47 UTC
  • mto: This revision was merged to the branch mainline in revision 1082.
  • Revision ID: aaron.bentley@canonical.com-20150902174647-06vmnsooo6yzd46t
Stop supplying env to subprocess calls.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
from __future__ import print_function
3
 
from argparse import ArgumentParser
4
 
import re
5
 
import sys
6
 
from textwrap import dedent
7
 
 
8
 
from utility import (
9
 
    add_basic_testing_arguments,
10
 
    configure_logging,
11
 
)
12
 
from deploy_stack import (
13
 
    BootstrapManager,
14
 
)
15
 
from assess_container_networking import (
16
 
    cleaned_bootstrap_context,
17
 
    ssh,
18
 
)
19
 
 
20
 
__metaclass__ = type
21
 
 
22
 
 
23
 
def parse_args(argv=None):
24
 
    """Parse all arguments."""
25
 
 
26
 
    description = dedent("""\
27
 
    Test container address allocation.
28
 
    For LXC and KVM, create machines of each type and test the network
29
 
    between LXC <--> LXC, KVM <--> KVM and LXC <--> KVM. Also test machine
30
 
    to outside world, DNS and that these tests still pass after a reboot. In
31
 
    case of failure pull logs and configuration files from the machine that
32
 
    we detected a problem on for later analysis.
33
 
    """)
34
 
    parser = add_basic_testing_arguments(ArgumentParser(
35
 
        description=description
36
 
    ))
37
 
    parser.add_argument(
38
 
        '--clean-environment', action='store_true', help=dedent("""\
39
 
        Attempts to re-use an existing environment rather than destroying it
40
 
        and creating a new one.
41
 
 
42
 
        On launch, if an environment exists, clean out services and machines
43
 
        from it rather than destroying it. If an environment doesn't exist,
44
 
        create one and use it.
45
 
 
46
 
        At termination, clean out services and machines from the environment
47
 
        rather than destroying it."""))
48
 
    return parser.parse_args(argv)
49
 
 
50
 
 
51
 
def assess_spaces_subnets(client):
52
 
    """Check that space and subnet functionality works as expected
53
 
    :param client: EnvJujuClient
54
 
    """
55
 
    network_config = {
56
 
        'default': ['subnet-0fb97566', 'subnet-d27d91a9'],
57
 
        'dmz': ['subnet-604dcd09', 'subnet-882d8cf3'],
58
 
        'apps': ['subnet-c13fbfa8', 'subnet-53da7a28'],
59
 
        'backend': ['subnet-5e4dcd37', 'subnet-7c2c8d07'],
60
 
    }
61
 
 
62
 
    charms_to_space = {
63
 
        'haproxy': {'space': 'dmz'},
64
 
        'mediawiki': {'space': 'apps'},
65
 
        'memcached': {'space': 'apps'},
66
 
        'mysql': {'space': 'backend'},
67
 
        'mysql-slave': {
68
 
            'space': 'backend',
69
 
            'charm': 'mysql',
70
 
        },
71
 
    }
72
 
 
73
 
    _assess_spaces_subnets(client, network_config, charms_to_space)
74
 
 
75
 
 
76
 
def _assess_spaces_subnets(client, network_config, charms_to_space):
77
 
    """Check that space and subnet functionality works as expected
78
 
    :param client: EnvJujuClient
79
 
    :param network_config: Map of 'space name' to ['subnet', 'list']
80
 
    :param charms_to_space: Map of 'unit name' to
81
 
           {'space': 'space name', 'charm': 'charm name (if not same as unit)}
82
 
    :return: None. Raises exception on failure.
83
 
    """
84
 
    for space in sorted(network_config.keys()):
85
 
        client.add_space(space)
86
 
        for subnet in network_config[space]:
87
 
            client.add_subnet(subnet, space)
88
 
 
89
 
    for name in sorted(charms_to_space.keys()):
90
 
        if 'charm' not in charms_to_space[name]:
91
 
            charms_to_space[name]['charm'] = name
92
 
        charm = charms_to_space[name]['charm']
93
 
        space = charms_to_space[name]['space']
94
 
        client.juju('deploy',
95
 
                    (charm, name, '--constraints', 'spaces=' + space))
96
 
 
97
 
    # Scale up. We don't specify constraints, but they should still be honored
98
 
    # per charm.
99
 
    client.juju('add-unit', 'mysql-slave')
100
 
    client.juju('add-unit', 'mediawiki')
101
 
    status = client.wait_for_started()
102
 
 
103
 
    spaces = client.list_space()
104
 
 
105
 
    unit_priv_address = {}
106
 
    units_found = 0
107
 
    for service in sorted(status.status['services'].values()):
108
 
        for unit_name, unit in service.get('units', {}).items():
109
 
            units_found += 1
110
 
            addrs = ssh(client, unit['machine'], 'ip -o addr')
111
 
            for addr in re.findall(r'^\d+:\s+(\w+)\s+inet\s+(\S+)',
112
 
                                   addrs, re.MULTILINE):
113
 
                if addr[0] != 'lo':
114
 
                    unit_priv_address[unit_name] = addr[1]
115
 
 
116
 
    cidrs_in_space = {}
117
 
    for name, attrs in spaces['spaces'].iteritems():
118
 
        cidrs_in_space[name] = []
119
 
        for cidr in attrs:
120
 
            cidrs_in_space[name].append(cidr)
121
 
 
122
 
    units_checked = 0
123
 
    for space, cidrs in cidrs_in_space.iteritems():
124
 
        for cidr in cidrs:
125
 
            for unit, address in unit_priv_address.iteritems():
126
 
                if ipv4_in_cidr(address, cidr):
127
 
                    units_checked += 1
128
 
                    charm = unit.split('/')[0]
129
 
                    if charms_to_space[charm]['space'] != space:
130
 
                        raise ValueError("Found {} in {}, expected {}".format(
131
 
                            unit, space, charms_to_space[charm]['space']))
132
 
 
133
 
    if units_found != units_checked:
134
 
        raise ValueError("Could not find spaces for all units")
135
 
 
136
 
    return units_checked
137
 
 
138
 
 
139
 
def ipv4_to_int(ipv4):
140
 
    """Convert an IPv4 dotted decimal address to an integer"""
141
 
    b = [int(b) for b in ipv4.split('.')]
142
 
    return b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]
143
 
 
144
 
 
145
 
def ipv4_in_cidr(ipv4, cidr):
146
 
    """Returns True if the given address is in the given CIDR"""
147
 
    if '/' in ipv4:
148
 
        ipv4, _ = ipv4.split('/')
149
 
    ipv4 = ipv4_to_int(ipv4)
150
 
    value, bits = cidr.split('/')
151
 
    subnet = ipv4_to_int(value)
152
 
    mask = 0xFFFFFFFF & (0xFFFFFFFF << (32-int(bits)))
153
 
    return (ipv4 & mask) == subnet
154
 
 
155
 
 
156
 
def main(argv=None):
157
 
    args = parse_args(argv)
158
 
    configure_logging(args.verbose)
159
 
    bs_manager = BootstrapManager.from_args(args)
160
 
    bs_manager.client.enable_feature('address-allocation')
161
 
    with cleaned_bootstrap_context(bs_manager, args) as ctx:
162
 
        assess_spaces_subnets(bs_manager.client)
163
 
    return ctx.return_code
164
 
 
165
 
 
166
 
if __name__ == '__main__':
167
 
    sys.exit(main())