Revert "Updates scenario files for hunter branch"
[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 shutil
13 import subprocess
14
15 from apex.common.exceptions import JumpHostNetworkException
16 from apex.common import parsers
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 NET_CFG_PATH = '/etc/sysconfig/network-scripts'
28
29
30 def configure_bridges(ns):
31     """
32     Configures IP on jumphost bridges
33     :param ns: network_settings
34     :return: None
35     """
36     bridge_networks = ['admin']
37     if 'external' in ns.enabled_network_list:
38         bridge_networks.append('external')
39     for network in bridge_networks:
40         if network == 'external':
41             net_config = ns['networks'][network][0]
42         else:
43             net_config = ns['networks'][network]
44         cidr = net_config['cidr']
45         interface = ip_utils.get_interface(NET_MAP[network], cidr.version)
46
47         if interface:
48             logging.info("Bridge {} already configured with IP: {}".format(
49                 NET_MAP[network], interface.ip))
50         else:
51             logging.info("Will configure IP for {}".format(NET_MAP[network]))
52             ovs_ip = net_config['overcloud_ip_range'][1]
53             if cidr.version == 6:
54                 ipv6_br_path = "/proc/sys/net/ipv6/conf/{}/disable_" \
55                                "ipv6".format(NET_MAP[network])
56                 with open(ipv6_br_path, 'w') as f:
57                     print(0, file=f)
58             try:
59                 ip_prefix = "{}/{}".format(ovs_ip, cidr.prefixlen)
60                 subprocess.check_call(['ip', 'addr', 'add', ip_prefix, 'dev',
61                                       NET_MAP[network]])
62                 subprocess.check_call(['ip', 'link', 'set', 'up', NET_MAP[
63                     network]])
64                 logging.info("IP configured: {} on bridge {}".format(ovs_ip,
65                              NET_MAP[network]))
66             except subprocess.CalledProcessError:
67                 logging.error("Unable to configure IP address on "
68                               "bridge {}".format(NET_MAP[network]))
69                 raise
70
71
72 def generate_ifcfg_params(if_file, network):
73     """
74     Generates and validates ifcfg parameters required for a network
75     :param if_file: ifcfg file to parse
76     :param network: Apex network
77     :return: dictionary of generated/validated ifcfg params
78     """
79     ifcfg_params = parsers.parse_ifcfg_file(if_file)
80     if not ifcfg_params['IPADDR']:
81         logging.error("IPADDR missing in {}".format(if_file))
82         raise JumpHostNetworkException("IPADDR missing in {}".format(if_file))
83     if not (ifcfg_params['NETMASK'] or ifcfg_params['PREFIX']):
84         logging.error("NETMASK/PREFIX missing in {}".format(if_file))
85         raise JumpHostNetworkException("NETMASK/PREFIX missing in {}".format(
86             if_file))
87     if network == 'external' and not ifcfg_params['GATEWAY']:
88         logging.error("GATEWAY is required to be in {} for external "
89                       "network".format(if_file))
90         raise JumpHostNetworkException("GATEWAY is required to be in {} for "
91                                        "external network".format(if_file))
92
93     if ifcfg_params['DNS1'] or ifcfg_params['DNS2']:
94         ifcfg_params['PEERDNS'] = 'yes'
95     else:
96         ifcfg_params['PEERDNS'] = 'no'
97     return ifcfg_params
98
99
100 def is_ovs_bridge(bridge):
101     """
102     Finds an OVS bridge
103     :param bridge: OVS bridge to find
104     :return: boolean if OVS bridge exists
105     """
106     try:
107         output = subprocess.check_output(['ovs-vsctl', 'show'],
108                                          stderr=subprocess.STDOUT)
109         if bridge not in output.decode('utf-8'):
110             logging.debug("Bridge {} not found".format(bridge))
111             return False
112         else:
113             logging.debug("Bridge {} found".format(bridge))
114             return True
115     except subprocess.CalledProcessError:
116         logging.error("Unable to validate OVS bridge {}".format(bridge))
117         raise
118
119
120 def dump_ovs_ports(bridge):
121     """
122     Returns
123     :param bridge: OVS bridge to list ports
124     :return: list of ports
125     """
126     try:
127         output = subprocess.check_output(['ovs-vsctl', 'list-ports', bridge],
128                                          stderr=subprocess.STDOUT)
129     except subprocess.CalledProcessError:
130         logging.error("Unable to show ports for {}".format(bridge))
131         raise
132     return output.decode('utf-8').strip().split('\n')
133
134
135 def attach_interface_to_ovs(bridge, interface, network):
136     """
137     Attaches jumphost interface to OVS for baremetal deployments
138     :param bridge: bridge to attach to
139     :param interface: interface to attach to bridge
140     :param network: Apex network type for these interfaces
141     :return: None
142     """
143
144     if_file = os.path.join(NET_CFG_PATH, "ifcfg-{}".format(interface))
145     ovs_file = os.path.join(NET_CFG_PATH, "ifcfg-{}".format(bridge))
146
147     logging.info("Attaching interface: {} to bridge: {} on network {}".format(
148         bridge, interface, network
149     ))
150
151     if not is_ovs_bridge(bridge):
152         subprocess.check_call(['ovs-vsctl', 'add-br', bridge])
153     elif interface in dump_ovs_ports(bridge):
154         logging.debug("Interface already attached to bridge")
155         return
156
157     if not os.path.isfile(if_file):
158         logging.error("Interface ifcfg not found: {}".format(if_file))
159         raise FileNotFoundError("Interface file missing: {}".format(if_file))
160     ifcfg_params = generate_ifcfg_params(if_file, network)
161     shutil.move(if_file, "{}.orig".format(if_file))
162     if_content = """DEVICE={}
163 DEVICETYPE=ovs
164 TYPE=OVSPort
165 PEERDNS=no
166 BOOTPROTO=static
167 NM_CONTROLLED=no
168 ONBOOT=yes
169 OVS_BRIDGE={}
170 PROMISC=yes""".format(interface, bridge)
171
172     bridge_content = """DEVICE={}
173 DEVICETYPE=ovs
174 BOOTPROTO=static
175 ONBOOT=yes
176 TYPE=OVSBridge
177 PROMISC=yes""".format(bridge)
178     for param, value in ifcfg_params.items():
179         if value:
180             bridge_content += "\n{}={}".format(param, value)
181
182     logging.debug("New interface file content:\n{}".format(if_content))
183     logging.debug("New bridge file content:\n{}".format(bridge_content))
184     with open(if_file, 'w') as fh:
185         fh.write(if_content)
186     with open(ovs_file, 'w') as fh:
187         fh.write(bridge_content)
188     logging.info("New network ifcfg files written")
189     logging.info("Restarting Linux networking")
190     try:
191         subprocess.check_call(['systemctl', 'restart', 'network'])
192     except subprocess.CalledProcessError:
193         logging.error("Failed to restart Linux networking")
194         raise
195
196
197 def detach_interface_from_ovs(network):
198     """
199     Detach interface from OVS for baremetal deployments
200     :param network: Apex network to detach single interface from
201     :return: None
202     """
203
204     bridge = NET_MAP[network]
205     logging.debug("Detaching interfaces from bridge on network: {}".format(
206         network))
207     # ensure bridge exists
208     if not is_ovs_bridge(bridge):
209         return
210
211     # check if real port is on bridge
212     for interface in dump_ovs_ports(bridge):
213         if interface and not interface.startswith('vnet'):
214             logging.debug("Interface found: {}".format(interface))
215             real_interface = interface
216             break
217     else:
218         logging.info("No jumphost interface exists on bridge {}".format(
219             bridge))
220         return
221
222     # check if original backup ifcfg file exists or create
223     orig_ifcfg_file = os.path.join(NET_CFG_PATH,
224                                    "ifcfg-{}.orig".format(real_interface))
225     ifcfg_file = orig_ifcfg_file[:-len('.orig')]
226     bridge_ifcfg_file = os.path.join(NET_CFG_PATH,
227                                      "ifcfg-{}".format(bridge))
228     if os.path.isfile(orig_ifcfg_file):
229         logging.debug("Original interface file found: "
230                       "{}".format(orig_ifcfg_file))
231     else:
232         logging.info("No original ifcfg file found...will attempt to use "
233                      "bridge ifcfg file and re-create")
234         if os.path.isfile(bridge_ifcfg_file):
235             ifcfg_params = generate_ifcfg_params(bridge_ifcfg_file, network)
236             if_content = """DEVICE={}
237 BOOTPROTO=static
238 ONBOOT=yes
239 TYPE=Ethernet
240 NM_CONTROLLED=no""".format(real_interface)
241             for param, value in ifcfg_params.items():
242                 if value:
243                     if_content += "\n{}={}".format(param, value)
244             logging.debug("Interface file content:\n{}".format(if_content))
245             # write original backup
246             with open(orig_ifcfg_file, 'w') as fh:
247                 fh.write(if_content)
248             logging.debug("Original interface file created: "
249                           "{}".format(orig_ifcfg_file))
250         else:
251             logging.error("Unable to find original interface config file: {} "
252                           "or bridge config file:{}".format(orig_ifcfg_file,
253                                                             bridge_ifcfg_file))
254             raise FileNotFoundError("Unable to locate bridge or original "
255                                     "interface ifcfg file")
256
257     # move original file back and rewrite bridge ifcfg
258     shutil.move(orig_ifcfg_file, ifcfg_file)
259     bridge_content = """DEVICE={}
260 DEVICETYPE=ovs
261 BOOTPROTO=static
262 ONBOOT=yes
263 TYPE=OVSBridge
264 PROMISC=yes""".format(bridge)
265     with open(bridge_ifcfg_file, 'w') as fh:
266         fh.write(bridge_content)
267     # restart linux networking
268     logging.info("Restarting Linux networking")
269     try:
270         subprocess.check_call(['systemctl', 'restart', 'network'])
271     except subprocess.CalledProcessError:
272         logging.error("Failed to restart Linux networking")
273         raise
274
275
276 def remove_ovs_bridge(network):
277     """
278     Unconfigure and remove an OVS bridge
279     :param network: Apex network to remove OVS bridge for
280     :return:
281     """
282     bridge = NET_MAP[network]
283     if is_ovs_bridge(bridge):
284         logging.info("Removing bridge: {}".format(bridge))
285         try:
286             subprocess.check_call(['ovs-vsctl', 'del-br', bridge])
287         except subprocess.CalledProcessError:
288             logging.error('Unable to destroy OVS bridge')
289             raise
290
291         logging.debug('Bridge destroyed')
292         bridge_ifcfg_file = os.path.join(NET_CFG_PATH,
293                                          "ifcfg-{}".format(bridge))
294         if os.path.isfile(bridge_ifcfg_file):
295             os.remove(bridge_ifcfg_file)
296             logging.debug("Bridge ifcfg file removed: {}".format(
297                 bridge_ifcfg_file))
298         else:
299             logging.debug('Bridge ifcfg file not found')