a36c9d1bfc20a69dbba3ac9f1149be35a6a2de58
[ta/cm-plugins.git] / validators / src / NetworkingValidation.py
1 #! /usr/bin/python
2 # Copyright 2019 Nokia
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #    http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import json
17 import re
18 from netaddr import IPNetwork
19
20 from cmdatahandlers.api import validation
21 from cmframework.apis import cmvalidator
22
23
24 class NetworkingValidation(cmvalidator.CMValidator):
25     SUBSCRIPTION = r'^cloud\.networking$'
26     DOMAIN = 'cloud.networking'
27
28     MAX_MTU = 9000
29     MIN_MTU = 1280
30     MIN_VLAN = 2
31     MAX_VLAN = 4094
32     MAX_PROVNET_LEN = 64
33     MAX_DNS = 2
34     PROVNET_NAME_MATCH = r'^[a-zA-Z][\da-zA-Z-_]+[\da-zA-Z]$'
35     NET_DOMAIN_MATCH = PROVNET_NAME_MATCH
36     MAX_NET_DOMAIN_LEN = MAX_PROVNET_LEN
37     DEFAULT_ROUTE_DEST = '0.0.0.0/0'
38
39     NETWORK_DOMAINS = 'network_domains'
40     INFRA_EXTERNAL = 'infra_external'
41     INFRA_INTERNAL = 'infra_internal'
42     INFRA_STORAGE_CLUSTER = 'infra_storage_cluster'
43     INFRA_NETWORKS = [INFRA_EXTERNAL,
44                       INFRA_INTERNAL,
45                       INFRA_STORAGE_CLUSTER]
46
47     DNS = 'dns'
48     MTU = 'mtu'
49     VLAN = 'vlan'
50     GATEWAY = 'gateway'
51     CIDR = 'cidr'
52     IP_START = 'ip_range_start'
53     IP_END = 'ip_range_end'
54     ROUTES = 'routes'
55     TO = 'to'
56     VIA = 'via'
57
58     PROVIDER_NETWORKS = 'provider_networks'
59     VLAN_RANGES = 'vlan_ranges'
60     SHARED = 'shared'
61
62     INPUT_ERR_CONTEXT = 'validate_set() input'
63     ERR_INPUT_NOT_DICT = 'Invalid %s, not a dictionary' % INPUT_ERR_CONTEXT
64
65     ERR_MISSING = 'Missing {1} configuration in {0}'
66     ERR_NOT_DICT = 'Invalid {1} value in {0}: Empty or not a dictionary'
67     ERR_NOT_LIST = 'Invalid {1} value in {0}: Empty, contains duplicates or not a list'
68     ERR_NOT_STR = 'Invalid {1} value in {0}: Not a string'
69     ERR_NOT_INT = 'Invalid {1} value in {0}: Not an integer'
70     ERR_NOT_BOOL = 'Invalid {1} value in {0}: Not a boolean value'
71
72     ERR_MTU = 'Invalid {} mtu: Not in range %i - %i' % (MIN_MTU, MAX_MTU)
73     ERR_VLAN = 'Invalid {} vlan: Not in range %i - %i' % (MIN_VLAN, MAX_VLAN)
74     ERR_DUPLICATE_INFRA_VLAN = 'Same VLAN ID {} used for multiple infra networks'
75     ERR_CIDRS_OVERLAPPING = 'Network CIDR values {} and {} are overlapping'
76     ERR_GW_NOT_SUPPORTED = 'Gateway address not supported for {}'
77     ERR_INVALID_ROUTES = 'Invalid static routes format for {0} {1}'
78     ERR_DEFAULT_ROUTE = 'Default route not supported for {0} {1}'
79
80     ERR_VLAN_RANGES_FORMAT = 'Invalid {} vlan_ranges format'
81     ERR_VLAN_RANGES_OVERLAPPING = 'Provider network vlan ranges {} and {} are overlapping'
82
83     ERR_INVALID_PROVNET_NAME = 'Invalid provider network name'
84     ERR_PROVNET_LEN = 'Too long provider network name, max %s chars' % MAX_PROVNET_LEN
85     ERR_SHARED_NETWORKS = 'Only one provider network can be configured as shared'
86
87     ERR_INVALID_NET_DOMAIN_NAME = 'Invalid network domain name'
88     ERR_NET_DOMAIN_LEN = 'Too long network domain name, max %s chars' % MAX_NET_DOMAIN_LEN
89
90     ERR_TOO_MANY_DNS = 'Too many DNS server IP addresses, max %i supported' % MAX_DNS
91
92     ERR_MTU_INSIDE_NETWORK_DOMAIN = 'Missplaced MTU inside {} network domain {}'
93
94     @staticmethod
95     def err_input_not_dict():
96         raise validation.ValidationError(NetworkingValidation.ERR_INPUT_NOT_DICT)
97
98     @staticmethod
99     def err_missing(context, key):
100         raise validation.ValidationError(NetworkingValidation.ERR_MISSING.format(context, key))
101
102     @staticmethod
103     def err_not_dict(context, key):
104         raise validation.ValidationError(NetworkingValidation.ERR_NOT_DICT.format(context, key))
105
106     @staticmethod
107     def err_not_list(context, key):
108         raise validation.ValidationError(NetworkingValidation.ERR_NOT_LIST.format(context, key))
109
110     @staticmethod
111     def err_not_str(context, key):
112         raise validation.ValidationError(NetworkingValidation.ERR_NOT_STR.format(context, key))
113
114     @staticmethod
115     def err_not_int(context, key):
116         raise validation.ValidationError(NetworkingValidation.ERR_NOT_INT.format(context, key))
117
118     @staticmethod
119     def err_not_bool(context, key):
120         raise validation.ValidationError(NetworkingValidation.ERR_NOT_BOOL.format(context, key))
121
122     @staticmethod
123     def err_mtu(context):
124         raise validation.ValidationError(NetworkingValidation.ERR_MTU.format(context))
125
126     @staticmethod
127     def err_vlan(context):
128         raise validation.ValidationError(NetworkingValidation.ERR_VLAN.format(context))
129
130     @staticmethod
131     def err_duplicate_vlan(vid):
132         raise validation.ValidationError(NetworkingValidation.ERR_DUPLICATE_INFRA_VLAN.format(vid))
133
134     @staticmethod
135     def err_vlan_ranges_format(provnet):
136         err = NetworkingValidation.ERR_VLAN_RANGES_FORMAT.format(provnet)
137         raise validation.ValidationError(err)
138
139     @staticmethod
140     def err_vlan_ranges_overlapping(range1, range2):
141         ranges = sorted([range1, range2])
142         err = NetworkingValidation.ERR_VLAN_RANGES_OVERLAPPING.format(ranges[0], ranges[1])
143         raise validation.ValidationError(err)
144
145     @staticmethod
146     def err_invalid_provnet_name():
147         raise validation.ValidationError(NetworkingValidation.ERR_INVALID_PROVNET_NAME)
148
149     @staticmethod
150     def err_provnet_len():
151         raise validation.ValidationError(NetworkingValidation.ERR_PROVNET_LEN)
152
153     @staticmethod
154     def err_invalid_net_domain_name():
155         raise validation.ValidationError(NetworkingValidation.ERR_INVALID_NET_DOMAIN_NAME)
156
157     @staticmethod
158     def err_net_domain_len():
159         raise validation.ValidationError(NetworkingValidation.ERR_NET_DOMAIN_LEN)
160
161     @staticmethod
162     def err_cidrs_overlapping(cidr1, cidr2):
163         cidrs = sorted([cidr1, cidr2])
164         err = NetworkingValidation.ERR_CIDRS_OVERLAPPING.format(cidrs[0], cidrs[1])
165         raise validation.ValidationError(err)
166
167     @staticmethod
168     def err_gw_not_supported(network):
169         raise validation.ValidationError(NetworkingValidation.ERR_GW_NOT_SUPPORTED.format(network))
170
171     @staticmethod
172     def err_invalid_routes(network, domain):
173         err = NetworkingValidation.ERR_INVALID_ROUTES.format(network, domain)
174         raise validation.ValidationError(err)
175
176     @staticmethod
177     def err_default_route(network, domain):
178         err = NetworkingValidation.ERR_DEFAULT_ROUTE.format(network, domain)
179         raise validation.ValidationError(err)
180
181     @staticmethod
182     def err_too_many_dns():
183         raise validation.ValidationError(NetworkingValidation.ERR_TOO_MANY_DNS)
184
185     @staticmethod
186     def err_shared_networks():
187         raise validation.ValidationError(NetworkingValidation.ERR_SHARED_NETWORKS)
188
189     @staticmethod
190     def err_mtu_inside_network_domain(infra, domain):
191         err = NetworkingValidation.ERR_MTU_INSIDE_NETWORK_DOMAIN.format(infra, domain)
192         raise validation.ValidationError(err)
193
194     @staticmethod
195     def is_dict(conf):
196         return isinstance(conf, dict)
197
198     @staticmethod
199     def key_exists(conf_dict, key):
200         return key in conf_dict
201
202     @staticmethod
203     def val_is_str(conf_dict, key):
204         return isinstance(conf_dict[key], basestring)
205
206     @staticmethod
207     def val_is_list(conf_dict, key):
208         return isinstance(conf_dict[key], list)
209
210     @staticmethod
211     def val_is_non_empty_list(conf_dict, key):
212         return (isinstance(conf_dict[key], list) and
213                 len(conf_dict[key]) > 0 and
214                 len(conf_dict[key]) == len(set(conf_dict[key])))
215
216     @staticmethod
217     def val_is_non_empty_dict(conf_dict, key):
218         return isinstance(conf_dict[key], dict) and len(conf_dict[key]) > 0
219
220     @staticmethod
221     def val_is_int(conf_dict, key):
222         return isinstance(conf_dict[key], (int, long))
223
224     @staticmethod
225     def val_is_bool(conf_dict, key):
226         return isinstance(conf_dict[key], bool)
227
228     @staticmethod
229     def key_must_exist(conf_dict, entry, key):
230         if not NetworkingValidation.key_exists(conf_dict[entry], key):
231             NetworkingValidation.err_missing(entry, key)
232
233     @staticmethod
234     def must_be_str(conf_dict, entry, key):
235         NetworkingValidation.key_must_exist(conf_dict, entry, key)
236         if not NetworkingValidation.val_is_str(conf_dict[entry], key):
237             NetworkingValidation.err_not_str(entry, key)
238
239     @staticmethod
240     def must_be_list(conf_dict, entry, key):
241         NetworkingValidation.key_must_exist(conf_dict, entry, key)
242         if not NetworkingValidation.val_is_non_empty_list(conf_dict[entry], key):
243             NetworkingValidation.err_not_list(entry, key)
244
245     @staticmethod
246     def must_be_dict(conf_dict, entry, key):
247         NetworkingValidation.key_must_exist(conf_dict, entry, key)
248         if not NetworkingValidation.val_is_non_empty_dict(conf_dict[entry], key):
249             NetworkingValidation.err_not_dict(entry, key)
250
251     @staticmethod
252     def exists_as_dict(conf_dict, entry, key):
253         if not NetworkingValidation.key_exists(conf_dict[entry], key):
254             return False
255         if not NetworkingValidation.val_is_non_empty_dict(conf_dict[entry], key):
256             NetworkingValidation.err_not_dict(entry, key)
257         return True
258
259     @staticmethod
260     def exists_as_int(conf_dict, entry, key):
261         if not NetworkingValidation.key_exists(conf_dict[entry], key):
262             return False
263         if not NetworkingValidation.val_is_int(conf_dict[entry], key):
264             NetworkingValidation.err_not_int(entry, key)
265         return True
266
267     @staticmethod
268     def exists_as_bool(conf_dict, entry, key):
269         if not NetworkingValidation.key_exists(conf_dict[entry], key):
270             return False
271         if not NetworkingValidation.val_is_bool(conf_dict[entry], key):
272             NetworkingValidation.err_not_bool(entry, key)
273         return True
274
275     def __init__(self):
276         cmvalidator.CMValidator.__init__(self)
277         self.utils = validation.ValidationUtils()
278         self.conf = None
279         self.net_conf = None
280
281     def get_subscription_info(self):
282         return self.SUBSCRIPTION
283
284     def validate_set(self, props):
285         self.prepare_validate(props)
286         self.validate()
287
288     def prepare_validate(self, props):
289         if not self.is_dict(props):
290             self.err_input_not_dict()
291
292         if not self.key_exists(props, self.DOMAIN):
293             self.err_missing(self.INPUT_ERR_CONTEXT, self.DOMAIN)
294
295         self.net_conf = json.loads(props[self.DOMAIN])
296         self.conf = {self.DOMAIN: self.net_conf}
297
298         if not self.val_is_non_empty_dict(self.conf, self.DOMAIN):
299             self.err_not_dict(self.INPUT_ERR_CONTEXT, self.DOMAIN)
300
301     def validate(self):
302         self.validate_dns()
303         self.validate_default_mtu()
304         self.validate_infra_networks()
305         self.validate_provider_networks()
306         self.validate_no_overlapping_cidrs()
307
308     def validate_dns(self):
309         self.must_be_list(self.conf, self.DOMAIN, self.DNS)
310         for server in self.net_conf[self.DNS]:
311             self.utils.validate_ip_address(server)
312         if len(self.net_conf[self.DNS]) > self.MAX_DNS:
313             self.err_too_many_dns()
314
315     def validate_default_mtu(self):
316         self.validate_mtu(self.conf, self.DOMAIN)
317
318     def validate_infra_networks(self):
319         self.validate_infra_internal()
320         self.validate_infra_external()
321         self.validate_infra_storage_cluster()
322         self.validate_no_duplicate_infra_vlans()
323
324     def validate_infra_internal(self):
325         self.validate_network_exists(self.INFRA_INTERNAL)
326         self.validate_infra_network(self.INFRA_INTERNAL)
327         self.validate_no_gateway(self.INFRA_INTERNAL)
328
329     def validate_infra_external(self):
330         self.validate_network_exists(self.INFRA_EXTERNAL)
331         self.validate_infra_network(self.INFRA_EXTERNAL)
332         self.validate_gateway(self.INFRA_EXTERNAL)
333
334     def validate_infra_storage_cluster(self):
335         if self.network_exists(self.INFRA_STORAGE_CLUSTER):
336             self.validate_network_domains(self.INFRA_STORAGE_CLUSTER)
337             self.validate_infra_network(self.INFRA_STORAGE_CLUSTER)
338             self.validate_no_gateway(self.INFRA_STORAGE_CLUSTER)
339
340     def validate_infra_network(self, network, vlan_must_exist=False):
341         self.validate_mtu(self.net_conf, network)
342         self.validate_cidr(network)
343         self.validate_vlan(network, vlan_must_exist)
344         self.validate_ip_range(network)
345         self.validate_routes(network)
346         self.validate_no_mtu_inside_network_domain(network)
347
348     def validate_no_duplicate_infra_vlans(self):
349         domvids = {}
350         for network in self.INFRA_NETWORKS:
351             if self.key_exists(self.net_conf, network):
352                 for domain, domain_conf in self.net_conf[network][self.NETWORK_DOMAINS].iteritems():
353                     if self.key_exists(domain_conf, self.VLAN):
354                         if domain not in domvids:
355                             domvids[domain] = []
356                         domvids[domain].append(domain_conf[self.VLAN])
357         for vids in domvids.itervalues():
358             prev_vid = 0
359             for vid in sorted(vids):
360                 if vid == prev_vid:
361                     self.err_duplicate_vlan(vid)
362                 prev_vid = vid
363
364     def validate_no_overlapping_cidrs(self):
365         cidrs = []
366         for network in self.INFRA_NETWORKS:
367             if self.key_exists(self.net_conf, network):
368                 for domain_conf in self.net_conf[network][self.NETWORK_DOMAINS].itervalues():
369                     cidrs.append(IPNetwork(domain_conf[self.CIDR]))
370         for idx, cidr1 in enumerate(cidrs):
371             for cidr2 in cidrs[(idx+1):]:
372                 if not (cidr1[0] > cidr2[-1] or cidr1[-1] < cidr2[0]):
373                     self.err_cidrs_overlapping(str(cidr1), str(cidr2))
374
375     def validate_ip_range(self, network):
376         domains = self.net_conf[network][self.NETWORK_DOMAINS]
377         for domain in domains:
378             ip_start = self.get_ip_range_start(domains, domain)
379             ip_end = self.get_ip_range_end(domains, domain)
380             self.utils.validate_ip_range(ip_start, ip_end)
381
382     def get_ip_range_start(self, domains, domain):
383         if self.key_exists(domains[domain], self.IP_START):
384             self.validate_ip_range_limiter(domains, domain, self.IP_START)
385             return domains[domain][self.IP_START]
386         return str(IPNetwork(domains[domain][self.CIDR])[1])
387
388     def get_ip_range_end(self, domains, domain):
389         if self.key_exists(domains[domain], self.IP_END):
390             self.validate_ip_range_limiter(domains, domain, self.IP_END)
391             return domains[domain][self.IP_END]
392         return str(IPNetwork(domains[domain][self.CIDR])[-2])
393
394     def validate_ip_range_limiter(self, domains, domain, key):
395         self.must_be_str(domains, domain, key)
396         self.utils.validate_ip_address(domains[domain][key])
397         self.utils.validate_ip_in_subnet(domains[domain][key],
398                                          domains[domain][self.CIDR])
399
400     def validate_provider_networks(self):
401         if self.network_exists(self.PROVIDER_NETWORKS):
402             for netname in self.net_conf[self.PROVIDER_NETWORKS]:
403                 self.validate_providernet(netname)
404             self.validate_shared_provider_network(self.net_conf[self.PROVIDER_NETWORKS])
405
406     def validate_providernet(self, netname):
407         self.validate_providernet_name(netname)
408         self.must_be_dict(self.net_conf, self.PROVIDER_NETWORKS, netname)
409         self.validate_mtu(self.net_conf[self.PROVIDER_NETWORKS], netname)
410         self.validate_vlan_ranges(self.net_conf[self.PROVIDER_NETWORKS], netname)
411
412     def validate_shared_provider_network(self, provider_conf):
413         shared_counter = 0
414         for netname in provider_conf:
415             if self.exists_as_bool(provider_conf, netname, self.SHARED):
416                 if provider_conf[netname][self.SHARED] is True:
417                     shared_counter += 1
418         if shared_counter > 1:
419             self.err_shared_networks()
420
421     def validate_mtu(self, conf, network):
422         if self.exists_as_int(conf, network, self.MTU):
423             mtu = conf[network][self.MTU]
424             if mtu < self.MIN_MTU or mtu > self.MAX_MTU:
425                 self.err_mtu(network)
426
427     def validate_no_mtu_inside_network_domain(self, network):
428         domains = self.net_conf[network][self.NETWORK_DOMAINS]
429         for domain in domains:
430             if self.key_exists(domains[domain], self.MTU):
431                 self.err_mtu_inside_network_domain(network, domain)
432
433     def validate_vlan(self, network, must_exist=False):
434         domains = self.net_conf[network][self.NETWORK_DOMAINS]
435         for domain in domains:
436             if must_exist and not self.key_exists(domains[domain], self.VLAN):
437                 self.err_missing(network, self.VLAN)
438             if self.exists_as_int(domains, domain, self.VLAN):
439                 self.validate_vlan_id(network, domains[domain][self.VLAN])
440
441     def validate_network_exists(self, network):
442         self.must_be_dict(self.conf, self.DOMAIN, network)
443         self.validate_network_domains(network)
444
445     def validate_network_domains(self, network):
446         self.must_be_dict(self.net_conf, network, self.NETWORK_DOMAINS)
447         for domain in self.net_conf[network][self.NETWORK_DOMAINS]:
448             self.validate_net_domain_name(domain)
449
450     def validate_net_domain_name(self, domain_name):
451         if (not isinstance(domain_name, basestring) or
452                 not re.match(self.NET_DOMAIN_MATCH, domain_name)):
453             self.err_invalid_net_domain_name()
454         if len(domain_name) > self.MAX_NET_DOMAIN_LEN:
455             self.err_net_domain_len()
456
457     def network_exists(self, network):
458         return self.exists_as_dict(self.conf, self.DOMAIN, network)
459
460     def validate_cidr(self, network):
461         domains = self.net_conf[network][self.NETWORK_DOMAINS]
462         for domain in domains:
463             self.must_be_str(domains, domain, self.CIDR)
464             self.utils.validate_subnet_address(domains[domain][self.CIDR])
465
466     def validate_gateway(self, network):
467         domains = self.net_conf[network][self.NETWORK_DOMAINS]
468         for domain in domains:
469             self.must_be_str(domains, domain, self.GATEWAY)
470             self.utils.validate_ip_address(domains[domain][self.GATEWAY])
471             self.utils.validate_ip_in_subnet(domains[domain][self.GATEWAY],
472                                              domains[domain][self.CIDR])
473             self.utils.validate_ip_not_in_range(domains[domain][self.GATEWAY],
474                                                 self.get_ip_range_start(domains, domain),
475                                                 self.get_ip_range_end(domains, domain))
476
477     def validate_no_gateway(self, network):
478         for domain_conf in self.net_conf[network][self.NETWORK_DOMAINS].itervalues():
479             if self.key_exists(domain_conf, self.GATEWAY):
480                 self.err_gw_not_supported(network)
481
482     def validate_routes(self, network):
483         domains = self.net_conf[network][self.NETWORK_DOMAINS]
484         for domain in domains:
485             if self.key_exists(domains[domain], self.ROUTES):
486                 if (not self.val_is_list(domains[domain], self.ROUTES) or
487                         not domains[domain][self.ROUTES]):
488                     self.err_invalid_routes(network, domain)
489                 for route in domains[domain][self.ROUTES]:
490                     self.validate_route(network, domain, route)
491                     self.utils.validate_ip_in_subnet(route[self.VIA],
492                                                      domains[domain][self.CIDR])
493                     self.utils.validate_ip_not_in_range(route[self.VIA],
494                                                         self.get_ip_range_start(domains, domain),
495                                                         self.get_ip_range_end(domains, domain))
496
497     def validate_route(self, network, domain, route):
498         if (not self.is_dict(route) or
499                 self.TO not in route or
500                 self.VIA not in route or
501                 not self.val_is_str(route, self.TO) or
502                 not self.val_is_str(route, self.VIA)):
503             self.err_invalid_routes(network, domain)
504         self.utils.validate_subnet_address(route[self.TO])
505         self.utils.validate_ip_address(route[self.VIA])
506         if route[self.TO] == self.DEFAULT_ROUTE_DEST:
507             self.err_default_route(network, domain)
508
509     def validate_providernet_name(self, netname):
510         if not isinstance(netname, basestring) or not re.match(self.PROVNET_NAME_MATCH, netname):
511             self.err_invalid_provnet_name()
512         if len(netname) > self.MAX_PROVNET_LEN:
513             self.err_provnet_len()
514
515     def validate_vlan_ranges(self, provnet_conf, provnet):
516         self.must_be_str(provnet_conf, provnet, self.VLAN_RANGES)
517         vlan_ranges = []
518         for vlan_range in provnet_conf[provnet][self.VLAN_RANGES].split(','):
519             vids = vlan_range.split(':')
520             if len(vids) != 2:
521                 self.err_vlan_ranges_format(provnet)
522             try:
523                 start = int(vids[0])
524                 end = int(vids[1])
525             except ValueError:
526                 self.err_vlan_ranges_format(provnet)
527             self.validate_vlan_id(provnet, start)
528             self.validate_vlan_id(provnet, end)
529             if end < start:
530                 self.err_vlan_ranges_format(provnet)
531             vlan_ranges.append([start, end])
532         self.validate_vlan_ranges_not_overlapping(vlan_ranges)
533
534     def validate_vlan_ranges_not_overlapping(self, vlan_ranges):
535         for idx, range1 in enumerate(vlan_ranges):
536             for range2 in vlan_ranges[(idx+1):]:
537                 if not (range1[0] > range2[1] or range1[1] < range2[0]):
538                     self.err_vlan_ranges_overlapping(range1, range2)
539
540     def validate_vlan_id(self, network, vid):
541         if vid < self.MIN_VLAN or vid > self.MAX_VLAN:
542             self.err_vlan(network)