diff --git a/src/config/api-server/vnc_cfg_api_server/vnc_cfg_types.py b/src/config/api-server/vnc_cfg_api_server/vnc_cfg_types.py index f9b4add1161..1c854ae8e6c 100644 --- a/src/config/api-server/vnc_cfg_api_server/vnc_cfg_types.py +++ b/src/config/api-server/vnc_cfg_api_server/vnc_cfg_types.py @@ -1948,6 +1948,7 @@ def post_dbe_create(cls, tenant_name, obj_dict, db_conn): bindings = obj_dict.get('virtual_machine_interface_bindings', {}) kvps = bindings.get('key_value_pair', []) + # Manage baremetal provisioning here if kvps: kvp_dict = cls._kvp_to_dict(kvps) vnic_type = kvp_dict.get('vnic_type') @@ -1955,22 +1956,12 @@ def post_dbe_create(cls, tenant_name, obj_dict, db_conn): # Process only if port profile exists and physical links are specified phy_links = json.loads(kvp_dict.get('profile')) if phy_links and phy_links.get('local_link_information'): - link_information = phy_links['local_link_information'][0] - vlan_tag = 0 - li_fq_name = ['default-global-system-config', - link_information['switch_info'], - link_information['port_id'],] - li_fq_name = li_fq_name + ['%s.%s' %(link_information['port_id'], vlan_tag)] - li_obj = LogicalInterface( - parent_type='physical-interface', fq_name=li_fq_name, - logical_interface_vlan_tag=vlan_tag) - ok, resp = api_server.internal_request_create('logical-interface', - li_obj.serialize_to_json()) - li_uuid = resp['logical-interface']['uuid'] - api_server.internal_request_ref_update('logical-interface', - li_uuid, 'ADD', - 'virtual-machine-interface', obj_dict['uuid'], - relax_ref_for_delete=True) + links = phy_links['local_link_information'] + if len(links) > 1: + cls._manage_lag_interface(obj_dict['uuid'], api_server, db_conn, links) + else: + cls._create_logical_interface(obj_dict['uuid'], api_server, db_conn, + links[0]['switch_info'], links[0]['port_id']) # Create ref to native/vn-default routing instance vn_refs = obj_dict.get('virtual_network_refs') @@ -2126,6 +2117,101 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, return True, "" # end pre_dbe_update + @classmethod + def _create_lag_interface(cls, api_server, db_conn, prouter_name, phy_interfaces): + lag_interface_name = "ae" + phy_interfaces[0][2:] + + # create lag object + lag_obj = LinkAggregationGroup(parent_type='physical-router', + fq_name=['default-global-system-config', prouter_name, lag_interface_name], + link_aggregation_group_lacp_enabled=True) + + ok, resp = api_server.internal_request_create('link-aggregation-group', + lag_obj.serialize_to_json()) + + lag_uuid = resp['link-aggregation-group']['uuid'] + + # create lag interface + lag_interface_obj = PhysicalInterface(parent_type='physical-router', + fq_name=['default-global-system-config', prouter_name, lag_interface_name], + physical_interface_type='lag') + + ok, resp = api_server.internal_request_create( + 'physical-interface', + lag_interface_obj.serialize_to_json() + ) + phy_interface_uuid = resp['physical-interface']['uuid'] + + # get physical interface uuids + phy_interface_uuids = [phy_interface_uuid] + for phy_interface_name in phy_interfaces: + fq_name = ['default-global-system-config', prouter_name, phy_interface_name] + phy_interface_uuids.append( + db_conn.fq_name_to_uuid('physical_interface', fq_name)) + + # add physical interfaces to the lag + for uuid in phy_interface_uuids: + api_server.internal_request_ref_update('link-aggregation-group', + lag_uuid, 'ADD', 'physical-interface', uuid, relax_ref_for_delete=True) + + return lag_interface_name + # end _create_lag_interface + + @classmethod + def _manage_lag_interface(cls, vmi_id, api_server, db_conn, phy_links): + tors = {} + for link in phy_links: + tor_name = link['switch_info'] + port_name = link['port_id'] + if not tors.get(tor_name): + tors[tor_name] = { 'phy_interfaces': [port_name] } + else: + tors[tor_name]['phy_interfaces'].append(port_name) + + for tor_name, tor_info in tors.iteritems(): + if len(tor_info['phy_interfaces']) > 1: + # need to create lag interface and lag group + vmi_connected_phy_interface = cls._create_lag_interface( + api_server, db_conn, tor_name, tor_info['phy_interfaces']) + else: + vmi_connected_phy_interface = tor_info['phy_interfaces'][0] + + # Get Mac address for vmi + ok, result = cls.dbe_read(db_conn,'virtual_machine_interface', vmi_id) + if ok: + mac = result['virtual_machine_interface_mac_addresses']['mac_address'] + esi = "00:00:00:00:" + mac[0] + cls._create_logical_interface(vmi_id, api_server, db_conn, tor_name, + vmi_connected_phy_interface, esi=esi) + + @classmethod + def _create_logical_interface(cls, vim_id, api_server, db_conn, tor, link, esi=None): + vlan_tag = 0 + li_fq_name = ['default-global-system-config', tor, link] + li_fq_name = li_fq_name + ['%s.%s' %(link, vlan_tag)] + + li_obj = LogicalInterface(parent_type='physical-interface', + fq_name=li_fq_name, + logical_interface_vlan_tag=vlan_tag) + + ok, resp = api_server.internal_request_create('logical-interface', + li_obj.serialize_to_json()) + li_uuid = resp['logical-interface']['uuid'] + api_server.internal_request_ref_update('logical-interface', + li_uuid, 'ADD', 'virtual-machine-interface', vim_id, + relax_ref_for_delete=True) + + # If this is a multi-homed interface, set the Ethernet Segment Identifier + if esi: + # Set the ESI for this multi-homed interface + fq_name = ['default-global-system-config', tor, link] + pi_uuid = db_conn.fq_name_to_uuid('physical_interface', fq_name) + ok, pi_obj = cls.dbe_read(db_conn,'physical_interface', pi_uuid) + if ok: + pi_obj['ethernet_segment_identifier'] = esi + db_conn.dbe_update('physical_interface', pi_uuid, pi_obj) + + @classmethod def post_dbe_update(cls, id, fq_name, obj_dict, db_conn, prop_collection_updates=None, ref_update=None): @@ -2139,27 +2225,19 @@ def post_dbe_update(cls, id, fq_name, obj_dict, db_conn, oper_param['operation'] == 'set'): kvps.append(oper_param['value']) + # Manage the bindings for baremetal servers if kvps: kvp_dict = cls._kvp_to_dict(kvps) vnic_type = kvp_dict.get('vnic_type') if vnic_type == 'baremetal': phy_links = json.loads(kvp_dict.get('profile')) - link_information = phy_links['local_link_information'][0] - vlan_tag = 0 - li_fq_name = ['default-global-system-config', - link_information['switch_info'], - link_information['port_id'],] - li_fq_name = li_fq_name + ['%s.%s' %(link_information['port_id'], vlan_tag)] - li_obj = LogicalInterface( - parent_type='physical-interface', fq_name=li_fq_name, - logical_interface_vlan_tag=vlan_tag) - ok, resp = api_server.internal_request_create('logical-interface', - li_obj.serialize_to_json()) - li_uuid = resp['logical-interface']['uuid'] - api_server.internal_request_ref_update('logical-interface', - li_uuid, 'ADD', - 'virtual-machine-interface', id, - relax_ref_for_delete=True) + if phy_links and phy_links.get('local_link_information'): + links = phy_links['local_link_information'] + if len(links) > 1: + cls._manage_lag_interface(id, api_server, db_conn, links) + else: + cls._create_logical_interface(id, api_server, db_conn, + links[0]['switch_info'], links[0]['port_id'], esi=None) return True, '' # end post_dbe_update @@ -2182,9 +2260,32 @@ def pre_dbe_delete(cls, id, obj_dict, db_conn): delete_dict = {'virtual_machine_refs' : []} cls._check_vrouter_link(obj_dict, kvp_dict, delete_dict, db_conn) - # For baremetal, delete the logical interface + # For baremetal, delete the logical interface and related objects for lri_back_ref in obj_dict.get('logical_interface_back_refs') or []: - api_server.internal_request_delete('logical_interface', lri_back_ref['uuid']) + fqname = lri_back_ref['to'][:-1] + interface_name = fqname[-1] + + # Check if LAG interfaces are involved. If yes, clean then up + if interface_name.startswith('ae'): + # In addition to LAG interface, delete the physical interface created for LAG + phy_interface_uuid = db_conn.fq_name_to_uuid('physical_interface', fqname) + lag_interface_uuid = db_conn.fq_name_to_uuid('link_aggregation_group', fqname) + # Note: ths order of delettion is important + api_server.internal_request_delete('logical_interface', lri_back_ref['uuid']) + api_server.internal_request_delete('link_aggregation_group', lag_interface_uuid) + api_server.internal_request_delete('physical_interface', phy_interface_uuid) + else: + # Before deleting the logical interface, check if the parent physical interface + # has ESI set. If yes, clear it. + ok, li_obj = cls.dbe_read(db_conn,'logical_interface', lri_back_ref['uuid']) + if ok: + ok, pi_obj = cls.dbe_read(db_conn,'physical_interface', li_obj['parent_uuid']) + if ok: + if pi_obj.get('ethernet_segment_identifier'): + pi_obj['ethernet_segment_identifier'] = '' + db_conn.dbe_update('physical_interface', li_obj['parent_uuid'], pi_obj) + api_server.internal_request_delete('logical_interface', lri_back_ref['uuid']) + return True, "" # end pre_dbe_delete diff --git a/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py b/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py index c0ddd1804fd..a15b314e369 100644 --- a/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py +++ b/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py @@ -486,14 +486,339 @@ def test_baremetal_logical_interface_bindings(self): self.assertFalse(bound_logical_interface_found) # Clen up the resources - self._vnc_lib.physical_interface_delete(id=pi.uuid) - self._vnc_lib.physical_router_delete(id=pr.uuid) + self._vnc_lib.physical_interface_delete(id=pi_uuid) + self._vnc_lib.physical_router_delete(id=pr_uuid) self.delete_resource('port', proj_uuid, port_dict['id']) self._vnc_lib.security_group_delete(id=sg_obj.uuid) self._vnc_lib.virtual_router_delete(id=vr_obj) self._vnc_lib.virtual_network_delete(id=vn_obj.uuid) # end test_baremetal_logical_interface_bindings + def test_baremetal_logical_interface_bindings_with_lag(self): + """ This test tests the Logical to LAG interface binding. + + Multiple physical interfaces are created to represent + members of a Lag group. A Baremetal Server + is launched on a virtual network. As a part of this operation, + a LAG interface and an "ae" physical interface is created. This + auto generated physical inteface, along with the lag member + physical interfaces become the members of the lag interface. + A logical interface is created and bound to this auto generated + physical interface. + This test verifies the binidng and unbinding of these objects + takes place correctly. + """ + vn_obj = vnc_api.VirtualNetwork(self.id()) + vn_obj.add_network_ipam(vnc_api.NetworkIpam(), + vnc_api.VnSubnetsType( + [vnc_api.IpamSubnetType( + vnc_api.SubnetType('1.1.1.0', 24))])) + self._vnc_lib.virtual_network_create(vn_obj) + + vr_obj = vnc_api.VirtualRouter("myhost") + vr_obj = self._vnc_lib.virtual_router_create(vr_obj) + + sg_obj = vnc_api.SecurityGroup('default') + try: + self._vnc_lib.security_group_create(sg_obj) + except vnc_api.RefsExistError: + pass + + pr_name = self.id() + '_physical_router' + pr = vnc_api.PhysicalRouter(pr_name) + pr_uuid = self._vnc_lib.physical_router_create(pr) + pr_obj = self._vnc_lib.physical_router_read(id=pr_uuid) + + num_phy_interfaces = 2 + pi_uuid = [] + pi_fq_name = [] + binding_profile = {'local_link_information':[]} + for i in range(num_phy_interfaces): + pi_name = self.id() + 'ge-0/0/%s' %i + pi = vnc_api.PhysicalInterface(name=pi_name, + parent_obj=pr_obj) + pi_uuid.append(self._vnc_lib.physical_interface_create(pi)) + pi_obj = self._vnc_lib.physical_interface_read(id=pi_uuid[i]) + pi_fq_name.append(pi_obj.get_fq_name()) + profile = {'port_id': pi_fq_name[i][2], 'switch_id': pi_fq_name[i][2], + 'switch_info': pi_fq_name[i][1]} + binding_profile['local_link_information'].append(profile) + + proj_uuid = self._vnc_lib.fq_name_to_id('project', + fq_name=['default-domain', 'default-project']) + + context = {'operation': 'CREATE', + 'user_id': '', + 'is_admin': True, + 'roles': ''} + vnic_type = 'baremetal' + data = {'resource':{'network_id': vn_obj.uuid, + 'tenant_id': proj_uuid, + 'binding:profile': binding_profile, + 'binding:vnic_type': vnic_type, + 'binding:host_id': 'myhost'}} + body = {'context': context, 'data': data} + resp = self._api_svr_app.post_json('/neutron/port', body) + port_dict = json.loads(resp.text) + # Make sure that the binding profile for baremetal is set correctly + match = port_dict['binding:profile'] == binding_profile + self.assertTrue(match) + + # Make sure LAG is created + lag_found = False + lag_dict = self._vnc_lib.link_aggregation_groups_list() + lags = lag_dict['link-aggregation-groups'] + for l in lags: + lag_obj = self._vnc_lib.link_aggregation_group_read(id=l['uuid']) + if lag_obj.parent_uuid == pr_uuid: + lag_found = True + break + self.assertTrue(lag_found) + + # Make sure physical interface starting with "ae" got created + phy_interface_found = False + pi_dict = self._vnc_lib.physical_interfaces_list() + lis = pi_dict['physical-interfaces'] + for l in lis: + pi_obj = self._vnc_lib.physical_interface_read(id=l['uuid']) + if pi_obj.name.startswith('ae'): + phy_interface_found = True + new_pi_uuid = pi_obj.uuid + break + self.assertTrue(phy_interface_found) + + bound_logical_interface_found = False + li_dict = self._vnc_lib.logical_interfaces_list() + lis = li_dict['logical-interfaces'] + for l in lis: + li_obj = self._vnc_lib.logical_interface_read(id=l['uuid']) + if li_obj.parent_uuid == new_pi_uuid: + bound_logical_interface_found = True + break + + self.assertTrue(bound_logical_interface_found) + + # Now verify the delete funtion to ensure that the resources + # created to facilitate LAG interface are deleted with the + # deleetion of portDed and/or bound. + + self.delete_resource('port', proj_uuid, port_dict['id']) + + # Ensure that LAG interface is deleted + lag_dict = self._vnc_lib.link_aggregation_groups_list() + lags = lag_dict['link-aggregation-groups'] + if len(lags) > 0: + self.assertFalse(True) + + # Make sure physical interface starting with "ae" got deleted + phy_interface_found = False + pi_dict = self._vnc_lib.physical_interfaces_list() + lis = pi_dict['physical-interfaces'] + for l in lis: + pi_obj = self._vnc_lib.physical_interface_read(id=l['uuid']) + if pi_obj.name.startswith('ae'): + phy_interface_found = True + break + + self.assertFalse(phy_interface_found) + + # Ensure that the Logical Interface got deleted as well + li_dict = self._vnc_lib.logical_interfaces_list() + lis = li_dict['logical-interfaces'] + if len(lis) > 0: + self.assertFalse(True) + + # Clen up the resources + for i in range(num_phy_interfaces): + self._vnc_lib.physical_interface_delete(id=pi_uuid[i]) + self._vnc_lib.physical_router_delete(id=pr_uuid) + self._vnc_lib.security_group_delete(id=sg_obj.uuid) + self._vnc_lib.virtual_router_delete(id=vr_obj) + self._vnc_lib.virtual_network_delete(id=vn_obj.uuid) + # end test_baremetal_logical_interface_bindings_with_lag + + def test_baremetal_logical_interface_bindings_with_multi_homing(self): + """ This test tests the Bond interface that connects to two Tors. + + Multiple physical interfaces are created to represent + members of a Lag group. These physical interfaces are connected + two differnt TORs to create a multi-homing Lag. A Baremetal Server + is launched on a virtual network. As a part of this operation, + two logical interfaces are created and mapped to the VMI and + connected to respective physical interfaces. Additionally, ESI + (Ethernet Segment Identifier) is computed based upon the MAC of + VMI and added to physical interface properties. + This verfies that these objects are appropriately created. + Additionally, it verifies that they are cleaned it up when the + Baremetal server is decommisioned. + """ + vn_obj = vnc_api.VirtualNetwork(self.id()) + vn_obj.add_network_ipam(vnc_api.NetworkIpam(), + vnc_api.VnSubnetsType( + [vnc_api.IpamSubnetType( + vnc_api.SubnetType('1.1.1.0', 24))])) + self._vnc_lib.virtual_network_create(vn_obj) + + vr_obj = vnc_api.VirtualRouter("myhost") + vr_obj = self._vnc_lib.virtual_router_create(vr_obj) + + sg_obj = vnc_api.SecurityGroup('default') + try: + self._vnc_lib.security_group_create(sg_obj) + except vnc_api.RefsExistError: + pass + + number_of_tors = 2 + num_phy_interfaces_per_tor = 1 + pi_uuid = [] + pi_fq_name = [] + pr_uuid = [] + pr_obj = [] + tors = {} + binding_profile = {'local_link_information':[]} + for t in range(number_of_tors): + pr_name = self.id() + '_physical_router_%s' %t + pr = vnc_api.PhysicalRouter(pr_name) + pr_uuid.append(self._vnc_lib.physical_router_create(pr)) + pr_obj.append(self._vnc_lib.physical_router_read(id=pr_uuid[t])) + + for i in range(num_phy_interfaces_per_tor): + pi_name = self.id() + 'ge-0/0/%s' %i + pi = vnc_api.PhysicalInterface(name=pi_name, + parent_obj=pr_obj[t]) + pi_uuid.append(self._vnc_lib.physical_interface_create(pi)) + pi_obj = self._vnc_lib.physical_interface_read(id=pi_uuid[i]) + pi_fq_name.append(pi_obj.get_fq_name()) + profile = {'port_id': pi_fq_name[i][2], 'switch_id': pi_fq_name[i][2], + 'switch_info': pr_name} + #'switch_info': pi_fq_name[i][1]} + binding_profile['local_link_information'].append(profile) + + proj_uuid = self._vnc_lib.fq_name_to_id('project', + fq_name=['default-domain', 'default-project']) + + context = {'operation': 'CREATE', + 'user_id': '', + 'is_admin': True, + 'roles': ''} + vnic_type = 'baremetal' + data = {'resource':{'network_id': vn_obj.uuid, + 'tenant_id': proj_uuid, + 'binding:profile': binding_profile, + 'binding:vnic_type': vnic_type, + 'binding:host_id': 'myhost'}} + body = {'context': context, 'data': data} + resp = self._api_svr_app.post_json('/neutron/port', body) + port_dict = json.loads(resp.text) + # Make sure that the binding profile for baremetal is set correctly + match = port_dict['binding:profile'] == binding_profile + self.assertTrue(match) + + for t in range(number_of_tors): + if num_phy_interfaces_per_tor > 1: + # Make sure LAG is created + lag_found = False + lag_dict = self._vnc_lib.link_aggregation_groups_list() + lags = lag_dict['link-aggregation-groups'] + for l in lags: + lag_obj = self._vnc_lib.link_aggregation_group_read(id=l['uuid']) + if lag_obj.parent_uuid == pr_uuid: + lag_found = True + break + self.assertTrue(lag_found) + + # Make sure physical interface starting with "ae" got created + phy_interface_found = False + pi_dict = self._vnc_lib.physical_interfaces_list() + lis = pi_dict['physical-interfaces'] + for l in lis: + pi_obj = self._vnc_lib.physical_interface_read(id=l['uuid']) + if pi_obj.name.startswith('ae'): + phy_interface_found = True + new_pi_uuid = pi_obj.uuid + break + self.assertTrue(phy_interface_found) + + bound_logical_interface_found = False + li_dict = self._vnc_lib.logical_interfaces_list() + lis = li_dict['logical-interfaces'] + for l in lis: + li_obj = self._vnc_lib.logical_interface_read(id=l['uuid']) + if num_phy_interfaces_per_tor > 1: + expected_parent_uuid = new_pi_uuid + else: + expected_parent_uuid = pi_uuid[t] + if li_obj.parent_uuid == expected_parent_uuid: + bound_logical_interface_found = True + break + + self.assertTrue(bound_logical_interface_found) + + # Verify that the ESI is set correctly in the physical interfaces + esi = [] + for i in range(len(pi_uuid)): + pi_obj = self._vnc_lib.physical_interface_read(id=pi_uuid[i]) + if pi_obj.ethernet_segment_identifier: + esi.append(pi_obj.ethernet_segment_identifier) + if len(esi) < number_of_tors: + self.assertTrue(False) + + esi_match = True + esi_val = esi[0] + for i in range(len(esi)): + if esi_val != esi[i]: + esi_match = False + self.assertTrue(esi_match) + + # Now verify the delete funtion to ensure that the resources + # created to facilitate LAG interface are deleted with the + # deleetion of portDed and/or bound. + + self.delete_resource('port', proj_uuid, port_dict['id']) + + if num_phy_interfaces_per_tor > 1: + # Ensure that LAG interface is deleted + lag_dict = self._vnc_lib.link_aggregation_groups_list() + lags = lag_dict['link-aggregation-groups'] + if len(lags) > 0: + self.assertFalse(True) + + # Make sure physical interface starting with "ae" got deleted + phy_interface_found = False + pi_dict = self._vnc_lib.physical_interfaces_list() + lis = pi_dict['physical-interfaces'] + for l in lis: + pi_obj = self._vnc_lib.physical_interface_read(id=l['uuid']) + if pi_obj.name.startswith('ae'): + phy_interface_found = True + break + self.assertFalse(phy_interface_found) + + # Ensure that the Logical Interface got deleted as well + li_dict = self._vnc_lib.logical_interfaces_list() + lis = li_dict['logical-interfaces'] + if len(lis) > 0: + self.assertFalse(True) + + # Verify that the ESI is cleared from the physical interfaces + esi = [] + for i in range(len(pi_uuid)): + pi_obj = self._vnc_lib.physical_interface_read(id=pi_uuid[i]) + if pi_obj.ethernet_segment_identifier: + esi.append(pi_obj.ethernet_segment_identifier) + if len(esi) > 0: + self.assertTrue(False) + + # Clen up the resources + for i in range(len(pi_uuid)): + self._vnc_lib.physical_interface_delete(id=pi_uuid[i]) + self._vnc_lib.security_group_delete(id=sg_obj.uuid) + self._vnc_lib.virtual_router_delete(id=vr_obj) + self._vnc_lib.virtual_network_delete(id=vn_obj.uuid) + for t in range(len(pr_uuid)): + self._vnc_lib.physical_router_delete(id=pr_uuid[t]) + # end test_baremetal_logical_interface_bindings_with_multi_homing + def test_sg_rules_delete_when_peer_group_deleted_on_read_sg(self): sg1_obj = vnc_api.SecurityGroup('sg1-%s' %(self.id())) self._vnc_lib.security_group_create(sg1_obj)