f3f06ad6c3603a738e39e86ed3488079a9437879
[apex.git] / apex / network / jumphost.py
1 ##############################################################################
2 # Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 import logging
11 import os
12 import re
13 import shutil
14 import subprocess
15
16 from apex.common.exceptions import ApexDeployException
17 from apex.network import ip_utils
18
19 NET_MAP = {
20     'admin': 'br-admin',
21     'tenant': 'br-tenant',
22     'external': 'br-external',
23     'storage': 'br-storage',
24     'api': 'br-api'
25 }
26
27
28 def configure_bridges(ns):
29     """
30     Configures IP on jumphost bridges
31     :param ns: network_settings
32     :return: None
33     """
34     bridge_networks = ['admin']
35     if 'external' in ns.enabled_network_list:
36         bridge_networks.append('external')
37     for network in bridge_networks:
38         if network == 'external':
39             net_config = ns['networks'][network][0]
40         else:
41             net_config = ns['networks'][network]
42         cidr = net_config['cidr']
43         interface = ip_utils.get_interface(NET_MAP[network], cidr.version)
44
45         if interface:
46             logging.info("Bridge {} already configured with IP: {}".format(
47                 NET_MAP[network], interface.ip))
48         else:
49             logging.info("Will configure IP for {}".format(NET_MAP[network]))
50             ovs_ip = net_config['overcloud_ip_range'][1]
51             if cidr.version == 6:
52                 ipv6_br_path = "/proc/sys/net/ipv6/conf/{}/disable_" \
53                                "ipv6".format(NET_MAP[network])
54                 try:
55                     subprocess.check_call('echo', 0, '>', ipv6_br_path)
56                 except subprocess.CalledProcessError:
57                     logging.error("Unable to enable ipv6 on "
58                                   "bridge {}".format(NET_MAP[network]))
59                     raise
60             try:
61                 ip_prefix = "{}/{}".format(ovs_ip, cidr.prefixlen)
62                 subprocess.check_call(['ip', 'addr', 'add', ip_prefix, 'dev',
63                                       NET_MAP[network]])
64                 subprocess.check_call(['ip', 'link', 'set', 'up', NET_MAP[
65                     network]])
66                 logging.info("IP configured: {} on bridge {}".format(ovs_ip,
67                              NET_MAP[network]))
68             except subprocess.CalledProcessError:
69                 logging.error("Unable to configure IP address on "
70                               "bridge {}".format(NET_MAP[network]))
71
72
73 def attach_interface_to_ovs(bridge, interface, network):
74     """
75     Attaches jumphost interface to OVS for baremetal deployments
76     :param bridge: bridge to attach to
77     :param interface: interface to attach to bridge
78     :param network: Apex network type for these interfaces
79     :return: None
80     """
81
82     net_cfg_path = '/etc/sysconfig/network-scripts'
83     if_file = os.path.join(net_cfg_path, "ifcfg-{}".format(interface))
84     ovs_file = os.path.join(net_cfg_path, "ifcfg-{}".format(bridge))
85
86     logging.info("Attaching interface: {} to bridge: {} on network {}".format(
87         bridge, interface, network
88     ))
89
90     try:
91         output = subprocess.check_output(['ovs-vsctl', 'show'],
92                                          stderr=subprocess.STDOUT)
93         if bridge not in output.decode('utf-8'):
94             logging.debug("Bridge {} not found. Creating...".format(bridge))
95             subprocess.check_call(['ovs-vsctl', 'add-br', bridge])
96         else:
97             logging.debug("Bridge {} found".format(bridge))
98     except subprocess.CalledProcessError:
99         logging.error("Unable to validate/create OVS bridge {}".format(bridge))
100         raise
101     try:
102         output = subprocess.check_output(['ovs-vsctl', 'list-ports', bridge],
103                                          stderr=subprocess.STDOUT)
104         if interface in output.decode('utf-8'):
105             logging.debug("Interface already attached to bridge")
106             return
107     except subprocess.CalledProcessError as e:
108         logging.error("Unable to dump ports for bridge: {}".format(bridge))
109         logging.error("Error output: {}".format(e.output))
110         raise
111
112     if not os.path.isfile(if_file):
113         logging.error("Interface ifcfg not found: {}".format(if_file))
114         raise FileNotFoundError("Interface file missing: {}".format(if_file))
115
116     ifcfg_params = {
117         'IPADDR': '',
118         'NETMASK': '',
119         'GATEWAY': '',
120         'METRIC': '',
121         'DNS1': '',
122         'DNS2': '',
123         'PREFIX': ''
124     }
125     with open(if_file, 'r') as fh:
126         interface_output = fh.read()
127
128     for param in ifcfg_params.keys():
129         match = re.search("{}=(.*)\n".format(param), interface_output)
130         if match:
131             ifcfg_params[param] = match.group(1)
132
133     if not ifcfg_params['IPADDR']:
134         logging.error("IPADDR missing in {}".format(if_file))
135         raise ApexDeployException("IPADDR missing in {}".format(if_file))
136     if not (ifcfg_params['NETMASK'] or ifcfg_params['PREFIX']):
137         logging.error("NETMASK/PREFIX missing in {}".format(if_file))
138         raise ApexDeployException("NETMASK/PREFIX missing in {}".format(
139             if_file))
140     if network == 'external' and not ifcfg_params['GATEWAY']:
141         logging.error("GATEWAY is required to be in {} for external "
142                       "network".format(if_file))
143         raise ApexDeployException("GATEWAY is required to be in {} for "
144                                   "external network".format(if_file))
145
146     shutil.move(if_file, "{}.orig".format(if_file))
147     if_content = """DEVICE={}
148 DEVICETYPE=ovs
149 TYPE=OVSPort
150 PEERDNS=no
151 BOOTPROTO=static
152 NM_CONTROLLED=no
153 ONBOOT=yes
154 OVS_BRIDGE={}
155 PROMISC=yes""".format(interface, bridge)
156
157     bridge_content = """DEVICE={}
158 DEVICETYPE=ovs
159 BOOTPROTO=static
160 ONBOOT=yes
161 TYPE=OVSBridge
162 PROMISC=yes""".format(bridge)
163     peer_dns = 'no'
164     for param, value in ifcfg_params.items():
165         if value:
166             bridge_content += "\n{}={}".format(param, value)
167             if param == 'DNS1' or param == 'DNS2':
168                 peer_dns = 'yes'
169     bridge_content += "\n{}={}".format('PEERDNS', peer_dns)
170
171     logging.debug("New interface file content:\n{}".format(if_content))
172     logging.debug("New bridge file content:\n{}".format(bridge_content))
173     with open(if_file, 'w') as fh:
174         fh.write(if_content)
175     with open(ovs_file, 'w') as fh:
176         fh.write(bridge_content)
177     logging.info("New network ifcfg files written")
178     logging.info("Restarting Linux networking")
179     try:
180         subprocess.check_call(['systemctl', 'restart', 'network'])
181     except subprocess.CalledProcessError:
182         logging.error("Failed to restart Linux networking")
183         raise