import time from pprint import pprint from random import randint from geometry import Rect, Group from LANforge import LFUtils from base_profile import BaseProfile class VRProfile(BaseProfile): Default_Margin = 15 # margin between routers and router connections Default_VR_height = 250 Default_VR_width = 50 """ Virtual Router profile """ def __init__(self, local_realm, debug=False): super().__init__(local_realm=local_realm, debug=debug) self.vr_eid = None self.vr_name = None # self.created_rdds = [] self.cached_vrcx = {} self.cached_routers = {} # self.vrcx_data = { # 'shelf': 1, # 'resource': 1, # 'vr-name': None, # 'local_dev': None, # outer rdd # 'remote_dev': None, # inner rdd # "x": 200+ran, # "y": 0, # "width": 10, # "height": 10, # 'flags': 0, # "subnets": None, # "nexthop": None, # "vrrp_ip": "0.0.0.0" # } # # self.set_port_data = { # "shelf": 1, # "resource": 1, # "port": None, # "ip_addr": None, # "netmask": None, # "gateway": None # } """ https://unihd-cag.github.io/simple-geometry/reference/rect.html """ def get_netsmith_bounds(self, resource=None, debug=False): if (resource is None) or (resource < 1): raise ValueError("get_netsmith_bounds wants resource id") debug |= self.debug occupied_area = self.get_occupied_area(resource=resource, debug=debug) return Rect(x=0, y=0, height=occupied_area.height, width=occupied_area.width) def get_all_vrcx_bounds(self, resource=None, debug=False): """ Computes bounds of all free vrcx ports but omits Virtual Routers :param resource: :param debug: :return: rectangle encompasing all free vrcx ports or None """ if (resource is None) or (resource < 1): raise ValueError("get_netsmith_bounds wants resource id") vrcx_map = self.vrcx_list(resource=resource, debug=debug) rect_list = [] for eid,item in vrcx_map.items(): rect_list.append(self.vr_to_rect(item)) if len(rect_list) < 1: return None bounding_group = Group() for item in rect_list: bounding_group.append(item) bounding_group.update() return Rect(x=bounding_group.x, y=bounding_group.y, width=bounding_group.width, height=bounding_group.height) def vr_eid_to_url(self, eid_str=None, debug=False): debug |= self.debug if (eid_str is None) or ("" == eid_str) or (eid_str.index(".") < 1): raise ValueError("vr_eid_to_url cannot read eid[%s]" % eid_str) hunks = eid_str.split(".") if len(hunks) > 3: return "/vr/1/%s/%s" % (hunks[1], hunks[2]) if len(hunks) > 2: return "/vr/1/%s/%s" % (hunks[1], hunks[2]) return "/vr/1/%s/%s" % (hunks[0], hunks[1]) # probably a short eid def vr_to_rect(self, vr_dict=None, debug=False): debug |= self.debug if vr_dict is None: raise ValueError(__name__+": vr_dict should not be none") if debug: pprint(("vr_dict: ", vr_dict)) if "x" not in vr_dict: if "eid" not in vr_dict: raise ValueError("vr_to_rect: Unable to determine eid of rectangle to query") router_url = self.vr_eid_to_url(vr_dict["eid"]) expanded_router_j = self.json_get(router_url, debug_=debug) if expanded_router_j is None: raise ValueError("vr_to_rect: unable to determine vr using url [%s]"%router_url) vr_dict = expanded_router_j return self.to_rect(x=int(vr_dict["x"]), y=int(vr_dict["y"]), width=int(vr_dict["width"]), height=int(vr_dict["height"])) def to_rect(self, x=0, y=0, width=10, height=10): rect = Rect(x=int(x), y=int(y), width=int(width), height=int(height)) return rect def get_occupied_area(self, resource=1, debug=False): debug |= self.debug if (resource is None) or (resource == 0) or ("" == resource): raise ValueError("resource needs to be a number greater than 1") router_map = self.router_list(resource=resource, debug=debug) vrcx_map = self.vrcx_list(resource=resource, debug=debug) rect_list = [] for eid,item in router_map.items(): rect_list.append(self.vr_to_rect(item)) for eid,item in vrcx_map.items(): rect_list.append(self.vr_to_rect(item)) if len(rect_list) < 1: return None bounding_group = Group() for item in rect_list: #if debug: # pprint(("item:", item)) bounding_group.append(item) bounding_group.update() if debug: pprint(("get_occupied_area: bounding_group:", bounding_group)) time.sleep(5) return Rect(x=bounding_group.x, y=bounding_group.y, width=bounding_group.width, height=bounding_group.height) def vrcx_list(self, resource=None, do_sync=False, fields=["eid","x","y","height","width"], debug=False): """ :param resource: :param do_sync: :param debug: :return: """ debug |= self.debug if (resource is None) or (resource == ""): raise ValueError(__name__+ ": resource cannot be blank") if do_sync or (self.cached_vrcx is None) or (len(self.cached_vrcx) < 1): self.sync_netsmith(resource=resource, debug=debug) fields_str = ",".join(fields) if debug: pprint([ ("vrcx_list: fields", fields_str), ("fields_str", fields_str) ]) time.sleep(5) list_of_vrcx = self.json_get("/vrcx/1/%s/list?fields=%s" % (resource, fields_str), debug_=debug) mapped_vrcx = LFUtils.list_to_alias_map(json_list=list_of_vrcx, from_element="router-connections", debug_=debug) self.cached_vrcx = mapped_vrcx return self.cached_vrcx def router_list(self, resource=None, do_refresh=True, fields=("eid", "x", "y", "height", "width"), debug=False): """ Provides an updated list of routers, and caches the results to self.cached_routers. Call this method again to update the cached list. :param resource: :param debug: :return: list of routers provided by /vr/1/{resource}?fields=eid,x,y,height,width """ debug |= self.debug fields_str = ",".join(fields) if (resource is None) or (resource == ""): raise ValueError(__name__+"; router_list needs valid resource parameter") if do_refresh or (self.cached_routers is None) or (len(self.cached_routers) < 1): list_of_routers = self.json_get("/vr/1/%s/list?%s" % (resource, fields_str), debug_=debug) mapped_routers = LFUtils.list_to_alias_map(json_list=list_of_routers, from_element="virtual-routers", debug_=debug) self.cached_routers = mapped_routers if debug: pprint(("cached_routers: ", self.cached_routers)) return self.cached_routers def create_rdd(self, resource=1, ip_addr=None, netmask=None, gateway=None, suppress_related_commands_=True, debug_=False): rdd_data = { "shelf": 1, "resource": resource, "port": "rdd0", "peer_ifname": "rdd1" } # print("creating rdd0") self.json_post("/cli-json/add_rdd", rdd_data, ) rdd_data = { "shelf": 1, "resource": resource, "port": "rdd1", "peer_ifname": "rdd0" } # print("creating rdd1") # self.json_post("/cli-json/add_rdd", # rdd_data, # suppress_related_commands_=suppress_related_commands_, # debug_=debug_) # # self.set_port_data["port"] = "rdd0" # self.set_port_data["ip_addr"] = gateway # self.set_port_data["netmask"] = netmask # self.set_port_data["gateway"] = gateway # self.json_post("/cli-json/set_port", # self.set_port_data, # suppress_related_commands_=suppress_related_commands_, # debug_=debug_) # # self.set_port_data["port"] = "rdd1" # self.set_port_data["ip_addr"] = ip_addr # self.set_port_data["netmask"] = netmask # self.set_port_data["gateway"] = gateway # self.json_post("/cli-json/set_port", # self.set_port_data, # suppress_related_commands_=suppress_related_commands_, # debug_=debug_) # # self.created_rdds.append("rdd0") # self.created_rdds.append("rdd1") def create_vrcx(self, resource=1, local_dev=None, remote_dev=None, subnets=None, nexthop=None, flags=0, suppress_related_commands_=True, debug_=False): if self.vr_name is None: raise ValueError("vr_name must be set. Current name: %s" % self.vr_name) vrcx_data = {} vrcx_data["resource"] = resource vrcx_data["vr_name"] = self.vr_name vrcx_data["local_dev"] = local_dev vrcx_data["remote_dev"] = remote_dev vrcx_data["subnets"] = subnets vrcx_data["nexthop"] = nexthop vrcx_data["flags"] = flags self.json_post("/cli-json/add_vrcx", vrcx_data, suppress_related_commands_=suppress_related_commands_, debug_=debug_) def find_position(self, eid=None, target_group=None, debug=False): debug |= self.debug """ get rectangular coordinates of VR or VRCX :param eid: :param target_group: :return: """ pass def next_available_area(self, go_right=True, go_down=False, debug=False, height=Default_VR_height, width=Default_VR_width): """ Returns a coordinate adjacent to the right or bottom of the presently occupied area with a 15px margin. :param go_right: look to right :param go_down: look to bottom :param debug: :return: rectangle that that next next VR could occupy """ debug |= self.debug # pprint(("used_vrcx_area:", used_vrcx_area)) # print("used x %s, y %s" % (used_vrcx_area.right+15, used_vrcx_area.top+15 )) if not (go_right or go_down): raise ValueError("Either go right or go down") used_vrcx_area = self.get_occupied_area(resource=self.vr_eid[1], debug=debug) next_area = None if (go_right): next_area = Rect(x=used_vrcx_area.right+15, y=15, width=50, height=250) elif (go_down): next_area = Rect(x=15, y=used_vrcx_area.bottom+15, width=50, height=250) else: raise ValueError("Unexpected positioning") # pprint(("next_rh_area", next_area)) # print("next_rh_area: right %s, top %s" % (next_area.right, next_area.top )) # print("next_rh_area: x %s, y %s" % (next_area.x, next_area.y )) return next_area def is_inside_virtual_router(self, resource=None, vrcx_rect=None, vr_eid=None, debug=False): """ :param resource: resource id :param vrcx_rect: port rectangle, probably 10px x 10px :param vr_eid: 'all' or router_eid, None is not acceptable :param debug: :return: True if area is inside listed virtual router(s) """ debug |= self.debug if (resource is None) or (resource == 0) or ("" == resource): raise ValueError("resource needs to be a number greater than 1") if (vrcx_rect is None) or type(vrcx_rect ) or ("" == resource): raise ValueError("resource needs to be a number greater than 1") router_list = self.router_list(resource=resource, debug=debug) #router_list = self.json_get("/vr/1/%s/%s?fields=eid,x,y,height,width") if (router_list is None) or (len(router_list) < 1): return False for router in router_list: rect = self.vr_to_rect(router) if (vr_eid == "all"): if (vrcx_rect.is_inside_of(rect)): return True else: if (vr_eid == router["eid"]) and (vrcx_rect.is_inside_of(rect)): return True return False def find_cached_router(self, resource=0, router_name=None, debug=False): debug |= self.debug if (resource is None) or (resource == 0): raise ValueError(__name__+": find_cached_router needs resource_id") if (router_name is None) or (router_name == ""): raise ValueError(__name__+": find_cached_router needs router_name") temp_eid_str = "1.%s.1.65535.%s" % (resource, router_name) if temp_eid_str in self.cached_routers.keys(): return self.cached_routers[temp_eid_str] temp_eid_str = "1.%s." % resource for router in self.cached_routers.keys(): if debug: pprint(("cached_router: ", router)) if router.startswith(temp_eid_str) and router.endswith(router_name): return self.cached_routers[router] if self.exit_on_error: raise ValueError("Unable to find cached router %s"%temp_eid_str) # exit(1) return None def add_vrcx_to_router(self, vrcx_name=None, vr_eid=None, debug=False): """ This is the Java psuedocode: def moveConnection: found_router = findRouter(x, y) if connection.getRouter() is None: if found_router.addConnection(): free_vrxc.remove(connection) connection.setPosition(x, y) return if found_router is not None: router.remove(connection) free_vrcx.add(connection) else: if found_router != router: router.remove(connection) found_router.add(connection) connection.setPosition(x, y) :param vrcx_name: :param vr_eid: :param debug: :return: new coordinates tuple """ debug |= self.debug if debug: pprint([("move_vrcx: vr_eid:", vr_eid), ("vrcx_name:", vrcx_name), ("self.cached_routers, check vr_eid:", self.cached_routers)]) time.sleep(5) if (vrcx_name is None) or (vrcx_name == ""): raise ValueError(__name__+"empty vrcx_name") if (vr_eid is None) or (vr_eid == ""): raise ValueError(__name__+"empty vr_eid") my_vrcx_name = vrcx_name if (vrcx_name.index(".") > 0): hunks = vrcx_name.split(".") my_vrcx_name = hunks[-1] if debug: pprint([("move_vrcx: vr_eid:", vr_eid), ("vrcx_name:", my_vrcx_name), ("self.cached_routers, check vr_eid:", self.cached_routers)]) router_val = self.find_cached_router(resource=vr_eid[1], router_name=vr_eid[2]) if router_val is None: self.router_list(resource=vr_eid[1], debug=debug) router_val = self.find_cached_router(resource=vr_eid[1], router_name=vr_eid[2]) if router_val is None: raise ValueError(__name__+": move_vrcx: No router matches %s"%vr_eid) new_bounds = self.vr_to_rect(vr_dict=router_val, debug=self.debug) new_location = self.vrcx_landing_spot(bounds=new_bounds, debug=debug) self.json_post("/cli-json/add_vrcx", { "shelf": 1, "resource": vr_eid[1], "vr_name": vr_eid[2], "local_dev": my_vrcx_name, "x": new_location[0], "y": new_location[1], }, debug_=debug) if debug: pprint([ ("router_val", router_val), ("new_bounds", new_bounds), ("new_location", new_location), ("my_vrcx_name",my_vrcx_name), ("router_val",router_val) ]) return new_location def move_vr(self, eid=None, go_right=True, go_down=False, upper_left_x=None, upper_left_y=None, debug=False): """ :param eid: virtual router EID :param go_right: select next area to the right of things :param go_down: select next area below all things :param upper_left_x: integer value for specific x :param upper_left_y: integer value for specific y :return: """ debug |= self.debug used_vrcx_area = self.get_occupied_area(resource=self.vr_eid[1], debug=debug) def sync_netsmith(self, resource=0, delay=0.1, debug=False): """ This syncs the netsmith window. Doing a sync could destroy any move changes you just did. :param resource: :param delay: :param debug: :return: """ debug |= self.debug if (resource is None) or (resource < 1): raise ValueError("sync_netsmith: resource must be > 0") self.json_post("/vr/1/%s/0" % resource, { "action": "sync" }, debug_=True) time.sleep(delay) def apply_netsmith(self, resource=0, delay=2, timeout=30, debug=False): debug |= self.debug if resource is None or resource < 1: raise ValueError("refresh_netsmith: resource must be > 0") self.json_post("/vr/1/%s/0" % resource, { "action":"apply" }, debug_=debug) # now poll vrcx to check state state = "UNSET" cur_time = int(time.time()) end_time = int(time.time()) + (1000 * timeout) while (cur_time < end_time) and (state != "OK"): time.sleep(delay) state = "UNSET" connection_list = self.vrcx_list(resource=resource, do_sync=True, fields=["eid", "netsmith-state"], debug=debug) vrcx_list_keys = list(connection_list.keys()) if debug: pprint([ ("vrcx_list", connection_list), ("keys", vrcx_list_keys)]) time.sleep(5) if (connection_list is not None) and (len(vrcx_list_keys) > 0): if (vrcx_list_keys[0] is not None) and ("netsmith-state" in connection_list[vrcx_list_keys[0]]): item = connection_list[vrcx_list_keys[0]] if debug: pprint(("item zero", item)) state = item["netsmith-state"] else: self.logg("apply_netsmith: no vrcx list?") if (state != "UNSET"): continue vr_list = self.router_list(resource=resource, fields=("eid", "netsmith-state"), debug=debug) if (vr_list is not None) or (len(vr_list) > 0): if (vr_list[0] is not None) and ("netsmith-state" in vr_list[0]): state = vr_list[0]["netsmith-state"] else: self.logg("apply_netsmith: no vr_list?") return state def refresh_netsmith(self, resource=0, delay=0.03, debug=False): """ This does not do a netsmith->Apply. This does not do a netsmith sync. Doing a sync could destroy any move changes you just did. This is VirtualRouterPanel.privDoUpdate: for vr in virtual_routers: vr.ensurePortsCreated() for connection in free_router_connections: connection.ensurePortsCreated() for vr in virtual_routers: ... remove connections that are unbound for vr in virtual_routers: remove vr that cannot be found for connections in vrcx: remove connection not found or remove endpoint from free list for router in virtual_routers: update vr for connection in free_connections: update connection apply_vr_cfg show_card show_vr show_vrcx :param resource: :param delay: :param debug: :return: """ debug |= self.debug if resource is None or resource < 1: raise ValueError("refresh_netsmith: resource must be > 0") self.json_post("/cli-json/apply_vr_cfg", { "shelf": 1, "resource": resource }, debug_=debug, suppress_related_commands_=True) self.json_post("/cli-json/show_resources", { "shelf": 1, "resource": resource }, debug_=debug) time.sleep(delay) self.json_post("/cli-json/show_vr", { "shelf": 1, "resource": resource, "router": "all" }, debug_=debug) self.json_post("/cli-json/show_vrcx", { "shelf": 1, "resource": resource, "cx_name": "all" }, debug_=debug) time.sleep(delay * 2) def create(self, vr_name=None, debug=False, suppress_related_commands=True): # Create vr debug |= self.debug if vr_name is None: raise ValueError("vr_name must be set. Current name: %s" % vr_name) self.vr_eid = self.parent_realm.name_to_eid(vr_name) if debug: pprint(("self.vr_eid:", self.vr_eid)) # determine a free area to place a router next_area = self.next_available_area(go_right=True, debug=debug) self.add_vr_data = { "alias": self.vr_eid[2], "shelf": 1, "resource": self.vr_eid[1], "x": int(next_area.x), "y": 15, "width": 50, "height": 250, "flags": 0 } self.json_post("/cli-json/add_vr", self.add_vr_data, suppress_related_commands_=suppress_related_commands, debug_=debug) self.json_post("/cli-json/apply_vr_cfg", { "shelf": 1, "resource": self.vr_eid[1] }, debug_=debug, suppress_related_commands_=suppress_related_commands) time.sleep(1) self.apply_netsmith(resource=self.vr_eid[1], debug=debug) def wait_until_vrcx_appear(self, resource=0, name_list=None, timeout_sec=120, debug=False): debug |= self.debug if (name_list is None) or (len(name_list) < 1): raise ValueError("wait_until_vrcx_appear wants a non-empty name list") num_expected = len(name_list) num_found = 0 import time cur_time = int(time.time()) end_time = cur_time + timeout_sec sync_time = 10 while (num_found < num_expected) and (cur_time <= end_time): time.sleep(1) cur_time = int(time.time()) num_found = 0 response = self.json_get("/vrcx/1/%s/list" % resource) if (response is None) or ("router-connections" not in response): raise ValueError("unable to find router-connections for /vrcx/1/%s/list" % resource) vrcx_list = LFUtils.list_to_alias_map(json_list=response, from_element='router-connections', debug_=debug) num_found = len(vrcx_list) if (num_found < 1): self.logg("wait_until_vrcx_appear: zero vrcx in vrcx_list") raise ValueError("zero router-connections for /vrcx/1/%s/list" % resource) num_found = 0 for name in name_list: name = "1.%s.%s" % (resource, name) if name in vrcx_list: num_found += 1 if num_found == len(name_list): return True # this is should not be done yet # self.refresh_netsmith(resource=resource, debug=debug) if ((end_time - cur_time) % sync_time) == 0: self.sync_netsmith(resource=resource, debug=debug) time.sleep(1) if (num_found > 0) and (num_found < num_expected): self.refresh_netsmith(resource=resource, debug=debug) if debug: pprint([("response", response), ("list", vrcx_list), ("num_found", num_found), ("num_expected", num_expected) ]) self.logg("wait_until_vrcx_appear: timeout waiting for router-connections to appear") return False def remove_vr(self, eid=None, refresh=True, debug=False, delay=0.05, die_on_error=False, suppress_related_commands=True): if (eid is None) or (eid[1] is None) or (eid[2] is None): self.logg("remove_vr: invalid eid: ", audit_list=[eid]) if (die_on_error): raise ValueError("remove_vr: invalid eid") data = { "shelf": 1, "resource": eid[1], "router_name": eid[2] } self.json_post("/cli-json/rm_vr", data, debug_=debug, suppress_related_commands_=suppress_related_commands) time.sleep(delay) if (refresh): self.json_post("/cli-json/nc_show_vr", { "shelf": 1, "resource": eid[1], "router": "all" }, debug_=debug, suppress_related_commands_=suppress_related_commands) self.json_post("/cli-json/nc_show_vrcx", { "shelf": 1, "resource": eid[1], "cx_name": "all" }, debug_=debug, suppress_related_commands_=suppress_related_commands) def cleanup(self, resource=0, vr_id=0, delay=0.3, debug=False): debug |= self.debug if self.vr_eid is None: return if resource == 0: resource = self.vr_eid[1] if vr_id == 0: vr_id = self.vr_eid[2] data = { "shelf": 1, "resource": resource, "router_name": vr_id } self.json_post("/cli-json/rm_vr", data, debug_=debug, suppress_related_commands_=True) time.sleep(delay) self.refresh_netsmith(resource=resource, debug=debug) def add_vrcx(self, vr_eid=None, connection_name_list=None, debug=False): if (vr_eid is None) or (vr_eid == ""): raise ValueError(__name__+": add_vrcx wants router EID") existing_list = self.vrcx_list(resource=vr_eid[1], do_sync=True) if debug: pprint([ ("vr_eid", vr_eid), ("connect_names", connection_name_list), ("existing_list", existing_list) ]) time.sleep(10) edited_connection_list = [] if type(connection_name_list) == str: edited_connection_list.append(connection_name_list) else: edited_connection_list = connection_name_list if debug: pprint(("my_list was:", edited_connection_list)) time.sleep(1) # for vrcx_name in my_list: edited_connection_list[:] = ["1.%s.%s"%(vr_eid[1], x) if (not x.startswith("1.")) else None for x in edited_connection_list] if debug: pprint(("my list is now:", edited_connection_list)) # at this point move the vrcx into the vr for vrcx_name in edited_connection_list: print ("Looking for old coordinates of %s"%vrcx_name) if debug: pprint([("vrcx_name:", vrcx_name), ("existing_list", existing_list.get(vrcx_name))]) if existing_list.get(vrcx_name) is None: if debug: pprint(("existing_list:", existing_list)) raise ValueError("Is vrcx mis-named?") old_coords = self.vr_to_rect( existing_list.get(vrcx_name)) if old_coords is None: raise ValueError("old coordinates for vrcx disappeared") new_coords = self.add_vrcx_to_router(vrcx_name=vrcx_name, vr_eid=vr_eid, debug=debug) if debug: print("coordinates were %s and will become %s "%(old_coords, new_coords)) def vrcx_landing_spot(self, bounds=None, debug=False): """ :param bounds: Rect we will select position within a 15px margin inside :param debug: :return: tuple (new_x, new_y) within bounds """ if (bounds is None): raise ValueError(__name__+": missing bounds to land vrcx") if not isinstance(bounds, Rect): raise ValueError(__name__+": bounds not of type Rect") pprint([("bounds.x", bounds.x), ("bounds.y", bounds.y), ("bounds.width", bounds.x+bounds.width), ("bounds.height", bounds.y+bounds.height) ]) new_x = randint(bounds.x+15, bounds.x+bounds.width-15) new_y = randint(bounds.y+15, bounds.y+bounds.height-15) return (new_x, new_y) ### ### ###