• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

noironetworks / acc-provision / 6286

pending completion
6286

Pull #838

travis-ci-com

web-flow
Merge 7e0539686 into 2c5fd414f
Pull Request #838: Enabling opflex agent asyncjson config - cko-mvp-1

3113 of 4315 relevant lines covered (72.14%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.9
provision/acc_provision/acc_provision.py
1
#!/usr/bin/env python3
2

3
from __future__ import print_function, unicode_literals
1✔
4

5
import argparse
1✔
6
import base64
1✔
7
import copy
1✔
8
import functools
1✔
9
import ipaddress
1✔
10
import requests
1✔
11
import json
1✔
12
import os
1✔
13
import os.path
1✔
14
import random
1✔
15
import re
1✔
16
import string
1✔
17
import sys
1✔
18
import uuid
1✔
19

20
import pkg_resources
1✔
21
import pkgutil
1✔
22
import tarfile
1✔
23
import yaml
1✔
24
from yaml import SafeLoader
1✔
25

26
from itertools import combinations
1✔
27
from OpenSSL import crypto
1✔
28
from jinja2 import Environment, PackageLoader
1✔
29
from os.path import exists
1✔
30
if __package__ is None or __package__ == '':
1✔
31
    from apic_provision import Apic, ApicKubeConfig
×
32
    from cloud_provision import CloudProvision
×
33
else:
34
    from .apic_provision import Apic, ApicKubeConfig
1✔
35
    from .cloud_provision import CloudProvision
1✔
36

37

38
# This black magic forces pyyaml to load YAML strings as unicode rather
39
# than byte strings in Python 2, thus ensuring that the type of strings
40
# is consistent across versions.  From
41
# https://stackoverflow.com/a/2967461/3857947. Revisit for Python 3.
42
def construct_yaml_str(self, node):
1✔
43
    return self.construct_scalar(node)
1✔
44

45

46
SafeLoader.add_constructor(u'tag:yaml.org,2002:str', construct_yaml_str)
1✔
47

48
VERSION_FIELDS = [
1✔
49
    "cnideploy_version",
50
    "aci_containers_host_version",
51
    "opflex_agent_version",
52
    "aci_containers_controller_version",
53
    "aci_containers_operator_version",
54
    "openvswitch_version",
55
]
56

57

58
FLAVORS_PATH = os.path.dirname(os.path.realpath(__file__)) + "/flavors.yaml"
1✔
59
VERSIONS_PATH = os.path.dirname(os.path.realpath(__file__)) + "/versions.yaml"
1✔
60
CKO_VERSIONS_PATH = os.path.dirname(os.path.realpath(__file__)) + "/cko_versions.yaml"
1✔
61

62

63
with open(VERSIONS_PATH, 'r') as stream:
1✔
64
    try:
1✔
65
        doc = yaml.safe_load(stream)
1✔
66
    except yaml.YAMLError as exc:
×
67
        print(exc)
×
68
    VERSIONS = doc['versions']
1✔
69

70
with open(CKO_VERSIONS_PATH, 'r') as stream:
1✔
71
    try:
1✔
72
        doc = yaml.safe_load(stream)
1✔
73
    except yaml.YAMLError as exc:
×
74
        print(exc)
×
75
    CKO_VERSIONS = doc['cko_versions']
1✔
76

77
with open(FLAVORS_PATH, 'r') as stream:
1✔
78
    try:
1✔
79
        doc = yaml.safe_load(stream)
1✔
80
    except yaml.YAMLError as exc:
×
81
        print(exc)
×
82
    DEFAULT_FLAVOR_OPTIONS = doc['kubeFlavorOptions']
1✔
83
    FLAVORS = doc['flavors']
1✔
84

85

86
def info(msg):
1✔
87
    print("INFO: " + msg, file=sys.stderr)
1✔
88

89

90
def warn(msg):
1✔
91
    print("WARN: " + msg, file=sys.stderr)
1✔
92

93

94
def err(msg):
1✔
95
    print("ERR:  " + msg, file=sys.stderr)
1✔
96

97

98
def json_indent(s):
1✔
99
    return json.dumps(s, indent=4, separators=(',', ': '), sort_keys=True)
1✔
100

101

102
def yaml_quote(s):
1✔
103
    return "'%s'" % str(s).replace("'", "''")
×
104

105

106
def yaml_indent(s, **kwargs):
1✔
107
    return yaml.dump(s, **kwargs)
×
108

109

110
class SafeDict(dict):
1✔
111
    'Provide a default value for missing keys'
112
    def __missing__(self, key):
1✔
113
        return None
1✔
114

115

116
def list_unicode_strings(l):
1✔
117
    return "['" + "', '".join(l) + "']"
×
118

119

120
def deep_merge(user, default):
1✔
121
    if isinstance(user, dict) and isinstance(default, dict):
1✔
122
        for k, v in default.items():
1✔
123
            if k not in user:
1✔
124
                user[k] = v
1✔
125
            else:
126
                user[k] = deep_merge(user[k], v)
1✔
127
    return copy.deepcopy(user)
1✔
128

129

130
def config_default():
1✔
131
    # Default values for configuration
132
    default_config = {
1✔
133
        "operator_managed_config": {
134
            "enable_updates": False,
135
        },
136
        "aci_config": {
137
            "apic_version": "1.0",
138
            "system_id": None,
139
            "tenant": {
140
                "name": None,
141
            },
142
            "use_pre_existing_tenant": False,
143
            "use_legacy_kube_naming_convention": False,
144
            "vrf": {
145
                "name": None,
146
                "tenant": None,
147
            },
148
            "l3out": {
149
                "name": None,
150
                "external_networks": None,
151
            },
152
            "cluster_l3out": {
153
                "name": None,
154
                "svi": {
155
                    "mtu": 9000
156
                },
157
                "bgp": {
158
                    "peering": {
159
                        "prefixes": 500,
160
                        "remote_as_number": 64512,
161
                    },
162
                },
163
            },
164
            "vmm_domain": {
165
                "type": "Kubernetes",
166
                "encap_type": "vxlan",
167
                "mcast_fabric": "225.1.2.3",
168
                "mcast_range": {
169
                    "start": "225.20.1.1",
170
                    "end": "225.20.255.255",
171
                },
172
                "nested_inside": {
173
                    "portgroup": None,
174
                    "elag_name": None,
175
                    "duplicate_file_router_default_svc": False,
176
                },
177
            },
178
            "client_cert": False,
179
            "client_ssl": True,
180
            "kube_default_provide_kube_api": False,
181
            "no_physdom_for_node_epg": False,
182
            "disable_node_subnet_creation": False,
183
            "preexisting_kube_bd": None,
184
            "apic_subscription_delay": None,
185
            "apic_refreshticker_adjust": None,
186
            "opflex_device_delete_timeout": None,
187
        },
188
        "net_config": {
189
            "node_subnet": None,
190
            "pod_subnet": None,
191
            "pod_subnet_chunk_size": 32,
192
            "extern_dynamic": None,
193
            "extern_static": None,
194
            "node_svc_subnet": None,
195
            "kubeapi_vlan": None,
196
            "service_vlan": None,
197
            "service_monitor_interval": 5,
198
            "pbr_tracking_non_snat": False,
199
            "interface_mtu": None,
200
            "interface_mtu_headroom": None,
201
            "second_kubeapi_portgroup": False,
202
            "disable_wait_for_network": False,
203
            "duration_wait_for_network": 210,
204
            "kubeapi_vlan_mode": "regular",
205
            "cluster_svc_subnet": None,
206
            "advertise_cluster_svc_subnet": False,
207
        },
208
        "topology": {
209
            "rack": {
210
            },
211
        },
212
        "calico_config": {
213
            "net_config": {
214
                "block_size": 26,
215
                "encapsulation": "None",
216
                "nat_outgoing": "Disabled",
217
                "nodeSelector": "all()",
218
            },
219
        },
220
        "kube_config": {
221
            "controller": "1.1.1.1",
222
            "use_rbac_api": "rbac.authorization.k8s.io/v1",
223
            "use_apps_api": "apps/v1",
224
            "use_apps_apigroup": "apps",
225
            "host_agent_openshift_resource": False,
226
            "use_netpol_apigroup": "networking.k8s.io",
227
            "use_cluster_role": True,
228
            "no_wait_for_service_ep_readiness": False,
229
            "add_external_subnets_to_rdconfig": False,
230
            "image_pull_policy": "Always",
231
            "kubectl": "kubectl",
232
            "system_namespace": "aci-containers-system",
233
            "ovs_memory_limit": "1Gi",
234
            "reboot_opflex_with_ovs": "true",
235
            "snat_operator": {
236
                "name": "snat-operator",
237
                "watch_namespace": "",
238
                "globalinfo_name": "snatglobalinfo",
239
                "rdconfig_name": "routingdomain-config",
240
                "port_range": {
241
                    "start": 5000,
242
                    "end": 65000,
243
                    "ports_per_node": 3000,
244
                },
245
                "snat_namespace": "aci-containers-system",
246
                "contract_scope": "global",
247
                "disable_periodic_snat_global_info_sync": False,
248
                "sleep_time_snat_global_info_sync": None,
249
            },
250
            "max_nodes_svc_graph": 32,
251
            "opflex_mode": None,
252
            "opflex_agent_prometheus": "false",
253
            "host_agent_cni_bin_path": "/opt",
254
            "host_agent_cni_conf_path": "/etc",
255
            "generate_installer_files": False,
256
            "generate_cnet_file": False,
257
            "generate_apic_file": False,
258
            "use_host_netns_volume": False,
259
            "enable_endpointslice": False,
260
            "opflex_agent_opflex_asyncjson_enabled": "false",
261
            "opflex_agent_ovs_asyncjson_enabled": "false"
262
        },
263
        "istio_config": {
264
            "install_istio": False,
265
            "install_profile": "demo",
266
            "istio_ns": "istio-system",
267
            "istio_operator_ns": "istio-operator"
268
        },
269
        "registry": {
270
            "image_prefix": "noiro",
271
            "aci_cni_operator_version": None,
272
        },
273
        "logging": {
274
            "size": None,
275
            "controller_log_level": "info",
276
            "hostagent_log_level": "info",
277
            "opflexagent_log_level": "info",
278
        },
279
        "drop_log_config": {
280
            "enable": True,
281
        },
282
        "provision": {
283
            "upgrade_cluster": False,
284
        },
285
        "multus": {
286
            "disable": True,
287
        },
288
        "sriov_config": {
289
            "enable": False,
290
        },
291
        "dpu_config": {
292
            "enable": False,
293
        },
294
        "nodepodif_config": {
295
            "enable": False,
296
        },
297
        "lb_config": {
298
            "lb_type": "metallb",
299
        },
300
    }
301
    return default_config
1✔
302

303

304
def config_user(config_file):
1✔
305
    config = {}
1✔
306
    if config_file:
1✔
307
        if config_file == "-":
1✔
308
            info("Loading configuration from \"STDIN\"")
×
309
            data = sys.stdin.read()
×
310
            config = yaml.safe_load(data)
×
311
        else:
312
            info("Loading configuration from \"%s\"" % config_file)
1✔
313
            with open(config_file, 'r') as file:
1✔
314
                config = yaml.safe_load(file)
1✔
315
            with open(config_file, 'r') as file:
1✔
316
                data = file.read()
1✔
317
        user_input = re.sub('password:.*', '', data)
1✔
318
        config["user_input"] = user_input
1✔
319
    if config is None:
1✔
320
        config = {}
×
321
    return config
1✔
322

323

324
def config_discover(config, prov_apic):
1✔
325
    apic = None
1✔
326
    if prov_apic is not None:
1✔
327
        apic = get_apic(config)
×
328

329
    orig_infra_vlan = config["net_config"].get("infra_vlan")
1✔
330
    ret = {
1✔
331
        "net_config": {
332
            "infra_vlan": orig_infra_vlan,
333
        }
334
    }
335

336
    infra_vlan = config["discovered"]["infra_vlan"]
1✔
337
    if apic is not None:
1✔
338
        infra_vlan = apic.get_infravlan()
×
339

340
    if infra_vlan is not None:
1✔
341
        if orig_infra_vlan is not None and orig_infra_vlan != infra_vlan:
1✔
342
            warn("ACI infra_vlan (%s) is different from input file (%s)" %
1✔
343
                 (infra_vlan, orig_infra_vlan))
344
        if orig_infra_vlan is None or orig_infra_vlan != infra_vlan:
1✔
345
            info("Using infra_vlan from ACI: %s" %
1✔
346
                 (infra_vlan,))
347
        ret["net_config"]["infra_vlan"] = infra_vlan
1✔
348

349
    return ret
1✔
350

351

352
def config_set_dst(pod_cidr):
1✔
353
    rtr, mask = pod_cidr.split('/')
1✔
354
    ip = ipaddress.ip_address(rtr)
1✔
355
    if ip.version == 4:
1✔
356
        return "0.0.0.0/0"
1✔
357
    else:
358
        return "::/0"
1✔
359

360

361
def cidr_split(cidr):
1✔
362
    rtr, mask = cidr.split('/')
1✔
363
    ip = ipaddress.ip_address(rtr)
1✔
364
    if ip.version == 4:
1✔
365
        n = ipaddress.IPv4Network(cidr, strict=False)
1✔
366
    else:
367
        n = ipaddress.IPv6Network(cidr, strict=False)
1✔
368
    first, last = n[2], n[-2]
1✔
369
    return str(first), str(last), str(n[1]), str(n.network_address), mask, str(ip)
1✔
370

371

372
def normalize_cidr(cidr):
1✔
373
    # To convert CIDR network ending with .1 to .0. For eg, convert 10.0.0.1/16 to 10.0.0.0/16
374
    rtr, _ = cidr.split('/')
1✔
375
    ip = ipaddress.ip_address(rtr)
1✔
376
    if ip.version == 4:
1✔
377
        n = ipaddress.IPv4Network(cidr, strict=False)
1✔
378
    else:
379
        n = ipaddress.IPv6Network(cidr, strict=False)
1✔
380
    return str(n)
1✔
381

382

383
def config_adjust(args, config, prov_apic, no_random):
1✔
384
    if is_calico_flavor(config["flavor"]):
1✔
385
        l3out_name = ""
1✔
386
        if not config["aci_config"]["cluster_l3out"].get("name"):
1✔
387
            vlan_id = config["aci_config"]["cluster_l3out"]["svi"]["vlan_id"]
1✔
388
            l3out_name = "calico-l3out-fsvi-vlan-%s" % vlan_id
1✔
389
            config["aci_config"]["cluster_l3out"]["name"] = l3out_name
1✔
390
        l3out_name = config["aci_config"]["cluster_l3out"]["name"]
1✔
391
        if not config["aci_config"].get("system_id"):
1✔
392
            system_id = "calico-%s" % l3out_name if "calico" not in l3out_name else l3out_name
1✔
393
            system_id = system_id[:30]
1✔
394
            config["aci_config"]["system_id"] = system_id
1✔
395
    else:
396
        l3out_name = config["aci_config"]["l3out"]["name"]
1✔
397
    system_id = config["aci_config"]["system_id"]
1✔
398
    infra_vlan = config["net_config"]["infra_vlan"]
1✔
399
    node_subnet = config["net_config"]["node_subnet"]
1✔
400
    pod_subnet = config["net_config"]["pod_subnet"]
1✔
401
    extern_dynamic = config["net_config"]["extern_dynamic"]
1✔
402
    extern_static = config["net_config"]["extern_static"]
1✔
403
    node_svc_subnet = config["net_config"]["node_svc_subnet"]
1✔
404
    disable_wait_for_network = config["net_config"]["disable_wait_for_network"]
1✔
405
    duration_wait_for_network = config["net_config"]["duration_wait_for_network"]
1✔
406
    encap_type = config["aci_config"]["vmm_domain"]["encap_type"]
1✔
407
    opflex_mode = config["kube_config"]["opflex_mode"]
1✔
408
    istio_profile = config["istio_config"]["install_profile"]
1✔
409
    istio_namespace = config["istio_config"]["istio_ns"]
1✔
410
    istio_operator_ns = config["istio_config"]["istio_operator_ns"]
1✔
411
    enable_endpointslice = config["kube_config"]["enable_endpointslice"]
1✔
412
    token = str(uuid.uuid4())
1✔
413
    if (config["aci_config"]["tenant"]["name"]):
1✔
414
        config["aci_config"]["use_pre_existing_tenant"] = True
1✔
415
        tenant = config["aci_config"]["tenant"]["name"]
1✔
416
    else:
417
        tenant = system_id
1✔
418
    if not config["aci_config"]["use_legacy_kube_naming_convention"]:
1✔
419
        app_profile = Apic.ACI_PREFIX + system_id
1✔
420
        default_endpoint_group = Apic.ACI_PREFIX + "default"
1✔
421
        namespace_endpoint_group = Apic.ACI_PREFIX + "system"
1✔
422
        config["aci_config"]["nodes_epg"] = Apic.ACI_PREFIX + "nodes"
1✔
423
        bd_dn_prefix = "uni/tn-%s/BD-%s%s-" % (tenant, Apic.ACI_PREFIX, system_id)
1✔
424
        istio_epg = Apic.ACI_PREFIX + "istio"
1✔
425
    else:
426
        app_profile = "kubernetes"
1✔
427
        default_endpoint_group = "kube-default"
1✔
428
        namespace_endpoint_group = "kube-system"
1✔
429
        if config["aci_config"]["vmm_domain"]["type"] == "OpenShift":
1✔
430
            config["kube_config"]["system_namespace"] = "aci-containers-system"
1✔
431
        else:
432
            config["kube_config"]["system_namespace"] = "kube-system"
1✔
433
        config["aci_config"]["nodes_epg"] = "kube-nodes"
1✔
434
        bd_dn_prefix = "uni/tn-%s/BD-kube-" % tenant
1✔
435
        istio_epg = "kube-istio"
1✔
436

437
    aci_vrf_dn = "uni/tn-%s/ctx-%s" % (config["aci_config"]["vrf"]["tenant"], config["aci_config"]["vrf"]["name"])
1✔
438
    node_bd_dn = bd_dn_prefix + "node-bd"
1✔
439
    pod_bd_dn = bd_dn_prefix + "pod-bd"
1✔
440

441
    config["aci_config"]["app_profile"] = app_profile
1✔
442
    system_namespace = config["kube_config"]["system_namespace"]
1✔
443
    if args.version_token:
1✔
444
        token = args.version_token
1✔
445

446
    if not is_calico_flavor(config["flavor"]) and extern_static:
1✔
447
        static_service_ip_pool = [{"start": cidr_split(extern_static)[0], "end": cidr_split(extern_static)[1]}]
1✔
448
    else:
449
        static_service_ip_pool = []
1✔
450

451
    if not is_calico_flavor(config["flavor"]) and node_svc_subnet:
1✔
452
        node_service_ip_pool = [{"start": cidr_split(node_svc_subnet)[0], "end": cidr_split(node_svc_subnet)[1]}]
1✔
453
    else:
454
        node_service_ip_pool = []
1✔
455

456
    if is_calico_flavor(config["flavor"]):
1✔
457
        config["aci_config"]["cluster_l3out"]["svi"]["node_profile_name"] = l3out_name + "_node_prof"
1✔
458
        config["aci_config"]["cluster_l3out"]["svi"]["int_prof_name"] = l3out_name + "_int_prof"
1✔
459
        config["aci_config"]["cluster_l3out"]["svi"]["external_network"] = l3out_name + "_int_epg"
1✔
460
        config["aci_config"]["cluster_l3out"]["svi"]["external_network_svc"] = l3out_name + "_svc_epg"
1✔
461

462
    adj_config = {
1✔
463
        "aci_config": {
464
            "cluster_tenant": tenant,
465
            "physical_domain": {
466
                "domain": system_id + "-pdom",
467
                "vlan_pool": system_id + "-pool",
468
            },
469
            "vmm_domain": {
470
                "domain": system_id,
471
                "controller": system_id,
472
                "mcast_pool": system_id + "-mpool",
473
                "vlan_pool": system_id + "-vpool",
474
                "vlan_range": {
475
                    "start": None,
476
                    "end": None,
477
                }
478
            },
479
            "vrf": {
480
                "dn": aci_vrf_dn,
481
            },
482
            "sync_login": {
483
                "username": system_id,
484
                "password": generate_password(no_random),
485
                "certfile": "user-%s.crt" % system_id,
486
                "keyfile": "user-%s.key" % system_id,
487
                "cert_reused": False,
488
            },
489
            "node_bd_dn": node_bd_dn,
490
            "pod_bd_dn": pod_bd_dn,
491
            "kafka": {
492
            },
493
            "subnet_dn": {
494
            },
495
            "vrf_dn": {
496
            },
497
            "overlay_vrf": {
498
            },
499
        },
500
        "net_config": {
501
            "infra_vlan": infra_vlan,
502
            "gbp_pod_subnet": "%s/%s" % (cidr_split(pod_subnet)[2], cidr_split(pod_subnet)[4]),
503
            "gbp_node_subnet": "%s/%s" % (cidr_split(node_subnet)[2], cidr_split(node_subnet)[4]),
504
            "node_network_gateway": cidr_split(node_subnet)[5],
505
            "pod_network": normalize_cidr(pod_subnet),
506
            "node_network": normalize_cidr(node_subnet),
507
            "disable_wait_for_network": disable_wait_for_network,
508
            "duration_wait_for_network": duration_wait_for_network,
509
        },
510
        "node_config": {
511
            "encap_type": encap_type,
512
        },
513
        "istio_config": {
514
            "install_profile": istio_profile,
515
        },
516
        "kube_config": {
517
            "default_endpoint_group": {
518
                "tenant": tenant,
519
                "app_profile": app_profile,
520
                "group": default_endpoint_group,
521
            },
522
            "namespace_default_endpoint_group": {
523
                system_namespace: {
524
                    "tenant": tenant,
525
                    "app_profile": app_profile,
526
                    "group": namespace_endpoint_group,
527
                },
528
                istio_namespace: {
529
                    "tenant": tenant,
530
                    "app_profile": app_profile,
531
                    "group": istio_epg,
532
                },
533
                istio_operator_ns: {
534
                    "tenant": tenant,
535
                    "app_profile": app_profile,
536
                    "group": istio_epg,
537
                },
538
            },
539
            "pod_ip_pool": [
540
                {
541
                    "start": cidr_split(pod_subnet)[0],
542
                    "end": cidr_split(pod_subnet)[1],
543
                }
544
            ],
545
            "pod_network": [
546
                {
547
                    "subnet": "%s/%s" % cidr_split(pod_subnet)[3:5],
548
                    "gateway": cidr_split(pod_subnet)[2],
549
                    "routes": [
550
                        {
551
                            "dst": config_set_dst(pod_subnet),
552
                            "gw": cidr_split(pod_subnet)[2],
553
                        }
554
                    ],
555
                },
556
            ],
557
            "service_ip_pool": [
558
                {
559
                    "start": cidr_split(extern_dynamic)[0],
560
                    "end": cidr_split(extern_dynamic)[1],
561
                },
562
            ],
563
            "static_service_ip_pool": static_service_ip_pool,
564
            "node_service_ip_pool": node_service_ip_pool,
565
            "node_service_gw_subnets": [
566
                node_svc_subnet,
567
            ],
568
            "opflex_mode": opflex_mode,
569
            "enable_endpointslice": enable_endpointslice,
570
        },
571
        "registry": {
572
            "configuration_version": token,
573
        }
574
    }
575

576
    if config["aci_config"].get("apic_refreshtime"):  # APIC Subscription refresh timeout value
1✔
577
        apic_refreshtime = config["aci_config"]["apic_refreshtime"]
1✔
578
        adj_config["aci_config"]["apic_refreshtime"] = apic_refreshtime
1✔
579

580
    if config["kube_config"].get("ovs_memory_limit"):  # OVS memory limit to be set in K8S Spec
1✔
581
        adj_config["kube_config"]["ovs_memory_limit"] = config["kube_config"]["ovs_memory_limit"]
1✔
582

583
    if config["kube_config"].get("image_pull_policy"):  # imagePullPolicy to be set for ACI CNI pods in K8S Spec
1✔
584
        adj_config["kube_config"]["image_pull_policy"] = config["kube_config"]["image_pull_policy"]
1✔
585

586
    if config["istio_config"].get("install_istio"):  # Install istio control-plane by default?
1✔
587
        adj_config["istio_config"]["install_istio"] = config["istio_config"]["install_istio"]
1✔
588

589
    if config["istio_config"].get("install_profile"):  # Which istio profile to bring-up
1✔
590
        adj_config["istio_config"]["install_profile"] = config["istio_config"]["install_profile"]
1✔
591

592
    if config["net_config"].get("pbr_tracking_non_snat"):
1✔
593
        adj_config["net_config"]["pbr_tracking_non_snat"] = config["net_config"]["pbr_tracking_non_snat"]
1✔
594

595
    if config["net_config"].get("service_monitor_interval"):
1✔
596
        adj_config["net_config"]["service_monitor_interval"] = config["net_config"]["service_monitor_interval"]
1✔
597

598
    ns_value = {"tenant": tenant, "app_profile": app_profile, "group": namespace_endpoint_group}
1✔
599

600
    # To add kube-system namespace to ACI system EPG
601
    adj_config["kube_config"]["namespace_default_endpoint_group"]["kube-system"] = ns_value
1✔
602

603
    # Add openshift system namespaces to ACI system EPG
604
    if config["aci_config"]["vmm_domain"]["type"] == "OpenShift":
1✔
605
        ns_list = ["kube-service-catalog", "openshift-console", "openshift-dns", "openshift-authentication",
1✔
606
                   "openshift-authentication-operator", "openshift-monitoring", "openshift-web-console"]
607
        for ns in ns_list:
1✔
608
            adj_config["kube_config"]["namespace_default_endpoint_group"][ns] = ns_value
1✔
609

610
    if config["flavor"] == "k8s-overlay":
1✔
611
        ns_list = ["kube-system"]
1✔
612
        adj_config["kube_config"]["namespace_default_endpoint_group"].clear()
1✔
613
        for ns in ns_list:
1✔
614
            adj_config["kube_config"]["namespace_default_endpoint_group"][ns] = ns_value
1✔
615

616
    if not config["aci_config"]["vmm_domain"].get("injected_cluster_type"):
1✔
617
        adj_config["aci_config"]["vmm_domain"]["injected_cluster_type"] = ""
1✔
618
    if not config["aci_config"]["vmm_domain"].get("injected_cluster_provider"):
1✔
619
        adj_config["aci_config"]["vmm_domain"]["injected_cluster_provider"] = ""
1✔
620

621
    if config["sriov_config"].get("enable"):
1✔
622
        adj_config["vendors"] = "15b3"
1✔
623
        adj_config["drivers"] = "mlx5_core"
1✔
624
        adj_config["resourcePrefix"] = "mellanox.com"
1✔
625
        adj_config["resourceName"] = "cx5_sriov_switchdev"
1✔
626
        adj_config["pfNames"] = "enp193s0f0np0#2-59"
1✔
627
        adj_config["devices"] = ""
1✔
628
        adj_config["isRdma"] = "false"
1✔
629

630
        if 'device_info' in config["sriov_config"]:
1✔
631
            if 'devices' in config["sriov_config"]["device_info"]:
1✔
632
                adj_config["devices"] = str(config["sriov_config"]["device_info"].get("devices"))
1✔
633
            if config["sriov_config"]["device_info"].get("isRdma"):
1✔
634
                adj_config["isRdma"] = "true"
1✔
635

636
        if config["dpu_config"].get("enable"):
1✔
637
            if 'ip' in config["dpu_config"]:
1✔
638
                adj_config["dpuIp"] = str(config["dpu_config"]["ip"])
1✔
639
            else:
640
                adj_config["dpuIp"] = "192.168.200.2"
×
641

642
            if 'user' in config["dpu_config"]:
1✔
643
                adj_config["dpuUser"] = str(config["dpu_config"]["user"])
1✔
644
            else:
645
                adj_config["dpuUser"] = "opflex"
×
646

647
            if 'ovsdb_socket_port' in config["dpu_config"]:
1✔
648
                adj_config["dpu_ovsdb_socket"] = "tcp:" + adj_config["dpuIp"] + ":" + str(config["dpu_config"]["ovsdb_socket_port"])
1✔
649
            else:
650
                adj_config["dpu_ovsdb_socket"] = "tcp:" + adj_config["dpuIp"] + ":6640"
×
651

652
    return adj_config
1✔
653

654

655
def is_valid_mtu(xval):
1✔
656
    if xval is None:
1✔
657
        # use default configured on this host
658
        return True
1✔
659

660
    xmin = 1280   # for IPv6
1✔
661
    xmax = 8900   # leave 100 byte header for VxLAN
1✔
662
    try:
1✔
663
        x = int(xval)
1✔
664
        if xmin <= x <= xmax:
1✔
665
            return True
1✔
666
    except ValueError:
×
667
        pass
×
668
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
669

670

671
def is_valid_mtu_VirtualLIfP(xval):
1✔
672
    if xval is None:
1✔
673
        # use default configured on this host
674
        return True
×
675

676
    xmin = 576
1✔
677
    xmax = 9216
1✔
678
    try:
1✔
679
        x = int(xval)
1✔
680
        if xmin <= x <= xmax:
1✔
681
            return True
1✔
682
    except ValueError:
×
683
        pass
×
684
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
685

686

687
def is_valid_headroom(xval):
1✔
688
    if xval is None:
1✔
689
        # use default configured on this host
690
        return True
1✔
691

692
    xmin = 50
1✔
693
    try:
1✔
694
        x = int(xval)
1✔
695
        if x >= xmin:
1✔
696
            return True
1✔
697
    except ValueError:
×
698
        pass
×
699
    raise (Exception("Must be integer >= %d" % (xmin)))
×
700

701

702
def is_valid_apic_sub_delay(xval):
1✔
703
    if xval is None:
1✔
704
        # use default configured on this host
705
        return True
1✔
706

707
    xmin = 1
1✔
708
    xmax = 65535
1✔
709
    try:
1✔
710
        x = int(xval)
1✔
711
        if xmin <= x <= xmax:
1✔
712
            return True
1✔
713
    except ValueError:
×
714
        pass
×
715
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
716

717

718
def is_valid_dev_del_timeout(xval):
1✔
719
    if xval is None:
1✔
720
        # use default configured on this host
721
        return True
1✔
722

723
    xmin = 1
1✔
724
    xmax = 65535
1✔
725
    try:
1✔
726
        x = int(xval)
1✔
727
        if xmin <= x <= xmax:
1✔
728
            return True
1✔
729
    except ValueError:
×
730
        pass
×
731
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
732

733

734
def is_valid_sleep_time(xval):
1✔
735
    if xval is None:
1✔
736
        # use default configured on this host
737
        return True
1✔
738

739
    xmin = 1
1✔
740
    xmax = 300
1✔
741
    try:
1✔
742
        x = int(xval)
1✔
743
        if xmin <= x <= xmax:
1✔
744
            return True
1✔
745
    except ValueError:
×
746
        pass
×
747
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
748

749

750
def is_valid_ipsla_interval(xval):
1✔
751
    if xval is None:
1✔
752
        # use default configured on this host
753
        return True
×
754

755
    xmin = 0
1✔
756
    xmax = 65535
1✔
757
    try:
1✔
758
        x = int(xval)
1✔
759
        if xmin <= x <= xmax:
1✔
760
            return True
1✔
761
    except ValueError:
×
762
        pass
×
763
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
764

765

766
def is_valid_refreshtime(xval):
1✔
767
    if xval is None:
1✔
768
        # Not a required field.
769
        return True
1✔
770
    xmin = 0
1✔
771
    xmax = (12 * 60 * 60)  # 12Hrs is the max suggested subscription refresh time for APIC
1✔
772
    try:
1✔
773
        x = int(xval)
1✔
774
        if xmin <= x <= xmax:
1✔
775
            return True
1✔
776
    except ValueError:
×
777
        pass
×
778
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
779

780

781
def is_valid_apic_refreshticker_adjust(xval):
1✔
782
    if xval is None:
1✔
783
        # use default configured on this host(150 seconds)
784
        return True
1✔
785

786
    xmin = 1
1✔
787
    xmax = 65535
1✔
788
    try:
1✔
789
        x = int(xval)
1✔
790
        if xmin <= x <= xmax:
1✔
791
            return True
1✔
792
    except ValueError:
×
793
        pass
×
794
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
795

796

797
def is_valid_max_nodes_svc_graph(xval):
1✔
798
    if xval is None:
1✔
799
        return True
×
800
    xmin = 1
1✔
801
    xmax = 64
1✔
802
    try:
1✔
803
        x = int(xval)
1✔
804
        if xmin <= x <= xmax:
1✔
805
            return True
1✔
806
    except ValueError:
×
807
        pass
×
808
    raise (Exception("Must be integer between %d and %d" % (xmin, xmax)))
×
809

810

811
def is_valid_istio_install_profile(xval):
1✔
812
    if xval is None:
1✔
813
        # Not a required field - default will be set to demo
814
        return True
×
815
    validProfiles = ['demo', 'default', 'minimal', 'sds', 'remote']
1✔
816
    try:
1✔
817
        if xval in validProfiles:
1✔
818
            return True
1✔
819
    except ValueError:
×
820
        pass
×
821
    raise (Exception("Must be one of the profile in this List: ", validProfiles))
×
822

823

824
def is_valid_image_pull_policy(xval):
1✔
825
    if xval is None:
1✔
826
        # Not a required field - default will be set to Always
827
        return True
×
828
    validPullPolicies = ['Always', 'IfNotPresent', 'Never']
1✔
829
    try:
1✔
830
        if xval in validPullPolicies:
1✔
831
            return True
1✔
832
    except ValueError:
×
833
        pass
×
834
    raise (Exception("Must be one of the values in this List: ", validPullPolicies))
×
835

836

837
def is_valid_contract_scope(xval):
1✔
838
    if xval is None:
1✔
839
        # Not a required field - default will be set to demo
840
        return True
×
841
    validVersions = ['global', 'tenant', 'context']
1✔
842
    try:
1✔
843
        if xval in validVersions:
1✔
844
            return True
1✔
845
    except ValueError:
×
846
        pass
×
847
    raise (Exception("Must be one of the contract scopes in this List: ", validVersions))
×
848

849

850
def isOverlay(flavor):
1✔
851
    flav = SafeDict(FLAVORS[flavor])
1✔
852
    ovl = flav["overlay"]
1✔
853
    if ovl is True:
1✔
854
        return True
1✔
855

856
    return False
1✔
857

858

859
def config_validate(flavor_opts, config):
1✔
860
    def Raise(exception):
1✔
861
        raise exception
1✔
862

863
    required = lambda x: True if x else Raise(Exception("Missing option"))
1✔
864
    lower_in = lambda y: (
1✔
865
        lambda x: (
866
            (True if str(x).lower() in y
867
             else Raise(Exception("Invalid value: %s; "
868
                                  "Expected one of: {%s}" %
869
                                  (x, ','.join(y)))))))
870
    isname = lambda x, l: (1 < len(x) < l) and \
1✔
871
        x[0].isalpha() and x.replace('_', '').isalnum() \
872
        if x else Raise(Exception("Invalid name"))
873
    get = lambda t: functools.reduce(lambda x, y: x and x.get(y), t, config)
1✔
874

875
    if is_calico_flavor(config["flavor"]):
1✔
876
        checks = {
1✔
877
            # ACI config
878
            "aci_config/apic_host": (get(("aci_config", "apic_hosts")), required),
879
            "aci_config/vrf/name": (get(("aci_config", "vrf", "name")), required),
880
            "aci_config/vrf/tenant": (get(("aci_config", "vrf", "tenant")), required),
881
            # Network Config
882
            "net_config/pod_subnet": (get(("net_config", "pod_subnet")), required),
883
            "net_config/node_subnet": (get(("net_config", "node_subnet")), required),
884
        }
885
    elif not isOverlay(config["flavor"]) or config["aci_config"]["capic"]:
1✔
886
        checks = {
1✔
887
            # ACI config
888
            "aci_config/system_id": (get(("aci_config", "system_id")),
889
                                     lambda x: required(x) and isname(x, 32)),
890
            "aci_config/apic_refreshtime": (get(("aci_config", "apic_refreshtime")),
891
                                            is_valid_refreshtime),
892
            "aci_config/apic_refreshticker_adjust": (get(("aci_config", "apic_refreshticker_adjust")),
893
                                                     is_valid_apic_refreshticker_adjust),
894
            "aci_config/apic_subscription_delay": (get(("aci_config", "apic_subscription_delay")),
895
                                                   is_valid_apic_sub_delay),
896
            "aci_config/opflex_device_delete_timeout": (get(("aci_config", "opflex_device_delete_timeout")),
897
                                                        is_valid_dev_del_timeout),
898
            "aci_config/apic_host": (get(("aci_config", "apic_hosts")), required),
899
            "aci_config/vrf/name": (get(("aci_config", "vrf", "name")), required),
900
            "aci_config/vrf/tenant": (get(("aci_config", "vrf", "tenant")),
901
                                      required),
902
            # Istio config
903
            "istio_config/install_profile": (get(("istio_config", "install_profile")),
904
                                             is_valid_istio_install_profile),
905
            "kube_config/image_pull_policy": (get(("kube_config", "image_pull_policy")),
906
                                              is_valid_image_pull_policy),
907
            # Network Config
908
            "net_config/pod_subnet": (get(("net_config", "pod_subnet")),
909
                                      required),
910
        }
911
    else:
912
        checks = {
1✔
913
            "kube_config/image_pull_policy": (get(("kube_config", "image_pull_policy")),
914
                                              is_valid_image_pull_policy),
915
            # Network Config
916
            "net_config/pod_subnet": (get(("net_config", "pod_subnet")),
917
                                      required),
918
            "net_config/node_subnet": (get(("net_config", "node_subnet")),
919
                                       required),
920
        }
921
    if isOverlay(config["flavor"]):
1✔
922
        if (config["aci_config"]["capic"]):
1✔
923
            extra_checks = {
×
924
                "aci_config/vrf/region": (get(("aci_config", "vrf", "region")), required),
925
            }
926
        else:
927
            extra_checks = {}
1✔
928
    elif is_calico_flavor(config["flavor"]):
1✔
929
        extra_checks = {
1✔
930
            "aci_config/cluster_l3out/aep": (get(("aci_config", "cluster_l3out", "aep")), required),
931
            "aci_config/cluster_l3out/svi/mtu": (get(("aci_config", "cluster_l3out", "svi", "mtu")),
932
                                                 is_valid_mtu_VirtualLIfP),
933
            "aci_config/cluster_l3out/svi/vlan_id": (get(("aci_config", "cluster_l3out", "svi", "vlan_id")),
934
                                                     required),
935
            "aci_config/cluster_l3out/svi/floating_ip": (get(("aci_config", "cluster_l3out", "svi", "floating_ip")),
936
                                                         required),
937
            "aci_config/cluster_l3out/svi/secondary_ip": (get(("aci_config", "cluster_l3out", "svi", "secondary_ip")),
938
                                                          required),
939
            "aci_config/cluster_l3out/bgp/peering/aci_as_number":
940
            (get(("aci_config", "cluster_l3out", "bgp", "peering", "aci_as_number")), required),
941
            "net_config/extern_dynamic": (get(("net_config", "extern_dynamic")),
942
                                          required),
943
            "net_config/cluster_svc_subnet": (get(("net_config", "cluster_svc_subnet")),
944
                                              required),
945
        }
946

947
    else:
948
        extra_checks = {
1✔
949
            "net_config/node_subnet": (get(("net_config", "node_subnet")),
950
                                       required),
951
            "aci_config/aep": (get(("aci_config", "aep")), required),
952
            "aci_config/l3out/name": (get(("aci_config", "l3out", "name")),
953
                                      required),
954
            "aci_config/l3out/external-networks":
955
            (get(("aci_config", "l3out", "external_networks")), required),
956

957
            # Kubernetes config
958
            "kube_config/max_nodes_svc_graph": (get(("kube_config", "max_nodes_svc_graph")),
959
                                                is_valid_max_nodes_svc_graph),
960

961
            "kube_config/snat_operator/contract_scope": (get(("kube_config", "snat_operator", "contract_scope")),
962
                                                         is_valid_contract_scope),
963

964
            "kube_config/snat_operator/sleep_time_snat_global_info_sync": (get(("kube_config", "snat_operator", "sleep_time_snat_global_info_sync")),
965
                                                                           is_valid_sleep_time),
966

967
            # Network Config
968
            "net_config/infra_vlan": (get(("net_config", "infra_vlan")),
969
                                      required),
970
            "net_config/service_vlan": (get(("net_config", "service_vlan")),
971
                                        required),
972
            "net_config/extern_dynamic": (get(("net_config", "extern_dynamic")),
973
                                          required),
974
            "net_config/extern_static": (get(("net_config", "extern_static")),
975
                                         required),
976
            "net_config/node_svc_subnet": (get(("net_config", "node_svc_subnet")),
977
                                           required),
978
            "net_config/interface_mtu": (get(("net_config", "interface_mtu")),
979
                                         is_valid_mtu),
980
            "net_config/interface_mtu_headroom": (get(("net_config", "interface_mtu_headroom")),
981
                                                  is_valid_headroom),
982
            "net_config/service_monitor_interval": (get(("net_config", "service_monitor_interval")),
983
                                                    is_valid_ipsla_interval)
984
        }
985

986
        if (config["aci_config"]["vmm_domain"]["type"] == "OpenShift"):
1✔
987
            del extra_checks["net_config/extern_static"]
1✔
988

989
        if flavor_opts.get("apic", {}).get("use_kubeapi_vlan", True):
1✔
990
            checks["net_config/kubeapi_vlan"] = (
1✔
991
                get(("net_config", "kubeapi_vlan")), required)
992

993
    # Allow deletion of resources without isname check
994
    if get(("provision", "prov_apic")) is False and not is_calico_flavor(config["flavor"]):
1✔
995
        checks["aci_config/system_id"] = \
1✔
996
            (get(("aci_config", "system_id")), required)
997

998
    # Versions
999
    if not is_calico_flavor(config["flavor"]):
1✔
1000
        for field in flavor_opts.get('version_fields', VERSION_FIELDS):
1✔
1001
            checks[field] = (get(("registry", field)), required)
1✔
1002

1003
    if flavor_opts.get("apic", {}).get("associate_aep_to_nested_inside_domain",
1✔
1004
                                       False):
1005
        checks["aci_config/vmm_domain/nested_inside/type"] = (
×
1006
            get(("aci_config", "vmm_domain", "nested_inside", "type")),
1007
            required)
1008

1009
    if get(("aci_config", "vmm_domain", "encap_type")) == "vlan":
1✔
1010
        checks["aci_config/vmm_domain/vlan_range/start"] = \
1✔
1011
            (get(("aci_config", "vmm_domain", "vlan_range", "start")),
1012
             required)
1013
        checks["aci_config/vmm_domain/vlan_range/end"] = \
1✔
1014
            (get(("aci_config", "vmm_domain", "vlan_range", "end")),
1015
             required)
1016

1017
    if get(("aci_config", "vmm_domain", "nested_inside", "type")):
1✔
1018
        checks["aci_config/vmm_domain/nested_inside/type"] = \
1✔
1019
            (get(("aci_config", "vmm_domain", "nested_inside", "type")),
1020
             lower_in({"vmware"}))
1021
        checks["aci_config/vmm_domain/nested_inside/name"] = \
1✔
1022
            (get(("aci_config", "vmm_domain", "nested_inside", "name")),
1023
             required)
1024

1025
    if get(("aci_config", "vmm_domain", "nested_inside", "duplicate_file_router_default_svc")):
1✔
1026
        checks["aci_config/vmm_domain/nested_inside/installer_provisioned_lb_ip"] = \
1✔
1027
            (get(("aci_config", "vmm_domain", "nested_inside", "installer_provisioned_lb_ip")),
1028
             required)
1029

1030
    if get(("provision", "prov_apic")) is not None:
1✔
1031
        checks.update({
1✔
1032
            # auth for API access
1033
            "aci_config/apic_login/username":
1034
            (get(("aci_config", "apic_login", "username")), required),
1035
            "aci_config/apic_login/password":
1036
            (get(("aci_config", "apic_login", "password")), required),
1037
        })
1038

1039
    checks = deep_merge(checks, extra_checks)
1✔
1040
    ret = True
1✔
1041
    for k in sorted(checks.keys()):
1✔
1042
        value, validator = checks[k]
1✔
1043
        try:
1✔
1044
            if not validator(value):
1✔
1045
                raise Exception(k)
×
1046
        except Exception as e:
1✔
1047
            err("Invalid configuration for %s: %s" % (k, e))
1✔
1048
            ret = False
1✔
1049
    return ret
1✔
1050

1051

1052
def config_validate_preexisting(config, prov_apic):
1✔
1053
    try:
1✔
1054
        if isOverlay(config["flavor"]):
1✔
1055
            return True
1✔
1056

1057
        if prov_apic is not None:
1✔
1058
            apic = get_apic(config)
×
1059
            if apic is None:
×
1060
                return False
×
1061

1062
            aep_name = config["aci_config"]["aep"]
×
1063
            aep = apic.get_aep(aep_name)
×
1064
            if aep is None:
×
1065
                warn("AEP not defined in the APIC: %s" % aep_name)
×
1066

1067
            vrf_tenant = config["aci_config"]["vrf"]["tenant"]
×
1068
            vrf_name = config["aci_config"]["vrf"]["name"]
×
1069
            vrf_dn = config["aci_config"]["vrf"]["dn"]
×
1070
            l3out_name = config["aci_config"]["l3out"]["name"]
×
1071
            vrf = apic.get_vrf(vrf_dn)
×
1072
            if vrf is None:
×
1073
                warn("VRF not defined in the APIC: %s/%s" %
×
1074
                     (vrf_tenant, vrf_name))
1075
            l3out = apic.get_l3out(vrf_tenant, l3out_name)
×
1076
            if l3out is None:
×
1077
                warn("L3out not defined in the APIC: %s/%s" %
×
1078
                     (vrf_tenant, l3out_name))
1079
            else:
1080
                # get l3out context and check if it's the same as vrf in
1081
                # input config
1082
                result = apic.check_l3out_vrf(vrf_tenant, l3out_name, vrf_name, vrf_dn)
×
1083
                if not result:
×
1084
                    info("L3out and Kubernetes EPGs are configured in different VRFs")
×
1085

1086
            # Following code is to detect a legacy cluster
1087
            # kube_ap = apic.get_ap(config["aci_config"]["system_id"])
1088
            # if an app profile with the name "kubernetes" exists under system
1089
            # tenant, this means the cluster was provisioned with older
1090
            # naming convention. This is a fallback in case the user
1091
            # forgets to add the field to indicate an existing legacy
1092
            # cluster.
1093
            # if kube_ap:
1094
            #     config["aci_config"]["use_legacy_kube_naming_convention"] = True
1095
            #     if config["aci_config"]["vmm_domain"]["type"] == "OpenShift":
1096
            #         config["kube_config"]["system_namespace"] = "aci-containers-system"
1097
            #     else:
1098
            #         config["kube_config"]["system_namespace"] = "kube-system"
1099

1100
    except Exception as e:
×
1101
        warn("Unable to validate resources on APIC: {}".format(e))
×
1102
    return True
1✔
1103

1104

1105
def calico_config_validate_preexisting(config, prov_apic):
1✔
1106
    try:
1✔
1107
        apic = None
1✔
1108
        if prov_apic is not None:
1✔
1109
            apic = get_apic(config)
×
1110
            if apic is None:
×
1111
                return False
×
1112
            for rack in config["topology"]["rack"]:
×
1113
                for leaf in rack["leaf"]:
×
1114
                    if "local_ip" not in leaf:
×
1115
                        err("Please provide only anchor leaf nodes in the input file. Non-anchor leaf node provided is %s" % leaf["id"])
×
1116
                        return False
×
1117
            aep_name = config["aci_config"]["cluster_l3out"]["aep"]
×
1118
            vrf_tenant = config["aci_config"]["vrf"]["tenant"]
×
1119
            vrf_name = config["aci_config"]["vrf"]["name"]
×
1120
            vrf_dn = config["aci_config"]["vrf"]["dn"]
×
1121
            l3out_name = config["aci_config"]["l3out"]["name"]
×
1122
            aep = apic.get_aep(aep_name)
×
1123
            if aep is None:
×
1124
                err("AEP %s not created on the APIC. Please create the AEP and try again" % aep_name)
×
1125
                return False
×
1126
            check_local_asn = apic.get_local_asn()
×
1127
            aci_as_number = config["aci_config"]["cluster_l3out"]["bgp"]["peering"]["aci_as_number"]
×
1128
            if str(aci_as_number) != check_local_asn:
×
1129
                err("aci_as_number %s provided in the input file does not match the BGP route reflector asn %s on the APIC. "
×
1130
                    "This check is only made if -a option is used. Please ensure the flavor manifests that are generated from"
1131
                    "this step with -a are used instead of previous ones you may have had." % (aci_as_number, check_local_asn))
1132
                return False
×
1133
            tenant = apic.get_tenant(vrf_tenant)
×
1134
            if tenant is None:
×
1135
                err("Tenant %s not created on the APIC. Please create the tenant and try again" % vrf_tenant)
×
1136
                return False
×
1137
            vrf = apic.get_vrf(vrf_dn)
×
1138
            if vrf is None:
×
1139
                err("VRF %s/%s not created on the APIC. Please create the vrf and try again" % (vrf_tenant, vrf_name))
×
1140
                return False
×
1141
            l3out = apic.get_l3out(vrf_tenant, l3out_name)
×
1142
            if l3out is None:
×
1143
                err("External l3Out %s/%s not created on the APIC. Please create the external l3out and try again " % (vrf_tenant, l3out_name))
×
1144
                return False
×
1145
            map_l3out_vrf = apic.check_l3out_vrf(vrf_tenant, l3out_name, vrf_name, vrf_dn)
×
1146
            if not map_l3out_vrf:
×
1147
                err("VRF is not mapped to L3out %s/%s on the APIC. Please fix the configuration and try again" % (vrf_tenant, l3out_name))
×
1148
                return False
×
1149
            else:
1150
                check_ext_l3out_epg = apic.check_ext_l3out_epg(vrf_tenant, l3out_name)
×
1151
                if check_ext_l3out_epg is None:
×
1152
                    err("External l3out %s/%s does not have an external EPG configured on the APIC. Please fix the configuration and try again" %
×
1153
                        (vrf_tenant, l3out_name))
1154
                    return False
×
1155
    except Exception as e:
×
1156
        warn("Unable to validate resources on APIC: {}".format(e))
×
1157
    return True
1✔
1158

1159

1160
def generate_sample(filep, flavor):
1✔
1161
    if flavor in ["cloud", "eks"]:
1✔
1162
        data = pkgutil.get_data('acc_provision', 'templates/overlay-provision-config.yaml')
×
1163
    elif flavor == "aks":
1✔
1164
        data = pkgutil.get_data('acc_provision', 'templates/aks-provision-config.yaml')
×
1165
    elif flavor == "calico-3.23.2":
1✔
1166
        data = pkgutil.get_data('acc_provision', 'templates/calico-provision-config.yaml')
×
1167
    else:
1168
        data = pkgutil.get_data('acc_provision', 'templates/provision-config.yaml')
1✔
1169
    try:
1✔
1170
        filep.write(data)
1✔
1171
    except TypeError:
×
1172
        filep.write(data.decode(filep.encoding))
×
1173
    finally:
1174
        filep.flush()
1✔
1175
    return filep
1✔
1176

1177

1178
def generate_password(no_random):
1✔
1179
    chars = string.ascii_letters + string.digits + ("_-+=!" * 3)
1✔
1180
    ret = ''.join(random.SystemRandom().sample(chars, 20))
1✔
1181
    if no_random:
1✔
1182
        ret = "NotRandom!"
1✔
1183
    return ret
1✔
1184

1185

1186
def generate_cert(username, cert_file, key_file):
1✔
1187
    reused = False
1✔
1188
    if not exists(cert_file) or not exists(key_file):
1✔
1189
        info("  Private key file: \"%s\"" % key_file)
1✔
1190
        info("  Certificate file: \"%s\"" % cert_file)
1✔
1191

1192
        # create a key pair
1193
        k = crypto.PKey()
1✔
1194
        k.generate_key(crypto.TYPE_RSA, 1024)
1✔
1195

1196
        # create a self-signed cert
1197
        cert = crypto.X509()
1✔
1198
        cert.get_subject().C = "US"
1✔
1199
        cert.get_subject().O = "Cisco Systems"
1✔
1200
        cert.get_subject().CN = "User %s" % username
1✔
1201
        cert.set_serial_number(1000)
1✔
1202
        cert.gmtime_adj_notBefore(-12 * 60 * 60)
1✔
1203
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
1✔
1204
        cert.set_issuer(cert.get_subject())
1✔
1205
        cert.set_pubkey(k)
1✔
1206
        # Work around this bug:
1207
        # https://github.com/pyca/pyopenssl/issues/741
1208

1209
        # This should be b'sha1' on both 2 and 3, but the bug requires
1210
        # passing a string on Python 3.
1211
        if sys.version_info[0] >= 3:
1✔
1212
            hash_algorithm = 'sha1'
1✔
1213
        else:
1214
            hash_algorithm = b'sha1'
×
1215
        cert.sign(k, hash_algorithm)
1✔
1216

1217
        cert_data = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
1✔
1218
        key_data = crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
1✔
1219
        with open(cert_file, "wb") as certp:
1✔
1220
            certp.write(cert_data)
1✔
1221
        with open(key_file, "wb") as keyp:
1✔
1222
            keyp.write(key_data)
1✔
1223
    else:
1224
        # Do not overwrite previously generated data if it exists
1225
        reused = True
1✔
1226
        info("  Private key file: \"%s\"" % key_file)
1✔
1227
        info("  Certificate file: \"%s\"" % cert_file)
1✔
1228
        with open(cert_file, "rb") as certp:
1✔
1229
            cert_data = certp.read()
1✔
1230
        with open(key_file, "rb") as keyp:
1✔
1231
            key_data = keyp.read()
1✔
1232
    return key_data, cert_data, reused
1✔
1233

1234

1235
def get_jinja_template(file):
1✔
1236
    env = Environment(
1✔
1237
        loader=PackageLoader('acc_provision', 'templates'),
1238
        trim_blocks=True,
1239
        lstrip_blocks=True,
1240
        keep_trailing_newline=True
1241
    )
1242
    env.filters['base64enc'] = lambda s: base64.b64encode(s).decode("ascii")
1✔
1243
    env.filters['json'] = json_indent
1✔
1244
    env.filters['yaml'] = yaml_indent
1✔
1245
    env.filters['yaml_quote'] = yaml_quote
1✔
1246
    env.filters['list_unicode_strings'] = list_unicode_strings
1✔
1247
    template = env.get_template(file)
1✔
1248
    return template
1✔
1249

1250

1251
def generate_operator_tar(tar_path, cont_docs, config):
1✔
1252

1253
    # YAML file numbers generated start from 4 as first three are
1254
    # reserved for OpenShift specific files
1255
    file_start = 4
1✔
1256
    filenames = []
1✔
1257

1258
    # Function to construct filenames for each yaml
1259
    def gen_file_list(docs, counter, filenames):
1✔
1260
        for doc in docs:
1✔
1261
            filename = "cluster-network-" + str(counter).zfill(2) + "-" + doc['kind'] + "-" + doc['metadata']['name'] + ".yaml"
1✔
1262
            filenames.append(os.path.basename(filename))
1✔
1263
            with open(filename, 'w') as outfile:
1✔
1264
                yaml.safe_dump(doc, outfile, default_flow_style=False, encoding="utf-8")
1✔
1265
            counter += 1
1✔
1266
        return counter
1✔
1267

1268
    gen_file_list(cont_docs, file_start, filenames)
1✔
1269

1270
    # Create three extra files needed for Openshift 4.3 installer
1271
    extra_files = []
1✔
1272
    gen_inst_files = config["kube_config"]["generate_installer_files"]
1✔
1273
    gen_cnet_file = config["kube_config"]["generate_cnet_file"]
1✔
1274
    gen_apic_file = config["kube_config"]["generate_apic_file"]
1✔
1275
    if (config['multus']['disable'] is True) and (gen_inst_files or gen_cnet_file):
1✔
1276
        cnetfile_name = 'cluster-network-03-config.yaml'
1✔
1277
        extra_files.append(cnetfile_name)
1✔
1278

1279
    if gen_inst_files:
1✔
1280
        masterfile_name = '99-master-kubelet-node-ip.yaml'
1✔
1281
        workerfile_name = '99-worker-kubelet-node-port.yaml'
1✔
1282
        extra_files.append(masterfile_name)
1✔
1283
        extra_files.append(workerfile_name)
1✔
1284

1285
    if config["flavor"] == "openshift-4.6-esx":
1✔
1286
        workerfile_name = '99-worker-kubelet-node-ip.yaml'
1✔
1287
        extra_files.append(workerfile_name)
1✔
1288

1289
    if gen_apic_file:
1✔
1290
        apic_name = 'apic.json'
1✔
1291
        extra_files.append(apic_name)
1✔
1292

1293
    for x_file in extra_files:
1✔
1294
        x_template = get_jinja_template(x_file)
1✔
1295
        x_template.stream(config=config).dump(x_file)
1✔
1296
        filenames.append(x_file)
1✔
1297

1298
    # Create tar for the parsed files and delete the files too
1299
    tar = tarfile.open(tar_path, "w:gz", encoding="utf-8")
1✔
1300
    for name in filenames:
1✔
1301
        tar.add(name)
1✔
1302
        os.remove(name)
1✔
1303
    tar.close()
1✔
1304

1305

1306
def generate_rancher_yaml(args, config, operator_output, operator_tar, operator_cr_output):
1✔
1307
    if operator_output and operator_output != "/dev/null":
1✔
1308
        template = get_jinja_template('aci-network-provider-cluster.yaml')
1✔
1309
        outname = operator_output
1✔
1310
        # At this time, we do not use the aci-containers-operator with Rancher.
1311
        # The template to generate ACI CNI components is upstream in RKE code
1312
        # Here we generate the input file to feed into RKE, which looks almost
1313
        # the same as the acc-provision_input file
1314

1315
        # If no output containers(-o) deployment file is provided, print to stdout.
1316
        # Else, save to file.
1317
        if operator_output == "-":
1✔
1318
            outname = "<stdout>"
×
1319
            operator_output = sys.stdout
×
1320
        info("Writing Rancher network provider portion of cluster.yml to %s" % outname)
1✔
1321
        info("Use this network provider section in the cluster.yml you use with RKE")
1✔
1322
        if operator_output != sys.stdout:
1✔
1323
            with open(operator_output, "w") as fh:
1✔
1324
                fh.write(template.render(config=config))
1✔
1325
        else:
1326
            template.stream(config=config).dump(operator_output)
×
1327

1328

1329
def generate_rancher_1_3_13_yaml(args, config, operator_output, operator_tar, operator_cr_output):
1✔
1330
    if operator_output and operator_output != "/dev/null":
1✔
1331
        template = get_jinja_template('aci-network-provider-cluster-1-3-13.yaml')
1✔
1332
        outname = operator_output
1✔
1333
        # At this time, we do not use the aci-containers-operator with Rancher.
1334
        # The template to generate ACI CNI components is upstream in RKE code
1335
        # Here we generate the input file to feed into RKE, which looks almost
1336
        # the same as the acc-provision_input file
1337

1338
        # If no output containers(-o) deployment file is provided, print to stdout.
1339
        # Else, save to file.
1340
        if operator_output == "-":
1✔
1341
            outname = "<stdout>"
×
1342
            operator_output = sys.stdout
×
1343
        info("Writing Rancher network provider portion of cluster.yml to %s" % outname)
1✔
1344
        info("Use this network provider section in the cluster.yml you use with RKE")
1✔
1345
        if operator_output != sys.stdout:
1✔
1346
            with open(operator_output, "w") as fh:
1✔
1347
                fh.write(template.render(config=config))
1✔
1348
        else:
1349
            template.stream(config=config).dump(operator_output)
×
1350

1351

1352
def get_cko_mode(args, netopConfig):
1✔
1353
    cko_mode = "unamanged"
1✔
1354
    if args.cko_mode is not None:
1✔
1355
        cko_mode = args.cko_mode
1✔
1356
    netopConfig["managedComponent"] = cko_mode
1✔
1357
    return netopConfig
1✔
1358

1359

1360
def is_calico_flavor(flavor):
1✔
1361
    return SafeDict(FLAVORS[flavor]).get("calico_cni")
1✔
1362

1363

1364
def generate_nettools_deployment_files(args, netopConfig):
1✔
1365
    netop_version = args.cko_version
1✔
1366
    nettools_spec_template = get_jinja_template('cko/' + netop_version + '/nettools/nettools-manifests.yaml')
1✔
1367
    nettools_spec_output = nettools_spec_template.render(config=netopConfig)
1✔
1368
    connectivity_checker_CR_template = get_jinja_template('cko/' + netop_version + '/nettools/connectivity-checker-cr.yaml')
1✔
1369
    connectivity_checker_CR_outout = connectivity_checker_CR_template.render(config=netopConfig)
1✔
1370
    errorPodReporting_CR_template = get_jinja_template('cko/' + netop_version + '/nettools/error-pod-reporting-cr.yaml')
1✔
1371
    errorPodReporting_CR_output = errorPodReporting_CR_template.render(config=netopConfig)
1✔
1372
    base64_encoded_nettools_crds = base64.b64encode(nettools_spec_output.encode('ascii')).decode('ascii')
1✔
1373
    base64_encoded_connectivity_checker_cr = base64.b64encode(connectivity_checker_CR_outout.encode('ascii')).decode('ascii')
1✔
1374
    base64_encoded_errorPodReporting_cr = base64.b64encode(errorPodReporting_CR_output.encode('ascii')).decode('ascii')
1✔
1375
    netopConfig['connectivity_checker']['base64_encoded_nettools_crds'] = base64_encoded_nettools_crds
1✔
1376
    netopConfig['connectivity_checker']['base64_encoded_connectivity_checker_cr'] = base64_encoded_connectivity_checker_cr
1✔
1377
    netopConfig['connectivity_checker']['base64_encoded_errorPodReporting_cr'] = base64_encoded_errorPodReporting_cr
1✔
1378
    return netopConfig
1✔
1379

1380

1381
def generate_argocd_deployment_files(args, netopConfig):
1✔
1382
    argocd_spec_template = get_jinja_template('argocd.yaml')
1✔
1383
    argocd_spec_output = argocd_spec_template.render(config=netopConfig)
1✔
1384
    base64_encoded_argocd_manifests = base64.b64encode(argocd_spec_output.encode('ascii')).decode('ascii')
1✔
1385
    netopConfig['gitops']['base64_encoded_argo_manifests'] = base64_encoded_argocd_manifests
1✔
1386
    return netopConfig
1✔
1387

1388

1389
def generate_calico_deployment_files(args, config, network_operator_output):
1✔
1390
    filenames = ["tigera_operator.yaml", "custom_resources_aci_calico.yaml", "custom_resources_calicoctl.yaml"]
1✔
1391
    if network_operator_output and network_operator_output != "/dev/null":
1✔
1392
        calico_crds_template = get_jinja_template('tigera-operator.yaml')
1✔
1393
        calico_crds_output = calico_crds_template.render(config=config)
1✔
1394
        calico_crs_template = get_jinja_template('custom-resources-aci-calico.yaml')
1✔
1395
        calico_crs_output = calico_crs_template.render(config=config)
1✔
1396
        calicoctl_template = get_jinja_template('calicoctl.yaml')
1✔
1397
        calicoctl_output = calicoctl_template.render(config=config)
1✔
1398

1399
        bgp_peer = ''
1✔
1400
        bgp_node = ''
1✔
1401
        calico_bgp_peer_template = get_jinja_template('calico-bgp-peer.yaml')
1✔
1402
        calico_node_template = get_jinja_template('calico-node.yaml')
1✔
1403
        for item in config["topology"]["rack"]:
1✔
1404
            for node_name in item["node"]:
1✔
1405
                configTemp = dict()
1✔
1406
                configTemp["node_name"] = node_name["name"]
1✔
1407
                configTemp["id"] = item["id"]
1✔
1408
                bgp_node = bgp_node + "\n---\n" + calico_node_template.render(config=configTemp)
1✔
1409
            for leaf in item["leaf"]:
1✔
1410
                if "local_ip" in leaf:
1✔
1411
                    configTemp = dict(config)
1✔
1412
                    configTemp["local_ip"] = leaf["local_ip"]
1✔
1413
                    configTemp["peer_name"] = leaf["local_ip"].replace(".", "-")
1✔
1414
                    configTemp["id"] = item["id"]
1✔
1415
                    bgp_peer = bgp_peer + "\n---\n" + calico_bgp_peer_template.render(config=configTemp)
1✔
1416

1417
        calico_bgp_config_template = get_jinja_template('calico-bgp-config.yaml')
1✔
1418
        calico_bgp_config_output = calico_bgp_config_template.render(config=config)
1✔
1419

1420
        tigera_operator_yaml = calico_crds_output
1✔
1421
        custom_resources_aci_calico_yaml = calico_crs_output + "\n---\n" + calicoctl_output + bgp_node
1✔
1422
        custom_resources_calicoctl_yaml = calico_bgp_config_output + bgp_peer
1✔
1423

1424
        if args.cko:
1✔
1425
            netop_version = args.cko_version
1✔
1426
            netopConfig = dict(config)
1✔
1427
            netopConfig = get_cko_mode(args, netopConfig)
1✔
1428
            if "connectivity_checker" in netopConfig.keys():
1✔
1429
                netopConfig = generate_nettools_deployment_files(args, netopConfig)
1✔
1430
            if "gitops" in netopConfig.keys():
1✔
1431
                netopConfig = generate_argocd_deployment_files(args, netopConfig)
1✔
1432
            network_operator_spec_template = get_jinja_template('cko/' + netop_version + '/netop-manifest.yaml')
1✔
1433
            network_operator_spec_output = network_operator_spec_template.render(config=config)
1✔
1434
            network_operator_CR_template = get_jinja_template('calico-installer-cr.yaml')
1✔
1435
            base64_encoded_cko_calico_crds = base64.b64encode(calico_crds_output.encode('ascii')).decode('ascii')
1✔
1436
            base64_encoded_cko_calico_crs = base64.b64encode(calico_crs_output.encode('ascii')).decode('ascii')
1✔
1437
            base64_encoded_cko_calico_bgp = base64.b64encode(custom_resources_calicoctl_yaml.encode('ascii')).decode('ascii')
1✔
1438
            base64_encoded_cko_calicoctl = base64.b64encode(calicoctl_output.encode('ascii')).decode('ascii')
1✔
1439

1440
            netopConfig["aci_config"]["operator_version"] = netop_version
1✔
1441
            netopConfig["calico_config"]["cni_flavor_version"] = config["registry"]["version"]
1✔
1442
            netopConfig["calico_config"]["base64_encoded_calico_crds_spec"] = base64_encoded_cko_calico_crds
1✔
1443
            netopConfig["calico_config"]["base64_encoded_calico_crs_spec"] = base64_encoded_cko_calico_crs
1✔
1444
            netopConfig["calico_config"]["base64_encoded_calico_bgp_spec"] = base64_encoded_cko_calico_bgp
1✔
1445
            netopConfig["calico_config"]["base64_encoded_calicoctl_spec"] = base64_encoded_cko_calicoctl
1✔
1446

1447
            network_operator_platform_CR_template = get_jinja_template('platform-installer-cr.yaml')
1✔
1448
            network_operator_platform_CR = network_operator_platform_CR_template.render(config=netopConfig)
1✔
1449
            network_operator_CR_output = network_operator_CR_template.render(config=netopConfig)
1✔
1450
            network_operator_yaml = network_operator_spec_output + "\n---\n" + network_operator_CR_output + "\n---\n" + network_operator_platform_CR
1✔
1451

1452
            print("writing the deployment file")
1✔
1453
            with open(network_operator_output, "w") as fh:
1✔
1454
                fh.write(network_operator_yaml)
1✔
1455
            return True
1✔
1456

1457
        acc_provision_yaml = get_jinja_template('acc-provision-configmap.yaml').render(config=config)
1✔
1458
        custom_resources_aci_calico_yaml += "\n---\n" + acc_provision_yaml
1✔
1459
        with open("custom_resources_aci_calico.yaml", "w") as fh:
1✔
1460
            fh.write(custom_resources_aci_calico_yaml)
1✔
1461
        with open("custom_resources_calicoctl.yaml", "w") as fh:
1✔
1462
            fh.write(custom_resources_calicoctl_yaml)
1✔
1463
        with open("tigera_operator.yaml", "w") as fh:
1✔
1464
            fh.write(tigera_operator_yaml)
1✔
1465

1466
        if "tar.gz" not in network_operator_output:
1✔
1467
            err("Please provide the ouput file name in tar.gz format")
×
1468
            return False
×
1469
        with tarfile.open(network_operator_output, mode='w:gz') as tar:
1✔
1470
            for name in filenames:
1✔
1471
                tar.add(name, arcname=os.path.basename(name))
1✔
1472
                os.remove(name)
1✔
1473
            tar.close()
1✔
1474

1475
        print("Generated the deployment tar file")
1✔
1476

1477

1478
def generate_kube_yaml(args, config, operator_output, operator_tar, operator_cr_output):
1✔
1479
    kube_objects = [
1✔
1480
        "configmap", "secret", "serviceaccount",
1481
        "daemonset", "deployment",
1482
    ]
1483
    if config["kube_config"].get("use_openshift_security_context_constraints",
1✔
1484
                                 False):
1485
        kube_objects.append("securitycontextconstraints")
1✔
1486
    if config["kube_config"].get("use_cluster_role", False):
1✔
1487
        kube_objects.extend(["clusterrolebinding", "clusterrole"])
1✔
1488

1489
    if operator_output and operator_output != "/dev/null":
1✔
1490
        template = get_jinja_template('aci-containers.yaml')
1✔
1491
        outname = operator_output
1✔
1492
        tar_path = operator_tar
1✔
1493

1494
        # If no output containers(-o) deployment file is provided, print to stdout.
1495
        # Else, save to file and tar with the same name.
1496
        if operator_output == "-":
1✔
1497
            outname = "<stdout>"
×
1498
            applyname = "<filename>"
×
1499
            operator_output = sys.stdout
×
1500
        else:
1501
            applyname = os.path.basename(operator_output)
1✔
1502
            if not tar_path or tar_path == "-":
1✔
1503
                tar_path = operator_output + ".tar.gz"
1✔
1504

1505
        temp = ''.join(template.stream(config=config))
1✔
1506
        parsed_temp = temp.split("---")
1✔
1507

1508
        # Find the place where to put the acioperators configmap
1509
        for cmap_idx in range(len(parsed_temp)):
1✔
1510
            current_yaml = yaml.safe_load(parsed_temp[cmap_idx])
1✔
1511
            if current_yaml['kind'] == 'ConfigMap':
1✔
1512
                break
1✔
1513

1514
        # Generate and convert containers deployment to base64 and add
1515
        # as configMap entry to the operator deployment.
1516
        config["kube_config"]["deployment_base64"] = base64.b64encode(temp.encode('ascii')).decode('ascii')
1✔
1517
        if config["flavor"] != "k8s-overlay":
1✔
1518
            oper_cmap_template = get_jinja_template('aci-operators-configmap.yaml')
1✔
1519
            cmap_temp = ''.join(oper_cmap_template.stream(config=config))
1✔
1520

1521
            op_template = get_jinja_template('aci-operators.yaml')
1✔
1522
            output_from_parsed_template = op_template.render(config=config)
1✔
1523

1524
            # Generate acioperator CRD from template and add it to top
1525
            op_crd_template = get_jinja_template('aci-operators-crd.yaml')
1✔
1526
            op_crd_output = op_crd_template.render(config=config)
1✔
1527

1528
            acc_provision_crd_template = get_jinja_template('acc-provision-crd.yaml')
1✔
1529
            acc_provision_crd_temp = ''.join(acc_provision_crd_template.stream(config=config))
1✔
1530
            acc_provision_oper_cmap_template = get_jinja_template('acc-provision-configmap.yaml')
1✔
1531
            acc_provision_oper_cmap_temp = ''.join(acc_provision_oper_cmap_template.stream(config=config))
1✔
1532
            new_parsed_yaml = [op_crd_output] + parsed_temp[:cmap_idx] + [acc_provision_crd_temp] + \
1✔
1533
                [cmap_temp] + [acc_provision_oper_cmap_temp] + parsed_temp[cmap_idx:] + [output_from_parsed_template]
1534

1535
            new_deployment_file = '---'.join(new_parsed_yaml)
1✔
1536
        else:
1537
            new_deployment_file = temp
1✔
1538

1539
        if args.cko:
1✔
1540
            netop_version = args.cko_version
1✔
1541
            netopConfig = dict(config)
1✔
1542
            netopConfig = get_cko_mode(args, netopConfig)
1✔
1543
            if "connectivity_checker" in netopConfig.keys():
1✔
1544
                netopConfig = generate_nettools_deployment_files(args, netopConfig)
1✔
1545
            if "gitops" in netopConfig.keys():
1✔
1546
                netopConfig = generate_argocd_deployment_files(args, netopConfig)
1✔
1547
            network_operator_spec_template = get_jinja_template('cko/' + netop_version + '/netop-manifest.yaml')
1✔
1548
            network_operator_openshift_spec_template = get_jinja_template('cko/' + netop_version + '/netop-manifest-openshift.yaml')
1✔
1549
            network_operator_spec_output = network_operator_spec_template.render(config=config)
1✔
1550
            network_operator_openshift_spec_output = network_operator_openshift_spec_template.render(config=config)
1✔
1551

1552
            network_operator_CR_template = get_jinja_template('aci-installer-cr.yaml')
1✔
1553
            base64_encoded_cko_aci_spec = base64.b64encode(new_deployment_file.encode('ascii')).decode('ascii')
1✔
1554
            netopConfig["aci_config"]["operator_version"] = netop_version
1✔
1555
            netopConfig["aci_config"]["cni_flavor_version"] = config["registry"]["version"]
1✔
1556
            netopConfig["aci_config"]["base64_encoded_cko_aci_spec"] = base64_encoded_cko_aci_spec
1✔
1557
            network_operator_platform_CR_template = get_jinja_template('platform-installer-cr.yaml')
1✔
1558
            network_operator_platform_CR = network_operator_platform_CR_template.render(config=netopConfig)
1✔
1559
            network_operator_CR_output = network_operator_CR_template.render(config=netopConfig)
1✔
1560
            network_operator_yaml = network_operator_spec_output + "\n---\n" + network_operator_CR_output + "\n---\n" + network_operator_platform_CR
1✔
1561
            network_operator_openshift_yaml = network_operator_openshift_spec_output + "\n---\n" + network_operator_CR_output + \
1✔
1562
                "\n---\n" + network_operator_platform_CR
1563

1564
            # The next few files are to generate tar file with each
1565
            # containers and operator yaml in separate file. This is needed
1566
            # by OpenShift >= 4.3. If tar_path is provided(-z), we save the tar
1567
            # with that filename, else we use the provided containers
1568
            # deployment filepath. If neither is provided, we don't generate
1569
            # the tar.
1570
            if "openshift" in config["flavor"]:
1✔
1571
                if tar_path == "-":
1✔
1572
                    tar_path = "/dev/null"
×
1573
                else:
1574
                    deployment_docs = yaml.load_all(new_deployment_file, Loader=yaml.SafeLoader)
1✔
1575
                    print("writing the deployment tar file")
1✔
1576
                    generate_operator_tar(tar_path, deployment_docs, config)
1✔
1577

1578
            print("writing the deployment file")
1✔
1579
            if "openshift" in config["flavor"]:
1✔
1580
                with open(operator_output, "w") as fh:
1✔
1581
                    fh.write(network_operator_openshift_yaml)
1✔
1582
                return True
1✔
1583
            with open(operator_output, "w") as fh:
1✔
1584
                fh.write(network_operator_yaml)
1✔
1585
            return True
1✔
1586

1587
        if operator_output != sys.stdout:
1✔
1588
            with open(operator_output, "w") as fh:
1✔
1589
                fh.write(new_deployment_file)
1✔
1590
        else:
1591
            op_template.stream(config=config).dump(operator_output)
×
1592

1593
        if config["flavor"] != "k8s-overlay":
1✔
1594
            # The next few files are to generate tar file with each
1595
            # containers and operator yaml in separate file. This is needed
1596
            # by OpenShift >= 4.3. If tar_path is provided(-z), we save the tar
1597
            # with that filename, else we use the provided containers
1598
            # deployment filepath. If neither is provided, we don't generate
1599
            # the tar.
1600
            if tar_path == "-":
1✔
1601
                tar_path = "/dev/null"
×
1602
            else:
1603
                deployment_docs = yaml.load_all(new_deployment_file, Loader=yaml.SafeLoader)
1✔
1604
                generate_operator_tar(tar_path, deployment_docs, config)
1✔
1605

1606
            op_cr_template = get_jinja_template('aci-operators-cr.yaml')
1✔
1607
            if operator_cr_output and operator_cr_output != "/dev/null":
1✔
1608
                if operator_cr_output == "-":
1✔
1609
                    operator_cr_output = "/dev/null"
×
1610
                else:
1611
                    info("Writing kubernetes ACI operator CR to %s" % operator_cr_output)
1✔
1612
            op_cr_template.stream(config=config).dump(operator_cr_output)
1✔
1613

1614
        info("Writing kubernetes infrastructure YAML to %s" % outname)
1✔
1615
        if config["flavor"] != "k8s-overlay":
1✔
1616
            info("Writing ACI CNI operator tar to %s" % tar_path)
1✔
1617
        info("Apply infrastructure YAML using:")
1✔
1618
        info("  %s apply -f %s" %
1✔
1619
             (config["kube_config"]["kubectl"], applyname))
1620
        if not config["provision"]["upgrade_cluster"]:
1✔
1621
            info("Delete stale objects from older deployments using:")
1✔
1622
            info("  %s -n %s delete %s -l "
1✔
1623
                 " 'aci-containers-config-version,"
1624
                 "aci-containers-config-version notin (%s)'" %
1625
                 (config["kube_config"]["kubectl"],
1626
                  config["kube_config"]["system_namespace"],
1627
                  ",".join(kube_objects),
1628
                  str(config["registry"]["configuration_version"])))
1629
    return config
1✔
1630

1631

1632
def generate_apic_config(flavor_opts, config, prov_apic, apic_file):
1✔
1633
    apic = None
1✔
1634
    if prov_apic is not None:
1✔
1635
        apic = get_apic(config)
×
1636
    configurator = ApicKubeConfig(config, apic)
1✔
1637
    for k, v in flavor_opts.get("apic", {}).items():
1✔
1638
        setattr(configurator, k, v)
1✔
1639
    apic_config = configurator.get_config(config["aci_config"]["apic_version"])
1✔
1640
    if apic_file:
1✔
1641
        if apic_file == "-":
1✔
1642
            info("Writing apic configuration to \"STDOUT\"")
×
1643
            ApicKubeConfig.save_config(apic_config, sys.stdout)
×
1644
        else:
1645
            info("Writing apic configuration to \"%s\"" % apic_file)
1✔
1646
            with open(apic_file, 'w') as outfile:
1✔
1647
                ApicKubeConfig.save_config(apic_config, outfile)
1✔
1648

1649
    ret = True
1✔
1650
    sync_login = config["aci_config"]["sync_login"]["username"]
1✔
1651
    if prov_apic is not None:
1✔
1652
        apic = get_apic(config)
×
1653
        if apic is not None:
×
1654
            if prov_apic is True:
×
1655
                info("Provisioning configuration in APIC")
×
1656
                apic.provision(apic_config, sync_login)
×
1657
            if prov_apic is False:
×
1658
                info("Unprovisioning configuration in APIC")
×
1659
                system_id = config["aci_config"]["system_id"]
×
1660
                cluster_l3out_vrf_details = configurator.get_cluster_l3out_vrf_details()
×
1661
                cluster_l3out_tenant = cluster_l3out_vrf_details["tenant"]
×
1662
                vrf_tenant = config["aci_config"]["vrf"]["tenant"]
×
1663
                cluster_tenant = config["aci_config"]["cluster_tenant"]
×
1664
                old_naming = config["aci_config"]["use_legacy_kube_naming_convention"]
×
1665
                if is_calico_flavor(config["flavor"]):
×
1666
                    l3out_name = config["aci_config"]["cluster_l3out"]["name"]
×
1667
                    apic.unprovision(apic_config, system_id, cluster_l3out_tenant, vrf_tenant, cluster_tenant, old_naming, config,
×
1668
                                     l3out_name=l3out_name, cluster_l3out_vrf_details=cluster_l3out_vrf_details)
1669
                else:
1670
                    apic.unprovision(apic_config, system_id, cluster_l3out_tenant, vrf_tenant, cluster_tenant, old_naming, config)
×
1671
            ret = False if apic.errors > 0 else True
×
1672
    return ret
1✔
1673

1674

1675
def get_apic(config):
1✔
1676
    apic_host = config["aci_config"]["apic_hosts"][0]
1✔
1677
    apic_username = config["aci_config"]["apic_login"]["username"]
1✔
1678
    apic_password = config["aci_config"]["apic_login"]["password"]
1✔
1679
    timeout = config["aci_config"]["apic_login"]["timeout"]
1✔
1680
    debug = config["provision"]["debug_apic"]
1✔
1681
    save_to = config["provision"]["save_to"]
1✔
1682
    capic = config["aci_config"]["capic"]
1✔
1683

1684
    if config["aci_config"]["apic_proxy"]:
1✔
1685
        apic_host = config["aci_config"]["apic_proxy"]
×
1686
    apic = Apic(
1✔
1687
        apic_host, apic_username, apic_password,
1688
        timeout=timeout, debug=debug, capic=capic, save_to=save_to)
1689
    if apic.cookies is None:
1✔
1690
        return None
×
1691
    return apic
1✔
1692

1693

1694
class CustomFormatter(argparse.HelpFormatter):
1✔
1695
    def _format_action_invocation(self, action):
1✔
1696
        ret = super(CustomFormatter, self)._format_action_invocation(action)
1✔
1697
        ret = ret.replace(' ,', ',')
1✔
1698
        ret = ret.replace(' file,', ',')
1✔
1699
        ret = ret.replace(' name,', ',')
1✔
1700
        ret = ret.replace(' pass,', ',')
1✔
1701
        return ret
1✔
1702

1703

1704
def parse_args(show_help):
1✔
1705
    version = 'Unknown'
1✔
1706
    try:
1✔
1707
        version = pkg_resources.require("acc_provision")[0].version
1✔
1708
    except pkg_resources.DistributionNotFound:
×
1709
        # ignore, expected in case running from source
1710
        pass
×
1711

1712
    parser = argparse.ArgumentParser(
1✔
1713
        description='Provision an ACI/Kubernetes installation',
1714
        formatter_class=CustomFormatter,
1715
    )
1716
    parser.add_argument(
1✔
1717
        '-v', '--version', action='version', version=version)
1718
    parser.add_argument(
1✔
1719
        '--release', action='store_true', default=False, help='print git release info')
1720
    parser.add_argument(
1✔
1721
        '--debug', action='store_true', default=False,
1722
        help='enable debug')
1723
    parser.add_argument(
1✔
1724
        '--sample', action='store_true', default=False,
1725
        help='print a sample input file with fabric configuration')
1726
    parser.add_argument(
1✔
1727
        '-c', '--config', default="-", metavar='file',
1728
        help='input file with your fabric configuration')
1729
    parser.add_argument(
1✔
1730
        '-o', '--output', default="-", metavar='file',
1731
        help='output file for your kubernetes deployment')
1732
    parser.add_argument(
1✔
1733
        '-z', '--output_tar', default="-", metavar='file',
1734
        help='output zipped tar file for your kubernetes deployment')
1735
    parser.add_argument(
1✔
1736
        '-r', '--aci_operator_cr', default="-", metavar='file',
1737
        help='output file for your aci-operator deployment custom resource')
1738
    parser.add_argument(
1✔
1739
        '-a', '--apic', action='store_true', default=False,
1740
        help='create/validate the required APIC resources')
1741
    parser.add_argument(
1✔
1742
        '-d', '--delete', action='store_true', default=False,
1743
        help='delete the APIC resources that would have been created')
1744
    parser.add_argument(
1✔
1745
        '-u', '--username', default=None, metavar='name',
1746
        help='apic-admin username to use for APIC API access')
1747
    parser.add_argument(
1✔
1748
        '-p', '--password', default=None, metavar='pass',
1749
        help='apic-admin password to use for APIC API access')
1750
    parser.add_argument(
1✔
1751
        '-w', '--timeout', default=None, metavar='timeout',
1752
        help='wait/timeout to use for APIC API access')
1753
    parser.add_argument(
1✔
1754
        '--list-flavors', action='store_true', default=False,
1755
        help='list available configuration flavors')
1756
    parser.add_argument(
1✔
1757
        '-f', '--flavor', default=None, metavar='flavor',
1758
        help='set configuration flavor.  Example: openshift-3.6')
1759
    parser.add_argument(
1✔
1760
        '-t', '--version-token', default=None, metavar='token',
1761
        help='set a configuration version token. Default is UUID.')
1762
    parser.add_argument(
1✔
1763
        '--apic-proxy', default=None, metavar='addr',
1764
        help=argparse.SUPPRESS)
1765
    parser.add_argument(
1✔
1766
        '--test-data-out', default=None, metavar='file',
1767
        help='capture apic responses for test replay. E.g. ../testdata/apic_xx.json')
1768
    parser.add_argument(
1✔
1769
        '--skip-kafka-certs', action='store_true', default=False,
1770
        help='skip kafka certificate generation')
1771
    parser.add_argument(
1✔
1772
        '--upgrade', action='store_true', default=False,
1773
        help='generate kubernetes deployment file for cluster upgrade')
1774
    parser.add_argument(
1✔
1775
        '--disable-multus', default='true', metavar='disable_multus',
1776
        help='true/false to disable/enable multus in cluster')
1777
    parser.add_argument(
1✔
1778
        '--flavor-version', default=None, metavar='flavor_version', help='CNI upgrade/downgrade')
1779
    parser.add_argument(
1✔
1780
        '--cko', default=False, action='store_true', help='generates deployment spec for CKO')
1781
    parser.add_argument(
1✔
1782
        '--cko-version', default="0.9.0", metavar='cko_version', help='netop-manager operator version')
1783
    parser.add_argument(
1✔
1784
        '--cko-mode', default="managed", metavar='cko_mode', help='netop-manager cni managed/unmanaged')
1785
    parser.add_argument(
1✔
1786
        '--netop-image-tag', default=None, metavar='netop_image_tag', help='network operator image tag')
1787
    # This argument is set to True and used internally by the acc-provision-operator when invoking
1788
    # acc-provision. It is not meant to be invoked directly by the user from stand-alone acc-provision
1789
    # and hence set to False by default here and suppressed as well.
1790
    parser.add_argument(
1✔
1791
        '--operator-mode', default=False,
1792
        help=argparse.SUPPRESS, metavar='operator_mode')
1793
    # If the input has no arguments, show help output and exit
1794
    if show_help:
1✔
1795
        parser.print_help(sys.stderr)
×
1796
        sys.exit(1)
×
1797

1798
    return parser.parse_args()
1✔
1799

1800

1801
def get_versions(versions_url):
1✔
1802
    global VERSIONS
1803
    try:
1✔
1804
        # try as a URL
1805
        res = requests.get(versions_url)
1✔
1806
        versions_yaml = yaml.safe_load(res)
×
1807
        info("Loading versions from URL: " + versions_url)
×
1808
        VERSIONS = versions_yaml['versions']
×
1809

1810
    except Exception:
1✔
1811
        try:
1✔
1812
            # try as a local file
1813
            with open(versions_url, 'r') as res:
1✔
1814
                versions_yaml = yaml.safe_load(res)
×
1815
                info("Loading versions from local file: " + versions_url)
×
1816
                VERSIONS = versions_yaml['versions']
×
1817
        except Exception:
1✔
1818
            info("Unable to load versions from path: " + versions_url)
1✔
1819

1820

1821
def check_overlapping_subnets(config):
1✔
1822
    """Check if subnets are overlapping."""
1823
    if is_calico_flavor(config["flavor"]):
1✔
1824
        subnet_info = {
1✔
1825
            "pod_subnet": config["net_config"]["pod_subnet"],
1826
            "node_subnet": config["net_config"]["node_subnet"],
1827
            "extern_dynamic": config["net_config"]["extern_dynamic"],
1828
            "cluster_svc_subnet": config["net_config"]["cluster_svc_subnet"]
1829
        }
1830
    else:
1831
        subnet_info = {
1✔
1832
            "pod_subnet": config["net_config"]["pod_subnet"],
1833
            "node_subnet": config["net_config"]["node_subnet"],
1834
            "extern_dynamic": config["net_config"]["extern_dynamic"],
1835
            "node_svc_subnet": config["net_config"]["node_svc_subnet"]
1836
        }
1837

1838
    # Don't have extern_static field set for OpenShift flavors
1839
    if not is_calico_flavor(config["flavor"]) and config["net_config"]["extern_static"]:
1✔
1840
        subnet_info["extern_static"] = config["net_config"]["extern_static"]
1✔
1841

1842
    for sub1, sub2 in combinations(subnet_info.values(), r=2):
1✔
1843
        # Checking if sub1 and sub2 are IPv4 or IPv6
1844
        rtr1, _ = sub1.split("/")
1✔
1845
        ip1 = ipaddress.ip_address(rtr1)
1✔
1846
        if ip1.version == 4:
1✔
1847
            net1, net2 = ipaddress.IPv4Network(sub1, strict=False), ipaddress.IPv4Network(sub2, strict=False)
1✔
1848
        else:
1849
            net1, net2 = ipaddress.IPv6Network(sub1, strict=False), ipaddress.IPv6Network(sub2, strict=False)
1✔
1850
        out = net1.overlaps(net2)
1✔
1851
        if out:
1✔
1852
            return False
1✔
1853
    return True
1✔
1854

1855

1856
def check_image_pull_secret(config):
1✔
1857
    # Check if the image_pull_secret is valid
1858
    image_pull_secret = config["registry"]["image_pull_secret"]
1✔
1859
    # This is the regex used by kubectl to validate objects names
1860
    pattern = "[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*"
1✔
1861
    if not re.fullmatch(pattern, str(image_pull_secret)):
1✔
1862
        return False
1✔
1863
    return True
×
1864

1865

1866
def provision(args, apic_file, no_random):
1✔
1867
    config_file = args.config
1✔
1868
    output_file = args.output
1✔
1869
    output_tar = args.output_tar
1✔
1870
    operator_cr_output_file = args.aci_operator_cr
1✔
1871
    upgrade_cluster = args.upgrade
1✔
1872

1873
    prov_apic = None
1✔
1874
    if args.apic:
1✔
1875
        prov_apic = True
1✔
1876
    if args.delete:
1✔
1877
        prov_apic = False
1✔
1878

1879
    timeout = None
1✔
1880
    if args.timeout:
1✔
1881
        try:
×
1882
            if int(args.timeout) >= 0:
×
1883
                timeout = int(args.timeout)
×
1884
        except ValueError:
×
1885
            # ignore that timeout value
1886
            warn("Invalid timeout value ignored: '%s'" % timeout)
×
1887

1888
    generate_cert_data = True
1✔
1889
    if args.delete:
1✔
1890
        output_file = "/dev/null"
1✔
1891
        output_tar = "/dev/null"
1✔
1892
        operator_cr_output_file = "/dev/null"
1✔
1893
        generate_cert_data = False
1✔
1894

1895
    if args.operator_mode:
1✔
1896
        generate_cert_data = False
1✔
1897

1898
    # Print sample, if needed
1899
    if args.sample:
1✔
1900
        generate_sample(sys.stdout, args.flavor)
1✔
1901
        return True
1✔
1902

1903
    # command line config
1904
    config = {
1✔
1905
        "aci_config": {
1906
            "apic_login": {
1907
            },
1908
            "capic": False,
1909
            "apic_proxy": args.apic_proxy,
1910
        },
1911
        "provision": {
1912
            "prov_apic": prov_apic,
1913
            "debug_apic": args.debug,
1914
            "save_to": args.test_data_out,
1915
            "skip-kafka-certs": args.skip_kafka_certs,
1916
        },
1917
        "operator_mode": args.operator_mode,
1918
    }
1919

1920
    if upgrade_cluster:
1✔
1921
        output_tar = "/dev/null"
1✔
1922
        config["provision"]["upgrade_cluster"] = True
1✔
1923

1924
    # infra_vlan is not part of command line input, but we do
1925
    # pass it as a command line arg in unit tests to pass in
1926
    # configuration which would otherwise be discovered from
1927
    # the APIC
1928
    config["discovered"] = {"infra_vlan": getattr(args, "infra_vlan", None)}
1✔
1929

1930
    flavor = args.flavor
1✔
1931
    if args.username:
1✔
1932
        config["aci_config"]["apic_login"]["username"] = args.username
1✔
1933
    elif os.environ.get('ACC_PROVISION_USERNAME'):
×
1934
        config["aci_config"]["apic_login"]["username"] = os.environ.get('ACC_PROVISION_USERNAME')
×
1935

1936
    if args.password:
1✔
1937
        config["aci_config"]["apic_login"]["password"] = args.password
1✔
1938
    elif os.environ.get('ACC_PROVISION_PASS'):
1✔
1939
        config["aci_config"]["apic_login"]["password"] = os.environ.get('ACC_PROVISION_PASS')
×
1940

1941
    config["aci_config"]["apic_login"]["timeout"] = timeout
1✔
1942

1943
    # Create config
1944
    user_config = config_user(config_file)
1✔
1945
    if 'aci_config' in user_config and 'use_legacy_kube_naming_convention' in user_config['aci_config'] and 'tenant' in user_config['aci_config']:
1✔
1946
        err("Not allowed to set tenant and use_legacy_kube_naming_convention fields at the same time")
1✔
1947
        return False
1✔
1948

1949
    if flavor == "cloud" and 'use_legacy_kube_naming_convention' in user_config['aci_config']:
1✔
1950
        err("use_legacy_kube_naming_convention not allowed in cloud flavor")
×
1951
        return False
×
1952

1953
    if user_config:
1✔
1954
        if 'versions_url' in user_config and 'path' in user_config['versions_url']:
1✔
1955
            versions_url = user_config['versions_url']['path']
1✔
1956
            get_versions(versions_url)
1✔
1957

1958
    config['user_config'] = copy.deepcopy(user_config)
1✔
1959
    deep_merge(config, user_config)
1✔
1960

1961
    if flavor in FLAVORS:
1✔
1962
        info("Using configuration flavor " + flavor)
1✔
1963
        deep_merge(config, {"flavor": flavor})
1✔
1964
        if "config" in FLAVORS[flavor]:
1✔
1965
            deep_merge(config, FLAVORS[flavor]["config"])
1✔
1966
        if "default_version" in FLAVORS[flavor]:
1✔
1967
            deep_merge(config, {
1✔
1968
                "registry": {
1969
                    "version": FLAVORS[flavor]["default_version"]
1970
                }
1971
            })
1972
            if args.flavor_version is not None:
1✔
1973
                config["registry"]["version"] = args.flavor_version
×
1974
            if args.netop_image_tag is not None:
1✔
1975
                config["registry"]["network_operator_version"] = args.netop_image_tag
×
1976
    else:
1977
        err("Unknown flavor %s" % flavor)
×
1978
        return False
×
1979
    flavor_opts = FLAVORS[flavor].get("options", DEFAULT_FLAVOR_OPTIONS)
1✔
1980

1981
    deep_merge(config, config_default())
1✔
1982

1983
    if (args.disable_multus == 'false'):
1✔
1984
        config['multus']['disable'] = False
×
1985

1986
    if config["registry"]["version"] in VERSIONS:
1✔
1987
        deep_merge(config,
1✔
1988
                   {"registry": VERSIONS[config["registry"]["version"]]})
1989

1990
    if args.cko and args.cko_version in CKO_VERSIONS:
1✔
1991
        deep_merge(config,
1✔
1992
                   {"registry": CKO_VERSIONS[args.cko_version]})
1993

1994
    # Discoverd state (e.g. infra-vlan) overrides the config file data
1995
    if isOverlay(flavor):
1✔
1996
        config["net_config"]["infra_vlan"] = None
1✔
1997
    else:
1998
        config = deep_merge(config_discover(config, prov_apic), config)
1✔
1999

2000
    # Validate APIC access
2001
    if prov_apic is not None:
1✔
2002
        apic = get_apic(config)
1✔
2003
        apic_version = apic.apic_version
1✔
2004
        if apic is None:
1✔
2005
            err("Not able to login to the APIC, please check username or password")
×
2006
            return False
×
2007
        config["aci_config"]["apic_version"] = apic_version
1✔
2008

2009
    # Validate config
2010
    try:
1✔
2011
        if not config_validate(flavor_opts, config):
1✔
2012
            err("Please fix configuration and retry.")
1✔
2013
            return False
1✔
2014
    except Exception as ex:
×
2015
        print("%s") % ex
×
2016

2017
    # Verify if overlapping subnet present in config input file
2018
    if not check_overlapping_subnets(config):
1✔
2019
        err("overlapping subnets found in configuration input file")
1✔
2020
        return False
1✔
2021

2022
    # Verify that image_pull_secret is a valid K8s secret name and not a YAML string
2023
    if "registry" in config.keys() and "image_pull_secret" in config["registry"]:
1✔
2024
        if not check_image_pull_secret(config):
1✔
2025
            err("Invalid image_pull_secret value, it must be a valid DNS subdomain name.")
1✔
2026
            return False
1✔
2027

2028
    # Adjust config based on convention/apic data
2029
    adj_config = config_adjust(args, config, prov_apic, no_random)
1✔
2030
    deep_merge(config, adj_config)
1✔
2031

2032
    if is_calico_flavor(config["flavor"]) and not calico_config_validate_preexisting(config, prov_apic):
1✔
2033
        return False
×
2034
    else:
2035
        # Advisory checks, including apic checks, ignore failures
2036
        if not config_validate_preexisting(config, prov_apic):
1✔
2037
            # Ignore failures, this check is just advisory for now
2038
            pass
2039

2040
    # generate key and cert if needed
2041
    username = config["aci_config"]["sync_login"]["username"]
1✔
2042
    certfile = config["aci_config"]["sync_login"]["certfile"]
1✔
2043
    keyfile = config["aci_config"]["sync_login"]["keyfile"]
1✔
2044
    key_data, cert_data = None, None
1✔
2045
    reused = True
1✔
2046
    if generate_cert_data:
1✔
2047
        if not exists(certfile) or not exists(keyfile):
1✔
2048
            if is_calico_flavor(config["flavor"]):
1✔
2049
                if args.cko:
×
2050
                    info("Generating certs for network-operator")
×
2051
                else:
2052
                    info("Generating certs for calico based kubernetes controller")
×
2053
            else:
2054
                info("Generating certs for kubernetes controller")
1✔
2055
        else:
2056
            if is_calico_flavor(config["flavor"]):
1✔
2057
                if args.cko:
1✔
2058
                    info("Reusing existing certs for network-operator")
1✔
2059
                else:
2060
                    info("Reusing existing certs for calico based kubernetes controller")
1✔
2061
            else:
2062
                info("Reusing existing certs for kubernetes controller")
1✔
2063
        key_data, cert_data, reused = generate_cert(username, certfile, keyfile)
1✔
2064
    config["aci_config"]["sync_login"]["key_data"] = key_data
1✔
2065
    config["aci_config"]["sync_login"]["cert_data"] = cert_data
1✔
2066
    config["aci_config"]["sync_login"]["cert_reused"] = reused
1✔
2067

2068
    if is_calico_flavor(config["flavor"]):
1✔
2069
        print("Using flavor: ", config["flavor"])
1✔
2070
        gen = flavor_opts.get("template_generator", generate_calico_deployment_files)
1✔
2071
        if not callable(gen):
1✔
2072
            gen = globals()[gen]
1✔
2073
        if args.cko:
1✔
2074
            gen(args, config, output_file)
1✔
2075
        else:
2076
            gen(args, config, output_tar)
1✔
2077

2078
        ret = generate_apic_config(flavor_opts, config, prov_apic, apic_file)
1✔
2079
        return ret
1✔
2080

2081
    if config["registry"]["aci_cni_operator_version"] is not None:
1✔
2082
        config["registry"]["aci_containers_operator_version"] = config["registry"]["aci_cni_operator_version"]
1✔
2083
        config["registry"]["acc_provision_operator_version"] = config["registry"]["aci_cni_operator_version"]
1✔
2084

2085
    if flavor in ["cloud", "aks", "eks"]:
1✔
2086
        if prov_apic is None:
1✔
2087
            return True
×
2088
        print("Configuring cAPIC")
1✔
2089
        config["aci_config"]["capic"] = True
1✔
2090

2091
        apic = get_apic(config)
1✔
2092
        if apic is None:
1✔
2093
            print("APIC login failed")
×
2094
            return False
×
2095
        cloud_prov = CloudProvision(apic, config, args)
1✔
2096
        return cloud_prov.Run(flavor_opts, generate_kube_yaml)
1✔
2097

2098
    # generate output files; and program apic if needed
2099
    gen = flavor_opts.get("template_generator", generate_kube_yaml)
1✔
2100
    if not callable(gen):
1✔
2101
        gen = globals()[gen]
1✔
2102
    gen(args, config, output_file, output_tar, operator_cr_output_file)
1✔
2103

2104
    if flavor == "k8s-overlay":
1✔
2105
        return True
1✔
2106

2107
    if args.cko and prov_apic is not None:
1✔
2108
        apic = get_apic(config)
×
2109
        mcast_pool = config["aci_config"]["vmm_domain"]["mcast_range"]
×
2110
        configured_mcast_pool = apic.get_mcast_pool(config["aci_config"]["vmm_domain"]["mcast_pool"])
×
2111
        if configured_mcast_pool and (mcast_pool != configured_mcast_pool):
×
2112
            info("Requested mcast_pool: %s does not match APIC configured: %s, using the latter" % (mcast_pool, configured_mcast_pool))
×
2113
            config["aci_config"]["vmm_domain"]["mcast_range"] = configured_mcast_pool
×
2114

2115
    if (config['net_config']['second_kubeapi_portgroup'] and prov_apic is not None):
1✔
2116
        apic = get_apic(config)
×
2117
        nested_vswitch_vlanpool = apic.get_vmmdom_vlanpool_tDn(config['aci_config']['vmm_domain']['nested_inside']['name'])
×
2118
        config['aci_config']['vmm_domain']['nested_inside']['vlan_pool'] = nested_vswitch_vlanpool
×
2119

2120
    ret = generate_apic_config(flavor_opts, config, prov_apic, apic_file)
1✔
2121
    return ret
1✔
2122

2123

2124
def main(args=None, apic_file=None, no_random=False):
1✔
2125
    # apic_file and no_random are used by the test functions
2126
    # len(sys.argv) == 1 when acc-provision is called w/o arguments
2127
    if args is None:
1✔
2128
        args = parse_args(len(sys.argv) == 1)
1✔
2129

2130
    if args.release:
1✔
2131
        try:
×
2132
            release_file_path = os.path.dirname(os.path.realpath(__file__)) + '/RELEASE-VERSION'
×
2133
            release = open(release_file_path, "r").read().rstrip()
×
2134
            print(release, file=sys.stderr)
×
2135
        except Exception:
×
2136
            info("Release info not present in this package")
×
2137
        return
×
2138

2139
    if args.list_flavors:
1✔
2140
        info("Available configuration flavors:")
1✔
2141
        for flavor in sorted(FLAVORS, key=lambda x: int(FLAVORS[x]['order'])):
1✔
2142
            if not FLAVORS[flavor]['hidden']:
1✔
2143
                desc = FLAVORS[flavor]["desc"]
1✔
2144
                if FLAVORS[flavor]["status"]:
1✔
2145
                    desc = desc + " [" + FLAVORS[flavor]["status"] + "]"
1✔
2146
                info(flavor + ":\t" + desc)
1✔
2147
        return
1✔
2148

2149
    if args.flavor is None:
1✔
2150
        err("Flavor not provided. Use -f to pass a flavor name, --list-flavors to see a list of supported flavors")
×
2151
        sys.exit(1)
×
2152

2153
    if args.flavor is not None and args.flavor not in FLAVORS:
1✔
2154
        err("Invalid configuration flavor: " + args.flavor)
×
2155
        sys.exit(1)
×
2156

2157
    if args.disable_multus is not None and (args.disable_multus != 'true' and args.disable_multus != 'false'):
1✔
2158
        err("Invalid configuration for disable_multus:" + args.disable_multus + " <Valid values: true/false>")
×
2159
        sys.exit(1)
×
2160

2161
    success = True
1✔
2162
    if args.debug:
1✔
2163
        success = provision(args, apic_file, no_random)
1✔
2164
    else:
2165
        try:
×
2166
            success = provision(args, apic_file, no_random)
×
2167
        except KeyboardInterrupt:
×
2168
            pass
×
2169
        except Exception as e:
×
2170
            success = False
×
2171
            err("%s: %s" % (e.__class__.__name__, e))
×
2172

2173
    if not success:
1✔
2174
        sys.exit(1)
1✔
2175

2176

2177
if __name__ == "__main__":
1✔
2178
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc