From bd278816693b27537c787f1d6dc93a3852c707ff Mon Sep 17 00:00:00 2001
From: Sayali Naval <sayalinaval@gmail.com>
Date: Sat, 24 Feb 2024 03:29:44 +0000
Subject: [PATCH] Add support for EPG static paths as top level resources

Change-Id: Ie347a8e6a70e6870fd8d371991f7acadb3ea625e
---
 .../drivers/apic_aim/mechanism_driver.py      |  65 ++--
 .../unit/plugins/ml2plus/test_apic_aim.py     | 342 ++++++++++++------
 2 files changed, 273 insertions(+), 134 deletions(-)

diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py
index fd118cada..6ce09fa39 100644
--- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py
+++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py
@@ -5665,14 +5665,27 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                     return
                 epg = self.aim.get(aim_ctx, epg)
                 # Update old host values.
-                paths = set([(x['path'], x['encap'], x['host'])
-                             for x in epg.static_paths if x['host'] != host])
-                # Add new static ports.
-                paths |= set([(x.link.path, x.encap, x.link.host_name)
-                              for x in static_ports])
-                self.aim.update(aim_ctx, epg, static_paths=[
-                    {'path': x[0], 'encap': x[1], 'host': x[2]}
-                    for x in paths])
+                for static_port in static_ports:
+                    static_path = self.aim.get(aim_ctx,
+                            aim_resource.EPGStaticPath(
+                                tenant_name=epg.tenant_name,
+                                app_profile_name=epg.app_profile_name,
+                                epg_name=epg.name,
+                                path=static_port.link.path))
+                    if static_path:
+                        # Update old host values.
+                        self.aim.update(aim_ctx, static_path,
+                                        host=static_port.link.host_name)
+                    else:
+                        # Add new static ports.
+                        static_path = aim_resource.EPGStaticPath(
+                                tenant_name=epg.tenant_name,
+                                app_profile_name=epg.app_profile_name,
+                                epg_name=epg.name,
+                                path=static_port.link.path,
+                                encap=static_port.encap,
+                                host=static_port.link.host_name)
+                        self.aim.create(aim_ctx, static_path)
 
     def _get_topology_from_path(self, path):
         """Convert path string to toplogy elements.
@@ -5962,20 +5975,32 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
         epg = self.aim.get(aim_ctx, epg)
         # Static paths configured on an EPG can be uniquely
         # identified by their path attribute.
-        to_remove = [static_port.link.path]
+        to_remove = static_port.link.path
         if to_remove:
-            epg.static_paths = [p for p in epg.static_paths
-                                if p.get('path') not in to_remove]
+            static_path = self.aim.get(aim_ctx, aim_resource.EPGStaticPath(
+                tenant_name=epg.tenant_name,
+                app_profile_name=epg.app_profile_name,
+                epg_name=epg.name,
+                path=to_remove))
+            if static_path:
+                self.aim.delete(aim_ctx, static_path)
+                LOG.debug('Removing static path %s for EPG %s',
+                          static_path, epg)
         if not remove and static_port:
-            static_info = {'path': static_port.link.path,
-                           'encap': static_port.encap,
-                           'mode': static_port.mode}
+            host = ""
             if static_port.link.host_name:
-                static_info['host'] = static_port.link.host_name
-            epg.static_paths.append(static_info)
-        LOG.debug('Setting static paths for EPG %s to %s',
-                  epg, epg.static_paths)
-        self.aim.update(aim_ctx, epg, static_paths=epg.static_paths)
+                host = static_port.link.host_name
+            static_path = aim_resource.EPGStaticPath(
+                    tenant_name=epg.tenant_name,
+                    app_profile_name=epg.app_profile_name,
+                    epg_name=epg.name,
+                    path=static_port.link.path,
+                    encap=static_port.encap,
+                    mode=static_port.mode,
+                    host=host)
+            LOG.debug('Adding static path %s for EPG %s',
+                      static_path, epg)
+            self.aim.create(aim_ctx, static_path)
 
     def _get_static_ports(self, plugin_context, host, segment,
                           port_context=None):
@@ -7210,7 +7235,6 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                     expected_epg = mgr.expected_aim_resource(epg)
                     expected_epg.vmm_domains = actual_epg.vmm_domains
                     expected_epg.physical_domains = actual_epg.physical_domains
-                    expected_epg.static_paths = actual_epg.static_paths
                     # REVISIT: Move to ValidationManager, just before
                     # comparing actual and expected resources?
                     expected_epg.openstack_vmm_domain_names = [
@@ -7550,7 +7574,6 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
         epg.physical_domain_names = []
         epg.vmm_domains = []
         epg.physical_domains = []
-        epg.static_paths = []
         epg.epg_contract_masters = epg_contract_masters
         epg.monitored = False
         qos_policy_binding = net_db.get('qos_policy_binding')
diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py
index 7eb38e13f..afccb3307 100644
--- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py
+++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py
@@ -6741,7 +6741,6 @@ class TestPortBinding(ApicAimTestCase):
         parent_port_id = parent_port['id']
         epg = self._net_2_epg(net1['network'])
         epg = self.aim_mgr.get(aim_ctx, epg)
-        self.assertEqual([], epg.static_paths)
         if with_subports:
             kwargs = {'provider:network_type': subport_net_type,
                       'provider:physical_network': subport_physnet}
@@ -6753,7 +6752,6 @@ class TestPortBinding(ApicAimTestCase):
                 sb_net1['network']['id'])['port']
             epg = self._net_2_epg(sb_net1['network'])
             epg = self.aim_mgr.get(aim_ctx, epg)
-            self.assertEqual([], epg.static_paths)
 
             sb_net2 = self._make_network(self.fmt, 'subport_net2', True,
                                          arg_list=arg_list, **kwargs)
@@ -6763,7 +6761,6 @@ class TestPortBinding(ApicAimTestCase):
                 sb_net2['network']['id'])['port']
             epg = self._net_2_epg(sb_net2['network'])
             epg = self.aim_mgr.get(aim_ctx, epg)
-            self.assertEqual([], epg.static_paths)
 
             # For now, trunk just has subport from sb_net1.
             subports = [{'port_id': subport_net1_port['id'],
@@ -6800,18 +6797,28 @@ class TestPortBinding(ApicAimTestCase):
                              net1['network']['provider:segmentation_id'])
         epg = self._net_2_epg(net1['network'])
         epg = self.aim_mgr.get(aim_ctx, epg)
+        path = 'topology/pod-1/paths-501/pathep-[eth1/1]'
+        static_path = self.aim_mgr.get(aim_ctx, aim_resource.EPGStaticPath(
+            tenant_name=epg.tenant_name,
+            app_profile_name=epg.app_profile_name,
+            epg_name=epg.name,
+            path=path))
         vlan_name = 'vlan-%s' % access_vlan
-        self.assertEqual([{'path': 'topology/pod-1/paths-501/pathep-[eth1/1]',
-                          'mode': 'untagged', 'encap': vlan_name}],
-                         epg.static_paths)
+        self.assertIsNotNone(static_path)
+        self.assertEqual('untagged', static_path.mode)
+        self.assertEqual(vlan_name, static_path.encap)
         if with_subports:
             epg = self._net_2_epg(sb_net1['network'])
             epg = self.aim_mgr.get(aim_ctx, epg)
+            static_path = self.aim_mgr.get(aim_ctx, aim_resource.EPGStaticPath(
+                tenant_name=epg.tenant_name,
+                app_profile_name=epg.app_profile_name,
+                epg_name=epg.name,
+                path=path))
             vlan = 'vlan-%s' % (134 if not inherit else
                 sb_net1['network']['provider:segmentation_id'])
-            self.assertEqual(
-                [{'path': 'topology/pod-1/paths-501/pathep-[eth1/1]',
-                 'encap': vlan}], epg.static_paths)
+            self.assertIsNotNone(static_path)
+            self.assertEqual(vlan, static_path.encap)
         trunk = self._show_trunk(net1['network']['tenant_id'],
                                  trunk['id'])['trunk']
         # Trunk status is ACTIVE if parent and subports are bound.
@@ -6867,9 +6874,13 @@ class TestPortBinding(ApicAimTestCase):
             epg = self.aim_mgr.get(aim_ctx, epg)
             vlan_name = 'vlan-%s' % (135 if not inherit else
                 sb_net2['network']['provider:segmentation_id'])
-            self.assertEqual(
-                [{'path': 'topology/pod-1/paths-501/pathep-[eth1/1]',
-                 'encap': vlan_name}], epg.static_paths)
+            static_path = self.aim_mgr.get(aim_ctx, aim_resource.EPGStaticPath(
+                tenant_name=epg.tenant_name,
+                app_profile_name=epg.app_profile_name,
+                epg_name=epg.name,
+                path=path))
+            self.assertIsNotNone(static_path)
+            self.assertEqual(vlan_name, static_path.encap)
             subports.extend(add_subports)
             self._update_trunk(net1['network']['tenant_id'],
                                trunk['id'], subports, remove=True)
@@ -6882,7 +6893,6 @@ class TestPortBinding(ApicAimTestCase):
                 self.assertEqual('', subport['binding:host_id'])
                 epg = self._net_2_epg(net['network'])
                 epg = self.aim_mgr.get(aim_ctx, epg)
-                self.assertEqual([], epg.static_paths)
             # Trunk should still be active with only the bound parent port.
             trunk = self._show_trunk(net1['network']['tenant_id'],
                                      trunk['id'])['trunk']
@@ -10435,17 +10445,21 @@ class TestPortVlanNetwork(ApicAimTestCase):
             with self.port(subnet=sub1) as p1:
                 # unbound port -> no static paths expected
                 epg = self.aim_mgr.get(aim_ctx, epg)
-                self.assertEqual([], epg.static_paths)
                 self._validate()
 
                 # bind to host h1
                 p1 = self._bind_port_to_host(p1['port']['id'], 'h1')
                 vlan_h1 = self._check_binding(p1['port']['id'])
                 epg = self.aim_mgr.get(aim_ctx, epg)
-                self.assertEqual(
-                    [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_h1,
-                      'host': 'h1'}],
-                    epg.static_paths)
+                static_path = self.aim_mgr.get(aim_ctx,
+                        aim_resource.EPGStaticPath(
+                            tenant_name=epg.tenant_name,
+                            app_profile_name=epg.app_profile_name,
+                            epg_name=epg.name,
+                            path=self.hlink1.path))
+                self.assertIsNotNone(static_path)
+                self.assertEqual('h1', static_path.host)
+                self.assertEqual('vlan-%s' % vlan_h1, static_path.encap)
                 self._validate()
                 p1 = self._show('ports', p1['port']['id'])
 
@@ -10469,17 +10483,21 @@ class TestPortVlanNetwork(ApicAimTestCase):
                 p1 = self._bind_port_to_host(p1['port']['id'], 'h2')
                 vlan_h2 = self._check_binding(p1['port']['id'])
                 epg = self.aim_mgr.get(aim_ctx, epg)
-                self.assertEqual(
-                    [{'path': hlink2.path, 'encap': 'vlan-%s' % vlan_h2,
-                      'host': 'h2'}],
-                    epg.static_paths)
+                static_path = self.aim_mgr.get(aim_ctx,
+                        aim_resource.EPGStaticPath(
+                            tenant_name=epg.tenant_name,
+                            app_profile_name=epg.app_profile_name,
+                            epg_name=epg.name,
+                            path=hlink2.path))
+                self.assertIsNotNone(static_path)
+                self.assertEqual('h2', static_path.host)
+                self.assertEqual('vlan-%s' % vlan_h2, static_path.encap)
                 self._validate()
 
                 # delete port
                 self._delete('ports', p1['port']['id'])
                 self._check_no_dynamic_segment(net1['id'])
                 epg = self.aim_mgr.get(aim_ctx, epg)
-                self.assertEqual([], epg.static_paths)
                 self._validate()
 
     def test_port_lifecycle_internal_network(self):
@@ -10680,10 +10698,15 @@ class TestPortVlanNetwork(ApicAimTestCase):
                 vlan_p1 = self._check_binding(p1['port']['id'])
                 if not is_svi:
                     epg = self.aim_mgr.get(aim_ctx, epg)
-                    self.assertEqual(
-                        [{'path': self.hlink1.path,
-                          'encap': 'vlan-%s' % vlan_p1, 'host': 'h1'}],
-                        epg.static_paths)
+                    static_path = self.aim_mgr.get(aim_ctx,
+                            aim_resource.EPGStaticPath(
+                                tenant_name=epg.tenant_name,
+                                app_profile_name=epg.app_profile_name,
+                                epg_name=epg.name,
+                                path=self.hlink1.path))
+                    self.assertIsNotNone(static_path)
+                    self.assertEqual('h1', static_path.host)
+                    self.assertEqual('vlan-%s' % vlan_p1, static_path.encap)
                 else:
                     ext_net = aim_resource.ExternalNetwork.from_dn(
                         net1[DN]['ExternalNetwork'])
@@ -10753,10 +10776,16 @@ class TestPortVlanNetwork(ApicAimTestCase):
                     self.assertEqual(vlan_p1, vlan_p2)
                     if not is_svi:
                         epg = self.aim_mgr.get(aim_ctx, epg)
-                        self.assertEqual(
-                            [{'path': self.hlink1.path,
-                              'encap': 'vlan-%s' % vlan_p2, 'host': 'h1'}],
-                            epg.static_paths)
+                        static_path = self.aim_mgr.get(aim_ctx,
+                                aim_resource.EPGStaticPath(
+                                    tenant_name=epg.tenant_name,
+                                    app_profile_name=epg.app_profile_name,
+                                    epg_name=epg.name,
+                                    path=self.hlink1.path))
+                        self.assertIsNotNone(static_path)
+                        self.assertEqual('h1', static_path.host)
+                        self.assertEqual('vlan-%s' % vlan_p2,
+                            static_path.encap)
                     else:
                         l3out_node = self.aim_mgr.get(aim_ctx, l3out_node)
                         self.assertEqual(l3out_node.router_id, apic_router_id)
@@ -10783,10 +10812,16 @@ class TestPortVlanNetwork(ApicAimTestCase):
                     self._check_binding(p1['port']['id'])
                     if not is_svi:
                         epg = self.aim_mgr.get(aim_ctx, epg)
-                        self.assertEqual(
-                            [{'path': self.hlink1.path,
-                              'encap': 'vlan-%s' % vlan_p1, 'host': 'h1'}],
-                            epg.static_paths)
+                        static_path = self.aim_mgr.get(aim_ctx,
+                            aim_resource.EPGStaticPath(
+                                tenant_name=epg.tenant_name,
+                                app_profile_name=epg.app_profile_name,
+                                epg_name=epg.name,
+                                path=self.hlink1.path))
+                        self.assertIsNotNone(static_path)
+                        self.assertEqual('h1', static_path.host)
+                        self.assertEqual('vlan-%s' % vlan_p1,
+                            static_path.encap)
                     else:
                         for l3out_if in l3out_ifs:
                             l3out_if = self.aim_mgr.get(aim_ctx, l3out_if)
@@ -10810,7 +10845,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
                 self._check_no_dynamic_segment(net1['id'])
                 if not is_svi:
                     epg = self.aim_mgr.get(aim_ctx, epg)
-                    self.assertEqual([], epg.static_paths)
                 else:
                     for l3out_if in l3out_ifs:
                         l3out_if = self.aim_mgr.get(aim_ctx, l3out_if)
@@ -10857,10 +10891,15 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
         if not is_svi:
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual(
-                [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_p1,
-                  'host': 'h1'}],
-                epg1.static_paths)
+            static_path = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=self.hlink1.path))
+            self.assertIsNotNone(static_path)
+            self.assertEqual('h1', static_path.host)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path.encap)
             net2 = self._make_network(self.fmt, 'net2', True)['network']
             epg2 = self._net_2_epg(net2)
         else:
@@ -10932,10 +10971,15 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
         if not is_svi:
             epg2 = self.aim_mgr.get(aim_ctx, epg2)
-            self.assertEqual(
-                [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_p2,
-                  'host': 'h1'}],
-                epg2.static_paths)
+            static_path = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg2.tenant_name,
+                    app_profile_name=epg2.app_profile_name,
+                    epg_name=epg2.name,
+                    path=self.hlink1.path))
+            self.assertIsNotNone(static_path)
+            self.assertEqual('h1', static_path.host)
+            self.assertEqual('vlan-%s' % vlan_p2, static_path.encap)
         else:
             ext_net = aim_resource.ExternalNetwork.from_dn(
                 net2[DN]['ExternalNetwork'])
@@ -11041,13 +11085,17 @@ class TestPortVlanNetwork(ApicAimTestCase):
         self._check_no_dynamic_segment(net2['id'])
         if not is_svi:
             epg2 = self.aim_mgr.get(aim_ctx, epg2)
-            self.assertEqual([], epg2.static_paths)
 
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual(
-                [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_p1,
-                  'host': 'h1'}],
-                epg1.static_paths)
+            static_path = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=self.hlink1.path))
+            self.assertIsNotNone(static_path)
+            self.assertEqual('h1', static_path.host)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path.encap)
         else:
             for l3out_if1 in l3out_if1s:
                 l3out_if1 = self.aim_mgr.get(aim_ctx, l3out_if1)
@@ -11150,12 +11198,24 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
         if not is_svi:
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual(
-                [{'path': hlink_1a.path, 'encap': 'vlan-%s' % vlan_p1,
-                  'host': 'h1'},
-                 {'path': hlink_1b.path, 'encap': 'vlan-%s' % vlan_p1,
-                  'host': 'h1'}],
-                epg1.static_paths)
+            static_path1 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=hlink_1a.path))
+            self.assertIsNotNone(static_path1)
+            self.assertEqual('h1', static_path1.host)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path1.encap)
+            static_path2 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=hlink_1b.path))
+            self.assertIsNotNone(static_path2)
+            self.assertEqual('h1', static_path2.host)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path2.encap)
         else:
             ext_net = aim_resource.ExternalNetwork.from_dn(
                 net1[DN]['ExternalNetwork'])
@@ -11279,15 +11339,33 @@ class TestPortVlanNetwork(ApicAimTestCase):
                                        host_name='h1')
         if not is_svi:
             epg2 = self.aim_mgr.get(aim_ctx, epg2)
-            self.assertTrue(
-                g_utils.is_equal(
-                    [{'path': host_links[0].path, 'encap': 'vlan-%s' % vlan_p2,
-                      'host': 'h1'},
-                     {'path': host_links[1].path, 'encap': 'vlan-%s' % vlan_p2,
-                      'host': 'h1'},
-                     {'path': host_links[2].path, 'encap': 'vlan-%s' % vlan_p2,
-                      'host': 'h1'}],
-                    epg2.static_paths))
+            static_path1 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg2.tenant_name,
+                    app_profile_name=epg2.app_profile_name,
+                    epg_name=epg2.name,
+                    path=host_links[0].path))
+            self.assertIsNotNone(static_path1)
+            self.assertEqual('h1', static_path1.host)
+            self.assertEqual('vlan-%s' % vlan_p2, static_path1.encap)
+            static_path2 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg2.tenant_name,
+                    app_profile_name=epg2.app_profile_name,
+                    epg_name=epg2.name,
+                    path=host_links[1].path))
+            self.assertIsNotNone(static_path2)
+            self.assertEqual('h1', static_path2.host)
+            self.assertEqual('vlan-%s' % vlan_p2, static_path2.encap)
+            static_path3 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg2.tenant_name,
+                    app_profile_name=epg2.app_profile_name,
+                    epg_name=epg2.name,
+                    path=host_links[2].path))
+            self.assertIsNotNone(static_path3)
+            self.assertEqual('h1', static_path3.host)
+            self.assertEqual('vlan-%s' % vlan_p2, static_path3.encap)
         else:
             ext_net = aim_resource.ExternalNetwork.from_dn(
                 net2[DN]['ExternalNetwork'])
@@ -11331,14 +11409,25 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
         if not is_svi:
             epg2 = self.aim_mgr.get(aim_ctx, epg2)
-            self.assertEqual([], epg2.static_paths)
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual(
-                [{'path': hlink_1a.path, 'encap': 'vlan-%s' % vlan_p1,
-                  'host': 'h1'},
-                 {'path': hlink_1b.path, 'encap': 'vlan-%s' % vlan_p1,
-                  'host': 'h1'}],
-                epg1.static_paths)
+            static_path1 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=hlink_1a.path))
+            self.assertIsNotNone(static_path1)
+            self.assertEqual('h1', static_path1.host)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path1.encap)
+            static_path2 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=hlink_1b.path))
+            self.assertIsNotNone(static_path2)
+            self.assertEqual('h1', static_path2.host)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path2.encap)
         else:
             for l3out_if2 in l3out_if2s:
                 l3out_if2 = self.aim_mgr.get(aim_ctx, l3out_if2)
@@ -11396,7 +11485,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
         if not is_svi:
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual([], epg1.static_paths)
         else:
             for l3out_if1a in l3out_if1as:
                 l3out_if1a = self.aim_mgr.get(aim_ctx, l3out_if1a)
@@ -11468,12 +11556,24 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
             if not is_svi:
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual(
-                    [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_p1,
-                      'host': 'h1'},
-                     {'path': hlink2.path, 'encap': 'vlan-%s' % vlan_p2,
-                      'host': 'h2'}],
-                    sorted(epg1.static_paths, key=lambda x: x['path']))
+                static_path1 = self.aim_mgr.get(aim_ctx,
+                    aim_resource.EPGStaticPath(
+                        tenant_name=epg1.tenant_name,
+                        app_profile_name=epg1.app_profile_name,
+                        epg_name=epg1.name,
+                        path=self.hlink1.path))
+                self.assertIsNotNone(static_path1)
+                self.assertEqual('h1', static_path1.host)
+                self.assertEqual('vlan-%s' % vlan_p1, static_path1.encap)
+                static_path2 = self.aim_mgr.get(aim_ctx,
+                    aim_resource.EPGStaticPath(
+                        tenant_name=epg1.tenant_name,
+                        app_profile_name=epg1.app_profile_name,
+                        epg_name=epg1.name,
+                        path=hlink2.path))
+                self.assertIsNotNone(static_path2)
+                self.assertEqual('h2', static_path2.host)
+                self.assertEqual('vlan-%s' % vlan_p2, static_path2.encap)
             else:
                 ext_net = aim_resource.ExternalNetwork.from_dn(
                     net1[DN]['ExternalNetwork'])
@@ -11564,10 +11664,15 @@ class TestPortVlanNetwork(ApicAimTestCase):
             self._delete('ports', p2['port']['id'])
             if not is_svi:
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual(
-                    [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_p1,
-                      'host': 'h1'}],
-                    epg1.static_paths)
+                static_path = self.aim_mgr.get(aim_ctx,
+                    aim_resource.EPGStaticPath(
+                        tenant_name=epg1.tenant_name,
+                        app_profile_name=epg1.app_profile_name,
+                        epg_name=epg1.name,
+                        path=self.hlink1.path))
+                self.assertIsNotNone(static_path)
+                self.assertEqual('h1', static_path.host)
+                self.assertEqual('vlan-%s' % vlan_p1, static_path.encap)
             else:
                 for l3out_if1b in l3out_if1bs:
                     l3out_if1b = self.aim_mgr.get(aim_ctx, l3out_if1b)
@@ -11598,7 +11703,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
 
             if not is_svi:
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual([], epg1.static_paths)
             else:
                 for l3out_if1a in l3out_if1as:
                     l3out_if1a = self.aim_mgr.get(aim_ctx, l3out_if1a)
@@ -11657,7 +11761,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
                 self._check_binding(p1['port']['id'])
 
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual([], epg1.static_paths)
 
             hlink42 = aim_infra.HostLink(host_name='h42',
                                          interface_name='eth0')
@@ -11667,7 +11770,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
                 self._check_binding(p2['port']['id'])
 
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual([], epg1.static_paths)
 
     def _test_topology_rpc_no_ports(self, is_svi=False):
         nctx = n_context.get_admin_context()
@@ -11702,7 +11804,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
             self.assertIsNone(self.aim_mgr.get(aim_ctx, l3out_node1))
         else:
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual([], epg1.static_paths)
         # remove hostlink for h10
         self.driver.delete_link(nctx, 'h10', 'eth0', 'A:A', 0, 0, 0)
         self.assertIsNone(self.aim_mgr.get(aim_ctx, expected_hlink10))
@@ -11710,7 +11811,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
             self.assertIsNone(self.aim_mgr.get(aim_ctx, l3out_node1))
         else:
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual([], epg1.static_paths)
 
     def test_topology_rpc_no_ports(self):
         self._test_topology_rpc_no_ports()
@@ -11754,7 +11854,6 @@ class TestPortVlanNetwork(ApicAimTestCase):
                 self.assertIsNone(self.aim_mgr.get(aim_ctx, l3out_node))
             else:
                 epgs[x] = self.aim_mgr.get(aim_ctx, epgs[x])
-                self.assertEqual([], epgs[x].static_paths)
 
         expected_hlink10 = aim_infra.HostLink(host_name='h10',
             interface_name='eth0', interface_mac='A:A',
@@ -11764,11 +11863,18 @@ class TestPortVlanNetwork(ApicAimTestCase):
         def check_epg_static_paths(link_path):
             for x in range(0, len(epgs)):
                 epgs[x] = self.aim_mgr.get(aim_ctx, epgs[x])
-                expected_path = ([{'path': link_path,
-                                   'encap': 'vlan-%s' % vlans[x],
-                                   'host': 'h10'}]
-                                if link_path else [])
-                self.assertEqual(expected_path, epgs[x].static_paths)
+                static_path = None
+                if link_path:
+                    static_path = self.aim_mgr.get(aim_ctx,
+                        aim_resource.EPGStaticPath(
+                            tenant_name=epgs[x].tenant_name,
+                            app_profile_name=epgs[x].app_profile_name,
+                            epg_name=epgs[x].name,
+                            path=link_path))
+                    self.assertIsNotNone(static_path)
+                    self.assertEqual('h10', static_path.host)
+                    self.assertEqual('vlan-%s' % vlans[x],
+                            static_path.encap)
 
         def check_svi_paths(link_path):
             for x in range(0, len(nets)):
@@ -11898,7 +12004,6 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
                 self._check_binding(p1['port']['id'],
                     expected_binding_info=[('apic_aim', 'opflex')])
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual([], epg1.static_paths)
                 self._validate()
 
             # port on non-opflex host
@@ -11906,10 +12011,15 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
                 p2 = self._bind_port_to_host(p2['port']['id'], 'h1')
                 vlan_p2 = self._check_binding(p2['port']['id'])
                 epg1 = self.aim_mgr.get(aim_ctx, epg1)
-                self.assertEqual(
-                    [{'path': self.hlink1.path, 'encap': 'vlan-%s' % vlan_p2,
-                      'host': 'h1'}],
-                    epg1.static_paths)
+                static_path = self.aim_mgr.get(aim_ctx,
+                    aim_resource.EPGStaticPath(
+                        tenant_name=epg1.tenant_name,
+                        app_profile_name=epg1.app_profile_name,
+                        epg_name=epg1.name,
+                        path=self.hlink1.path))
+                self.assertIsNotNone(static_path)
+                self.assertEqual('h1', static_path.host)
+                self.assertEqual('vlan-%s' % vlan_p2, static_path.encap)
                 self._validate()
 
     def test_mixed_ports_on_network_with_default_domains(self):
@@ -12588,11 +12698,16 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
             p1 = self._bind_port_to_host(p1['id'], 'h1', **kwargs)['port']
             vlan_p1 = self._check_binding(p1['id'],
                 expected_binding_info=expected_binding_info)
-            static_path_1 = {'path': bm_path1, 'encap': 'vlan-%s' % vlan_p1,
-                             'mode': 'untagged'}
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertEqual([static_path_1],
-                             epg1.static_paths)
+            static_path1 = self.aim_mgr.get(aim_ctx,
+                aim_resource.EPGStaticPath(
+                    tenant_name=epg1.tenant_name,
+                    app_profile_name=epg1.app_profile_name,
+                    epg_name=epg1.name,
+                    path=bm_path1))
+            self.assertIsNotNone(static_path1)
+            self.assertEqual('untagged', static_path1.mode)
+            self.assertEqual('vlan-%s' % vlan_p1, static_path1.encap)
             self.assertEqual(set([]),
                              set(self._doms(epg1.vmm_domains)))
             self.assertEqual(set(['ph1']),
@@ -12615,11 +12730,16 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
                 expected_binding_info=expected_binding_info,
                 top_bound_physnet='physnet3',
                 bottom_bound_physnet='physnet2')
-            static_path_2 = {'path': bm_path2, 'encap': 'vlan-%s' % vlan_p2,
-                             'mode': 'untagged'}
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertIn(static_path_1, epg1.static_paths)
-            self.assertIn(static_path_2, epg1.static_paths)
+            static_path2 = self.aim_mgr.get(aim_ctx,
+                    aim_resource.EPGStaticPath(
+                        tenant_name=epg1.tenant_name,
+                        app_profile_name=epg1.app_profile_name,
+                        epg_name=epg1.name,
+                        path=bm_path2))
+            self.assertIsNotNone(static_path2)
+            self.assertEqual('untagged', static_path2.mode)
+            self.assertEqual('vlan-%s' % vlan_p2, static_path2.encap)
             self.assertEqual(set([]),
                              set(self._doms(epg1.vmm_domains)))
             self.assertEqual(set(['ph1', 'ph2']),
@@ -12629,8 +12749,6 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
             kwargs = {portbindings.PROFILE: {}}
             self._bind_port_to_host(p1['id'], None, **kwargs)['port']
             epg1 = self.aim_mgr.get(aim_ctx, epg1)
-            self.assertNotIn(static_path_1, epg1.static_paths)
-            self.assertIn(static_path_2, epg1.static_paths)
 
     def test_no_host_domain_mappings(self):
         with db_api.CONTEXT_READER.using(self.db_session):
@@ -13076,11 +13194,9 @@ class TestPortOnPhysicalNodeSingleDriver(TestPortOnPhysicalNode):
                 # VNICs).
                 doms = [physical_domain]
                 if delete:
-                    static_path = []
-                self.assertEqual(static_path, epg.static_paths)
-                self.assertEqual(set(doms),
-                                 set(self._doms(epg.physical_domains,
-                                                with_type=False)))
+                    self.assertEqual(set(doms),
+                            set(self._doms(epg.physical_domains,
+                                with_type=False)))
 
         if net_type == 'vlan':
             expected_binding_info = [('apic_aim', 'vlan')]