go.ligato.io/vpp-agent/v3@v3.5.0/tests/robot/libraries/kubernetes/kube_config_gen.py (about) 1 import yaml 2 import os 3 import math 4 5 """Generates YAML config files for use with kubernetes.""" 6 7 8 def mac_hex(number): 9 """Convert integer to hexadecimal for incrementing MAC addresses. 10 11 :param number: Integer number less than 100. 12 :type number: int 13 14 :returns: 2-digit hexadecimal representation of the input number. 15 :rtype: str 16 """ 17 18 temp = hex(number)[2:] 19 if number < 16: 20 temp = "0{0}".format(temp) 21 elif number > 99: 22 raise NotImplementedError( 23 "Incrementing MAC addresses only implemented up to 99.") 24 else: 25 pass 26 return temp 27 28 29 def yaml_replace_line(yaml_string, line_identifier, replacement): 30 """Replace a single line in the specified string. 31 32 :param yaml_string: String to replace in. 33 :param line_identifier: A string which uniquely identifies the target line. 34 :param replacement: String to replace the target line with. 35 :type yaml_string: str 36 :type line_identifier: str 37 :type replacement: str 38 39 :returns: Full string with the target line replaced. 40 :rtype: str 41 """ 42 for line in yaml_string.splitlines(): 43 if line_identifier in line: 44 whitespace = len(line) - len(line.lstrip(" ")) 45 return yaml_string.replace(line, "{spaces}{content}".format( 46 spaces=" " * whitespace, 47 content=replacement 48 )) 49 50 51 class YamlConfigGenerator(object): 52 """Config generator object.""" 53 def __init__(self, vnf_count, novpp_count, memif_per_vnf, 54 template_dir, 55 vswitch_image, vnf_image, sfc_image): 56 """Initialize config generator with topology parameters. 57 58 :param vnf_count: Number of VNF nodes. 59 :param novpp_count: Number of non-VPP nodes. 60 :param template_dir: Path to .yaml config templates. 61 :type vnf_count: int 62 :type novpp_count: int 63 :type template_dir: str 64 """ 65 66 self.vnf_count = int(vnf_count) 67 self.novpp_count = int(novpp_count) 68 self.memif_per_vnf = int(memif_per_vnf) 69 self.images = {"vswitch": vswitch_image, 70 "vnf": vnf_image, 71 "sfc": sfc_image} 72 self.templates = {} 73 self.output = {} 74 self.load_templates(template_dir) 75 76 if self.novpp_count % self.memif_per_vnf != 0: 77 raise NotImplementedError("Number of non-VPP containers must be" 78 " a multiple of bridge domain count.") 79 80 def load_templates(self, template_dir): 81 """Open yaml template files and save into templates dictionary. 82 83 :param template_dir: Path to directory containing the templates. 84 :type template_dir: str 85 """ 86 87 with open("{0}/sfc-k8.yaml".format(template_dir), "r") as sfc: 88 self.templates["sfc"] = sfc.read() 89 with open("{0}/vnf-vpp.yaml".format(template_dir), "r") as vnf: 90 self.templates["vnf"] = vnf.read() 91 with open("{0}/novpp.yaml".format(template_dir), "r") as novpp: 92 self.templates["novpp"] = novpp.read() 93 with open("{0}/vswitch.yaml".format(template_dir), "r") as vswitch: 94 self.templates["vswitch"] = vswitch.read() 95 96 def generate_config(self, output_path): 97 """Generate topology config .yaml files for Kubernetes. 98 99 :param output_path: Path to where the output files should be placed. 100 :type output_path: str 101 102 :returns: Generated topology in python dictionary format. 103 :rtype: dict 104 """ 105 topology = self.generate_sfc_config() 106 self.generate_vnf_config() 107 self.generate_novpp_config() 108 self.generate_vswitch_config() 109 self.write_config_files(output_path) 110 return topology 111 112 def generate_sfc_config(self): 113 """Generate SFC configuration YAML file based on the desired topology. 114 115 :returns: Generated topology in python dictionary format. 116 :rtype: dict 117 118 Topology description: 119 120 One vswitch node, running VPP and agent. 121 122 A number of VNFs equal to self.vnf_count, each running VPP and agent. 123 124 A number of non-VPP nodes equal to self.novpp_count. 125 126 A number of bridge domains configured on the vswitch equal 127 to self.memif_per_vnf. Each bridge contains one memif interface 128 to every VNF node. Non-VPP containers are distributed evenly across 129 the bridges and connected using veth->af_packet. 130 """ 131 entities_list = [] 132 133 for bridge_index in range(self.memif_per_vnf): 134 entity = { 135 "name": "L2Bridge-{0}".format(bridge_index), 136 "description": "Vswitch L2 bridge.", 137 "type": 3, 138 "bd_parms": { 139 "learn": True, 140 "flood": True, 141 "forward": True, 142 "unknown_unicast_flood": True 143 }, 144 "elements": [{ 145 "container": "agent_vpp_vswitch", 146 "port_label": "L2Bridge-{0}".format(bridge_index), 147 "etcd_vpp_switch_key": "agent_vpp_vswitch", 148 "type": 5 149 }] 150 } 151 152 for vnf_index in range(self.vnf_count): 153 new_element = { 154 "container": "vnf-vpp-{index}".format(index=vnf_index), 155 "port_label": "vnf{0}_memif{1}".format(vnf_index, 156 bridge_index), 157 "mac_addr": "02:01:01:01:{0}:{1}".format( 158 mac_hex(bridge_index + 1), 159 mac_hex(vnf_index + 1)), 160 "ipv4_addr": "192.168.{0}.{1}".format( 161 bridge_index + 1, 162 vnf_index + 1), 163 "l2fib_macs": [ 164 "192.168.{0}.{1}".format( 165 bridge_index + 1, 166 vnf_index + 1) 167 ], 168 "type": 2, 169 "etcd_vpp_switch_key": "agent_vpp_vswitch" 170 } 171 172 entity["elements"].append(new_element) 173 novpp_range = int(math.ceil( 174 float(self.novpp_count) / float(self.memif_per_vnf) 175 )) 176 177 bridge_novpp_index = self.vnf_count + 1 178 for novpp_index in range( 179 novpp_range * bridge_index, 180 (novpp_range * bridge_index) + novpp_range): 181 new_element = { 182 "container": "novpp-{index}".format(index=novpp_index), 183 "port_label": "veth_novpp{index}".format(index=novpp_index), 184 "mac_addr": "02:01:01:01:{0}:{1}".format( 185 mac_hex(bridge_index + 1), 186 mac_hex(novpp_index + self.vnf_count + 1)), 187 "ipv4_addr": "192.168.{0}.{1}".format( 188 bridge_index + 1, 189 bridge_novpp_index), 190 "type": 3, 191 "etcd_vpp_switch_key": "agent_vpp_vswitch" 192 } 193 entity["elements"].append(new_element) 194 bridge_novpp_index += 1 195 196 entities_list.append(entity) 197 198 output = "" 199 for line in yaml.dump( 200 entities_list, 201 default_flow_style=False 202 ).splitlines(): 203 output += " "*6 + line + "\n" 204 205 template = self.templates["sfc"] 206 if "---" in template: 207 sections = template.split("---") 208 for section in sections: 209 if "sfc_entities:" in section: 210 output = template.replace(section, section + output) 211 else: 212 output = template + output 213 214 self.output["sfc"] = yaml_replace_line( 215 output, 216 "image:", 217 "image: {0}".format(self.images["sfc"])) 218 219 topology = [] 220 for bridge_segment in entities_list: 221 segment_topology = {"vnf": [], "novpp": []} 222 for element in bridge_segment["elements"]: 223 if element["container"].startswith("vnf-vpp"): 224 segment_topology["vnf"].append( 225 {"name": element["container"], 226 "ip": element["ipv4_addr"]}) 227 elif element["container"].startswith("novpp"): 228 segment_topology["novpp"].append( 229 {"name": element["container"], 230 "ip": element["ipv4_addr"]}) 231 topology.append(segment_topology) 232 return topology 233 234 def generate_vnf_config(self): 235 """Read VNF configuration YAML file and modify pod replication count 236 and image name. 237 """ 238 239 template = self.templates["vnf"] 240 output = yaml_replace_line( 241 template, 242 "replicas:", 243 "replicas: {0}".format(self.vnf_count)) 244 output = yaml_replace_line( 245 output, 246 "image:", 247 "image: {0}".format(self.images["vnf"])) 248 self.output["vnf"] = output 249 250 def generate_novpp_config(self): 251 """Read non-VPP configuration YAML file and modify pod replication count. 252 """ 253 254 template = self.templates["novpp"] 255 output = yaml_replace_line( 256 template, 257 "replicas:", 258 "replicas: {0}".format(self.novpp_count)) 259 self.output["novpp"] = output 260 261 def generate_vswitch_config(self): 262 """Read vswitch configuration yaml file and modify image name. 263 """ 264 template = self.templates["vswitch"] 265 output = yaml_replace_line( 266 template, 267 "image:", 268 "image: {0}".format(self.images["vswitch"])) 269 self.output["vswitch"] = output 270 271 def write_config_files(self, output_path): 272 """Write the generated config files to the specified directory. 273 274 :param output_path: Path to write files to. 275 :type output_path: str 276 """ 277 if not os.path.exists(output_path): 278 os.makedirs(output_path) 279 280 with open("{0}/sfc.yaml".format(output_path), "w") as sfc: 281 sfc.write(self.output["sfc"]) 282 with open("{0}/vnf.yaml".format(output_path), "w") as vnf: 283 vnf.write(self.output["vnf"]) 284 with open("{0}/novpp.yaml".format(output_path), "w") as novpp: 285 novpp.write(self.output["novpp"]) 286 with open("{0}/vswitch.yaml".format(output_path), "w") as vswitch: 287 vswitch.write(self.output["vswitch"]) 288 289 290 def generate_config(vnf_count, novpp_count, memif_per_vnf, 291 template_path, output_path, 292 vswitch_image, vnf_image, sfc_image): 293 """Generate YAML config using the arguments and templates provided. Write 294 generated files to the specified path.""" 295 generator = YamlConfigGenerator( 296 vnf_count, novpp_count, memif_per_vnf, 297 template_path, 298 vswitch_image, vnf_image, sfc_image) 299 300 topology = generator.generate_config(output_path) 301 return topology