Change NIC template format
[apex.git] / lib / python / apex / net_env.py
1 ##############################################################################
2 # Copyright (c) 2016 Feng Pan (fpan@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
11 import yaml
12 import logging
13 import ipaddress
14 from . import ip_utils
15
16
17 ADMIN_NETWORK = 'admin_network'
18 PRIVATE_NETWORK = 'private_network'
19 PUBLIC_NETWORK = 'public_network'
20 STORAGE_NETWORK = 'storage_network'
21 API_NETWORK = 'api_network'
22 OPNFV_NETWORK_TYPES = [ADMIN_NETWORK, PRIVATE_NETWORK, PUBLIC_NETWORK,
23                        STORAGE_NETWORK, API_NETWORK]
24
25
26 class NetworkSettings:
27     """
28     This class parses APEX network settings yaml file into an object. It
29     generates or detects all missing fields for deployment.
30
31     The resulting object will be used later to generate network environment file
32     as well as configuring post deployment networks.
33
34     Currently the parsed object is dumped into a bash global definition file
35     for deploy.sh consumption. This object will later be used directly as
36     deployment script move to python.
37     """
38     def __init__(self, filename, network_isolation):
39         with open(filename, 'r') as network_settings_file:
40             self.settings_obj = yaml.load(network_settings_file)
41             self.network_isolation = network_isolation
42             self.enabled_network_list = []
43             self._validate_input()
44
45     def _validate_input(self):
46         """
47         Validates the network settings file and populates all fields.
48
49         NetworkSettingsException will be raised if validation fails.
50         """
51         if ADMIN_NETWORK not in self.settings_obj or \
52                 self.settings_obj[ADMIN_NETWORK].get('enabled') != True:
53             raise NetworkSettingsException("You must enable admin_network "
54                                            "and configure it explicitly or "
55                                            "use auto-detection")
56         if self.network_isolation and \
57             (PUBLIC_NETWORK not in self.settings_obj or
58                 self.settings_obj[PUBLIC_NETWORK].get('enabled') != True):
59             raise NetworkSettingsException("You must enable public_network "
60                                            "and configure it explicitly or "
61                                            "use auto-detection")
62
63         for network in OPNFV_NETWORK_TYPES:
64             if network in self.settings_obj:
65                 if self.settings_obj[network].get('enabled') == True:
66                     logging.info("{} enabled".format(network))
67                     self._config_required_settings(network)
68                     self._config_ip_range(network=network,
69                                           setting='usable_ip_range',
70                                           start_offset=21, end_offset=21)
71                     self._config_optional_settings(network)
72                     self.enabled_network_list.append(network)
73                 else:
74                     logging.info("{} disabled, will collapse with "
75                                  "admin_network".format(network))
76             else:
77                 logging.info("{} is not in specified, will collapse with "
78                              "admin_network".format(network))
79
80     def _config_required_settings(self, network):
81         """
82         Configures either CIDR or bridged_interface setting
83
84         cidr takes precedence if both cidr and bridged_interface are specified
85         for a given network.
86
87         When using bridged_interface, we will detect network setting on the
88         given NIC in the system. The resulting config in settings object will
89         be an ipaddress.network object, replacing the NIC name.
90         """
91         cidr = self.settings_obj[network].get('cidr')
92         nic_name = self.settings_obj[network].get('bridged_interface')
93
94         if cidr:
95             cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
96             self.settings_obj[network]['cidr'] = cidr
97             logging.info("{}_cidr: {}".format(network, cidr))
98             return 0
99         elif nic_name:
100             # If cidr is not specified, we need to know if we should find
101             # IPv6 or IPv4 address on the interface
102             if self.settings_obj[network].get('ipv6') == True:
103                 address_family = 6
104             else:
105                 address_family = 4
106             nic_interface = ip_utils.get_interface(nic_name, address_family)
107             if nic_interface:
108                 self.settings_obj[network]['bridged_interface'] = nic_interface
109                 logging.info("{}_bridged_interface: {}".
110                              format(network, nic_interface))
111                 return 0
112             else:
113                 raise NetworkSettingsException("Auto detection failed for {}: "
114                                                "Unable to find valid ip for "
115                                                "interface {}"
116                                                .format(network, nic_name))
117
118         else:
119             raise NetworkSettingsException("Auto detection failed for {}: "
120                                            "either bridge_interface or cidr "
121                                            "must be specified"
122                                            .format(network))
123
124     def _config_ip_range(self, network, setting, start_offset=None,
125                          end_offset=None, count=None):
126         """
127         Configures IP range for a given setting.
128
129         If the setting is already specified, no change will be made.
130
131         The spec for start_offset, end_offset and count are identical to
132         ip_utils.get_ip_range.
133         """
134         ip_range = self.settings_obj[network].get(setting)
135         interface = self.settings_obj[network].get('bridged_interface')
136
137         if not ip_range:
138             cidr = self.settings_obj[network].get('cidr')
139             ip_range = ip_utils.get_ip_range(start_offset=start_offset,
140                                              end_offset=end_offset,
141                                              count=count,
142                                              cidr=cidr,
143                                              interface=interface)
144             self.settings_obj[network][setting] = ip_range
145
146         logging.info("{}_{}: {}".format(network, setting, ip_range))
147
148     def _config_ip(self, network, setting, offset):
149         """
150         Configures IP for a given setting.
151
152         If the setting is already specified, no change will be made.
153
154         The spec for offset is identical to ip_utils.get_ip
155         """
156         ip = self.settings_obj[network].get(setting)
157         interface = self.settings_obj[network].get('bridged_interface')
158
159         if not ip:
160             cidr = self.settings_obj[network].get('cidr')
161             ip = ip_utils.get_ip(offset, cidr, interface)
162             self.settings_obj[network][setting] = ip
163
164         logging.info("{}_{}: {}".format(network, setting, ip))
165
166     def _config_optional_settings(self, network):
167         """
168         Configures optional settings:
169         - admin_network:
170             - provisioner_ip
171             - dhcp_range
172             - introspection_range
173         - public_network:
174             - provisioner_ip
175             - floating_ip
176             - gateway
177         """
178         if network == ADMIN_NETWORK:
179             self._config_ip(network, 'provisioner_ip', 1)
180             self._config_ip_range(network=network, setting='dhcp_range',
181                                   start_offset=2, count=9)
182             self._config_ip_range(network=network,
183                                   setting='introspection_range',
184                                   start_offset=11, count=9)
185         elif network == PUBLIC_NETWORK:
186             self._config_ip(network, 'provisioner_ip', 1)
187             self._config_ip_range(network=network,
188                                   setting='floating_ip',
189                                   end_offset=2, count=20)
190             self._config_gateway(network)
191
192     def _config_gateway(self, network):
193         """
194         Configures gateway setting for a given network.
195
196         If cidr is specified, we always use the first address in the address
197         space for gateway. Otherwise, we detect the system gateway.
198         """
199         gateway = self.settings_obj[network].get('gateway')
200         interface = self.settings_obj[network].get('bridged_interface')
201
202         if not gateway:
203             cidr = self.settings_obj[network].get('cidr')
204             if cidr:
205                 gateway = ip_utils.get_ip(1, cidr)
206             else:
207                 gateway = ip_utils.find_gateway(interface)
208
209             if gateway:
210                 self.settings_obj[network]['gateway'] = gateway
211             else:
212                 raise NetworkSettingsException("Failed to set gateway")
213
214         logging.info("{}_gateway: {}".format(network, gateway))
215
216
217     def dump_bash(self, path=None):
218         """
219         Prints settings for bash consumption.
220
221         If optional path is provided, bash string will be written to the file
222         instead of stdout.
223         """
224         bash_str = ''
225         for network in self.enabled_network_list:
226             for key, value in self.settings_obj[network].items():
227                 bash_str += "{}_{}={}\n".format(network, key, value)
228         bash_str += "enabled_network_list='{}'\n" \
229             .format(' '.join(self.enabled_network_list))
230         bash_str += "ip_addr_family={}\n".format(self.get_ip_addr_family())
231         if path:
232             with open(path, 'w') as file:
233                 file.write(bash_str)
234         else:
235             print(bash_str)
236
237     def get_ip_addr_family(self):
238         """
239         Returns IP address family for current deployment.
240
241         If any enabled network has IPv6 CIDR, the deployment is classified as
242         IPv6.
243         """
244         for network in self.enabled_network_list:
245             cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
246             if cidr.version == 6:
247                 return 6
248
249         return 4
250
251 class NetworkSettingsException(Exception):
252     def __init__(self, value):
253         self.value = value
254
255     def __str__(self):
256             return self.value