diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/AUTHORS neutron-16.4.2/AUTHORS --- neutron-16.0.0~b3~git2020041516.5f42488a9a/AUTHORS 2020-04-15 20:24:50.000000000 +0000 +++ neutron-16.4.2/AUTHORS 2021-11-12 13:57:34.000000000 +0000 @@ -37,6 +37,7 @@ Alexander Ignatov Alexander Ignatyev Alexander Maretskiy +Alexander Vlasov Alexandra Settle Alexandros Soumplis Alexei Kornienko @@ -45,6 +46,7 @@ Aliaksandr Dziarkach Alin Balutoiu Alin Serdean +Alin-Gabriel Serdean Allain Legacy Allain Legacy Aman Kumar @@ -77,6 +79,7 @@ Anna Khmelnitsky Ante Karamatic Anthony Chow +Anthony Timmins Anthony Veiga Anton Frolov Aparupa @@ -153,6 +156,7 @@ Chandan Kumar Chang Bo Guo ChangBo Guo(gcb) +Charles Farquhar Chengli XU Chengqian Liu Chirag Shahani @@ -177,9 +181,11 @@ Cédric Ollivier Dan Florea Dan Prince +Dan Radez Dan Wendlandt Dane LeBlanc Daniel Alvarez +Daniel Alvarez Sanchez Daniel Bengtsson Daniel Gollub Daniel Gonzalez @@ -206,6 +212,7 @@ David Rabel David Ripton David Shaughnessy +David Sinquin David Wahlstrom David-gb Dazhao @@ -240,10 +247,13 @@ Edan David Edgar Magana Edgar Magana +Eduardo Olivares Edward Hope-Morley Einst Crazy Elena Ezhova Elod Illes +Elvira García +Elvira García Ruiz Emilien Macchi Emilien Macchi EmilienM @@ -283,6 +293,7 @@ Gandharva Gary Kotton Gary Kotton +Gaudenz Steinlin Gauvain Pocentek Genadi Chereshnya Gerard Braad @@ -314,6 +325,7 @@ He Jie Xu He Qing He Yongli +Hemanth Nakkina Hemanth Ravi Henry Gessau Henry Gessau @@ -483,6 +495,7 @@ Li Ma Li Ma Li Xipeng +Li YaJie Li Zhixin Li-zhigang Liang Bo @@ -506,9 +519,11 @@ Luiz H Ozaki Lujin Lujin Luo +Lukas Steiner Luke Gorrie Luong Anh Tuan Ly Loi +Maciej Jozefczyk Maciej Józefczyk Maciej Józefczyk Madhav Puri @@ -519,6 +534,7 @@ Manish Godara Manjeet Singh Bhatia Manjunath Patil +Marc Gariepy Marc Koderer Marga Millet Marga Millet @@ -547,6 +563,7 @@ Matt Riedemann Matt Riedemann Matt Thompson +Matt Vinall Matt Welch Matthew Booth Matthew Edmonds @@ -565,6 +582,7 @@ Michael Smith Michael Still Michal Arbet +Michał Nasiadka Miguel Angel Ajo Miguel Angel Ajo Miguel Lavalle @@ -623,7 +641,10 @@ Nir Magnezi Numan Siddique Numan Siddique +Nurmatov Mamatisa Oleg Bondarev +Oleg Bondarev +Oliver Walsh OmarM Omer Anson Ondřej Nový @@ -666,6 +687,7 @@ QunyingRan Rabi Mishra Radosław Piliszek +Rafael Folco Rahul Priyadarshi Raildo Mascena Rajaram Mallya @@ -713,6 +735,7 @@ Roman Podoliaka Roman Podolyaka Roman Prykhodchenko +Roman Safronov Roman Sokolkov Romil Gupta Ronald Bradford @@ -852,13 +875,16 @@ Sławek Kapłoński Sławek Kapłoński Takaaki Suzuki +Takashi Kajinami Takashi NATSUME Takuma Watanabe +Tamerlan Abu Tan Lin Tang Chen Tatyana Leontovich Terry Wilson Thierry Carrez +Thomas Bachman Thomas Bechtold Thomas Goirand Thomas Herve @@ -916,11 +942,13 @@ Weidong Shao Wenran Xiao Wenxin Wang +Weronika Sikora Wim De Clercq Wlodzimierz Borkowski Wu Wenxiang XiaojueGuan Xiaolin Zhang +Xiaoyu Min XieYingYun Xu Chen Xu Han Peng @@ -1078,6 +1106,7 @@ liudong liuqing liusheng +liuyulong liyingjun lizheming lizheng @@ -1199,6 +1228,7 @@ zhangboye zhangdebo1987 zhanghao +zhanghao zhanghao2 zhangyanxian zhangyanxian diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/ChangeLog neutron-16.4.2/ChangeLog --- neutron-16.0.0~b3~git2020041516.5f42488a9a/ChangeLog 2020-04-15 20:24:49.000000000 +0000 +++ neutron-16.4.2/ChangeLog 2021-11-12 13:57:33.000000000 +0000 @@ -1,10 +1,363 @@ CHANGES ======= +16.4.2 +------ + +* [DVR] Fix update of the MTU in the DVR HA routers +* Check a namespace existence by checking only its own directory +* Don't setup bridge controller if it is already set +* Fix OVN migration workload creation order +* [ovn] Stop monitoring the SB MAC\_Binding table to reduce mem footprint +* [OVN Migration] Remove trunk's subports from the nodes +* Delete log entries when SG or port is deleted +* [OVN Migration] Remove qr and dhcp ports from the nodes +* [OVN] Tune OVN routers to reduce the mem footprint for ML2/OVN +* [OVN] Update the DHCP options when the metadata port is modified +* [DVR] Fix update of the MTU in the SNAT namespace +* [ovn] Add logs for ovs to ovn migration +* [OVN] Allow IP allocation with different segments for OVN service ports +* [DVR] Check if SNAT iptables manager is initialized +* [OVN] Set NB/SB "connection" inactivity probe +* Fix "\_sync\_metadata\_ports" with no DHCP subnets +* [DVR] Set arp entries only for single IPs given as allowed addr pair +* Replace cirros 0.4.0 by 0.5.2 in ovn migration create-resources.sh.j2 +* Remove dhcp\_extra\_opt name after first newline character +* Delete SG log entries when SG is deleted +* [ovn] metadata functional tests don't support Chassis\_Private +* [Functional] Wait for the initial state of ha router before test +* Populate self.floating\_ips\_dict using "ip rule" information +* Revert "[L3][HA] Retry when setting HA router GW status." +* Fix neutron\_pg\_drop-related startup issues +* VLAN "allocate\_partially\_specified\_segment" can return any physnet +* Randomize segmentation ID assignation +* Make test\_throttler happy +* Ensure net dict has provider info on precommit delete + +16.4.1 +------ + +* Skip FIP check if VALIDATE\_MIGRATION is not True +* Don't use singleton in routes.middleware.RoutesMiddleware +* ovn: Consider all router ports in is\_lsp\_router\_port() +* Remove dhcp\_extra\_opt value after first newline character +* Do not fail if the agent load is not bumped +* Trivial: check if ipv6 is available for IptablesManager +* Make ARP protection commands compatible with "ebtables-nft" +* [OVN] Fix Router Availability Zones for segmented networks +* Fix typo in OVN SUPPORTED\_DHCP\_OPTS\_MAPPING dictionary (ia-addr) +* [L3] Use processing queue for network update events +* Add extra logs to the network update callback in L3 agent +* [stable only] Disable functional IPv6 GRE tests +* [ovn]: Create neutron\_pg\_drop Port Group on init +* Implement namespace creation method +* Set "floatingip.fixed\_port" as viewonly +* Provide integer number to arping "-w" parameter +* [OVN] Fix ML2/OVN + Neutron DHCP agent use case +* ovn-migration: Delete FIP agent gateway ports + +16.4.0 +------ + +* Update arp entry of snat port on qrouter ns +* [OVN] Do not fail when processing SG rule deletion +* [OVN] neutron-ovn-metadat-agent add retry logic for sb\_idl +* Use "multiprocessing.Queue" for "TestNeutronServer" related tests +* Copy existing IPv6 leases to generated lease file +* Use TCP keepalives for ovsdb connections +* [OVN] Disable mcast\_flood on localnet ports +* Remove FIP agent's gw port when L3 agent is deleted +* Improve Subnet delete performance +* Improve Subnet create performance +* Force to close http connection after notify about HA router status +* [stable/ussuri] Set USE\_PYTHON3 for neutron-tempest-slow-py3 job +* ovn: Don't use dict.remove() for filtering dhcp ports in db-sync +* Read keepalived initial state in parallel to interface monitoring +* Make default hypervisor hostname compatible with libvirt +* Add a single option to override the default hypervisor name +* Provide the rpc\_response\_max\_timeout parameter to sriov-agent +* Added common config and SR-IOV agent config to sanity check +* [DHCP] Fix cleanup\_deleted\_ports method +* Make phynet paramter also is optional when network\_segment\_range enabled +* Use local and ip address to create vxlan interface +* Updates for python3.8 +* [Stable only] Set irrelevant-files in some additional jobs +* [DVR] Send allowed address pairs info to the L3 agents +* [OVN] Fix: Disabling snat after it was enabled +* Install "pyroute2" as a doc job depedency +* [ovs fw] Restrict IPv6 NA and DHCP(v6) IP and MAC source addresses +* ovn-migration: Use DHCP nodes to configure dnsmasq +* add\_fake\_chassis() may need to create a Chassis\_Private + +16.3.2 +------ + +* ovn: Do not set reside-on-redirect-chassis on distributed FIP +* Rely on worker count for HashRing caching +* Pass existing DB obj to save DB requests +* ovn-migration: UNDERCLOUD\_NODE\_USER variable +* Remove unneeded DB register retrieval and refresh in network update +* Improve Network delete performance +* Improve Subnet update performance +* designate: allow PTR zone creation to fail +* [OVN] Simplify connection creation logic +* [OVN] Check for lock in check\_for\_mcast\_flood\_reports +* Provide the rpc\_response\_max\_timeout parameter to metadata-agent +* trivial: Make driver\_controller's \_attrs\_to\_driver py3 compatible +* Don't ever give up trying to connect to OVN DBs +* [OVN] Only account for bound ports in metadata agent +* [OVN] MetadataProxyHandler to conditionally monitor both Chassis's tables +* [L3] Check agent gateway port robustly +* DHCP notification optimization +* Change reference to OvnDbNotifyHandler.\_watched\_events +* Get only FIP ID on network delete +* Remove redundant \_ensure\_default\_security\_group +* Check for existence instead of fetching the whole net object +* [OVN] Fix FDB table not registered in OvnSbIdl +* Group execution of SQL functional tests +* Add locks for setting iptables rules in l3 and metadata agents +* Remove class "Timer" +* Fix "\_get\_sg\_members" method +* Remove FT "test\_has\_offline\_migrations\_\*" tests +* [ovn] Add neutron network to metadata namespace names +* [OVN] External ports (SR-IOV) QoS is handled by SR-IOV agent +* [ovn]: Remove unwanted IP addresses from OVN ports +* Call install\_ingress\_direct\_goto\_flows() when ovs restarts +* [FT] Reduce "test\_walk\_versions" upgrade executions +* [OVN] Make delete\_router\_port() less error prone +* Test SQL cast in "get\_total\_reservations\_map" +* [OVN] Set send\_periodic to False on provider networks +* Fix invalid JSON generated by quota details +* Schedule networks to new segments if needed + +16.3.1 +------ + +* Don't run some more complex CI jobs on unrelated changes +* Switch tempest jobs to neutron specific ones +* Disable not used services in the tempest and rally jobs +* [SR-IOV] Do not fail if ip-link vf "min\_tx\_rate" is not supported +* [L3] Delete DvrFipGatewayPortAgentBindings after no gw ports +* Lock sg updates while in \_apply\_port\_filter() +* Clean port forwarding cache when router is DOWN +* [OVN] Set mcast\_flood\_reports on LSPs +* Don't configure dnsmasq entries for "network" ports +* Revert "DVR: Remove control plane arp updates for DVR" +* Add minimum bw qos rule validation for network +* Fix multicast traffic with IGMP snooping enabled +* Disable cinder services on neutron grenade jobs +* Add some wait time between stopping and starting again ovsdb monitor +* Make neutron-tempest-dvr-ha-multinode-full to be 2 nodes job +* Reduce number of the workers in fullstack gate job +* Ignore python warnings in the fullstack job +* [L3][HA] Retry when setting HA router GW status +* Fix wrong packet\_type set for IPv6 GRE tunnels in OVS +* Stop metadata proxy gracefully +* Delete HA metadata proxy PID and config with elevated privileges +* Improve "get\_devices\_with\_ip" performance +* [OVS FW] Allow egress ICMPv6 only for know addresses +* Remove update\_initial\_state() method from the HA router +* Migrate "netstat" to oslo.privsep +* [OVS FW] Clean conntrack entries with mark == CT\_MARK\_INVALID +* Fix deletion of rfp interfaces when router is re-enabled +* Avoid race condition when processing RowEvents +* [OVN] ovn-metadata-agent: Retry registering Chassis at startup +* Fix update of trunk subports during live migration +* Add extension unit tests for conntrack\_helper plugin +* Fix incorrect exception catch when update floating ip port forwarding +* Don't try to create default SG when security groups are disabled +* Process DHCP events in order if related +* [OVN] Update metadata port ony for requested subnet +* Fix losses of ovs flows when ovs is restarted +* Make test\_agent\_show only look for its own agents +* Do not update agents "alive" state in TestAgentApi +* Optimize get\_ports with QoS extension +* [OVN] Ensure metadata checksum +* Auto-remove floating agent gw ports on net/subnet delete +* [QoS] Get only min bw rules when extending port dict +* Optimize get\_ports with trunk extension +* Improve DHCP agent's debug messages + +16.3.0 +------ + +* Use consistent filter API syntax +* ovn: Support live migration to DPDK nodes +* [FT] Add the datapath binding wait event to the watched list +* Ensure "keepalived" is correcly disabled +* Disable dns-integration API extension if it's not enabled in ML2 +* Add WaitForPortCreateEvent in BaseOVSTestCase +* Randomize port name in "BaseOVSTestCase" +* Dropping lower constraints testing (stable Ussuri) +* Limit usage of resources in the fullstack tests job +* Improve the CIDRs overlap check method for router add interface +* Don't fail if FIP is not in port forwarding cache during cleaning +* Upgrade RPC version of SecurityGroup\*Rpc +* [GRE] Add possibility to create GRE tunnels over IPv6 +* Fix calling of add\_tunnel\_port method from sanity checks module +* Fix imports order in neutron.services.ovn\_l3\_plugin module +* Neutron ovs agent: set mtu of smartnic port +* Fix OVS conjunctive IP flows cleanup +* ovn: Always use UTC for Hash ring timestamps +* [OVN] Fix inconsistent IGMP configuration +* Flush ebtables arp protect chains before deleting them +* Fix removal of dvr-src mac flows when non-gateway port on router is deleted +* [ussuri] Fix tests with new pip resolver +* fix dhcp bulk reload exceptions +* Ensure ovsdb\_probe\_interval set before connect() +* Add locks for methods which sets nat rules in router +* Fix migration from the HA to non-HA routers +* Rehome api tests for propagate\_uplink\_status +* Fix ovs agent, avoiding import error +* Fix formatting error in agent/linux/external\_process.py +* Switch tripleo standalone job to content provider +* [OVN] Fix get/update/delete of non-OVN agents +* Local mac direct flow for non-openflow firewall +* ovs firewall: fix mac learning on the ingress rule table when ovs offload enabled +* "scope" conversion only just before pyroute2 method call +* [Functional] Add logging to the check test file function +* "round" may bump 1 second to 2 if sleep takes more than 1.49 sec +* Import "oslo\_config.cfg" before "eventlet" +* windows: fix terminating processes +* Support gateway which is not in subnet CIDR in ha\_router +* Default dnsmasq --conf-file to /dev/null +* Make NeutronOvsdbIdl singleton +* [OVN] update\_port should not remove values from external\_ids + +16.2.0 +------ + +* ovn migration: Fix neutron server container name +* ovn: Use new OVS commit hash +* [OVN] Fix test\_add\_interface\_in\_use negative test +* Remove "vf\_management" and "vf\_extended\_management" checks +* Ensure fip ip rules deleted when fip removed +* Remove some unnecessary usages of verify() +* Don't raise FileNotFoundError during disabling keepalived +* Not remove the running router when MQ is unreachable +* Handle properly existing LLA address during l3 agent restart +* Add 'keepalived\_use\_no\_track' config option +* Fix deletion of subnet\_id from pd\_subnets +* [OVN] Use the Chassis\_Private table for agents healthcheck +* Clean up some of the OVN agent API methods +* Fix get\_ipv6\_llas method in the interface driver + +16.1.0 +------ + +* [ovn]: gracefully handle logical switch ports with tag set to None +* ovn-migration-mtu: Support migrating MTU of GRE networks +* ovn-migration-mtu: Support providing project/user domain by name +* Fix port can not be created with the sg of other project +* migration: Restart OVS after setting protocols +* port\_forwarding: validate args before invoking db update +* Fix validation of IPv6 subnets with external RAs +* String to byte conversion should provide the encoding type +* [OVS][FW] Remote SG IDs left behind when a SG is removed +* Use "replace" to remove a section of a string +* [ovn] Use normalized remote prefix IPs in OVN driver +* [Stable only] Drop \*-master jobs +* ovn migration: Support stack name +* [OVN] Allow IP allocation with different segments for OVN service ports +* [OVN] Extra DHCP options validation: Log invalid options +* ovn-migration: Remove docker references +* Fix neutron-ovn-tempest-ovs-master-fedora job +* Ensure drop flows on br-int at agent startup for DVR too +* [OVN] Fix logic issue while deleting port with QoS +* [OVN] Unify OVN/OVS compilation +* migration: Use ansible-inventory to parse tripleo inventory +* [OVN] Stop using neutron\_tempest\_plugin in OVN singlenode job +* Stop installing Octavia in OVN jobs +* [OVN] Wait for WaitForDataPathBindingCreateEvent event in functional tests +* Optionally use admin powers when deleting DNS records +* Auto-delete dhcp ports on segment delete +* Fix pep8 job +* [DVR] Related routers should be included if are requested +* [ci] Fix several rally task arguments +* Add config option \`\`http\_retries\`\` +* Make DVR router support FLAT network for ovs-agent +* [OVN] Fix db-sync-util Traceback when port security not enabled +* [OVN] Allow use of ovn-sync mechanism driver +* [OVN] OVN driver to adapt to enable\_distributed\_floating\_ip changes +* Improve log message when port losts its vlan tag +* Do not block connection between br-int and br-phys on startup +* Workaround for TCP checksum issue with ovs-dpdk and veth pair +* [OVN] Add support for router availability zones +* L3 agent scheduler should return a valid index if manual scheduling +* DhcpFilter should always return a valid index if "force\_scheduling" +* Use pyroute2 for SRIOV VF commands +* Fix the wrong value for QoS rate conversion to bytes/s +* [OVN] Load segments plugin in case not loaded in maintanance task +* Make \_ensure\_default\_security\_group method atomic +* [OVS] Make QoS OVS agent deletion operations more resilient +* [OVN] Avoid unnecessary DB writes during agent liveness check +* Fixes dnsmasq host file parsing with "addr6\_list" +* Configure privsep in SR-IOV agent +* ovn: Remove is\_port\_groups\_supported() code +* No rpc\_response\_max\_timeout in LB-agent +* Migrate neutron grenade jobs to be native Zuul v3 +* Fix ussuri gates +* Fix ssh to nodes during ML2/OVS to ML2/OVN migration +* ovn-migration: Stop ml2/ovs agents before installing OVN resources +* [OVN] Create localnet port for each created segment +* Don't check if any bridges were recrected when OVS was restarted +* Fix iptables rules comments +* [OVN] Override notify\_nova config in neutron-ovn-db-sync-util +* [OVN] Don't set virtual port type on ports with similar addresses +* Fix Traceback when running neutron-ipset-cleanup tool +* Blacklist the OVN tempest IPv6 hotplug test +* [OVN] Enhance port's extra DHCP options support +* [DVR] Reconfigure re-created physical bridges for dvr routers +* Delete segment RPs when network is deleted +* Ensure that stale flows are cleaned from phys\_bridges +* Fix neutron-ovn-db-sync-util issues +* Allow usage of legacy 3rd-party interface drivers +* Add sg name in after delete event kwargs +* Update OVN local.conf example files +* Set class ovsdb\_conection to None +* Fix ovn-db-sync-util after removing l3 ovsdb connection +* [ovn] devstack needs to support openflow15 +* [OVN]: Make \_delete\_port() more error-resilent +* [OVN][metadata] Adding ERROR trace upon unexpected data +* Add Rocky milestone tag for alembic migration revisions +* Cap pycodestyle to be < 2.6.0 +* Switch to stable/ussuri neutron-tempest-plugin jobs +* [ovn]: Fix l3\_plugin.add\_router\_interface to comply with RouterPluginBase +* Report L3 extensions enabled in the L3 agent's config +* Use dhcp-host tag support when supported + +16.0.0 +------ + +* Monkey patch original current\_thread \_active +* Add Octavia file in devstack/lib +* Add Prelude for Ussuri release notes +* Imported Translations from Zanata +* Update TOX\_CONSTRAINTS\_FILE for stable/ussuri +* Update .gitreview for stable/ussuri + +16.0.0.0rc1 +----------- + +* Lock subnets during port creation and subnet deletion +* [L3 HA] Add "no\_track" option to VIPs in keepalived config +* [OVN] External ports: Account for VNIC\_DIRECT\_PHYSICAL / VNIC\_MACVTAP +* Provide correct fip cidr when deleting port forwarding +* [OVN] Enable qos service plugin in tempest jobs +* Additions to ovn-db-local.conf.sample +* Add rootwrap filter rule for radvd-kill script +* Add "rbac-address-scope" to OVN supported extensions +* Fix "TestMonitorDaemon" keepalived-state-change input parameters +* Improve port retrieval when validating auto address * OVN: Add note re IGMP groups upon ovn-controller service restart +* [Fullstack] Handle properly BrokenPipeError exception +* Wait until agent metadata is in SB chassis register +* Refactor OVN client QoS extension * Allow sharing of subnet pools via RBAC mechanism +* Update OVN local.conf sample file * Promote tempest and fullstack uwsgi jobs to be voting * [OVN] Bump up transaction timeout for functional tests +* Default (shared) network segment range is not mandatory * [OVN] Add IGMP snooping configuration guide * Add fullstack tests for stateless security groups * Enable back mac spoofing and disabled port security tests @@ -30,6 +383,7 @@ * Use dict .get() to avoid a KeyError in the segment plugin * Enable neutron-ovn jobs in check queue * Switch tripleo based job to be run on Centos 8 +* [Security] fix allowed-address-pair 0.0.0.0/0 issue * Revert "Switch to use cast method in dhcp\_ready\_on\_ports method" * [OVN] Fix: DevStack guide * [ovn] Documentation: Fix broken links in the OVN Doc @@ -41,6 +395,7 @@ * Switch to new engine facade in test\_allowedaddresspairs\_db module * Reno only - Make stateless allocation segment aware * Add known gaps between ML2/OVS and OVN +* Fix fake\_meta\_dvr\_port initialization * [ovn] Stricter matching on metadata port binding event * Allow sharing of address scopes via RBAC mechanism * Clear lsp.addresses always if port is OVN LB VIP port diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/changelog neutron-16.4.2/debian/changelog --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/changelog 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/changelog 2023-04-18 14:13:42.000000000 +0000 @@ -1,3 +1,259 @@ +neutron (2:16.4.2-0ubuntu6.2) focal-security; urgency=medium + + * SECURITY UPDATE: uncontrolled resource consumption flaw + - debian/patches/CVE-2022-3277.patch: do not allow a tenant to create a + default SG for another one in neutron/db/securitygroups_db.py, + neutron/tests/unit/db/test_securitygroups_db.py. + - CVE-2022-3277 + + -- Marc Deslauriers Tue, 18 Apr 2023 10:13:42 -0400 + +neutron (2:16.4.2-0ubuntu6.1) focal; urgency=medium + + * d/p/ovn-Filter-ACL-columns-when-syncing-the-DB.patch: Backport fix for + neutron-ovn-db-sync-util to ensure that ACL columns are filtered to + include only those used by the OVN mechanism driver (LP: #1951296). + + -- Edward Hope-Morley Tue, 28 Mar 2023 16:11:11 +0100 + +neutron (2:16.4.2-0ubuntu6) focal; urgency=medium + + * d/control: Add openvswitch-common dependency to neutron-server + binary package (LP: #1960319). + + -- Edward Hope-Morley Fri, 27 Jan 2023 13:38:38 +0000 + +neutron (2:16.4.2-0ubuntu5) focal; urgency=medium + + Backport fix for port dns_domain sync (LP: #1873091): + * d/p/sync-the-dns-assignment-with-the-actual-designate-dn.patch + + -- Edward Hope-Morley Tue, 20 Sep 2022 13:41:55 +0100 + +neutron (2:16.4.2-0ubuntu4) focal; urgency=medium + + [ Zhang Hua ] + * d/p/defer-flow-deletion-in-openvswitch-firewall.patch: + Defer flow deletion in openvswitch firewall (LP: #1975674) + + [ Edward Hope-Morley ] + * Ensure ovn virtual port type not removed (LP: #1973276) + - d/p/ovn-allow-vip-ports-with-defined-device-owner.patch + - d/p/set-type-virtual-for-ovn-lsp-with-parent-ports.patch + + -- Corey Bryant Thu, 15 Sep 2022 15:42:05 -0400 + +neutron (2:16.4.2-0ubuntu3) focal; urgency=medium + + * d/p/partially-revert-do-not-link-up-ha-router-gateway-in.patch: + Picked from upstream to ensure that ha_confs path is created + when the keepalived manager is initialised (LP: #1965297). + + -- Corey Bryant Tue, 05 Jul 2022 15:50:56 -0400 + +neutron (2:16.4.2-0ubuntu2) focal; urgency=medium + + * d/p/lp1975594*.patch: Backport of various patches to fix OVN + functionality on Ussuri (LP: #1975594). + + -- Corey Bryant Tue, 24 May 2022 09:48:43 -0400 + +neutron (2:16.4.2-0ubuntu1) focal; urgency=medium + + * New stable point release for OpenStack Ussuri (LP: #1956994). + * d/p/revert-l3-ha-retry-when-setting-ha-router-gw-status.patch: + Remove after patch is released upstream + + -- Chris MacNaughton Tue, 11 Jan 2022 21:11:43 +0000 + +neutron (2:16.4.1-0ubuntu2) focal; urgency=medium + + * d/p/lp1934912-set-arp-entries-only-for-single-ip.patch: Cherry-pick + upstream patch (LP: #1934912) + + -- Chris MacNaughton Fri, 01 Oct 2021 12:18:58 +0000 + +neutron (2:16.4.1-0ubuntu1) focal; urgency=medium + + [ Corey Bryant ] + * d/p/revert-rely-on-worker-count-for-hashring-caching.patch: Dropped. + Fixed upstream by https://review.opendev.org/c/openstack/neutron/+/800679 + in the 16.4.1 stable release. + + [ Chris MacNaughton ] + * New stable point release for OpenStack Ussuri (LP: #1943712). + * d/p/provide-integer-argument-to-arping.patch: Removed after + inclusion in upstream release. + + -- Chris MacNaughton Fri, 17 Sep 2021 10:05:08 +0000 + +neutron (2:16.4.0-0ubuntu3) focal; urgency=medium + + * d/p/revert-l3-ha-retry-when-setting-ha-router-gw-status.patch: Revert + upstream patch that introduced regression that prevented full restore + of HA routers on restart of L3 agent (LP: #1927868). + + -- Corey Bryant Wed, 28 Jul 2021 17:20:43 -0400 + +neutron (2:16.4.0-0ubuntu2) focal; urgency=medium + + * d/p/provide-integer-argument-to-arping.patch: Cherry-pick upstream + patch to ensure gratuitous APRs are correctly sent (LP: #1885169). + + -- Chris MacNaughton Fri, 16 Jul 2021 14:25:28 +0000 + +neutron (2:16.4.0-0ubuntu1) focal; urgency=medium + + * New stable point release for OpenStack Ussuri (LP: #1935030). + * Remove patches that have landed upstream in this point release: + - d/p/updates-for-python3.8.patch + - d/p/0001-Update-arp-entry-of-snat-port-on-qrouter-ns.patch + + -- Chris MacNaughton Tue, 13 Jul 2021 12:40:23 +0000 + +neutron (2:16.3.2-0ubuntu3) focal; urgency=medium + + * d/p/revert-rely-on-worker-count-for-hashring-caching.patch: Revert + patch from 16.3.2 due to SR-IOV regression (LP: #1931244). + + -- Corey Bryant Tue, 08 Jun 2021 12:57:47 -0400 + +neutron (2:16.3.2-0ubuntu2) focal; urgency=medium + + * d/p/updates-for-python3.8.patch: Cherry-picked from + https://review.opendev.org/c/openstack/neutron/+/793417 + to ensure py38 keepalived-state-change cleanup (LP: #1929832). + * d/p/d/p/0001-L3-Check-agent-gateway-port-robustly.patch: Dropped. + Fixed in 16.3.2 stable point release. + + -- Corey Bryant Thu, 27 May 2021 15:54:50 -0400 + +neutron (2:16.3.2-0ubuntu1) focal; urgency=medium + + * New stable point release for OpenStack Ussuri (LP: #1927976). + + -- Chris MacNaughton Thu, 20 May 2021 12:45:50 +0000 + +neutron (2:16.3.1-0ubuntu1.1) focal; urgency=medium + + [ Hemanth Nakkina ] + * Fix to check L3 agent gateway port robustly (LP: #1883089) + - d/p/0001-L3-Check-agent-gateway-port-robustly.patch. + + -- Chris MacNaughton Thu, 06 May 2021 10:39:19 +0000 + +neutron (2:16.3.1-0ubuntu1) focal; urgency=medium + + * New stable point release for OpenStack Ussuri (LP: #1923036). + * d/p/0001-Fix-deletion-of-rfp-interfaces-when-router-is-re-ena.patch, + d/p/improve-get-devices-with-ip-performance.patch, + d/p/revert-dvr-remove-control-plane-arp-updates.patch: Removed + after patch landed upstream. + + -- Chris MacNaughton Mon, 12 Apr 2021 12:13:59 +0000 + +neutron (2:16.3.0-0ubuntu3) focal; urgency=medium + + * d/p/revert-dvr-remove-control-plane-arp-updates.patch: Cherry-picked + from https://review.opendev.org/c/openstack/neutron/+/777903 to prevent + permanent arp entries that never get deleted (LP: #1916761). + * d/p/improve-get-devices-with-ip-performance.patch: Performance of + get_devices_with_ip is improved to limit the amount of information + to be sent and reduce the number of syscalls. (LP: #1896734). + + -- Corey Bryant Mon, 08 Mar 2021 13:26:42 -0500 + +neutron (2:16.3.0-0ubuntu2) focal; urgency=medium + + * Backport fix for dvr-snat missig rfp interfaces (LP: #1894843) + - d/p/0001-Fix-deletion-of-rfp-interfaces-when-router-is-re-ena.patch + + -- Edward Hope-Morley Wed, 24 Feb 2021 10:07:51 +0000 + +neutron (2:16.3.0-0ubuntu1) focal; urgency=medium + + * d/watch: Add trailing slash to Neutron URL. + * New stable point release for OpenStack Ussuri (LP: #1915786). + * d/p/fix-removal-of-dvr-src-mac-flows.patch, + d/p/ovn-fix-inconsistent-igmp-configuration.patch: Removed after patch landed + upstream. + + -- Chris MacNaughton Tue, 16 Feb 2021 16:09:39 +0000 + +neutron (2:16.2.0-0ubuntu3) focal; urgency=medium + + * d/p/ovn-fix-inconsistent-igmp-configuration.patch: Cherry-picked from + upstream stable/ussuri to ensure flooding of unregistered multicast + packets to all ports is disabled (LP: #1904399). + + -- Corey Bryant Mon, 08 Feb 2021 12:31:39 -0500 + +neutron (2:16.2.0-0ubuntu2) focal; urgency=medium + + * d/p/fix-removal-of-dvr-src-mac-flows.patch: Cherry-picked from upstream to + fix removal of dvr-src mac flows when non-gateway port on router is deleted + (LP: #1892405). + + -- Corey Bryant Thu, 07 Jan 2021 15:10:11 -0500 + +neutron (2:16.2.0-0ubuntu1) focal; urgency=medium + + * d/control: Update VCS paths for move to lp:~ubuntu-openstack-dev. + * New stable point release for OpenStack Stein (LP: #1899051). + * d/p/skip-iptest.patch: Refreshed. + * d/p/Ensure-fip-ip-rules-deleted-when-fip-removed.patch: Removed because + change landed upstream. + + -- Chris MacNaughton Mon, 12 Oct 2020 09:07:49 +0000 + +neutron (2:16.1.0-0ubuntu2) focal; urgency=medium + + * d/p/Ensure-fip-ip-rules-deleted-when-fip-removed.patch + Backport fix for dvr fip ip rule cleanup (LP: #1891673) + + -- Edward Hope-Morley Wed, 09 Sep 2020 09:25:20 +0100 + +neutron (2:16.1.0-0ubuntu1) focal; urgency=medium + + * New stable point release for OpenStack Ussuri (LP: #1892139). + * d/control: Align (Build-)Depends with upstream. + + -- Chris MacNaughton Thu, 27 Aug 2020 05:31:05 +0000 + +neutron (2:16.0.0-0ubuntu0.20.04.2) focal; urgency=medium + + * Ensure OVS and linuxbridge cleanup systemd units are not restarted + on upgrade or started on install (LP: #1885264): + - d/rules: Pass --no-stop-on-upgrade to dh_install{systemd,init} for + ovs and linuxbridge cleanup systemd units. + - d/rules: Pass --no-start to dh_install{systemd,init} for + ovs and linuxbridge cleanup systemd units. + - d/rules: Also pass --no-restart-after-upgrade to deal with bug + in older debhelper versions in Ubuntu Cloud Archive. + - d/rules: Pass appropriate -X flags to each call to + dh_install{systemd,init} to ensure that duplicate snippets are + not generated in maintainer scripts. + + -- James Page Thu, 02 Jul 2020 08:27:02 +0100 + +neutron (2:16.0.0-0ubuntu0.20.04.1) focal; urgency=medium + + * d/watch: Scope to 16.x series. + * New upstream release for OpenStack Ussuri (LP: #1877642). + * d/p/monkey-patch-original-current-thread.patch: Dropped. Fixed + in upstream release. + * d/gbp.conf: Create stable/ussuri branch. + + -- Corey Bryant Wed, 13 May 2020 17:06:16 -0400 + +neutron (2:16.0.0~b3~git2020041516.5f42488a9a-0ubuntu3) groovy; urgency=medium + + * d/p/monkey-patch-original-current-thread.patch: Cherry-picked from + https://review.opendev.org/724753. This fixes neutron service failures + with Python 3.8 (LP: #1863021). + + -- Corey Bryant Thu, 30 Apr 2020 16:42:56 -0400 + neutron (2:16.0.0~b3~git2020041516.5f42488a9a-0ubuntu2) focal; urgency=medium * d/neutron-common.postinst: Ensure subdirectories and files under diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/control neutron-16.4.2/debian/control --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/control 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/control 2023-03-28 15:11:11.000000000 +0000 @@ -26,6 +26,7 @@ python3-futurist (>= 1.2.0), python3-hacking (>= 1.1.0), python3-httplib2 (>= 0.9.1), + python3-isort (>= 4.3.4), python3-jinja2 (>= 2.10), python3-keystoneauth1 (>= 3.14.0), python3-keystonemiddleware (>= 4.17.0), @@ -91,8 +92,8 @@ python3-webtest (>= 2.0.27), rename, Standards-Version: 4.1.4 -Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/neutron -Vcs-Git: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/neutron +Vcs-Browser: https://git.launchpad.net/~ubuntu-openstack-dev/ubuntu/+source/neutron +Vcs-Git: https://git.launchpad.net/~ubuntu-openstack-dev/ubuntu/+source/neutron Homepage: https://docs.openstack.org/neutron Package: neutron-common @@ -347,6 +348,7 @@ Architecture: all Depends: adduser, + openvswitch-common, python3-neutron (= ${source:Version}), ${misc:Depends}, ${python3:Depends}, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/gbp.conf neutron-16.4.2/debian/gbp.conf --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/gbp.conf 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/gbp.conf 2023-03-28 15:11:11.000000000 +0000 @@ -1,5 +1,5 @@ [DEFAULT] -debian-branch = master +debian-branch = stable/ussuri upstream-tag = %(version)s pristine-tar = True diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/CVE-2022-3277.patch neutron-16.4.2/debian/patches/CVE-2022-3277.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/CVE-2022-3277.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/CVE-2022-3277.patch 2023-04-18 14:13:36.000000000 +0000 @@ -0,0 +1,53 @@ +From cbeee87fa44cd200d4997e02042098460167dce1 Mon Sep 17 00:00:00 2001 +From: Brian Haley +Date: Thu, 1 Sep 2022 21:13:44 -0400 +Subject: [PATCH] Do not allow a tenant to create a default SG for another one + +The attempt to list security groups for a project, or any +random string, can create a default SG for it. Only allow if +privileges support it. + +Closes-bug: #1988026 + +Change-Id: Ieef7011f48cd2188d4254ff16d90a6465bbabfe3 +(cherry picked from commit 01fc2b9195f999df4d810df4ee63f77ecbc81f7e) +--- + neutron/db/securitygroups_db.py | 4 ++++ + neutron/tests/unit/db/test_securitygroups_db.py | 12 ++++++++++++ + 2 files changed, 16 insertions(+) + +diff --git a/neutron/db/securitygroups_db.py b/neutron/db/securitygroups_db.py +index 28238358ae8..fcfd5caccf8 100644 +--- a/neutron/db/securitygroups_db.py ++++ b/neutron/db/securitygroups_db.py +@@ -862,6 +862,10 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase, + + :returns: the default security group id for given tenant. + """ ++ # Do not allow a tenant to create a default SG for another one. ++ # See Bug 1987410. ++ if tenant_id != context.tenant_id and not context.is_admin: ++ return + if not extensions.is_extension_supported(self, 'security-group'): + return + default_group_id = self._get_default_sg_id(context, tenant_id) +diff --git a/neutron/tests/unit/db/test_securitygroups_db.py b/neutron/tests/unit/db/test_securitygroups_db.py +index 6925e976eee..1bdecdc034f 100644 +--- a/neutron/tests/unit/db/test_securitygroups_db.py ++++ b/neutron/tests/unit/db/test_securitygroups_db.py +@@ -615,3 +615,15 @@ class SecurityGroupDbMixinTestCase(testlib_api.SqlTestCase): + self.mixin._ensure_default_security_group(self.ctx, 'tenant_1') + create_sg.assert_not_called() + get_default_sg_id.assert_not_called() ++ ++ def test__ensure_default_security_group_tenant_mismatch(self): ++ with mock.patch.object( ++ self.mixin, '_get_default_sg_id') as get_default_sg_id,\ ++ mock.patch.object( ++ self.mixin, 'create_security_group') as create_sg: ++ context = mock.Mock() ++ context.tenant_id = 'tenant_0' ++ context.is_admin = False ++ self.mixin._ensure_default_security_group(context, 'tenant_1') ++ create_sg.assert_not_called() ++ get_default_sg_id.assert_not_called() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/defer-flow-deletion-in-openvswitch-firewall.patch neutron-16.4.2/debian/patches/defer-flow-deletion-in-openvswitch-firewall.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/defer-flow-deletion-in-openvswitch-firewall.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/defer-flow-deletion-in-openvswitch-firewall.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,41 @@ +From 1a01d0e66a430d6716cf969facb15f626f0ed1a6 Mon Sep 17 00:00:00 2001 +From: Henning Eggers +Date: Wed, 25 May 2022 11:17:43 +0200 +Subject: [PATCH] Defer flow deletion in openvswitch firewall + +Reduces the deletion time of conjunction flows on hypervisors +where virtual machines reside which are part of a security +group that has remote security groups as target which contain +thousands of ports. + +Without deferred deletion the agent will call ovs-ofctl several +hundred times in succession, during this time the agent will +block any new vm creation or neutron port modifications on this +hypervisor. + +This patch has been tested using a single network with a single +vm with a security group that points to a remote security group +with 2000 ports. + +During testing without the patch, the iteration time for deletion +was at around 500 seconds. After adding the patch to the l2 agent +on the test environment the same deletion time went down to +4 seconds. + +Closes-Bug: #1975674 +Change-Id: I46b1fe94b2e358f7f4b2cd4943a74ebaf84f51b8 +(cherry picked from commit e09b128f416a809cd7734aba8ab52220ea01b2e2) +(cherry picked from commit 30ef996f8aa0b0bc57a280690871f1081946ffee) +Signed-off-by: Zhang Hua +--- + neutron/agent/linux/openvswitch_firewall/firewall.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- neutron-16.4.2.orig/neutron/agent/linux/openvswitch_firewall/firewall.py ++++ neutron-16.4.2/neutron/agent/linux/openvswitch_firewall/firewall.py +@@ -1553,4 +1553,4 @@ class OVSFirewallDriver(firewall.Firewal + # will not match with the ip flow's cookie so OVS won't actually + # delete the flow + flow['cookie'] = ovs_lib.COOKIE_ANY +- self._delete_flows(deferred=False, **flow) ++ self._delete_flows(**flow) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1934912-set-arp-entries-only-for-single-ip.patch neutron-16.4.2/debian/patches/lp1934912-set-arp-entries-only-for-single-ip.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1934912-set-arp-entries-only-for-single-ip.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1934912-set-arp-entries-only-for-single-ip.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,89 @@ +From d0cf4638f595c38a67974a45d5ef76dcf34e8918 Mon Sep 17 00:00:00 2001 +From: Slawek Kaplonski +Date: Thu, 08 Jul 2021 15:53:39 +0200 +Subject: [PATCH] [DVR] Set arp entries only for single IPs given as allowed addr pair + +In allowed address pairs of the port there can be given not single IP +address but whole CIDR. In such case ARP entries for IPs from such +cidr will not be added in the DVR router namespace. + +Closes-Bug: #1934912 +Change-Id: I7bdefea943379125f93b116bb899446b874d9505 +(cherry picked from commit 19375b3e78ad6b635793b716e5ecabd53dc73a76) +--- + +diff --git a/neutron/agent/l3/dvr_local_router.py b/neutron/agent/l3/dvr_local_router.py +index 6df6967..0efb8ad 100644 +--- a/neutron/agent/l3/dvr_local_router.py ++++ b/neutron/agent/l3/dvr_local_router.py +@@ -365,12 +365,18 @@ + device=device, + device_exists=device_exists) + for allowed_address_pair in p.get('allowed_address_pairs', []): +- self._update_arp_entry(allowed_address_pair['ip_address'], +- allowed_address_pair['mac_address'], +- subnet_id, +- 'add', +- device=device, +- device_exists=device_exists) ++ if ('/' not in str(allowed_address_pair['ip_address']) or ++ common_utils.is_cidr_host( ++ allowed_address_pair['ip_address'])): ++ ip_address = common_utils.cidr_to_ip( ++ allowed_address_pair['ip_address']) ++ self._update_arp_entry( ++ ip_address, ++ allowed_address_pair['mac_address'], ++ subnet_id, ++ 'add', ++ device=device, ++ device_exists=device_exists) + + # subnet_ports does not have snat port if the port is still unbound + # by the time this function is called. So ensure to add arp entry +diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py +index f17b624..71706ad 100644 +--- a/neutron/tests/functional/agent/l3/test_dvr_router.py ++++ b/neutron/tests/functional/agent/l3/test_dvr_router.py +@@ -1006,13 +1006,18 @@ + # cache is properly populated. + self.agent.conf.agent_mode = 'dvr_snat' + router_info = self.generate_dvr_router_info(enable_snat=True) +- expected_neighbors = ['35.4.1.10', '10.0.0.10'] ++ expected_neighbors = ['35.4.1.10', '10.0.0.10', '10.200.0.3'] ++ allowed_address_net = netaddr.IPNetwork('10.100.0.0/30') + port_data = { + 'fixed_ips': [{'ip_address': expected_neighbors[0]}], + 'mac_address': 'fa:3e:aa:bb:cc:dd', + 'device_owner': DEVICE_OWNER_COMPUTE, + 'allowed_address_pairs': [ + {'ip_address': expected_neighbors[1], ++ 'mac_address': 'fa:3e:aa:bb:cc:dd'}, ++ {'ip_address': '10.200.0.3/32', ++ 'mac_address': 'fa:3e:aa:bb:cc:dd'}, ++ {'ip_address': str(allowed_address_net), + 'mac_address': 'fa:3e:aa:bb:cc:dd'}] + } + self.agent.plugin_rpc.get_ports_by_subnet.return_value = [port_data] +@@ -1020,11 +1025,18 @@ + internal_device = router1.get_internal_device_name( + router_info['_interfaces'][0]['id']) + for expected_neighbor in expected_neighbors: +- neighbor = ip_lib.dump_neigh_entries(4, internal_device, +- router1.ns_name, +- dst=expected_neighbor) ++ neighbor = ip_lib.dump_neigh_entries( ++ lib_constants.IP_VERSION_4, internal_device, ++ router1.ns_name, ++ dst=expected_neighbor) + self.assertNotEqual([], neighbor) + self.assertEqual(expected_neighbor, neighbor[0]['dst']) ++ for not_expected_neighbor in allowed_address_net: ++ neighbor = ip_lib.dump_neigh_entries( ++ lib_constants.IP_VERSION_4, internal_device, ++ router1.ns_name, ++ dst=str(not_expected_neighbor)) ++ self.assertEqual([], neighbor) + + def _assert_rfp_fpr_mtu(self, router, expected_mtu=1500): + dev_mtu = self.get_device_mtu( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0001-fix-RowNotFound-exception-while-waiting-for-meta.patch neutron-16.4.2/debian/patches/lp1975594-0001-fix-RowNotFound-exception-while-waiting-for-meta.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0001-fix-RowNotFound-exception-while-waiting-for-meta.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0001-fix-RowNotFound-exception-while-waiting-for-meta.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,136 @@ +From 7699feb4a61661129c79bc7da517f07f1257124c Mon Sep 17 00:00:00 2001 +From: Lucas Alvares Gomes +Date: Wed, 3 Feb 2021 14:00:13 +0000 +Subject: [PATCH] [OVN] Fix RowNotFound exception while waiting for metadata + networks + +In the set_port_status_up() the OVN driver waits for the metadata to be +provisioned (15 seconds) [0] prior to sending the event to Nova notifying +that the provisioning of the port is done (network-vif-plugged). But +there could be a race condition while trying to get that information +which results in a RowNotFound being raise in the waiting loop. + +Once that happens, the exception is bubbled up and the OVN driver end up +not sending the event to Nova and the instance will fail to deploy (it +will be stuck in BUILD state until it times out). + +This patch changes the logic of the method looking for the metadata +network information to not raise RowNotFound so that the waiting loop +can iteract again [0] until the information is available. + +[0] +https://github.com/openstack/neutron/blob/cbd72e2f4846ec64ff6e6ef24099a8e90ddebf31/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py#L1124 + +Change-Id: I3c106ace74b5c6e4ed0cb7e827baf5d6595ec5d0 +Closes-Bug: #1914394 +Signed-off-by: Lucas Alvares Gomes +(cherry picked from commit b618d98541599ad0ef73be41a3edd83d1fc75a56) +(cherry picked from commit cbf3fe098bb44605174b8e582d56814fd36632c7) +Conflicts cleanly resolved by removing not relevant code added before +the addittion of the TestSBImplIdlOvn class. Also added missing +ovsdbapp.backend import: + neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py +--- + .../ovn/mech_driver/ovsdb/impl_idl_ovn.py | 7 ++- + neutron/tests/unit/fake_resources.py | 1 + + .../mech_driver/ovsdb/test_impl_idl_ovn.py | 50 +++++++++++++++++++ + 3 files changed, 57 insertions(+), 1 deletion(-) + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py +index b2cc6ebb85..02e3146517 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py +@@ -849,7 +849,12 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend): + + def get_chassis_metadata_networks(self, chassis_name): + """Return a list with the metadata networks the chassis is hosting.""" +- chassis = self.lookup('Chassis', chassis_name) ++ try: ++ chassis = self.lookup('Chassis', chassis_name) ++ except idlutils.RowNotFound: ++ LOG.warning("Couldn't find Chassis named %s in OVN while looking " ++ "for metadata networks", chassis_name) ++ return [] + proxy_networks = chassis.external_ids.get( + 'neutron-metadata-proxy-networks', None) + return proxy_networks.split(',') if proxy_networks else [] +diff --git a/neutron/tests/unit/fake_resources.py b/neutron/tests/unit/fake_resources.py +index 4dc87a23ec..3f59d08aaf 100644 +--- a/neutron/tests/unit/fake_resources.py ++++ b/neutron/tests/unit/fake_resources.py +@@ -410,6 +410,7 @@ class FakeOvsdbTable(FakeResource): + ovsdb_table_attrs = { + 'rows': {}, + 'columns': {}, ++ 'indexes': [], + } + + # Overwrite default attributes. +diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py +index 3b666c5714..fafec52553 100644 +--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py ++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py +@@ -17,6 +17,8 @@ import uuid + + import mock + ++from ovsdbapp.backend import ovs_idl ++ + from neutron.common.ovn import constants as ovn_const + from neutron.common.ovn import utils + from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn +@@ -758,3 +760,51 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn): + self._tables.pop('Port_Group', None) + port_groups = self.nb_ovn_idl.get_port_groups() + self.assertEqual({}, port_groups) ++ ++ ++class TestSBImplIdlOvn(TestDBImplIdlOvn): ++ ++ fake_set = { ++ 'chassis': [ ++ {'name': 'fake-chassis', ++ 'external_ids': {'neutron-metadata-proxy-networks': 'fake-id'}}], ++ } ++ ++ def setUp(self): ++ super(TestSBImplIdlOvn, self).setUp() ++ self.chassis_table = fakes.FakeOvsdbTable.create_one_ovsdb_table() ++ ++ self._tables = {} ++ self._tables['Chassis'] = self.chassis_table ++ ++ with mock.patch.object(impl_idl_ovn.OvsdbSbOvnIdl, 'from_worker', ++ return_value=mock.Mock()): ++ with mock.patch.object(ovs_idl.Backend, 'autocreate_indices', ++ create=True): ++ impl_idl_ovn.OvsdbSbOvnIdl.ovsdb_connection = None ++ self.sb_ovn_idl = impl_idl_ovn.OvsdbSbOvnIdl(mock.MagicMock()) ++ ++ self.sb_ovn_idl.idl.tables = self._tables ++ ++ def _load_sb_db(self): ++ fake_chassis = TestSBImplIdlOvn.fake_set['chassis'] ++ self._load_ovsdb_fake_rows(self.chassis_table, fake_chassis) ++ ++ @mock.patch.object(impl_idl_ovn.LOG, 'warning') ++ def test_get_chassis_metadata_networks_chassis_empty(self, mock_log): ++ chassis_name = 'fake-chassis' ++ result = self.sb_ovn_idl.get_chassis_metadata_networks(chassis_name) ++ ++ mock_log.assert_called_once_with( ++ "Couldn't find Chassis named %s in OVN while looking " ++ "for metadata networks", chassis_name) ++ self.assertEqual([], result) ++ ++ @mock.patch.object(impl_idl_ovn.LOG, 'warning') ++ def test_get_chassis_metadata_networks(self, mock_log): ++ chassis_name = 'fake-chassis' ++ self._load_sb_db() ++ result = self.sb_ovn_idl.get_chassis_metadata_networks(chassis_name) ++ ++ self.assertFalse(mock_log.called) ++ self.assertEqual(['fake-id'], result) +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0002-stable-only-ovn-Update-get-datapath-id-to-network-fr.patch neutron-16.4.2/debian/patches/lp1975594-0002-stable-only-ovn-Update-get-datapath-id-to-network-fr.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0002-stable-only-ovn-Update-get-datapath-id-to-network-fr.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0002-stable-only-ovn-Update-get-datapath-id-to-network-fr.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,59 @@ +From 135e618c22326d6a23b47662d16443a43db840bb Mon Sep 17 00:00:00 2001 +From: "hailun.huang" +Date: Mon, 11 Oct 2021 11:25:45 +0800 +Subject: [PATCH] [stable only][ovn]Update get datapath id to network from + Port_Binding + +The value about neutron-metadata-proxy-networks of Chassis's + external_ids are network_id, and the datapath field of + Port_Binding table is the uuid of Datapath_Binding tables. +When call function set_port_status_up after boot server instance, + it check datapath in neutron-metadata-proxy-networks of + Chassis's external_ids, the datapath id is not match with + network id, so change get datapath id from Port_Binding + to get network id. +Since [1], this is not needed in Xena. +[1]https://review.opendev.org/c/openstack/neutron/+/791997 + +Closes-Bug: #1946588 +Change-Id: Ib3c1bea9b805dc7d9967f66b3b0136efb3e8e08d +(cherry picked from commit f5ea47b09507e7bd620ba3bcd1af771238d79d6d) +(cherry picked from commit cff314f92e158c8142dcd238a072fe2a7ca04d87) +--- + .../ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py | 5 +++-- + .../ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py | 4 +++- + 2 files changed, 6 insertions(+), 3 deletions(-) + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py +index b2cc6ebb85..f14927715a 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py +@@ -906,6 +906,7 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend): + def get_logical_port_chassis_and_datapath(self, name): + for port in self._tables['Port_Binding'].rows.values(): + if port.logical_port == name: +- datapath = str(port.datapath.uuid) ++ network_id = utils.get_network_name_from_datapath( ++ port.datapath) + chassis = port.chassis[0].name if port.chassis else None +- return chassis, datapath ++ return chassis, network_id +diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py +index 24caa93904..3a065de510 100644 +--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py ++++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py +@@ -145,8 +145,10 @@ class TestSbApi(base.FunctionalTestCase, + chassis, switch, port, binding = self._add_switch_port( + self.data['chassis'][0]['name']) + self.api.lsp_bind(port.name, chassis.name).execute(check_error=True) ++ network_id = binding.datapath.external_ids['name'].replace( ++ 'neutron-', '') + self.assertEqual( +- (chassis.name, str(binding.datapath.uuid)), ++ (chassis.name, network_id), + self.api.get_logical_port_chassis_and_datapath(port.name)) + + +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0003-add-reverse-DNS-records.patch neutron-16.4.2/debian/patches/lp1975594-0003-add-reverse-DNS-records.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0003-add-reverse-DNS-records.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0003-add-reverse-DNS-records.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,155 @@ +From 6f7bf0f0129b2714f99d18faa8a0bd87a2a3799e Mon Sep 17 00:00:00 2001 +From: yatinkarel +Date: Mon, 20 Dec 2021 13:07:25 +0530 +Subject: [PATCH] [OVN] Add reverse DNS records + +PTR DNS requests support is available since +ovn-21.06.0[1]. + +This patch adds/removes required enteries for each ip +address of the port in DNS NB table, For example for ip +addresses "10.0.0.4 fd5a:cdd8:f382:0:f816:3eff:fe5b:bb6" +and fqdn "vm1.ovn.test." following enteries are added:- +- 4.0.0.10.in-addr.arpa="vm1.ovn.test" +- 6.b.b.0.b.5.e.f.f.f.e.3.6.1.8.f.0.0.0.0.2.8.3.f.8.d.d.c.a.5.d.f.ip6.arpa="vm1.ovn.test" + +[1] https://github.com/ovn-org/ovn/commit/82a4e44 + +Closes-Bug: #1951872 +Change-Id: If03a2ad2475cdb390c4388d6869cd0b2a0555eb7 +(cherry picked from commit 5b597d6e4ee647502d3f2aecd46de63f409317b4) +(cherry picked from commit 5b30b14ad726e41793c76ecd0fca8ee66cf51607) +(cherry picked from commit 591570c5f4069234bef460274aa5442b60eed5de) +(cherry picked from commit 3664aa1bb421cc1d7add0c2c5209af27b384ec1b) +Conflicts, trivial due to change near imports at top of file: + neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py +--- + .../drivers/ovn/mech_driver/ovsdb/ovn_client.py | 13 +++++++++++++ + .../ovn/mech_driver/ovsdb/test_ovn_db_resources.py | 14 ++++++++++++++ + .../ovn/mech_driver/ovsdb/test_ovn_db_sync.py | 7 +++++++ + 3 files changed, 34 insertions(+) + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +index 96c4457d19..1aa18dbf12 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +@@ -2240,6 +2240,10 @@ class OVNClient(object): + port_dns_records[fqdn] = dns_assignment['ip_address'] + else: + port_dns_records[fqdn] += " " + dns_assignment['ip_address'] ++ # Add reverse DNS enteries for port only for fqdn ++ for ip in port_dns_records[fqdn].split(" "): ++ ptr_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ port_dns_records[ptr_record] = fqdn + + return port_dns_records + +@@ -2288,14 +2292,18 @@ class OVNClient(object): + net_dns_domain = net.get('dns_domain', '').rstrip('.') + + hostnames = [] ++ ips = [] + for dns_assignment in port['dns_assignment']: + hostname = dns_assignment['hostname'] + fqdn = dns_assignment['fqdn'].rstrip('.') ++ ip = dns_assignment['ip_address'] + if hostname not in hostnames: + hostnames.append(hostname) + net_dns_fqdn = hostname + '.' + net_dns_domain + if net_dns_domain and net_dns_fqdn != fqdn: + hostnames.append(net_dns_fqdn) ++ if ip not in ips: ++ ips.append(ip) + + if fqdn not in hostnames: + hostnames.append(fqdn) +@@ -2304,3 +2312,8 @@ class OVNClient(object): + if ls_dns_record.records.get(hostname): + txn.add(self._nb_idl.dns_remove_record( + ls_dns_record.uuid, hostname, if_exists=True)) ++ for ip in ips: ++ ptr_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ if ls_dns_record.records.get(ptr_record): ++ txn.add(self._nb_idl.dns_remove_record( ++ ls_dns_record.uuid, ptr_record, if_exists=True)) +diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py +index e8a9fdafa8..558a11ac84 100644 +--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py ++++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py +@@ -965,6 +965,9 @@ class TestDNSRecords(base.TestOVNFunctionalBase): + 'records': {'n1p1': port_ips, 'n1p1.ovn.test': port_ips, + 'n1p1.net-n1': port_ips}} + ] ++ for ip in port_ips.split(" "): ++ p_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ expected_dns_records[0]['records'][p_record] = 'n1p1.ovn.test' + + self._validate_dns_records(expected_dns_records) + self._validate_ls_dns_records(n1_lswitch_name, +@@ -990,6 +993,9 @@ class TestDNSRecords(base.TestOVNFunctionalBase): + expected_dns_records.append( + {'external_ids': {'ls_name': n2_lswitch_name}, + 'records': {'n2p1': port_ips, 'n2p1.ovn.test': port_ips}}) ++ for ip in port_ips.split(" "): ++ p_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ expected_dns_records[1]['records'][p_record] = 'n2p1.ovn.test' + self._validate_dns_records(expected_dns_records) + self._validate_ls_dns_records(n1_lswitch_name, + [expected_dns_records[0]]) +@@ -1007,6 +1013,9 @@ class TestDNSRecords(base.TestOVNFunctionalBase): + expected_dns_records[0]['records']['n1p2'] = port_ips + expected_dns_records[0]['records']['n1p2.ovn.test'] = port_ips + expected_dns_records[0]['records']['n1p2.net-n1'] = port_ips ++ for ip in port_ips.split(" "): ++ p_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ expected_dns_records[0]['records'][p_record] = 'n1p2.ovn.test' + self._validate_dns_records(expected_dns_records) + self._validate_ls_dns_records(n1_lswitch_name, + [expected_dns_records[0]]) +@@ -1020,6 +1029,11 @@ class TestDNSRecords(base.TestOVNFunctionalBase): + res = req.get_response(self.api) + self.assertEqual(200, res.status_int) + expected_dns_records[0]['records'].pop('n1p1') ++ port_ips = " ".join([f['ip_address'] ++ for f in n1p1['port']['fixed_ips']]) ++ for ip in port_ips.split(" "): ++ p_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ expected_dns_records[0]['records'].pop(p_record) + expected_dns_records[0]['records'].pop('n1p1.ovn.test') + expected_dns_records[0]['records'].pop('n1p1.net-n1') + self._validate_dns_records(expected_dns_records) +diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py +index 7678367ff1..e9103536ec 100644 +--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py ++++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py +@@ -12,6 +12,7 @@ + # License for the specific language governing permissions and limitations + # under the License. + ++import netaddr + from neutron.common.ovn import acl as acl_utils + from neutron.common.ovn import constants as ovn_const + from neutron.common.ovn import utils +@@ -162,6 +163,9 @@ class TestOvnNbSync(base.TestOVNFunctionalBase): + self.expected_dns_records[0]['records'][hname] = port_ips + hname = 'n1-' + p + '.ovn.test' + self.expected_dns_records[0]['records'][hname] = port_ips ++ for ip in port_ips.split(" "): ++ p_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ self.expected_dns_records[0]['records'][p_record] = hname + self.expected_ports_with_unknown_addr.append(lport_name) + + if p == 'p1': +@@ -512,6 +516,9 @@ class TestOvnNbSync(base.TestOVNFunctionalBase): + self.expected_dns_records[1]['records'][hname] = port_ips + hname = 'n4-' + p + '.ovn.test' + self.expected_dns_records[1]['records'][hname] = port_ips ++ for ip in port_ips.split(" "): ++ p_record = netaddr.IPAddress(ip).reverse_dns.rstrip(".") ++ self.expected_dns_records[1]['records'][p_record] = hname + + n4_port_dict[p] = port['port']['id'] + self.lport_dhcp_ignored.append(port['port']['id']) +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0004-accept-OVS-system-id-as-non-UUID-formatted-strin.patch neutron-16.4.2/debian/patches/lp1975594-0004-accept-OVS-system-id-as-non-UUID-formatted-strin.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0004-accept-OVS-system-id-as-non-UUID-formatted-strin.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0004-accept-OVS-system-id-as-non-UUID-formatted-strin.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,133 @@ +From 162b02195c41d93f3ef8af77dafd460877d1bb2c Mon Sep 17 00:00:00 2001 +From: Rodolfo Alonso Hernandez +Date: Mon, 20 Dec 2021 14:14:20 +0000 +Subject: [PATCH] [OVN] Accept OVS system-id as non UUID formatted string + +Accept OVS system-id non UUID formatted strings. The OVN metadata +agent will generate a unique UUID from the OVS system-id. If this +string is a UUID, this value will be used. If not, the OVN metadata +agent will generate a UUID based on the provided string. + +This patch amends [1]. + +[1]https://review.opendev.org/c/openstack/neutron/+/819634 + +Closes-Bug: #1952550 + +Conflicts: + neutron/agent/ovn/metadata/agent.py + neutron/tests/unit/agent/ovn/metadata/test_agent.py + +Change-Id: I42a8a767a6ef9454419b26f80339394759644faf +(cherry picked from commit 79037c951637dc06d47b6d354776d116a1d2a9ad) +(cherry picked from commit 6da4432fed255f3bcf3831f5d0520ab389ce36e5) +(cherry picked from commit b07eeb2789abc79c0fa86db0bdf7bc111ea725ac) +(cherry picked from commit 4bf531448eac151b53b05cae0a5ca51060ca38c3) +--- + neutron/agent/ovn/metadata/agent.py | 20 ++++++++++++---- + .../unit/agent/ovn/metadata/test_agent.py | 23 +++++++++++++++++++ + 2 files changed, 38 insertions(+), 5 deletions(-) + +diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py +index 0dda32309d..0e9b470d08 100644 +--- a/neutron/agent/ovn/metadata/agent.py ++++ b/neutron/agent/ovn/metadata/agent.py +@@ -14,12 +14,12 @@ + + import collections + import re ++import uuid + + from neutron_lib import constants as n_const + from oslo_concurrency import lockutils + from oslo_log import log + from oslo_utils import netutils +-from oslo_utils import uuidutils + from ovsdbapp.backend.ovs_idl import event as row_event + from ovsdbapp.backend.ovs_idl import vlog + import six +@@ -48,6 +48,8 @@ OVN_VIF_PORT_TYPES = ("", "external", ) + MetadataPortInfo = collections.namedtuple('MetadataPortInfo', ['mac', + 'ip_addresses']) + ++OVN_METADATA_UUID_NAMESPACE = uuid.UUID('d34bf9f6-da32-4871-9af8-15a4626b41ab') ++ + + def _sync_lock(f): + """Decorator to block all operations for a global sync call.""" +@@ -185,9 +187,16 @@ class MetadataAgent(object): + + def _load_config(self): + self.chassis = self._get_own_chassis_name() ++ try: ++ self.chassis_id = uuid.UUID(self.chassis) ++ except ValueError: ++ # OVS system-id could be a non UUID formatted string. ++ self.chassis_id = uuid.uuid5(OVN_METADATA_UUID_NAMESPACE, ++ self.chassis) ++ + self.ovn_bridge = self._get_ovn_bridge() +- LOG.debug("Loaded chassis %s and ovn bridge %s.", +- self.chassis, self.ovn_bridge) ++ LOG.info("Loaded chassis name %s (UUID: %s) and ovn bridge %s.", ++ self.chassis, self.chassis_id, self.ovn_bridge) + + @_sync_lock + def resync(self): +@@ -245,8 +254,9 @@ class MetadataAgent(object): + # NOTE(lucasagomes): db_add() will not overwrite the UUID if + # it's already set. + table = ('Chassis_Private' if self.has_chassis_private else 'Chassis') +- ext_ids = { +- ovn_const.OVN_AGENT_METADATA_ID_KEY: uuidutils.generate_uuid()} ++ # Generate unique, but consistent metadata id for chassis name ++ agent_id = uuid.uuid5(self.chassis_id, 'metadata_agent') ++ ext_ids = {ovn_const.OVN_AGENT_METADATA_ID_KEY: str(agent_id)} + self.sb_idl.db_add(table, self.chassis, 'external_ids', + ext_ids).execute(check_error=True) + +diff --git a/neutron/tests/unit/agent/ovn/metadata/test_agent.py b/neutron/tests/unit/agent/ovn/metadata/test_agent.py +index fa86f9554f..40a0cdd284 100644 +--- a/neutron/tests/unit/agent/ovn/metadata/test_agent.py ++++ b/neutron/tests/unit/agent/ovn/metadata/test_agent.py +@@ -13,10 +13,12 @@ + # limitations under the License. + + import collections ++import uuid + + import mock + from oslo_config import cfg + from oslo_config import fixture as config_fixture ++from oslo_utils import uuidutils + + from neutron.agent.linux import ip_lib + from neutron.agent.linux.ip_lib import IpAddrCommand as ip_addr +@@ -322,3 +324,24 @@ class TestMetadataAgent(base.BaseTestCase): + expected_dps = ['0', '1', '2'] + self._test_update_chassis_metadata_networks_helper( + dp, remove, expected_dps, txn_called=False) ++ ++ def test__load_config(self): ++ # Chassis name UUID formatted string. OVN bridge "br-ovn". ++ valid_uuid_str = uuidutils.generate_uuid() ++ self.agent.ovs_idl.db_get.return_value.execute.side_effect = [ ++ {'system-id': valid_uuid_str}, {'ovn-bridge': 'br-ovn'}] ++ self.agent._load_config() ++ self.assertEqual(valid_uuid_str, self.agent.chassis) ++ self.assertEqual(uuid.UUID(valid_uuid_str), self.agent.chassis_id) ++ self.assertEqual('br-ovn', self.agent.ovn_bridge) ++ ++ # Chassis name non UUID formatted string. OVN bridge not defined, ++ # "br-int" assigned by default. ++ self.agent.ovs_idl.db_get.return_value.execute.side_effect = [ ++ {'system-id': 'RandomName1'}, {}] ++ self.agent._load_config() ++ generated_uuid = uuid.uuid5(agent.OVN_METADATA_UUID_NAMESPACE, ++ 'RandomName1') ++ self.assertEqual('RandomName1', self.agent.chassis) ++ self.assertEqual(generated_uuid, self.agent.chassis_id) ++ self.assertEqual('br-int', self.agent.ovn_bridge) +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0005-add-wait-event-for-metadataagent-sb_idl.patch neutron-16.4.2/debian/patches/lp1975594-0005-add-wait-event-for-metadataagent-sb_idl.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0005-add-wait-event-for-metadataagent-sb_idl.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0005-add-wait-event-for-metadataagent-sb_idl.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,66 @@ +From ea364deb01a2bc70945bc955d69205d75f9c8a0d Mon Sep 17 00:00:00 2001 +From: shanyunfan33 +Date: Thu, 16 Dec 2021 10:48:53 +0800 +Subject: [PATCH] Add wait event for metadataagent sb_idl + +resolve bug:'MetadataAgent' object has no attribute 'sb_idl' + +Closes-Bug: #1953295 +Change-Id: Id83bee82e5c476395ff422dcaf89c0095e74a8ec +(cherry picked from commit 385b0ad203da40a2c0110948c0f46feb78ead2be) +--- + neutron/agent/ovn/metadata/agent.py | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py +index 0e9b470d08..dd64e644e0 100644 +--- a/neutron/agent/ovn/metadata/agent.py ++++ b/neutron/agent/ovn/metadata/agent.py +@@ -14,6 +14,7 @@ + + import collections + import re ++import threading + import uuid + + from neutron_lib import constants as n_const +@@ -184,6 +185,18 @@ class MetadataAgent(object): + self._process_monitor = external_process.ProcessMonitor( + config=self.conf, + resource_type='metadata') ++ self._sb_idl = None ++ self._post_fork_event = threading.Event() ++ ++ @property ++ def sb_idl(self): ++ if not self._sb_idl: ++ self._post_fork_event.wait() ++ return self._sb_idl ++ ++ @sb_idl.setter ++ def sb_idl(self, val): ++ self._sb_idl = val + + def _load_config(self): + self.chassis = self._get_own_chassis_name() +@@ -228,6 +241,7 @@ class MetadataAgent(object): + # Chassis table. + # Open the connection to OVN SB database. + self.has_chassis_private = False ++ self._post_fork_event.clear() + try: + self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( + chassis=self.chassis, tables=tables + ('Chassis_Private', ), +@@ -238,6 +252,9 @@ class MetadataAgent(object): + chassis=self.chassis, tables=tables, + events=events + (ChassisCreateEvent(self), )).start() + ++ # Now IDL connections can be safely used. ++ self._post_fork_event.set() ++ + # Do the initial sync. + self.sync() + +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0006-Use-Port_Binding-up-column-to-set-Neutron-port-statu.patch neutron-16.4.2/debian/patches/lp1975594-0006-Use-Port_Binding-up-column-to-set-Neutron-port-statu.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0006-Use-Port_Binding-up-column-to-set-Neutron-port-statu.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0006-Use-Port_Binding-up-column-to-set-Neutron-port-statu.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,918 @@ +From 8b871a5ea7eda92a7e566c48af259dc82c6a9496 Mon Sep 17 00:00:00 2001 +From: Luis Tomas Bolivar +Date: Mon, 13 Dec 2021 11:30:58 +0100 +Subject: [PATCH] Use Port_Binding up column to set Neutron port status + +Currently in order to bring Neutron port to active, Neutron waits for +Logical Switch Port to become up in OVN. That means ovn-controller +changes the up status in SB DB and northd propagates it up to NB DB. +We do not need to wait and can save some time if we use the newly added +SB DB up column instead, when possible: +https://github.com/ovn-org/ovn/commit/4d3cb42b076bb58fd8f01ab8ad146ffd539f2152 + +This patch also includes squashed patch: +https://review.opendev.org/c/openstack/neutron/+/823818 +Ensure only the right events are processed + +There is no need to set up CR-LRP ports up, as well as there is no +need to re-process the router ports after the initial creation + +This patch also includes squashed patch: +https://review.opendev.org/c/openstack/neutron/+/823412 +Ensure subports transition to DOWN + +Due to a bug in OVN seting the subports SB DB "up" field to False +after subport detachment from the trunk port, the subports are never +transition from ACTIVE to DOWN after this patch: +https://review.opendev.org/c/openstack/neutron/+/821544 + +This patch ensures the chassis is also considered for such cases, +and ports without chassis but "up" field set to True are +transitioned to DOWN. +Closes-Bug: #1956233 + +Change-Id: Ib071889271f4e4d6acd83b219bf908a9ae80ce5c +(cherry picked from commit 37d4195b516f12b683b774f0561561b172dd15c6) +(squashed commit from 5e036a6b281e4331f396473e299b26b2537d5322) +(squashed commit from 553f462656c2b7ee1e9be6b1e4e7c446c12cc9aa) +--- + neutron/common/ovn/constants.py | 4 + + .../ovn/mech_driver/ovsdb/ovsdb_monitor.py | 290 +++++++++------ + .../mech_driver/ovsdb/test_ovsdb_monitor.py | 19 + + .../mech_driver/ovsdb/test_ovsdb_monitor.py | 331 ++++++++++-------- + 4 files changed, 391 insertions(+), 253 deletions(-) + +diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py +index 1966a0dabc..e18b36d00c 100644 +--- a/neutron/common/ovn/constants.py ++++ b/neutron/common/ovn/constants.py +@@ -281,6 +281,10 @@ LSP_OPTIONS_VIRTUAL_IP_KEY = 'virtual-ip' + LSP_OPTIONS_MCAST_FLOOD_REPORTS = 'mcast_flood_reports' + LSP_OPTIONS_MCAST_FLOOD = 'mcast_flood' + ++# Port Binding types ++PB_TYPE_PATCH = 'patch' ++PB_TYPE_VIRTUAL = 'virtual' ++ + HA_CHASSIS_GROUP_DEFAULT_NAME = 'default_ha_chassis_group' + HA_CHASSIS_GROUP_HIGHEST_PRIORITY = 32767 + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +index 58947554d6..84d7926984 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +@@ -172,47 +172,6 @@ class ChassisEvent(row_event.RowEvent): + self.handle_ha_chassis_group_changes(event, row, old) + + +-class PortBindingChassisUpdateEvent(row_event.RowEvent): +- """Event for matching a port moving chassis +- +- If the LSP is up and the Port_Binding chassis has just changed, +- there is a good chance the host died without cleaning up the chassis +- column on the Port_Binding. The port never goes down, so we won't +- see update the driver with the LogicalSwitchPortUpdateUpEvent which +- only monitors for transitions from DOWN to UP. +- """ +- +- def __init__(self, driver): +- self.driver = driver +- table = 'Port_Binding' +- events = (self.ROW_UPDATE,) +- super(PortBindingChassisUpdateEvent, self).__init__( +- events, table, None) +- self.event_name = self.__class__.__name__ +- +- def match_fn(self, event, row, old=None): +- # NOTE(twilson) ROW_UPDATE events always pass old, but chassis will +- # only be set if chassis has changed +- old_chassis = getattr(old, 'chassis', None) +- if not (row.chassis and old_chassis) or row.chassis == old_chassis: +- return False +- if row.type == ovn_const.OVN_CHASSIS_REDIRECT: +- return False +- try: +- lsp = self.driver._nb_ovn.lookup('Logical_Switch_Port', +- row.logical_port) +- except idlutils.RowNotFound: +- LOG.warning("Logical Switch Port %(port)s not found for " +- "Port_Binding %(binding)s", +- {'port': row.logical_port, 'binding': row.uuid}) +- return False +- +- return bool(lsp.up) +- +- def run(self, event, row, old=None): +- self.driver.set_port_status_up(row.logical_port) +- +- + class PortBindingChassisEvent(row_event.RowEvent): + """Port_Binding update event - set chassis for chassisredirect port. + +@@ -246,8 +205,8 @@ class PortBindingChassisEvent(row_event.RowEvent): + router, host) + + +-class LogicalSwitchPortCreateUpEvent(row_event.RowEvent): +- """Row create event - Logical_Switch_Port 'up' = True. ++class PortBindingCreateUpEvent(row_event.RowEvent): ++ """Row create event - Port_Binding 'up' = True. + + On connection, we get a dump of all ports, so if there is a neutron + port that is down that has since been activated, we'll catch it here. +@@ -256,73 +215,200 @@ class LogicalSwitchPortCreateUpEvent(row_event.RowEvent): + + def __init__(self, driver): + self.driver = driver +- table = 'Logical_Switch_Port' ++ table = 'Port_Binding' + events = (self.ROW_CREATE,) +- super(LogicalSwitchPortCreateUpEvent, self).__init__( +- events, table, (('up', '=', True),)) +- self.event_name = 'LogicalSwitchPortCreateUpEvent' ++ super(PortBindingCreateUpEvent, self).__init__(events, table, None) ++ self.event_name = 'PortBindingCreateUpEvent' ++ ++ def match_fn(self, event, row, old): ++ if row.type in (ovn_const.PB_TYPE_VIRTUAL, ++ ovn_const.OVN_CHASSIS_REDIRECT): ++ # NOTE(ltomasbo): Skipping virtual ports as they are not being ++ # set to ACTIVE ++ # NOTE(ltomasbo): No need to handle cr ports ++ return False ++ if row.type == ovn_const.PB_TYPE_PATCH: ++ # NOTE(ltomasbo): Only handle the logical_switch_port side, ++ # not the router side. ++ if (row.logical_port.startswith('lrp-') or ++ row.logical_port.startswith('cr-lrp')): ++ return False ++ return True ++ # TODO(ltomasbo): Remove the checkings for 'up' column once minimal ++ # ovn version has it (v21.03.0). The match_fn can be then replaced ++ # by different init method above: ++ # super().__init__( ++ # events, table, (('up', '=', True), ('type', '=', ''),)) ++ if hasattr(row, 'up'): ++ # NOTE(ltomasbo): Due to bug in core ovn not setting the up field ++ # to DOWN in some cases (for example subports detachment from ++ # trunks), we need to also check the chassis is set to claim the ++ # port as ACTIVE ++ return row.chassis and bool(row.up[0]) ++ elif row.chassis: ++ return True ++ return False + + def run(self, event, row, old): +- self.driver.set_port_status_up(row.name) ++ self.driver.set_port_status_up(row.logical_port) + + +-class LogicalSwitchPortCreateDownEvent(row_event.RowEvent): +- """Row create event - Logical_Switch_Port 'up' = False ++class PortBindingCreateDownEvent(row_event.RowEvent): ++ """Row create event - Port_Binding 'up' = False + + On connection, we get a dump of all ports, so if there is a neutron + port that is up that has since been deactivated, we'll catch it here. + This event will not be generated for new ports getting created. + """ ++ + def __init__(self, driver): + self.driver = driver +- table = 'Logical_Switch_Port' ++ table = 'Port_Binding' + events = (self.ROW_CREATE,) +- super(LogicalSwitchPortCreateDownEvent, self).__init__( +- events, table, (('up', '=', False),)) +- self.event_name = 'LogicalSwitchPortCreateDownEvent' ++ super(PortBindingCreateDownEvent, self).__init__(events, table, None) ++ self.event_name = 'PortBindingCreateDownEvent' ++ ++ def match_fn(self, event, row, old): ++ if row.type in [ovn_const.PB_TYPE_VIRTUAL, ovn_const.PB_TYPE_PATCH, ++ ovn_const.OVN_CHASSIS_REDIRECT]: ++ # NOTE(ltomasbo): Skipping as virtual ports are not being set to ++ # ACTIVE ++ # Patch ports are set to UP on creation, no need to update ++ # No need to handle cr ports ++ return False ++ ++ # TODO(ltomasbo): Remove the checkings for 'up' column once minimal ++ # ovn version has it (v21.03.0). The match_fn can be then replaced ++ # by different init method above: ++ # super().__init__( ++ # events, table, (('up', '=', False), ('type', '=', ''),)) ++ if hasattr(row, 'up'): ++ # NOTE(ltomasbo): Due to bug in core ovn not setting the up field ++ # to DOWN in some cases (for example subports detachment from ++ # trunks), we need to also check if the chassis is unset to set ++ # the port as DOWN ++ return not row.chassis or not bool(row.up[0]) ++ elif not row.chassis: ++ return True ++ return False + + def run(self, event, row, old): +- self.driver.set_port_status_down(row.name) ++ self.driver.set_port_status_down(row.logical_port) + + +-class LogicalSwitchPortUpdateUpEvent(row_event.RowEvent): +- """Row update event - Logical_Switch_Port 'up' going from False to True ++class PortBindingUpdateUpEvent(row_event.RowEvent): ++ """Row update event - Port_Binding 'up' going from False to True + + This happens when the VM goes up. +- New value of Logical_Switch_Port 'up' will be True and the old value will +- be False. ++ New value of Port_Binding 'up' will be True and the old value will ++ be False. Or if that column does not exists, the chassis will be set ++ and the old chassis value will be empty. + """ ++ + def __init__(self, driver): + self.driver = driver +- table = 'Logical_Switch_Port' ++ table = 'Port_Binding' + events = (self.ROW_UPDATE,) +- super(LogicalSwitchPortUpdateUpEvent, self).__init__( +- events, table, (('up', '=', True),), +- old_conditions=(('up', '=', False),)) +- self.event_name = 'LogicalSwitchPortUpdateUpEvent' ++ super(PortBindingUpdateUpEvent, self).__init__(events, table, None) ++ self.event_name = 'PortBindingUpdateUpEvent' ++ ++ def match_fn(self, event, row, old): ++ if row.type in (ovn_const.PB_TYPE_VIRTUAL, ++ ovn_const.OVN_CHASSIS_REDIRECT): ++ # NOTE(ltomasbo): Skipping virtual ports as they are not being ++ # set to ACTIVE ++ # NOTE(ltomasbo): No need to handle cr ports ++ return False ++ if row.type == ovn_const.PB_TYPE_PATCH: ++ # NOTE(ltomasbo): Only handle the logical_switch_port side, ++ # not the router side. ++ if (row.logical_port.startswith('lrp-') or ++ row.logical_port.startswith('cr-lrp')): ++ return False ++ try: ++ if old.mac: ++ # NOTE(ltomasbo): only execute it once (the first update ++ # event for this port), as you don't need to set it to ++ # active several time ++ return True ++ except AttributeError: ++ return False ++ return False ++ # TODO(ltomasbo): Remove the checkings for 'up' column once minimal ++ # ovn version has it (v21.03.0). The match_fn can be then replaced ++ # by different init method above: ++ # super().__init__( ++ # events, table, (('up', '=', True), ('type', '=', '')), ++ # old_conditions=(('up', '=', False),)) ++ try: ++ if hasattr(row, 'up'): ++ # NOTE(ltomasbo): Due to bug in core ovn not setting the up ++ # field to DOWN in some cases (for example subports detachment ++ # from trunks), we need to also check the chassis is set to ++ # claim the port as ACTIVE ++ return (bool(row.up[0]) and not bool(old.up[0]) and ++ row.chassis) ++ elif row.chassis and not old.chassis: ++ return True ++ except AttributeError: ++ # NOTE(ltomasbo): do not process if there is no old up/chassis ++ # information ++ return False ++ return False + + def run(self, event, row, old): +- self.driver.set_port_status_up(row.name) ++ self.driver.set_port_status_up(row.logical_port) + + +-class LogicalSwitchPortUpdateDownEvent(row_event.RowEvent): +- """Row update event - Logical_Switch_Port 'up' going from True to False ++class PortBindingUpdateDownEvent(row_event.RowEvent): ++ """Row update event - Port_Binding 'up' going from True to False + + This happens when the VM goes down. +- New value of Logical_Switch_Port 'up' will be False and the old value will +- be True. ++ New value of Port_Binding 'up' will be False and the old value will ++ be True. Or if that column does not exists, the chassis will be unset ++ and the old chassis will be set. + """ ++ + def __init__(self, driver): + self.driver = driver +- table = 'Logical_Switch_Port' ++ table = 'Port_Binding' + events = (self.ROW_UPDATE,) +- super(LogicalSwitchPortUpdateDownEvent, self).__init__( +- events, table, (('up', '=', False),), +- old_conditions=(('up', '=', True),)) +- self.event_name = 'LogicalSwitchPortUpdateDownEvent' ++ super(PortBindingUpdateDownEvent, self).__init__(events, table, None) ++ self.event_name = 'PortBindingUpdateDownEvent' ++ ++ def match_fn(self, event, row, old): ++ if row.type in [ovn_const.PB_TYPE_VIRTUAL, ovn_const.PB_TYPE_PATCH, ++ ovn_const.OVN_CHASSIS_REDIRECT]: ++ # NOTE(ltomasbo): Skipping as virtual ports are not being set to ++ # ACTIVE ++ # Patch ports are meant to be always UP, after creation, no need ++ # to update ++ # No need to handle cr ports ++ return False ++ # TODO(ltomasbo): Remove the checkings for 'up' column once minimal ++ # ovn version has it (v21.03.0). The match_fn can be then replaced ++ # by different init method above: ++ # super().__init__( ++ # events, table, (('up', '=', False), ('type', '=', '')), ++ # old_conditions=(('up', '=', True),)) ++ try: ++ if hasattr(row, 'up'): ++ # NOTE(ltomasbo): Due to bug in core ovn not setting the up ++ # field to DOWN in some cases (for example subports detachment ++ # from trunks), we need to also check if the chassis is being ++ # unset to set the port as DOWN ++ return ((not bool(row.up[0]) and bool(old.up[0])) or ++ (not row.chassis and old.chassis)) ++ elif not row.chassis and old.chassis: ++ return True ++ except AttributeError: ++ # NOTE(ltomasbo): do not process if there is no old up/chassis ++ # information ++ return False ++ return False + + def run(self, event, row, old): +- self.driver.set_port_status_down(row.name) ++ self.driver.set_port_status_down(row.logical_port) + + + class FIPAddDeleteEvent(row_event.RowEvent): +@@ -490,17 +576,9 @@ class OvnNbIdl(OvnIdlDistributedLock): + + def __init__(self, driver, remote, schema): + super(OvnNbIdl, self).__init__(driver, remote, schema) +- self._lsp_update_up_event = LogicalSwitchPortUpdateUpEvent(driver) +- self._lsp_update_down_event = LogicalSwitchPortUpdateDownEvent(driver) +- self._lsp_create_up_event = LogicalSwitchPortCreateUpEvent(driver) +- self._lsp_create_down_event = LogicalSwitchPortCreateDownEvent(driver) + self._fip_create_delete_event = FIPAddDeleteEvent(driver) + +- self.notify_handler.watch_events([self._lsp_create_up_event, +- self._lsp_create_down_event, +- self._lsp_update_up_event, +- self._lsp_update_down_event, +- self._fip_create_delete_event]) ++ self.notify_handler.watch_events([self._fip_create_delete_event]) + + @classmethod + def from_server(cls, connection_string, schema_name, driver): +@@ -510,25 +588,20 @@ class OvnNbIdl(OvnIdlDistributedLock): + helper.register_all() + return cls(driver, connection_string, helper) + +- def unwatch_logical_switch_port_create_events(self): +- """Unwatch the logical switch port create events. + +- When the ovs idl client connects to the ovsdb-server, it gets +- a dump of all logical switch ports as events and we need to process +- them at start up. +- After the startup, there is no need to watch these events. +- So unwatch these events. +- """ +- self.notify_handler.unwatch_events([self._lsp_create_up_event, +- self._lsp_create_down_event]) +- self._lsp_create_up_event = None +- self._lsp_create_down_event = None ++class OvnSbIdl(OvnIdlDistributedLock): + +- def post_connect(self): +- self.unwatch_logical_switch_port_create_events() ++ def __init__(self, driver, remote, schema): ++ super(OvnSbIdl, self).__init__(driver, remote, schema) + ++ self._pb_create_up_event = PortBindingCreateUpEvent(driver) ++ self._pb_create_down_event = PortBindingCreateDownEvent(driver) + +-class OvnSbIdl(OvnIdlDistributedLock): ++ self.notify_handler.watch_events([ ++ self._pb_create_up_event, ++ self._pb_create_down_event, ++ PortBindingUpdateUpEvent(driver), ++ PortBindingUpdateDownEvent(driver)]) + + @classmethod + def from_server(cls, connection_string, schema_name, driver): +@@ -556,8 +629,23 @@ class OvnSbIdl(OvnIdlDistributedLock): + self._chassis_event = ChassisEvent(self.driver) + self._portbinding_event = PortBindingChassisEvent(self.driver) + self.notify_handler.watch_events( +- [self._chassis_event, self._portbinding_event, +- PortBindingChassisUpdateEvent(self.driver)]) ++ [self._chassis_event, self._portbinding_event]) ++ ++ self.unwatch_port_binding_create_events() ++ ++ def unwatch_port_binding_create_events(self): ++ """Unwatch the port binding create events. ++ ++ When the ovs idl client connects to the ovsdb-server, it gets ++ a dump of all port binding events and we need to process ++ them at start up. ++ After the startup, there is no need to watch these events. ++ So unwatch these events. ++ """ ++ self.notify_handler.unwatch_events([self._pb_create_up_event, ++ self._pb_create_down_event]) ++ self._pb_create_up_event = None ++ self._pb_create_down_event = None + + + class OvnInitPGNbIdl(OvnIdl): +diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +index 7377db76af..1c443c79c8 100644 +--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py ++++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +@@ -194,12 +194,18 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase): + lambda: not self._check_mac_binding_exists(macb_id), + timeout=15, sleep=1) + ++ def _get_port_uuid(self, port_id): ++ sb_port = self.sb_api.db_find( ++ 'Port_Binding', ('logical_port', '=', port_id)).execute()[0] ++ return sb_port['_uuid'] ++ + def _test_port_binding_and_status(self, port_id, action, status): + # This function binds or unbinds port to chassis and + # checks if port status matches with input status + core_plugin = directory.get_plugin() + self.sb_api.check_for_row_by_value_and_retry( + 'Port_Binding', 'logical_port', port_id) ++ port_uuid = self._get_port_uuid(port_id) + + def check_port_status(status): + port = core_plugin.get_ports( +@@ -208,8 +214,21 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase): + if action == 'bind': + self.sb_api.lsp_bind(port_id, self.chassis, + may_exist=True).execute(check_error=True) ++ try: ++ self.sb_api.db_set('Port_Binding', port_uuid, ++ ('up', True)).execute(check_error=True) ++ except KeyError: ++ self.sb_api.db_set('Port_Binding', port_uuid, ++ ('chassis', 'host-1')).execute( ++ check_error=True) + else: + self.sb_api.lsp_unbind(port_id).execute(check_error=True) ++ try: ++ self.sb_api.db_set('Port_Binding', port_uuid, ++ ('up', False)).execute(check_error=True) ++ except KeyError: ++ self.sb_api.db_set('Port_Binding', port_uuid, ++ ('chassis', None)).execute(check_error=True) + n_utils.wait_until_true(lambda: check_port_status(status)) + + def test_port_up_down_events(self): +diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +index 9ef8712ccb..a707fd56ac 100644 +--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py ++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +@@ -85,7 +85,15 @@ OVN_SB_SCHEMA = { + "min": 0, "max": "unlimited"}}}, + "isRoot": True, + "indexes": [["name"]] +- } ++ }, ++ "Port_Binding": { ++ "columns": { ++ "logical_port": {"type": "string"}, ++ "type": {"type": "string"}, ++ "chassis": {"type": "string"}, ++ "up": {"type": {"key": "boolean", "min": 0, "max": 1}}}, ++ "indexes": [["logical_port"]], ++ "isRoot": True}, + } + } + +@@ -274,35 +282,6 @@ class TestOvnIdlDistributedLock(base.BaseTestCase): + self.assertFalse(self.idl.notify_handler.notify.called) + + +-class TestPortBindingChassisUpdateEvent(base.BaseTestCase): +- def setUp(self): +- super(TestPortBindingChassisUpdateEvent, self).setUp() +- self.driver = mock.Mock() +- self.event = ovsdb_monitor.PortBindingChassisUpdateEvent(self.driver) +- +- def _test_event(self, event, row, old): +- if self.event.matches(event, row, old): +- self.event.run(event, row, old) +- self.driver.set_port_status_up.assert_called() +- else: +- self.driver.set_port_status_up.assert_not_called() +- +- def test_event_matches(self): +- # NOTE(twilson) This primarily tests implementation details. If a +- # scenario test is written that handles shutting down a compute +- # node uncleanly and performing a 'host-evacuate', this can be removed +- pbtable = fakes.FakeOvsdbTable.create_one_ovsdb_table( +- attrs={'name': 'Port_Binding'}) +- ovsdb_row = fakes.FakeOvsdbRow.create_one_ovsdb_row +- self.driver._nb_ovn.lookup.return_value = ovsdb_row(attrs={'up': True}) +- self._test_event( +- self.event.ROW_UPDATE, +- ovsdb_row(attrs={'_table': pbtable, 'chassis': 'one', +- 'type': '_fake_', 'logical_port': 'foo'}), +- ovsdb_row(attrs={'_table': pbtable, 'chassis': 'two', +- 'type': '_fake_'})) +- +- + class TestOvnNbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase): + + def setUp(self): +@@ -314,105 +293,6 @@ class TestOvnNbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase): + self.driver.set_port_status_up = mock.Mock() + self.driver.set_port_status_down = mock.Mock() + +- def _test_lsp_helper(self, event, new_row_json, old_row_json=None, +- table=None): +- row_uuid = uuidutils.generate_uuid() +- if not table: +- table = self.lp_table +- lp_row = ovs_idl.Row.from_json(self.idl, table, +- row_uuid, new_row_json) +- if old_row_json: +- old_row = ovs_idl.Row.from_json(self.idl, table, +- row_uuid, old_row_json) +- else: +- old_row = None +- self.idl.notify(event, lp_row, updates=old_row) +- # Add a STOP EVENT to the queue +- self.idl.notify_handler.shutdown() +- # Execute the notifications queued +- self.idl.notify_handler.notify_loop() +- +- def test_lsp_up_create_event(self): +- row_data = {"up": True, "name": "foo-name"} +- self._test_lsp_helper('create', row_data) +- self.driver.set_port_status_up.assert_called_once_with("foo-name") +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_lsp_down_create_event(self): +- row_data = {"up": False, "name": "foo-name"} +- self._test_lsp_helper('create', row_data) +- self.driver.set_port_status_down.assert_called_once_with("foo-name") +- self.assertFalse(self.driver.set_port_status_up.called) +- +- def test_lsp_up_not_set_event(self): +- row_data = {"up": ['set', []], "name": "foo-name"} +- self._test_lsp_helper('create', row_data) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_unwatch_logical_switch_port_create_events(self): +- self.idl.unwatch_logical_switch_port_create_events() +- row_data = {"up": True, "name": "foo-name"} +- self._test_lsp_helper('create', row_data) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- +- row_data["up"] = False +- self._test_lsp_helper('create', row_data) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_post_connect(self): +- self.idl.post_connect() +- self.assertIsNone(self.idl._lsp_create_up_event) +- self.assertIsNone(self.idl._lsp_create_down_event) +- +- def test_lsp_up_update_event(self): +- new_row_json = {"up": True, "name": "foo-name"} +- old_row_json = {"up": False} +- self._test_lsp_helper('update', new_row_json, +- old_row_json=old_row_json) +- self.driver.set_port_status_up.assert_called_once_with("foo-name") +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_lsp_down_update_event(self): +- new_row_json = {"up": False, "name": "foo-name"} +- old_row_json = {"up": True} +- self._test_lsp_helper('update', new_row_json, +- old_row_json=old_row_json) +- self.driver.set_port_status_down.assert_called_once_with("foo-name") +- self.assertFalse(self.driver.set_port_status_up.called) +- +- def test_lsp_up_update_event_no_old_data(self): +- new_row_json = {"up": True, "name": "foo-name"} +- self._test_lsp_helper('update', new_row_json, +- old_row_json=None) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_lsp_down_update_event_no_old_data(self): +- new_row_json = {"up": False, "name": "foo-name"} +- self._test_lsp_helper('update', new_row_json, +- old_row_json=None) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_lsp_other_column_update_event(self): +- new_row_json = {"up": False, "name": "foo-name", +- "addresses": ["10.0.0.2"]} +- old_row_json = {"addresses": ["10.0.0.3"]} +- self._test_lsp_helper('update', new_row_json, +- old_row_json=old_row_json) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- +- def test_notify_other_table(self): +- new_row_json = {"name": "foo-name"} +- self._test_lsp_helper('create', new_row_json, +- table=self.idl.tables.get("Logical_Switch")) +- self.assertFalse(self.driver.set_port_status_up.called) +- self.assertFalse(self.driver.set_port_status_down.called) +- + @mock.patch.object(hash_ring_manager.HashRingManager, 'get_node') + def test_notify_different_target_node(self, mock_get_node): + mock_get_node.return_value = 'this-is-a-different-node' +@@ -432,23 +312,23 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase): + super(TestOvnSbIdlNotifyHandler, self).setUp() + sb_helper = ovs_idl.SchemaHelper(schema_json=OVN_SB_SCHEMA) + sb_helper.register_table('Chassis') ++ sb_helper.register_table('Port_Binding') + self.sb_idl = ovsdb_monitor.OvnSbIdl(self.driver, "remote", sb_helper) +- self.sb_idl.post_connect() +- self.chassis_table = self.sb_idl.tables.get('Chassis') + self.driver.update_segment_host_mapping = mock.Mock() ++ self.driver.set_port_status_up = mock.Mock() ++ self.driver.set_port_status_down = mock.Mock() + self.l3_plugin = directory.get_plugin(n_const.L3) + self.l3_plugin.schedule_unhosted_gateways = mock.Mock() + +- self.row_json = { ++ self.chassis_row_json = { + "name": "fake-name", + "hostname": "fake-hostname", + "external_ids": ['map', [["ovn-bridge-mappings", + "fake-phynet1:fake-br1"]]] + } + +- def _test_chassis_helper(self, event, new_row_json, old_row_json=None): ++ def _test_helper(self, event, table, new_row_json, old_row_json=None): + row_uuid = uuidutils.generate_uuid() +- table = self.chassis_table + row = ovs_idl.Row.from_json(self.sb_idl, table, row_uuid, new_row_json) + if old_row_json: + old_row = ovs_idl.Row.from_json(self.sb_idl, table, +@@ -461,76 +341,223 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase): + # Execute the notifications queued + self.sb_idl.notify_handler.notify_loop() + ++ def _test_chassis_helper(self, event, new_row_json, old_row_json=None, ++ table=None): ++ self.sb_idl.post_connect() ++ self.chassis_table = self.sb_idl.tables.get('Chassis') ++ if not table: ++ table = self.chassis_table ++ self._test_helper(event, table, new_row_json, old_row_json) ++ + def test_chassis_create_event(self): +- self._test_chassis_helper('create', self.row_json) ++ self._test_chassis_helper('create', self.chassis_row_json) + self.driver.update_segment_host_mapping.assert_called_once_with( + 'fake-hostname', ['fake-phynet1']) + self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with( + event_from_chassis=None) + + def test_chassis_delete_event(self): +- self._test_chassis_helper('delete', self.row_json) ++ self._test_chassis_helper('delete', self.chassis_row_json) + self.driver.update_segment_host_mapping.assert_called_once_with( + 'fake-hostname', []) + self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with( + event_from_chassis='fake-name') + + def test_chassis_update_event(self): +- old_row_json = copy.deepcopy(self.row_json) ++ old_row_json = copy.deepcopy(self.chassis_row_json) + old_row_json['external_ids'][1][0][1] = ( + "fake-phynet2:fake-br2") +- self._test_chassis_helper('update', self.row_json, old_row_json) ++ self._test_chassis_helper('update', self.chassis_row_json, ++ old_row_json) + self.driver.update_segment_host_mapping.assert_called_once_with( + 'fake-hostname', ['fake-phynet1']) + self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with( + event_from_chassis=None) + + def test_chassis_update_event_reschedule_not_needed(self): +- self.row_json['external_ids'][1].append(['foo_field', 'foo_value_new']) +- old_row_json = copy.deepcopy(self.row_json) ++ self.chassis_row_json['external_ids'][1].append(['foo_field', ++ 'foo_value_new']) ++ old_row_json = copy.deepcopy(self.chassis_row_json) + old_row_json['external_ids'][1][1][1] = ( + "foo_value") +- self._test_chassis_helper('update', self.row_json, old_row_json) ++ self._test_chassis_helper('update', self.chassis_row_json, ++ old_row_json) + self.driver.update_segment_host_mapping.assert_not_called() + self.l3_plugin.schedule_unhosted_gateways.assert_not_called() + + def test_chassis_update_event_reschedule_lost_physnet(self): +- old_row_json = copy.deepcopy(self.row_json) +- self.row_json['external_ids'][1][0][1] = '' +- self._test_chassis_helper('update', self.row_json, old_row_json) ++ old_row_json = copy.deepcopy(self.chassis_row_json) ++ self.chassis_row_json['external_ids'][1][0][1] = '' ++ self._test_chassis_helper('update', self.chassis_row_json, ++ old_row_json) + self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with( + event_from_chassis='fake-name') + + def test_chassis_update_event_reschedule_add_physnet(self): +- old_row_json = copy.deepcopy(self.row_json) +- self.row_json['external_ids'][1][0][1] += ',foo_physnet:foo_br' +- self._test_chassis_helper('update', self.row_json, old_row_json) ++ old_row_json = copy.deepcopy(self.chassis_row_json) ++ self.chassis_row_json['external_ids'][1][0][1] += ',foo_physnet:foo_br' ++ self._test_chassis_helper('update', self.chassis_row_json, ++ old_row_json) + self.driver.update_segment_host_mapping.assert_called_once_with( + 'fake-hostname', ['fake-phynet1', 'foo_physnet']) + self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with( + event_from_chassis=None) + + def test_chassis_update_event_reschedule_add_and_remove_physnet(self): +- old_row_json = copy.deepcopy(self.row_json) +- self.row_json['external_ids'][1][0][1] = 'foo_physnet:foo_br' +- self._test_chassis_helper('update', self.row_json, old_row_json) ++ old_row_json = copy.deepcopy(self.chassis_row_json) ++ self.chassis_row_json['external_ids'][1][0][1] = 'foo_physnet:foo_br' ++ self._test_chassis_helper('update', self.chassis_row_json, ++ old_row_json) + self.driver.update_segment_host_mapping.assert_called_once_with( + 'fake-hostname', ['foo_physnet']) + self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with( + event_from_chassis=None) + + def test_chassis_update_empty_no_external_ids(self): +- old_row_json = copy.deepcopy(self.row_json) ++ old_row_json = copy.deepcopy(self.chassis_row_json) + old_row_json.pop('external_ids') + with mock.patch( + 'neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' + 'ovsdb_monitor.ChassisEvent.' + 'handle_ha_chassis_group_changes') as mock_ha: +- self._test_chassis_helper('update', self.row_json, old_row_json) ++ self._test_chassis_helper('update', self.chassis_row_json, ++ old_row_json) + self.driver.update_segment_host_mapping.assert_not_called() + self.l3_plugin.schedule_unhosted_gateways.assert_not_called() + mock_ha.assert_not_called() + ++ def _test_port_binding_helper(self, event, new_row_json, ++ old_row_json=None, table=None): ++ self.port_binding_table = self.sb_idl.tables.get('Port_Binding') ++ if not table: ++ table = self.port_binding_table ++ self._test_helper(event, table, new_row_json, old_row_json) ++ ++ def test_port_binding_up_create_event(self): ++ row_data = {"type": "", "up": True, "logical_port": "foo-name", ++ "chassis": "foo-host"} ++ self._test_port_binding_helper('create', row_data) ++ self.driver.set_port_status_up.assert_called_once_with("foo-name") ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_up_create_patch_type_event(self): ++ # Note(ltomasbo): This will behave the same for up = True|False ++ # as the port needs to transition to up anyway ++ row_data = {"type": "patch", "logical_port": "foo-name", ++ "chassis": "foo-host"} ++ self._test_port_binding_helper('create', row_data) ++ self.driver.set_port_status_up.assert_called_once_with("foo-name") ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ row_data = {"type": "patch", "logical_port": "lrp-foo-name"} ++ self._test_port_binding_helper('create', row_data) ++ self.assertEqual(1, self.driver.set_port_status_up.call_count) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_up_create_virtual_type_event(self): ++ row_data = {"type": "virtual", "up": True, ++ "logical_port": "foo-name", "chassis": "foo-host"} ++ self._test_port_binding_helper('create', row_data) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_down_create_event(self): ++ row_data = {"type": "", "up": False, "logical_port": "foo-name", ++ "chassis": None} ++ self._test_port_binding_helper('create', row_data) ++ self.driver.set_port_status_down.assert_called_once_with("foo-name") ++ self.assertFalse(self.driver.set_port_status_up.called) ++ ++ def test_port_binding_down_create_virtual_type_event(self): ++ row_data = {"type": "virtual", "up": False, ++ "logical_port": "foo-name", "chassis": None} ++ self._test_port_binding_helper('create', row_data) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_up_update_event(self): ++ new_row_json = {"type": "", "up": True, "logical_port": "foo-name", ++ "chassis": "foo-host"} ++ old_row_json = {"type": "", "up": False, "logical_port": "foo-name", ++ "chassis": None} ++ self._test_port_binding_helper('update', new_row_json, ++ old_row_json=old_row_json) ++ self.driver.set_port_status_up.assert_called_once_with("foo-name") ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_down_update_event(self): ++ new_row_json = {"type": "", "up": False, "logical_port": "foo-name", ++ "chassis": None} ++ old_row_json = {"type": "", "up": True, "logical_port": "foo-name", ++ "chassis": "foo-host"} ++ self._test_port_binding_helper('update', new_row_json, ++ old_row_json=old_row_json) ++ self.driver.set_port_status_down.assert_called_once_with("foo-name") ++ self.assertFalse(self.driver.set_port_status_up.called) ++ ++ def test_port_binding_down_update_event_up_not_updated(self): ++ new_row_json = {"type": "", "up": True, "logical_port": "foo-name", ++ "chassis": None} ++ old_row_json = {"type": "", "up": True, "logical_port": "foo-name", ++ "chassis": "foo-host"} ++ self._test_port_binding_helper('update', new_row_json, ++ old_row_json=old_row_json) ++ self.driver.set_port_status_down.assert_called_once_with("foo-name") ++ self.assertFalse(self.driver.set_port_status_up.called) ++ ++ def test_port_binding_up_update_event_no_old_data(self): ++ new_row_json = {"type": "", "up": True, "logical_port": "foo-name", ++ "chassis": "foo-host"} ++ self._test_port_binding_helper('update', new_row_json, ++ old_row_json=None) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_down_update_event_no_old_data(self): ++ new_row_json = {"type": "", "up": False, "logical_port": "foo-name", ++ "chassis": None} ++ self._test_port_binding_helper('update', new_row_json, ++ old_row_json=None) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_port_binding_other_column_update_event(self): ++ new_row_json = {"type": "", "up": True, "name": "foo-name", ++ "addresses": ["10.0.0.2"]} ++ old_row_json = {"type": "", "up": True, "name": "foo-name", ++ "addresses": ["10.0.0.3"]} ++ self._test_port_binding_helper('update', new_row_json, ++ old_row_json=old_row_json) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_notify_other_table(self): ++ new_row_json = {"type": "", "up": True, "logical_port": "foo-name"} ++ self._test_port_binding_helper( ++ 'create', new_row_json, ++ table=self.sb_idl.tables.get("Chassis")) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_unwatch_port_binding_create_events(self): ++ self.sb_idl.unwatch_port_binding_create_events() ++ row_data = {"type": "", "up": True, "logical_port": "foo-name"} ++ self._test_port_binding_helper('create', row_data) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ row_data["up"] = False ++ self._test_port_binding_helper('create', row_data) ++ self.assertFalse(self.driver.set_port_status_up.called) ++ self.assertFalse(self.driver.set_port_status_down.called) ++ ++ def test_post_connect(self): ++ self.assertIsNotNone(self.sb_idl._pb_create_up_event) ++ self.assertIsNotNone(self.sb_idl._pb_create_down_event) ++ self.sb_idl.post_connect() ++ self.assertIsNone(self.sb_idl._pb_create_up_event) ++ self.assertIsNone(self.sb_idl._pb_create_down_event) ++ + + class TestChassisEvent(base.BaseTestCase): + +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0007-support-SB-OVSDB-connections-to-non-leader-servers.patch neutron-16.4.2/debian/patches/lp1975594-0007-support-SB-OVSDB-connections-to-non-leader-servers.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0007-support-SB-OVSDB-connections-to-non-leader-servers.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0007-support-SB-OVSDB-connections-to-non-leader-servers.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,118 @@ +From ac065c3aadbb1c4c94e59227dba91a13e9073ff5 Mon Sep 17 00:00:00 2001 +From: Terry Wilson +Date: Mon, 2 Aug 2021 16:32:50 -0500 +Subject: [PATCH] Support SB OVSDB connections to non-leader servers + +Since SB connections are read-mostly, they should be able to +connect to non-leader clustered ovsdb servers. This will allow +distributing the SB connections for ovn neutron-api and metadta +workers. + +Conflict: + neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py + +Change-Id: I7442170d015f195a5430e71567fbc7d67b81d385 +(cherry picked from commit 10f23398ce43a666c17feeef7c1b516ce3afddff) +(cherry picked from commit 396a1462363cbda62a94056dce218d505fe6f3ee) +(cherry picked from commit e26fd32ab8c0c8a37970e48f064163f8349bc2f1) +--- + neutron/agent/ovn/metadata/ovsdb.py | 9 ++++-- + .../ovn/mech_driver/ovsdb/ovsdb_monitor.py | 29 ++++++++++++------- + 2 files changed, 26 insertions(+), 12 deletions(-) + +diff --git a/neutron/agent/ovn/metadata/ovsdb.py b/neutron/agent/ovn/metadata/ovsdb.py +index 8eae982f05..18d9812f92 100644 +--- a/neutron/agent/ovn/metadata/ovsdb.py ++++ b/neutron/agent/ovn/metadata/ovsdb.py +@@ -40,8 +40,13 @@ class MetadataAgentOvnSbIdl(ovsdb_monitor.OvnIdl): + 'SB_Global') + for table in tables: + helper.register_table(table) +- super(MetadataAgentOvnSbIdl, self).__init__( +- None, connection_string, helper) ++ try: ++ super(MetadataAgentOvnSbIdl, self).__init__( ++ None, connection_string, helper, leader_only=False) ++ except TypeError: ++ # TODO(twilson) We can remove this when we require ovs>=2.12.0 ++ super(MetadataAgentOvnSbIdl, self).__init__( ++ None, connection_string, helper) + if chassis: + for table in set(tables).intersection({'Chassis', + 'Chassis_Private'}): +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +index 84d7926984..7b0bb376e2 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +@@ -458,9 +458,9 @@ class Ml2OvnIdlBase(connection.OvsdbIdl): + + + class BaseOvnIdl(Ml2OvnIdlBase): +- def __init__(self, remote, schema): ++ def __init__(self, remote, schema, **kwargs): + self.notify_handler = backports.RowEventHandler() +- super(BaseOvnIdl, self).__init__(remote, schema) ++ super(BaseOvnIdl, self).__init__(remote, schema, **kwargs) + + @classmethod + def from_server(cls, connection_string, schema_name): +@@ -482,13 +482,18 @@ class BaseOvnSbIdl(Ml2OvnIdlBase): + helper.register_table('Encap') + helper.register_table('Port_Binding') + helper.register_table('Datapath_Binding') +- return cls(connection_string, helper) ++ # Used by MaintenanceWorker which can use ovsdb locking ++ try: ++ return cls(connection_string, helper, leader_only=True) ++ except TypeError: ++ # TODO(twilson) We can remove this when we require ovs>=2.12.0 ++ return cls(connection_string, helper) + + + class OvnIdl(BaseOvnIdl): + +- def __init__(self, driver, remote, schema): +- super(OvnIdl, self).__init__(remote, schema) ++ def __init__(self, driver, remote, schema, **kwargs): ++ super(OvnIdl, self).__init__(remote, schema, **kwargs) + self.driver = driver + self.notify_handler = OvnDbNotifyHandler(driver) + # ovsdb lock name to acquire. +@@ -524,8 +529,8 @@ class OvnIdl(BaseOvnIdl): + + class OvnIdlDistributedLock(BaseOvnIdl): + +- def __init__(self, driver, remote, schema): +- super(OvnIdlDistributedLock, self).__init__(remote, schema) ++ def __init__(self, driver, remote, schema, **kwargs): ++ super(OvnIdlDistributedLock, self).__init__(remote, schema, **kwargs) + self.driver = driver + self.notify_handler = OvnDbNotifyHandler(driver) + self._node_uuid = self.driver.node_uuid +@@ -591,8 +596,8 @@ class OvnNbIdl(OvnIdlDistributedLock): + + class OvnSbIdl(OvnIdlDistributedLock): + +- def __init__(self, driver, remote, schema): +- super(OvnSbIdl, self).__init__(driver, remote, schema) ++ def __init__(self, driver, remote, schema, **kwargs): ++ super(OvnSbIdl, self).__init__(driver, remote, schema, **kwargs) + + self._pb_create_up_event = PortBindingCreateUpEvent(driver) + self._pb_create_down_event = PortBindingCreateDownEvent(driver) +@@ -616,7 +621,11 @@ class OvnSbIdl(OvnIdlDistributedLock): + helper.register_table('Port_Binding') + helper.register_table('Datapath_Binding') + helper.register_table('Connection') +- return cls(driver, connection_string, helper) ++ try: ++ return cls(driver, connection_string, helper, leader_only=False) ++ except TypeError: ++ # TODO(twilson) We can remove this when we require ovs>=2.12.0 ++ return cls(driver, connection_string, helper) + + def post_connect(self): + """Watch Chassis events. +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0008-fix-setting-table-monitoring-conditions.patch neutron-16.4.2/debian/patches/lp1975594-0008-fix-setting-table-monitoring-conditions.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/lp1975594-0008-fix-setting-table-monitoring-conditions.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/lp1975594-0008-fix-setting-table-monitoring-conditions.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,80 @@ +From a2d65cecdee746f4b8b6e42348c4bd9206e12cfd Mon Sep 17 00:00:00 2001 +From: Terry Wilson +Date: Tue, 22 Mar 2022 04:12:08 +0000 +Subject: [PATCH] Fix setting table monitoring conditions + +After the monitor_cond_since/update3 support patch in ovs +(46d44cf3be0), directly setting table.condition is broken. This +isn't something that was every truly supported. Prior to that +patch, using Idl.cond_change() before a connection was made +did not work, but after that patch it does. + +This patch uses the old behavior when the OVS library does not +have the ConditionState object, and uses cond_change() otherwise. + +Related-Bug: #1965819 +Change-Id: I0503037b803a3c99fb7988bc20394c111ac456db +(cherry picked from commit 37c2e6b708678d2e648f111d102b6394f4e704b0) +--- + neutron/agent/ovn/metadata/ovsdb.py | 2 +- + .../ovn/mech_driver/ovsdb/ovsdb_monitor.py | 17 ++++++++++++----- + 2 files changed, 13 insertions(+), 6 deletions(-) + +diff --git a/neutron/agent/ovn/metadata/ovsdb.py b/neutron/agent/ovn/metadata/ovsdb.py +index 18d9812f92..7817bdaf85 100644 +--- a/neutron/agent/ovn/metadata/ovsdb.py ++++ b/neutron/agent/ovn/metadata/ovsdb.py +@@ -50,7 +50,7 @@ class MetadataAgentOvnSbIdl(ovsdb_monitor.OvnIdl): + if chassis: + for table in set(tables).intersection({'Chassis', + 'Chassis_Private'}): +- self.tables[table].condition = [['name', '==', chassis]] ++ self.set_table_condition(table, [['name', '==', chassis]]) + if events: + self.notify_handler.watch_events(events) + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +index 7b0bb376e2..c07fd4aebd 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +@@ -23,6 +23,7 @@ from neutron_lib.utils import helpers + from oslo_config import cfg + from oslo_log import log + from oslo_utils import timeutils ++from ovs.db import idl as ovs_idl_mod + from ovs.stream import Stream + from ovsdbapp.backend.ovs_idl import connection + from ovsdbapp.backend.ovs_idl import event as row_event +@@ -456,6 +457,15 @@ class Ml2OvnIdlBase(connection.OvsdbIdl): + super(Ml2OvnIdlBase, self).__init__( + remote, schema, probe_interval=probe_interval, **kwargs) + ++ def set_table_condition(self, table_name, condition): ++ # Prior to ovs commit 46d44cf3be0, self.cond_change() doesn't work here ++ # but after that commit, setting table.condtion doesn't work. ++ if hasattr(ovs_idl_mod, 'ConditionState'): ++ self.cond_change(table_name, condition) ++ else: ++ # Can be removed after the minimum ovs version >= 2.17.0 ++ self.tables[table_name].condition = condition ++ + + class BaseOvnIdl(Ml2OvnIdlBase): + def __init__(self, remote, schema, **kwargs): +@@ -668,11 +678,8 @@ class OvnInitPGNbIdl(OvnIdl): + + def __init__(self, driver, remote, schema): + super(OvnInitPGNbIdl, self).__init__(driver, remote, schema) +- # self.cond_change() doesn't work here because we are setting the +- # condition *before an initial monitor request is made* so there is +- # no previous session whose condition we wish to change +- self.tables['Port_Group'].condition = [ +- ['name', '==', ovn_const.OVN_DROP_PORT_GROUP_NAME]] ++ self.set_table_condition( ++ 'Port_Group', [['name', '==', ovn_const.OVN_DROP_PORT_GROUP_NAME]]) + self.neutron_pg_drop_event = NeutronPgDropPortGroupCreated() + self.notify_handler.watch_event(self.neutron_pg_drop_event) + +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/ovn-allow-vip-ports-with-defined-device-owner.patch neutron-16.4.2/debian/patches/ovn-allow-vip-ports-with-defined-device-owner.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/ovn-allow-vip-ports-with-defined-device-owner.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/ovn-allow-vip-ports-with-defined-device-owner.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,156 @@ +From be933757faa3e26f689b0bb9a11216ab2dc8082d Mon Sep 17 00:00:00 2001 +From: Rodolfo Alonso Hernandez +Date: Thu, 12 May 2022 09:26:36 +0000 +Subject: [PATCH] [OVN] Allow VIP ports with a defined "device_owner" + +Now OVN VIP port can have a "device_owner" defined. + +Conflicts: + neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py + neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py + +Closes-Bug: #1973276 +Change-Id: Id1907481077d230e4461906a1a2a1447abac330a +(cherry picked from commit 4c37497e7c98180fb6b1118021acd8e96b7028d0) +(cherry picked from commit 9e2425111b6f7413098bbb38f6013d18b3804303) +--- + .../ovn/mech_driver/ovsdb/ovn_client.py | 17 +++--- + .../ovn/mech_driver/test_mech_driver.py | 58 +++++++++++-------- + 2 files changed, 44 insertions(+), 31 deletions(-) + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +index 19c6005640..9e4b5674db 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +@@ -248,14 +248,15 @@ class OVNClient(object): + subnet['cidr'].split('/')[1]) + + # Check if the port being created is a virtual port +- if (self._is_virtual_port_supported() and +- not port['device_owner']): +- parents = self.get_virtual_port_parents(ip_addr, port) +- if parents: +- port_type = ovn_const.LSP_TYPE_VIRTUAL +- options[ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY] = ip_addr +- options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY] = ( +- ','.join(parents)) ++ parents = self.get_virtual_port_parents(ip_addr, port) ++ if not parents: ++ continue ++ ++ port_type = ovn_const.LSP_TYPE_VIRTUAL ++ options[ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY] = ip_addr ++ options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY] = ( ++ ','.join(parents)) ++ break + + # Only adjust the OVN type if the port is not owned by Neutron + # DHCP agents. +diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +index 8ebe37523d..7e1ee8ce13 100644 +--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py ++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +@@ -120,6 +120,9 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase): + p = mock.patch.object(ovn_revision_numbers_db, 'bump_revision') + p.start() + self.addCleanup(p.stop) ++ self._virtual_port_parents = mock.patch.object( ++ ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ self.virtual_port_parents = self._virtual_port_parents.start() + + def test_delete_mac_binding_entries(self): + self.config(group='ovn', ovn_sb_private_key=None) +@@ -2143,6 +2146,9 @@ class OVNMechanismDriverTestCase(test_plugin.Ml2PluginV2TestCase): + p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1) + p.start() + self.addCleanup(p.stop) ++ self._virtual_port_parents = mock.patch.object( ++ ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ self.virtual_port_parents = self._virtual_port_parents.start() + + + class TestOVNMechanismDriverBasicGet(test_plugin.TestMl2BasicGet, +@@ -2282,6 +2288,9 @@ class TestOVNMechanismDriverSegment(test_segment.HostSegmentMappingTestCase): + p.start() + self.addCleanup(p.stop) + self.context = context.get_admin_context() ++ self._virtual_port_parents = mock.patch.object( ++ ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ self.virtual_port_parents = self._virtual_port_parents.start() + + def _test_segment_host_mapping(self): + # Disable the callback to update SegmentHostMapping by default, so +@@ -3157,6 +3166,9 @@ class TestOVNMechanismDriverSecurityGroup( + self.mech_driver._ovn_client._qos_driver = mock.Mock() + self.ctx = context.get_admin_context() + revision_plugin.RevisionPlugin() ++ self._virtual_port_parents = mock.patch.object( ++ ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ self.virtual_port_parents = self._virtual_port_parents.start() + + def _delete_default_sg_rules(self, security_group_id): + res = self._list( +@@ -3477,6 +3489,9 @@ class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase): + p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1) + p.start() + self.addCleanup(p.stop) ++ self._virtual_port_parents = mock.patch.object( ++ ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ self.virtual_port_parents = self._virtual_port_parents.start() + + def _create_fake_dhcp_port(self, device_id): + return {'network_id': 'fake', 'device_owner': const.DEVICE_OWNER_DHCP, +@@ -3681,30 +3696,27 @@ class TestOVNVVirtualPort(OVNMechanismDriverTestCase): + self.fmt, {'network': self.net}, + '10.0.0.1', '10.0.0.0/24')['subnet'] + +- @mock.patch.object(ovn_client.OVNClient, 'get_virtual_port_parents') +- def test_create_port_with_virtual_type_and_options(self, mock_get_parents): ++ def test_create_port_with_virtual_type_and_options(self): + fake_parents = ['parent-0', 'parent-1'] +- mock_get_parents.return_value = fake_parents +- port = {'id': 'virt-port', +- 'mac_address': '00:00:00:00:00:00', +- 'device_owner': '', +- 'network_id': self.net['id'], +- 'fixed_ips': [{'subnet_id': self.subnet['id'], +- 'ip_address': '10.0.0.55'}]} +- port_info = self.mech_driver._ovn_client._get_port_options( +- port) +- self.assertEqual(ovn_const.LSP_TYPE_VIRTUAL, port_info.type) +- self.assertEqual( +- '10.0.0.55', +- port_info.options[ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY]) +- self.assertIn( +- 'parent-0', +- port_info.options[ +- ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY]) +- self.assertIn( +- 'parent-1', +- port_info.options[ +- ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY]) ++ self.virtual_port_parents.return_value = fake_parents ++ for device_owner in ('', 'myVIPowner'): ++ port = {'id': 'virt-port', ++ 'mac_address': '00:00:00:00:00:00', ++ 'device_owner': device_owner, ++ 'network_id': self.net['id'], ++ 'fixed_ips': [{'subnet_id': self.subnet['id'], ++ 'ip_address': '10.0.0.55'}]} ++ port_info = self.mech_driver._ovn_client._get_port_options(port) ++ self.assertEqual(ovn_const.LSP_TYPE_VIRTUAL, port_info.type) ++ self.assertEqual( ++ '10.0.0.55', ++ port_info.options[ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY]) ++ self.assertIn( ++ 'parent-0', ++ port_info.options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY]) ++ self.assertIn( ++ 'parent-1', ++ port_info.options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY]) + + @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_ports') + def _test_set_unset_virtual_port_type(self, mock_get_ports, unset=False): +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/ovn-Filter-ACL-columns-when-syncing-the-DB.patch neutron-16.4.2/debian/patches/ovn-Filter-ACL-columns-when-syncing-the-DB.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/ovn-Filter-ACL-columns-when-syncing-the-DB.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/ovn-Filter-ACL-columns-when-syncing-the-DB.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,48 @@ +From 1255eded3c7c699e2d2d25e460fc23303a3091ca Mon Sep 17 00:00:00 2001 +From: Jakub Libosvar +Date: Wed, 17 Nov 2021 17:29:13 +0000 +Subject: [PATCH] ovn: Filter ACL columns when syncing the DB + +The patch filters columns from OVN DB only to those that are used by the +ovn mechanism driver. It means generated ACLs from Neutron DB and ACLs +obtained from the OVN DB will always have the same columns. This is +useful for db sync script when comparing if given security group rule +has corresponding ACL in the OVN DB. + +Closes-Bug: #1951296 +Signed-off-by: Jakub Libosvar +Change-Id: I39e3b987b8546fd970a933b846ed23c8a2588258 +(cherry picked from commit 23b99e2f127731c85f63c88c7144aa0a111c4abf) +--- + .../ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py | 12 +++++------- + 1 file changed, 5 insertions(+), 7 deletions(-) + +diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py +index 5ea397402e..10c2c1db3f 100644 +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py +@@ -220,16 +220,14 @@ class OvnNbSynchronizer(OvnDbSynchronizer): + + def _get_acls_from_port_groups(self): + ovn_acls = [] +- port_groups = self.ovn_api.db_list_rows('Port_Group').execute() +- for pg in port_groups: ++ acl_columns = (self.ovn_api._tables['ACL'].columns.keys() & ++ set(ovn_const.ACL_EXPECTED_COLUMNS_NBDB)) ++ acl_columns.discard('external_ids') ++ for pg in self.ovn_api.db_list_rows('Port_Group').execute(): + acls = getattr(pg, 'acls', []) + for acl in acls: +- acl_string = {} ++ acl_string = {k: getattr(acl, k) for k in acl_columns} + acl_string['port_group'] = pg.name +- for acl_key in getattr(acl, "_data", {}): +- acl_string[acl_key] = getattr(acl, acl_key) +- acl_string.pop('meter') +- acl_string.pop('external_ids') + ovn_acls.append(acl_string) + return ovn_acls + +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/partially-revert-do-not-link-up-ha-router-gateway-in.patch neutron-16.4.2/debian/patches/partially-revert-do-not-link-up-ha-router-gateway-in.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/partially-revert-do-not-link-up-ha-router-gateway-in.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/partially-revert-do-not-link-up-ha-router-gateway-in.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,313 @@ +From 6902ee2458f1844dd1fe09af2f3579d579af6a27 Mon Sep 17 00:00:00 2001 +From: Edward Hope-Morley +Date: Thu, 17 Mar 2022 12:56:21 +0000 +Subject: [PATCH] Partially revert "Do not link up HA router gateway in backup + node" + +This partially reverts commit c52029c39aa824a67095fbbf9e59eff769d92587. + +We revert everything except one minor addition to +neutron/agent/l3/ha_router.py which ensures that ha_confs path is +created when the keepalived manager is initialised. + +Closes-Bug: #1965297 +Change-Id: I14ad015c4344b32f7210c924902dac4e6ad1ae88 +(cherry picked from commit 36bf1df46df4de8f9ed0c19e1118480ce2e55d8a) +(cherry picked from commit 7cf7ae05659b363e54fbb04d2c02f91b17763921) +(cherry picked from commit 22f231fd8b2e145e63753712406ddbad01d0d08a) +(cherry picked from commit 61e951d668c7a6da97a71b8a7d8885fe446c685e) +(cherry picked from commit 6fd2dd1aa37ad3dd44b76be063139e2fcf8ccf5d) +--- + neutron/agent/l3/dvr_edge_ha_router.py | 4 +- + neutron/agent/l3/ha.py | 9 ---- + neutron/agent/l3/ha_router.py | 32 ++---------- + neutron/agent/l3/router_info.py | 20 +++----- + neutron/agent/linux/interface.py | 50 +++++-------------- + .../unit/agent/l3/test_dvr_local_router.py | 1 - + .../tests/unit/agent/linux/test_interface.py | 17 ------- + 7 files changed, 24 insertions(+), 109 deletions(-) + +--- a/neutron/agent/l3/dvr_edge_ha_router.py ++++ b/neutron/agent/l3/dvr_edge_ha_router.py +@@ -134,9 +134,7 @@ + + def _external_gateway_added(self, ex_gw_port, interface_name, + ns_name, preserve_ips): +- link_up = self.external_gateway_link_up() +- self._plug_external_gateway(ex_gw_port, interface_name, ns_name, +- link_up=link_up) ++ self._plug_external_gateway(ex_gw_port, interface_name, ns_name) + + def _is_this_snat_host(self): + return self.agent_conf.agent_mode == constants.L3_AGENT_MODE_DVR_SNAT +--- a/neutron/agent/l3/ha.py ++++ b/neutron/agent/l3/ha.py +@@ -163,15 +163,6 @@ + 'agent %(host)s', + state_change_data) + +- # Set external gateway port link up or down according to state +- if state == 'master': +- ri.set_external_gw_port_link_status(link_up=True, set_gw=True) +- elif state == 'backup': +- ri.set_external_gw_port_link_status(link_up=False) +- else: +- LOG.warning('Router %s has status %s, ' +- 'no action to router gateway device.', +- router_id, state) + # TODO(dalvarez): Fix bug 1677279 by moving the IPv6 parameters + # configuration to keepalived-state-change in order to remove the + # dependency that currently exists on l3-agent running for the IPv6 +--- a/neutron/agent/l3/ha_router.py ++++ b/neutron/agent/l3/ha_router.py +@@ -162,6 +162,10 @@ + throttle_restart_value=( + self.agent_conf.ha_vrrp_advert_int * THROTTLER_MULTIPLIER)) + ++ # The following call is required to ensure that if the state path does ++ # not exist it gets created. ++ self.keepalived_manager.get_full_config_file_path('test') ++ + config = self.keepalived_manager.config + + interface_name = self.get_ha_device_name() +@@ -459,9 +463,7 @@ + return port1_filtered == port2_filtered + + def external_gateway_added(self, ex_gw_port, interface_name): +- link_up = self.external_gateway_link_up() +- self._plug_external_gateway(ex_gw_port, interface_name, +- self.ns_name, link_up=link_up) ++ self._plug_external_gateway(ex_gw_port, interface_name, self.ns_name) + self._add_gateway_vip(ex_gw_port, interface_name) + self._disable_ipv6_addressing_on_interface(interface_name) + +@@ -525,27 +527,3 @@ + if (self.keepalived_manager.get_process().active and + self.ha_state == 'master'): + super(HaRouter, self).enable_radvd(internal_ports) +- +- def external_gateway_link_up(self): +- # Check HA router ha_state for its gateway port link state. +- # 'backup' instance will not link up the gateway port. +- return self.ha_state == 'master' +- +- def set_external_gw_port_link_status(self, link_up, set_gw=False): +- link_state = "up" if link_up else "down" +- LOG.info('Set router %s gateway device link state to %s.', +- self.router_id, link_state) +- +- ex_gw_port = self.get_ex_gw_port() +- ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or +- self.ex_gw_port and self.ex_gw_port['id']) +- if ex_gw_port_id: +- interface_name = self.get_external_device_name(ex_gw_port_id) +- ns_name = self.get_gw_ns_name() +- self.driver.set_link_status(interface_name, ns_name, +- link_up=link_up) +- if link_up and set_gw: +- preserve_ips = self.get_router_preserve_ips() +- self._external_gateway_settings(ex_gw_port, interface_name, +- ns_name, preserve_ips) +- self.routes_updated([], self.routes) +--- a/neutron/agent/l3/router_info.py ++++ b/neutron/agent/l3/router_info.py +@@ -696,16 +696,14 @@ + return [common_utils.ip_to_cidr(ip['floating_ip_address']) + for ip in floating_ips] + +- def _plug_external_gateway(self, ex_gw_port, interface_name, ns_name, +- link_up=True): ++ def _plug_external_gateway(self, ex_gw_port, interface_name, ns_name): + self.driver.plug(ex_gw_port['network_id'], + ex_gw_port['id'], + interface_name, + ex_gw_port['mac_address'], + namespace=ns_name, + prefix=EXTERNAL_DEV_PREFIX, +- mtu=ex_gw_port.get('mtu'), +- link_up=link_up) ++ mtu=ex_gw_port.get('mtu')) + + def _get_external_gw_ips(self, ex_gw_port): + gateway_ips = [] +@@ -765,11 +763,7 @@ + LOG.debug("External gateway added: port(%s), interface(%s), ns(%s)", + ex_gw_port, interface_name, ns_name) + self._plug_external_gateway(ex_gw_port, interface_name, ns_name) +- self._external_gateway_settings(ex_gw_port, interface_name, +- ns_name, preserve_ips) + +- def _external_gateway_settings(self, ex_gw_port, interface_name, +- ns_name, preserve_ips): + # Build up the interface and gateway IP addresses that + # will be added to the interface. + ip_cidrs = common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips']) +@@ -814,19 +808,17 @@ + return any(netaddr.IPAddress(gw_ip).version == 6 + for gw_ip in gateway_ips) + +- def get_router_preserve_ips(self): ++ def external_gateway_added(self, ex_gw_port, interface_name): + preserve_ips = self._list_floating_ip_cidrs() + list( + self.centralized_port_forwarding_fip_set) + preserve_ips.extend(self.agent.pd.get_preserve_ips(self.router_id)) +- return preserve_ips +- +- def external_gateway_added(self, ex_gw_port, interface_name): +- preserve_ips = self.get_router_preserve_ips() + self._external_gateway_added( + ex_gw_port, interface_name, self.ns_name, preserve_ips) + + def external_gateway_updated(self, ex_gw_port, interface_name): +- preserve_ips = self.get_router_preserve_ips() ++ preserve_ips = self._list_floating_ip_cidrs() + list( ++ self.centralized_port_forwarding_fip_set) ++ preserve_ips.extend(self.agent.pd.get_preserve_ips(self.router_id)) + self._external_gateway_added( + ex_gw_port, interface_name, self.ns_name, preserve_ips) + +--- a/neutron/agent/linux/interface.py ++++ b/neutron/agent/linux/interface.py +@@ -260,17 +260,16 @@ + + @abc.abstractmethod + def plug_new(self, network_id, port_id, device_name, mac_address, +- bridge=None, namespace=None, prefix=None, mtu=None, +- link_up=True): ++ bridge=None, namespace=None, prefix=None, mtu=None): + """Plug in the interface only for new devices that don't exist yet.""" + + def plug(self, network_id, port_id, device_name, mac_address, +- bridge=None, namespace=None, prefix=None, mtu=None, link_up=True): ++ bridge=None, namespace=None, prefix=None, mtu=None): + if not ip_lib.device_exists(device_name, + namespace=namespace): + self._safe_plug_new( + network_id, port_id, device_name, mac_address, bridge, +- namespace, prefix, mtu, link_up) ++ namespace, prefix, mtu) + else: + LOG.info("Device %s already exists", device_name) + if mtu: +@@ -280,20 +279,10 @@ + LOG.warning("No MTU configured for port %s", port_id) + + def _safe_plug_new(self, network_id, port_id, device_name, mac_address, +- bridge=None, namespace=None, prefix=None, mtu=None, link_up=True): +- try: +- self.plug_new( +- network_id, port_id, device_name, mac_address, bridge, +- namespace, prefix, mtu, link_up) +- except TypeError: +- LOG.warning("Interface driver's plug_new() method should now " +- "accept additional optional parameter 'link_up'. " +- "Usage of plug_new() method which takes from 5 to 9 " +- "positional arguments is now deprecated and will not " +- "be possible in W release.") +- self.plug_new( +- network_id, port_id, device_name, mac_address, bridge, +- namespace, prefix, mtu) ++ bridge=None, namespace=None, prefix=None, mtu=None): ++ self.plug_new( ++ network_id, port_id, device_name, mac_address, bridge, ++ namespace, prefix, mtu) + + @abc.abstractmethod + def unplug(self, device_name, bridge=None, namespace=None, prefix=None): +@@ -322,21 +311,10 @@ + LOG.warning("Interface driver cannot update MTU for ports") + self._mtu_update_warn_logged = True + +- def set_link_status(self, device_name, namespace=None, link_up=True): +- ns_dev = ip_lib.IPWrapper(namespace=namespace).device(device_name) +- if not ns_dev.exists(): +- LOG.debug("Device %s may concurrently be deleted.", device_name) +- return +- if link_up: +- ns_dev.link.set_up() +- else: +- ns_dev.link.set_down() +- + + class NullDriver(LinuxInterfaceDriver): + def plug_new(self, network_id, port_id, device_name, mac_address, +- bridge=None, namespace=None, prefix=None, mtu=None, +- link_up=True): ++ bridge=None, namespace=None, prefix=None, mtu=None): + pass + + def unplug(self, device_name, bridge=None, namespace=None, prefix=None): +@@ -372,8 +350,7 @@ + ovs.replace_port(device_name, *attrs) + + def plug_new(self, network_id, port_id, device_name, mac_address, +- bridge=None, namespace=None, prefix=None, mtu=None, +- link_up=True): ++ bridge=None, namespace=None, prefix=None, mtu=None): + """Plug in the interface.""" + if not bridge: + bridge = self.conf.OVS.integration_bridge +@@ -435,8 +412,7 @@ + else: + LOG.warning("No MTU configured for port %s", port_id) + +- if link_up: +- ns_dev.link.set_up() ++ ns_dev.link.set_up() + if self.conf.ovs_use_veth: + # ovs-dpdk does not do checksum calculations for veth interface + # (bug 1832021) +@@ -481,8 +457,7 @@ + DEV_NAME_PREFIX = 'ns-' + + def plug_new(self, network_id, port_id, device_name, mac_address, +- bridge=None, namespace=None, prefix=None, mtu=None, +- link_up=True): ++ bridge=None, namespace=None, prefix=None, mtu=None): + """Plugin the interface.""" + ip = ip_lib.IPWrapper() + +@@ -501,8 +476,7 @@ + LOG.warning("No MTU configured for port %s", port_id) + + root_veth.link.set_up() +- if link_up: +- ns_veth.link.set_up() ++ ns_veth.link.set_up() + + def unplug(self, device_name, bridge=None, namespace=None, prefix=None): + """Unplug the interface.""" +--- a/neutron/tests/unit/agent/l3/test_dvr_local_router.py ++++ b/neutron/tests/unit/agent/l3/test_dvr_local_router.py +@@ -954,7 +954,6 @@ + self.mock_driver.unplug.reset_mock() + self._set_ri_kwargs(agent, router['id'], router) + ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) +- ri._ha_state_path = self.get_temp_file_path('router_ha_state') + ri._create_snat_namespace = mock.Mock() + ri._plug_external_gateway = mock.Mock() + ri.initialize(mock.Mock()) +--- a/neutron/tests/unit/agent/linux/test_interface.py ++++ b/neutron/tests/unit/agent/linux/test_interface.py +@@ -685,20 +685,3 @@ + + self.ip_dev.assert_has_calls([mock.call('tap0', namespace=None), + mock.call().link.delete()]) +- +- +-class TestLegacyDriver(TestBase): +- +- def test_plug(self): +- self.device_exists.return_value = False +- with mock.patch('neutron.agent.linux.interface.LOG.warning') as log: +- driver = FakeLegacyInterfaceDriver(self.conf) +- try: +- driver.plug( +- '01234567-1234-1234-99', 'port-1234', 'tap0', +- 'aa:bb:cc:dd:ee:ff') +- except TypeError: +- self.fail("LinuxInterfaceDriver class can not call properly " +- "plug_new method from the legacy drivers that " +- "do not accept 'link_up' parameter.") +- log.assert_called_once() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/series neutron-16.4.2/debian/patches/series --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/series 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/patches/series 2023-04-18 14:13:36.000000000 +0000 @@ -1,2 +1,17 @@ skip-iptest.patch flake8-legacy.patch +partially-revert-do-not-link-up-ha-router-gateway-in.patch +lp1975594-0001-fix-RowNotFound-exception-while-waiting-for-meta.patch +lp1975594-0002-stable-only-ovn-Update-get-datapath-id-to-network-fr.patch +lp1975594-0003-add-reverse-DNS-records.patch +lp1975594-0004-accept-OVS-system-id-as-non-UUID-formatted-strin.patch +lp1975594-0005-add-wait-event-for-metadataagent-sb_idl.patch +lp1975594-0006-Use-Port_Binding-up-column-to-set-Neutron-port-statu.patch +lp1975594-0007-support-SB-OVSDB-connections-to-non-leader-servers.patch +lp1975594-0008-fix-setting-table-monitoring-conditions.patch +defer-flow-deletion-in-openvswitch-firewall.patch +ovn-allow-vip-ports-with-defined-device-owner.patch +set-type-virtual-for-ovn-lsp-with-parent-ports.patch +sync-the-dns-assignment-with-the-actual-designate-dn.patch +ovn-Filter-ACL-columns-when-syncing-the-DB.patch +CVE-2022-3277.patch diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/set-type-virtual-for-ovn-lsp-with-parent-ports.patch neutron-16.4.2/debian/patches/set-type-virtual-for-ovn-lsp-with-parent-ports.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/set-type-virtual-for-ovn-lsp-with-parent-ports.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/set-type-virtual-for-ovn-lsp-with-parent-ports.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,240 @@ +From 3b9f1be5e5d1287dda6670020a349381f8ba9993 Mon Sep 17 00:00:00 2001 +From: Rodolfo Alonso Hernandez +Date: Sat, 14 May 2022 23:54:10 +0000 +Subject: [PATCH] Set "type=virtual" for OVN LSP with parent ports + +This is a follow-up of [1]. Before this patch, any virtual logical +switch port that was updated and the "device_owner" was not and empty +string, had its type set to '' (empty string). + +This maintenance task, that is executed only once, lists all logical +switch ports, checks the presence or not of virtual parents and +sets the type to "virtual" if needed. + +Related-Bug: #1973276 + +[1]https://review.opendev.org/c/openstack/neutron/+/841711 + +Conflicts: + neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py + neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py + neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py + neutron/tests/unit/db/test_db_base_plugin_v2.py + neutron/tests/unit/extensions/test_portsecurity.py + neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py + +Change-Id: I6cf1167d556f0c2c2aa2013f05c809648020b377 +(cherry picked from commit 3c93da7bdf35c3be6081384b7f6d9a4ce13510d3) +(cherry picked from commit 316b55440ab7a2b27e0468a87545ee1db9e85b84) +(cherry picked from commit 8c79b631aa36d31be916427c2dbba10f5086389a) +(cherry picked from commit d6f395378ce4ed3697c0011afebe2c1d142d8040) +--- + neutron/common/ovn/utils.py | 7 ++++ + .../ovn/mech_driver/ovsdb/maintenance.py | 33 +++++++++++++++++++ + .../ovn/mech_driver/ovsdb/ovn_client.py | 10 ++---- + .../unit/extensions/test_portsecurity.py | 8 +++++ + .../ovn/mech_driver/ovsdb/test_maintenance.py | 19 +++++++++++ + .../ovn/mech_driver/test_mech_driver.py | 14 ++++---- + 6 files changed, 77 insertions(+), 14 deletions(-) + +--- a/neutron/common/ovn/utils.py ++++ b/neutron/common/ovn/utils.py +@@ -351,6 +351,13 @@ + return list(set(addresses + port_security)) + + ++def get_virtual_port_parents(nb_idl, virtual_ip, network_id, port_id): ++ ls = nb_idl.ls_get(ovn_name(network_id)).execute(check_error=True) ++ return [lsp.name for lsp in ls.ports ++ if lsp.name != port_id and ++ virtual_ip in get_ovn_port_addresses(lsp)] ++ ++ + def sort_ips_by_version(addresses): + ip_map = {'ip4': [], 'ip6': []} + for addr in addresses: +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py +@@ -701,6 +701,39 @@ + txn.add(cmd) + raise periodics.NeverAgain() + ++ # TODO(ralonsoh): Remove this in the Z+4 cycle ++ @periodics.periodic(spacing=600, run_immediately=True) ++ def update_port_virtual_type(self): ++ """Set type=virtual to those ports with parents ++ Before LP#1973276, any virtual port with "device_owner" defined, lost ++ its type=virtual. This task restores the type for those ports updated ++ before the fix https://review.opendev.org/c/openstack/neutron/+/841711. ++ """ ++ if not self.has_lock: ++ return ++ ++ context = n_context.get_admin_context() ++ cmds = [] ++ for lsp in self._nb_idl.lsp_list().execute(check_error=True): ++ if lsp.type != '': ++ continue ++ ++ port = self._ovn_client._plugin.get_port(context, lsp.name) ++ for ip in port.get('fixed_ips', []): ++ if utils.get_virtual_port_parents( ++ self._nb_idl, ip['ip_address'], port['network_id'], ++ port['id']): ++ cmds.append(self._nb_idl.db_set( ++ 'Logical_Switch_Port', lsp.uuid, ++ ('type', ovn_const.LSP_TYPE_VIRTUAL))) ++ break ++ ++ if cmds: ++ with self._nb_idl.transaction(check_error=True) as txn: ++ for cmd in cmds: ++ txn.add(cmd) ++ raise periodics.NeverAgain() ++ + + class HashRingHealthCheckPeriodics(object): + +--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py ++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +@@ -205,13 +205,6 @@ + external_ids=subnet_dhcp_options['external_ids']) + return {'cmd': add_dhcp_opts_cmd} + +- def get_virtual_port_parents(self, virtual_ip, port): +- ls = self._nb_idl.ls_get(utils.ovn_name(port['network_id'])).execute( +- check_error=True) +- return [lsp.name for lsp in ls.ports +- if lsp.name != port['id'] and +- virtual_ip in utils.get_ovn_port_addresses(lsp)] +- + def _get_port_options(self, port): + context = n_context.get_admin_context() + binding_prof = utils.validate_and_get_data_from_binding_profile(port) +@@ -246,7 +239,8 @@ + subnet['cidr'].split('/')[1]) + + # Check if the port being created is a virtual port +- parents = self.get_virtual_port_parents(ip_addr, port) ++ parents = utils.get_virtual_port_parents( ++ self._nb_idl, ip_addr, port['network_id'], port['id']) + if not parents: + continue + +--- a/neutron/tests/unit/extensions/test_portsecurity.py ++++ b/neutron/tests/unit/extensions/test_portsecurity.py +@@ -14,6 +14,7 @@ + # limitations under the License. + + import copy ++from unittest import mock + + from neutron_lib.api.definitions import port_security as psec + from neutron_lib.api import validators +@@ -24,6 +25,7 @@ + from neutron_lib.plugins import directory + from webob import exc + ++from neutron.common.ovn import utils as ovn_utils + from neutron.db import db_base_plugin_v2 + from neutron.db import portsecurity_db + from neutron.db import securitygroups_db +@@ -177,6 +179,12 @@ + + + class TestPortSecurity(PortSecurityDBTestCase): ++ ++ def setUp(self, plugin=None, service_plugins=None): ++ super().setUp(plugin) ++ self.mock_vp_parents = mock.patch.object( ++ ovn_utils, 'get_virtual_port_parents', return_value=None).start() ++ + def test_create_network_with_portsecurity_mac(self): + res = self._create_network('json', 'net1', True) + net = self.deserialize('json', res) +--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py ++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +@@ -114,7 +114,7 @@ + p.start() + self.addCleanup(p.stop) + self._virtual_port_parents = mock.patch.object( +- ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ ovn_utils, 'get_virtual_port_parents', return_value=[]) + self.virtual_port_parents = self._virtual_port_parents.start() + + def test_delete_mac_binding_entries(self): +@@ -1977,7 +1977,7 @@ + p.start() + self.addCleanup(p.stop) + self._virtual_port_parents = mock.patch.object( +- ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ ovn_utils, 'get_virtual_port_parents', return_value=[]) + self.virtual_port_parents = self._virtual_port_parents.start() + + +@@ -2119,7 +2119,7 @@ + self.addCleanup(p.stop) + self.context = context.get_admin_context() + self._virtual_port_parents = mock.patch.object( +- ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ ovn_utils, 'get_virtual_port_parents', return_value=[]) + self.virtual_port_parents = self._virtual_port_parents.start() + + def _test_segment_host_mapping(self): +@@ -2926,7 +2926,7 @@ + self.ctx = context.get_admin_context() + revision_plugin.RevisionPlugin() + self._virtual_port_parents = mock.patch.object( +- ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ ovn_utils, 'get_virtual_port_parents', return_value=[]) + self.virtual_port_parents = self._virtual_port_parents.start() + + def _delete_default_sg_rules(self, security_group_id): +@@ -3245,7 +3245,7 @@ + p.start() + self.addCleanup(p.stop) + self._virtual_port_parents = mock.patch.object( +- ovn_client.OVNClient, 'get_virtual_port_parents', return_value=[]) ++ ovn_utils, 'get_virtual_port_parents', return_value=[]) + self.virtual_port_parents = self._virtual_port_parents.start() + + def _create_fake_dhcp_port(self, device_id): +@@ -3450,10 +3450,12 @@ + self.subnet = self._make_subnet( + self.fmt, {'network': self.net}, + '10.0.0.1', '10.0.0.0/24')['subnet'] ++ self.mock_vp_parents = mock.patch.object( ++ ovn_utils, 'get_virtual_port_parents', return_value=None).start() + + def test_create_port_with_virtual_type_and_options(self): + fake_parents = ['parent-0', 'parent-1'] +- self.virtual_port_parents.return_value = fake_parents ++ self.mock_vp_parents.return_value = fake_parents + for device_owner in ('', 'myVIPowner'): + port = {'id': 'virt-port', + 'mac_address': '00:00:00:00:00:00', +--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py ++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +@@ -460,3 +460,22 @@ + options={'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'})] + nb_idl.update_lrouter.assert_has_calls(expected_calls) ++ ++ @mock.patch.object(utils, 'get_virtual_port_parents', ++ return_value=[mock.ANY]) ++ def test_update_port_virtual_type(self, *args): ++ nb_idl = self.fake_ovn_client._nb_idl ++ lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row( ++ attrs={'name': 'lsp0', 'type': ''}) ++ lsp1 = fakes.FakeOvsdbRow.create_one_ovsdb_row( ++ attrs={'name': 'lsp1', 'type': constants.LSP_TYPE_VIRTUAL}) ++ port0 = {'fixed_ips': [{'ip_address': mock.ANY}], ++ 'network_id': mock.ANY, 'id': mock.ANY} ++ nb_idl.lsp_list.return_value.execute.return_value = (lsp0, lsp1) ++ self.fake_ovn_client._plugin.get_port.return_value = port0 ++ ++ self.assertRaises( ++ periodics.NeverAgain, self.periodic.update_port_virtual_type) ++ expected_calls = [mock.call('Logical_Switch_Port', lsp0.uuid, ++ ('type', constants.LSP_TYPE_VIRTUAL))] ++ nb_idl.db_set.assert_has_calls(expected_calls) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/skip-iptest.patch neutron-16.4.2/debian/patches/skip-iptest.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/skip-iptest.patch 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/patches/skip-iptest.patch 2023-03-28 15:11:11.000000000 +0000 @@ -1,6 +1,6 @@ --- a/neutron/tests/unit/agent/linux/test_ipset_manager.py +++ b/neutron/tests/unit/agent/linux/test_ipset_manager.py -@@ -143,6 +143,7 @@ +@@ -147,6 +147,7 @@ class IpsetManagerTestCase(BaseIpsetMana self.verify_mock_calls() def test_set_members_adding_more_than_5(self): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/sync-the-dns-assignment-with-the-actual-designate-dn.patch neutron-16.4.2/debian/patches/sync-the-dns-assignment-with-the-actual-designate-dn.patch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/patches/sync-the-dns-assignment-with-the-actual-designate-dn.patch 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/debian/patches/sync-the-dns-assignment-with-the-actual-designate-dn.patch 2023-03-28 15:11:11.000000000 +0000 @@ -0,0 +1,62 @@ +From f6aed71cbf6a9a7da0c3c7aa5b6acf83ceea2fec Mon Sep 17 00:00:00 2001 +From: hamalq +Date: Thu, 28 May 2020 23:17:28 +0000 +Subject: [PATCH] Sync the dns-assignment with the actual designate dns-domain + +When a port is created the dns-assignment (dns-domain part) +was always taken form Neutron config dns_domain which is not +always true, since it could be Neutron network dns_domain or +the dns_domain sent when creating the port + +Change-Id: I7f4366ff5a26f73013433bfbfb299fd06294f359 +Closes-Bug:1873091 +(cherry picked from commit ea13f2e83f8c2de3def69b6c883a5c161c3a6180) +--- + neutron/plugins/ml2/extensions/dns_integration.py | 2 ++ + .../unit/plugins/ml2/extensions/test_dns_integration.py | 3 +++ + .../notes/fix-port-dns-assignment-9d916d77522abd65.yaml | 6 ++++++ + 3 files changed, 11 insertions(+) + create mode 100644 releasenotes/notes/fix-port-dns-assignment-9d916d77522abd65.yaml + +diff --git a/neutron/plugins/ml2/extensions/dns_integration.py b/neutron/plugins/ml2/extensions/dns_integration.py +index 60d5e06bf1..2bf79f79a5 100644 +--- a/neutron/plugins/ml2/extensions/dns_integration.py ++++ b/neutron/plugins/ml2/extensions/dns_integration.py +@@ -288,6 +288,8 @@ class DNSExtensionDriver(api.ExtensionDriver): + if dns_domain and dns_domain != lib_const.DNS_DOMAIN_DEFAULT: + if dns_data_db: + dns_name = dns_data_db.dns_name ++ if dns_data_db.current_dns_domain: ++ dns_domain = dns_data_db.current_dns_domain + return dns_name, dns_domain + + def _get_dns_names_for_port(self, ips, dns_data_db): +diff --git a/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py b/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py +index 959ab0b727..1cb8b750a2 100644 +--- a/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py ++++ b/neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py +@@ -164,6 +164,9 @@ class DNSIntegrationTestCase(test_plugin.Ml2PluginV2TestCase): + self.assertEqual(current_dns_name, dns_data_db['current_dns_name']) + self.assertEqual(previous_dns_name, + dns_data_db['previous_dns_name']) ++ curr_dns_domain = dns_data_db['current_dns_domain'] ++ for fqdn in port['dns_assignment']: ++ self.assertTrue(fqdn['fqdn'].endswith(curr_dns_domain)) + if current_dns_name: + self.assertEqual(current_dns_domain, + dns_data_db['current_dns_domain']) +diff --git a/releasenotes/notes/fix-port-dns-assignment-9d916d77522abd65.yaml b/releasenotes/notes/fix-port-dns-assignment-9d916d77522abd65.yaml +new file mode 100644 +index 0000000000..3a45c69e89 +--- /dev/null ++++ b/releasenotes/notes/fix-port-dns-assignment-9d916d77522abd65.yaml +@@ -0,0 +1,6 @@ ++--- ++features: ++ - | ++ The dns-assignment will reflect the dns-domain defined in the network or ++ sent by user when creating the port using --dns-domain rather than just ++ take the dns-domain defined in the neutron configuration +-- +2.34.1 + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/rules neutron-16.4.2/debian/rules --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/rules 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/rules 2023-03-28 15:11:11.000000000 +0000 @@ -28,18 +28,25 @@ dh_missing --fail-missing -X/usr/etc override_dh_installsystemd: - dh_installsystemd -pneutron-openvswitch-agent --name=neutron-ovs-cleanup \ + dh_installsystemd \ + -pneutron-openvswitch-agent --name=neutron-ovs-cleanup \ + --no-start --no-stop-on-upgrade --no-restart-after-upgrade \ neutron-ovs-cleanup.service - dh_installsystemd -pneutron-linuxbridge-agent --name=neutron-linuxbridge-cleanup \ + dh_installsystemd -Xneutron-ovs-cleanup.service \ + -pneutron-linuxbridge-agent --name=neutron-linuxbridge-cleanup \ + --no-start --no-stop-on-upgrade --no-restart-after-upgrade \ neutron-linuxbridge-cleanup.service - dh_installsystemd + dh_installsystemd -Xneutron-ovs-cleanup.service -Xneutron-linuxbridge-cleanup.service override_dh_installinit: - dh_installinit -pneutron-openvswitch-agent --no-start \ - --name=neutron-ovs-cleanup - dh_installinit -pneutron-linuxbridge-agent --no-start \ - --name=neutron-linuxbridge-cleanup - dh_installinit --error-handler=true + dh_installinit \ + -pneutron-openvswitch-agent --name=neutron-ovs-cleanup \ + --no-start --no-stop-on-upgrade + dh_installinit -Xneutron-ovs-cleanup \ + -pneutron-linuxbridge-agent --name=neutron-linuxbridge-cleanup \ + --no-start --no-stop-on-upgrade + dh_installinit -Xneutron-ovs-cleanup -Xneutron-linuxbridge-cleanup \ + --error-handler=true override_dh_auto_clean: dh_auto_clean diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/watch neutron-16.4.2/debian/watch --- neutron-16.0.0~b3~git2020041516.5f42488a9a/debian/watch 2020-04-17 11:27:46.000000000 +0000 +++ neutron-16.4.2/debian/watch 2023-03-28 15:11:11.000000000 +0000 @@ -1,3 +1,3 @@ version=3 opts="uversionmangle=s/\.([a-zA-Z])/~$1/;s/%7E/~/;s/\.0b/~b/;s/\.0rc/~rc/" \ - https://tarballs.opendev.org/openstack/neutron neutron-(15\.\d.*)\.tar\.gz + https://tarballs.opendev.org/openstack/neutron/ neutron-(16\.\d.*)\.tar\.gz diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/lib/octavia neutron-16.4.2/devstack/lib/octavia --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/lib/octavia 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/devstack/lib/octavia 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Save trace setting +XTRACE=$(set +o | grep xtrace) +set +o xtrace + +if is_plugin_enabled octavia; then + function octavia_create_network_interface_device { + INTERFACE=$1 + MGMT_PORT_ID=$2 + MGMT_PORT_MAC=$3 + + if [[ $NEUTRON_AGENT == "openvswitch" || $Q_AGENT == "openvswitch" || $NEUTRON_AGENT == "ovn" || $Q_AGENT == "ovn" ]]; then + if [[ $NEUTRON_AGENT == "ovn" || $Q_AGENT == "ovn" ]]; then + openstack subnet set --gateway none lb-mgmt-subnet + fi + sudo ovs-vsctl -- --may-exist add-port ${OVS_BRIDGE:-br-int} $INTERFACE -- set Interface $INTERFACE type=internal -- set Interface $INTERFACE external-ids:iface-status=active -- set Interface $INTERFACE external-ids:attached-mac=$MGMT_PORT_MAC -- set Interface $INTERFACE external-ids:iface-id=$MGMT_PORT_ID -- set Interface $INTERFACE external-ids:skip_cleanup=true + elif [[ $NEUTRON_AGENT == "linuxbridge" || $Q_AGENT == "linuxbridge" ]]; then + if ! ip link show $INTERFACE ; then + sudo ip link add $INTERFACE type veth peer name o-bhm0 + NETID=$(openstack network show lb-mgmt-net -c id -f value) + BRNAME=brq$(echo $NETID|cut -c 1-11) + sudo ip link set o-bhm0 master $BRNAME + sudo ip link set o-bhm0 up + fi + else + die "Unknown network controller - $NEUTRON_AGENT/$Q_AGENT" + fi + } + + function octavia_delete_network_interface_device { + if [[ $NEUTRON_AGENT == "openvswitch" || $Q_AGENT == "openvswitch" || $NEUTRON_AGENT == "ovn" || $Q_AGENT == "ovn" ]]; then + : # Do nothing + elif [[ $NEUTRON_AGENT == "linuxbridge" || $Q_AGENT == "linuxbridge" ]]; then + if ip link show $INTERFACE ; then + sudo ip link del $INTERFACE + fi + else + die "Unknown network controller - $NEUTRON_AGENT/$Q_AGENT" + fi + } +fi + +# Restore xtrace +$XTRACE diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/lib/ovn_agent neutron-16.4.2/devstack/lib/ovn_agent --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/lib/ovn_agent 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/lib/ovn_agent 2021-11-12 13:56:42.000000000 +0000 @@ -19,8 +19,11 @@ # There are some ovs functions OVN depends on that must be sourced from # the ovs neutron plugins. After doing this, the OVN overrides must be # re-sourced. -source $TOP_DIR/lib/neutron_plugins/ovs_base -source $TOP_DIR/lib/neutron_plugins/openvswitch_agent +source ${TOP_DIR}/lib/neutron_plugins/ovs_base +source ${TOP_DIR}/lib/neutron_plugins/openvswitch_agent + +# Load devstack ovs base functions +source $NEUTRON_DIR/devstack/lib/ovs # Defaults @@ -30,13 +33,7 @@ OVN_REPO=${OVN_REPO:-https://github.com/ovn-org/ovn.git} OVN_REPO_NAME=$(basename ${OVN_REPO} | cut -f1 -d'.') OVN_REPO_NAME=${OVN_REPO_NAME:-ovn} -OVN_BRANCH=${OVN_BRANCH:-master} - -# Set variables for building OVS from source -OVS_REPO=${OVS_REPO:-https://github.com/openvswitch/ovs.git} -OVS_REPO_NAME=$(basename ${OVS_REPO} | cut -f1 -d'.') -OVS_REPO_NAME=${OVS_REPO_NAME:-ovs} -OVS_BRANCH=${OVS_BRANCH:-master} +OVN_BRANCH=${OVN_BRANCH:-v20.03.0} if is_service_enabled tls-proxy; then OVN_PROTO=ssl @@ -133,7 +130,7 @@ Q_ML2_PLUGIN_TYPE_DRIVERS=${Q_ML2_PLUGIN_TYPE_DRIVERS:-local,flat,vlan,geneve} Q_ML2_TENANT_NETWORK_TYPE=${Q_ML2_TENANT_NETWORK_TYPE:-"geneve"} Q_ML2_PLUGIN_GENEVE_TYPE_OPTIONS=${Q_ML2_PLUGIN_GENEVE_TYPE_OPTIONS:-"vni_ranges=1:65536"} -Q_ML2_PLUGIN_EXT_DRIVERS=${Q_ML2_PLUGIN_EXT_DRIVERS:-port_security,dns} +Q_ML2_PLUGIN_EXT_DRIVERS=${Q_ML2_PLUGIN_EXT_DRIVERS:-port_security,dns,qos} ML2_L3_PLUGIN="ovn-router,trunk" @@ -162,7 +159,7 @@ # neutron-ovs-cleanup uses the OVSDB native interface. function ovn_base_setup_bridge { local bridge=$1 - local addbr_cmd="ovs-vsctl --no-wait -- --may-exist add-br $bridge" + local addbr_cmd="ovs-vsctl --no-wait -- --may-exist add-br $bridge -- set bridge $bridge protocols=OpenFlow13,OpenFlow15" if [ "$OVS_DATAPATH_TYPE" != "system" ] ; then addbr_cmd="$addbr_cmd -- set Bridge $bridge datapath_type=${OVS_DATAPATH_TYPE}" @@ -245,7 +242,7 @@ local ext_gw_ifc ext_gw_ifc=$(get_ext_gw_interface) - ovs-vsctl --may-exist add-br $ext_gw_ifc -- set bridge $ext_gw_ifc protocols=OpenFlow13 + ovs-vsctl --may-exist add-br $ext_gw_ifc -- set bridge $ext_gw_ifc protocols=OpenFlow13,OpenFlow15 ovs-vsctl set open . external-ids:ovn-bridge-mappings=$PHYSICAL_NETWORK:$ext_gw_ifc if [ -n "$FLOATING_RANGE" ]; then local cidr_len=${FLOATING_RANGE#*/} @@ -289,91 +286,6 @@ # OVN compilation functions # ------------------------- -# Fetch the ovs git repository and install packages needed for -# the compilation. -function _prepare_for_ovs_compilation { - local build_modules=$1 - clone_repository $OVS_REPO $DEST/$OVS_REPO_NAME $OVS_BRANCH - - if [[ "$build_modules" == "False" ]]; then - return - fi - - KERNEL_VERSION=`uname -r` - if is_fedora ; then - # is_fedora covers Fedora, RHEL, CentOS, etc... - if [[ "$os_VENDOR" == "Fedora" ]]; then - install_package elfutils-libelf-devel - KERNEL_VERSION=`echo $KERNEL_VERSION | cut --delimiter='-' --field 1` - elif [[ ${KERNEL_VERSION:0:2} != "3." ]]; then - # dash is illegal character in rpm version so replace - # them with underscore like it is done in the kernel - # https://github.com/torvalds/linux/blob/master/scripts/package/mkspec#L25 - # but only for latest series of the kernel, not 3.x - - KERNEL_VERSION=`echo $KERNEL_VERSION | tr - _` - fi - - echo NOTE: if kernel-devel-$KERNEL_VERSION or kernel-headers-$KERNEL_VERSION installation - echo failed, please, provide a repository with the package, or yum update / reboot - echo your machine to get the latest kernel. - - install_package kernel-devel-$KERNEL_VERSION - install_package kernel-headers-$KERNEL_VERSION - - elif is_ubuntu ; then - install_package linux-headers-$KERNEL_VERSION - fi -} - -# Reload the ovs kernel modules -function _reload_ovs_kernel_modules { - ovs_system=$(sudo ovs-dpctl dump-dps | grep ovs-system) - if [ -n "$ovs_system" ]; then - sudo ovs-dpctl del-dp ovs-system - fi - sudo modprobe -r vport_geneve - sudo modprobe -r openvswitch - sudo modprobe openvswitch || (dmesg && die $LINENO "FAILED TO LOAD openvswitch") - sudo modprobe vport-geneve || (dmesg && echo "FAILED TO LOAD vport-geneve") -} - -# Compile openvswitch and its kernel module -function _compile_ovs { - local build_modules=$1 - - # Install the dependencies - install_package autoconf automake libtool gcc patch make - # TODO(flaviof): Would prefer to use pip_install wrapper, but that is not - # useable right now because REQUIREMENTS_DIR variable is hard coded in - # starckrc - sudo pip3 install six - - _prepare_for_ovs_compilation $build_modules - - pushd $DEST/$OVS_REPO_NAME - [ -f configure ] || ./boot.sh - if [ ! -f config.status ] || [ configure -nt config.status ] ; then - if [[ "$build_modules" == "True" ]]; then - ./configure --with-linux=/lib/modules/$(uname -r)/build - else - ./configure - fi - fi - - make -j$(($(nproc) + 1)) - sudo make install - if [[ "$build_modules" == "True" ]]; then - sudo make INSTALL_MOD_DIR=kernel/net/openvswitch modules_install - if [ $? -eq 0 ]; then - _reload_ovs_kernel_modules - else - echo "Compiling OVS kernel modules failed" - fi - fi - popd -} - # compile_ovn() - Compile OVN from source and load needed modules # Accepts three parameters: # - first optional is False by default and means that @@ -387,9 +299,6 @@ local prefix=$2 local localstatedir=$3 - # First, compile OVS - _compile_ovs $build_modules - if [ -n "$prefix" ]; then prefix="--prefix=$prefix" fi @@ -447,14 +356,12 @@ # Install tox, used to generate the config (see devstack/override-defaults) pip_install tox - source $NEUTRON_DIR/devstack/lib/ovs remove_ovs_packages sudo rm -f $OVS_RUNDIR/* + compile_ovs $OVN_BUILD_MODULES if use_new_ovn_repository; then compile_ovn $OVN_BUILD_MODULES - else - compile_ovs $OVN_BUILD_MODULES fi sudo mkdir -p $OVS_RUNDIR @@ -499,6 +406,9 @@ export NETWORK_API_EXTENSIONS=$NETWORK_API_EXTENSIONS,$($PYTHON -c \ 'from neutron.common.ovn import extensions ;\ print(",".join(extensions.ML2_SUPPORTED_API_EXTENSIONS_OVN_L3))') + if is_service_enabled q-qos neutron-qos ; then + export NETWORK_API_EXTENSIONS="$NETWORK_API_EXTENSIONS,qos" + fi populate_ml2_config /$Q_PLUGIN_CONF_FILE ml2_type_geneve max_header_size=$OVN_GENEVE_OVERHEAD populate_ml2_config /$Q_PLUGIN_CONF_FILE ovn ovn_nb_connection="$OVN_NB_REMOTE" populate_ml2_config /$Q_PLUGIN_CONF_FILE ovn ovn_sb_connection="$OVN_SB_REMOTE" @@ -641,8 +551,8 @@ ovs-vsctl --no-wait set open_vswitch . external-ids:ovn-cms-options="enable-chassis-as-gw" fi - ovn_base_setup_bridge br-int - ovs-vsctl --no-wait set bridge br-int fail-mode=secure other-config:disable-in-band=true + # Note: ovn-controller will create and configure br-int once it is started. + # So, no need to create it now because nothing depends on that bridge here. local ovscmd="$OVS_SBINDIR/ovs-vswitchd --log-file --pidfile --detach" _run_process ovs-vswitchd "$ovscmd" "" "$STACK_GROUP" "root" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/lib/ovs neutron-16.4.2/devstack/lib/ovs --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/lib/ovs 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/lib/ovs 2021-11-12 13:56:42.000000000 +0000 @@ -10,9 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +# Defaults +# -------- + +# Set variables for building OVS from source OVS_REPO=${OVS_REPO:-https://github.com/openvswitch/ovs.git} OVS_REPO_NAME=$(basename ${OVS_REPO} | cut -f1 -d'.') -OVS_BRANCH=${OVS_BRANCH:-master} +OVS_REPO_NAME=${OVS_REPO_NAME:-ovs} +OVS_BRANCH=${OVS_BRANCH:-0047ca3a0290f1ef954f2c76b31477cf4b9755f5} # Functions @@ -33,7 +38,7 @@ # prepare_for_compilation() - Fetch ovs git repository and install packages needed for # compilation. -function prepare_for_compilation { +function prepare_for_ovs_compilation { local build_modules=${1:-False} OVS_DIR=$DEST/$OVS_REPO_NAME @@ -85,22 +90,24 @@ fi } -# load_kernel_modules() - load openvswitch kernel module -function load_kernel_modules { +# load_ovs_kernel_modules() - load openvswitch kernel module +function load_ovs_kernel_modules { load_module openvswitch load_module vport-geneve False dmesg | tail } -# reload_kernel_modules() - reload openvswitch kernel module -function reload_kernel_modules { - local ovs_system=$(sudo ovs-dpctl dump-dps | grep ovs-system) +# reload_ovs_kernel_modules() - reload openvswitch kernel module +function reload_ovs_kernel_modules { + set +e + ovs_system=$(sudo ovs-dpctl dump-dps | grep ovs-system) if [ -n "$ovs_system" ]; then sudo ovs-dpctl del-dp ovs-system fi + set -e sudo modprobe -r vport_geneve sudo modprobe -r openvswitch - load_kernel_modules + load_ovs_kernel_modules } # compile_ovs() - Compile OVS from source and load needed modules. @@ -123,7 +130,16 @@ localstatedir="--localstatedir=$localstatedir" fi - prepare_for_compilation $build_modules + prepare_for_ovs_compilation $build_modules + + KERNEL_VERSION=$(uname -r) + major_version=$(echo "${KERNEL_VERSION}" | cut -d '.' -f1) + patch_level=$(echo "${KERNEL_VERSION}" | cut -d '.' -f2) + if [ "${major_version}" -gt 5 ] || [ "${major_version}" == 5 ] && [ "${patch_level}" -gt 5 ]; then + echo "NOTE: KERNEL VERSION is ${KERNEL_VERSION} and OVS doesn't support compiling " + echo "Kernel module for version higher than 5.5. Skipping module compilation..." + build_modules="False" + fi if [ ! -f configure ] ; then ./boot.sh @@ -139,9 +155,9 @@ sudo make install if [[ "$build_modules" == "True" ]]; then sudo make INSTALL_MOD_DIR=kernel/net/openvswitch modules_install - reload_kernel_modules + reload_ovs_kernel_modules else - load_kernel_modules + load_ovs_kernel_modules fi cd $_pwd @@ -166,33 +182,6 @@ fi } - -# compile_ovs_kernel_module() - Compile openvswitch kernel module and load it -function compile_ovs_kernel_module { - local _pwd=$PWD - - prepare_for_compilation - - [ -f configure ] || ./boot.sh - if [ ! -f config.status ] || [ configure -nt config.status ] ; then - ./configure --with-linux=/lib/modules/$(uname -r)/build - fi - - action_openvswitch stop - - make -j$[$(nproc) + 1] - sudo make INSTALL_MOD_DIR=kernel/net/openvswitch modules_install - if [ $? -eq 0 ]; then - reload_kernel_modules - else - echo "Compiling OVS kernel module failed" - fi - - action_openvswitch start - - cd $_pwd -} - # start_new_ovs() - removes old ovs database, creates a new one and starts ovs function start_new_ovs () { sudo rm -f /etc/openvswitch/conf.db /etc/openvswitch/.conf.db~lock~ diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-compute-local.conf.sample neutron-16.4.2/devstack/ovn-compute-local.conf.sample --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-compute-local.conf.sample 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/ovn-compute-local.conf.sample 2021-11-12 13:56:42.000000000 +0000 @@ -32,7 +32,7 @@ #OVN_REPO=https://github.com/blp/ovs-reviews.git #OVN_BRANCH=ovn -enable_plugin neutron https://opendev.org/openstack/neutron +enable_plugin neutron https://opendev.org/openstack/neutron stable/ussuri disable_all_services enable_service n-cpu diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-db-local.conf.sample neutron-16.4.2/devstack/ovn-db-local.conf.sample --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-db-local.conf.sample 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/ovn-db-local.conf.sample 2021-11-12 13:56:42.000000000 +0000 @@ -22,7 +22,15 @@ #OVN_REPO=https://github.com/blp/ovs-reviews.git #OVN_BRANCH=ovn -enable_plugin neutron https://git.openstack.org/openstack/neutron +Q_AGENT=ovn +NEUTRON_AGENT=$Q_AGENT + +Q_ML2_PLUGIN_MECHANISM_DRIVERS=ovn,logger +Q_ML2_PLUGIN_TYPE_DRIVERS=local,flat,vlan,geneve +Q_ML2_TENANT_NETWORK_TYPE="geneve" +#SERVICE_HOST= + +enable_plugin neutron https://git.openstack.org/openstack/neutron stable/ussuri disable_all_services enable_service ovn-northd diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-local.conf.sample neutron-16.4.2/devstack/ovn-local.conf.sample --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-local.conf.sample 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/ovn-local.conf.sample 2021-11-12 13:56:42.000000000 +0000 @@ -34,8 +34,7 @@ enable_service ovn-controller enable_service q-ovn-metadata-agent -# Use Neutron instead of nova-network -disable_service n-net +# Use Neutron enable_service q-svc # Disable Neutron agents not used with OVN. @@ -45,7 +44,7 @@ disable_service q-meta # Enable services, these services depend on neutron plugin. -enable_plugin neutron https://opendev.org/openstack/neutron +enable_plugin neutron https://opendev.org/openstack/neutron stable/ussuri enable_service q-trunk enable_service q-dns #enable_service q-qos diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-vtep-local.conf.sample neutron-16.4.2/devstack/ovn-vtep-local.conf.sample --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/ovn-vtep-local.conf.sample 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/ovn-vtep-local.conf.sample 2021-11-12 13:56:42.000000000 +0000 @@ -22,7 +22,7 @@ #OVN_REPO=https://github.com/blp/ovs-reviews.git #OVN_BRANCH=ovn -enable_plugin neutron https://git.openstack.org/openstack/neutron +enable_plugin neutron https://git.openstack.org/openstack/neutron stable/ussuri disable_all_services enable_service ovn-controller-vtep diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/plugin.sh neutron-16.4.2/devstack/plugin.sh --- neutron-16.0.0~b3~git2020041516.5f42488a9a/devstack/plugin.sh 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/devstack/plugin.sh 2021-11-12 13:56:42.000000000 +0000 @@ -17,6 +17,7 @@ source $LIBDIR/fip_port_forwarding source $LIBDIR/uplink_status_propagation source $LIBDIR/tag_ports_during_bulk_creation +source $LIBDIR/octavia Q_BUILD_OVS_FROM_GIT=$(trueorfalse False Q_BUILD_OVS_FROM_GIT) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/requirements.txt neutron-16.4.2/doc/requirements.txt --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/requirements.txt 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/requirements.txt 2021-11-12 13:56:42.000000000 +0000 @@ -6,3 +6,6 @@ openstackdocstheme>=1.30.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 +# NOTE(ralonsoh): pyroute2 module should be installed and the version limited +# according to upper-constraints.txt +pyroute2>=0.5.7;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/archives/adv-features.rst neutron-16.4.2/doc/source/admin/archives/adv-features.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/archives/adv-features.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/admin/archives/adv-features.rst 2021-11-12 13:56:42.000000000 +0000 @@ -350,6 +350,9 @@ this is that Compute security group APIs are instances based and not port based as Networking. + - When creating or updating a port with a specified security group, + the admin tenant can use the security groups of other tenants. + Basic security group operations ------------------------------- diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/config-routed-networks.rst neutron-16.4.2/doc/source/admin/config-routed-networks.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/config-routed-networks.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/admin/config-routed-networks.rst 2021-11-12 13:56:42.000000000 +0000 @@ -27,7 +27,10 @@ multiple layer-2 networks (broadcast domains) or segments and enables the operator to present one network to users. However, the particular IP addresses available to an instance depend on the segment of the network -available on the particular compute node. +available on the particular compute node. Neutron port could be associated +with only one network segment, but there is an exception for OVN distributed +services like OVN Metadata. + Similar to conventional networking, layer-2 (switching) handles transit of traffic between ports on the same segment and layer-3 (routing) handles diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/intro-os-networking.rst neutron-16.4.2/doc/source/admin/intro-os-networking.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/intro-os-networking.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/admin/intro-os-networking.rst 2021-11-12 13:56:42.000000000 +0000 @@ -118,6 +118,9 @@ achieve with a plain provider network at the expense of guaranteed layer-2 connectivity. +Neutron port could be associated with only one network segment, +but there is an exception for OVN distributed services like OVN Metadata. + See :ref:`config-routed-provider-networks` for more information. .. _intro-os-networking-selfservice: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/ovn/igmp.rst neutron-16.4.2/doc/source/admin/ovn/igmp.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/ovn/igmp.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/admin/ovn/igmp.rst 2021-11-12 13:56:42.000000000 +0000 @@ -35,15 +35,15 @@ ~~~~~~~~~~~~~~~~~~~~~~~~ The ``igmp_snooping_enable`` configuration from Neutron is translated -into the ``mcast_snoop`` and ``mcast_flood_unregistered`` options set -in the ``other_config`` column from the ``Logical_Switch`` table in the -OVN Northbound Database: +into the ``mcast_snoop`` option set in the ``other_config`` column +from the ``Logical_Switch`` table in the OVN Northbound Database +(``mcast_flood_unregistered`` is always "false"): .. code-block:: bash $ ovn-nbctl list Logical_Switch _uuid : d6a2fbcd-aaa4-4b9e-8274-184238d66a15 - other_config : {mcast_flood_unregistered="true", mcast_snoop="true"} + other_config : {mcast_flood_unregistered="false", mcast_snoop="true"} ... .. end diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/ovn/refarch/refarch.rst neutron-16.4.2/doc/source/admin/ovn/refarch/refarch.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/admin/ovn/refarch/refarch.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/admin/ovn/refarch/refarch.rst 2021-11-12 13:56:42.000000000 +0000 @@ -218,10 +218,23 @@ Security Groups/Rules --------------------- -Each security group will map to 2 Address_Sets in the OVN NB and SB -tables, one for ipv4 and another for ipv6, which will be used to hold ip -addresses for the ports that belong to the security group, so that rules -with remote_group_id can be efficiently applied. +When a Neutron Security Group is created, the equivalent Port Group in OVN +(pg- is created). This Port Group references Neutron SG id +in its external_ids column. + +When a Neutron Port is created, the equivalent Logical Port in OVN is added to +those Port Groups associated to the Neutron Security Groups this port belongs +to. + +When a Neutron Port is deleted, the associated Logical Port in OVN is deleted. +Since the schema includes a weak reference to the port, when the LSP gets +deleted, it is automatically deleted from any Port Group entry where it was +previously present. + +Every time a security group rule is created, instead of figuring out the ports +affected by its SG and inserting an ACL row which will be referenced by +different Logical Switches, we just reference it from the associated +Port Group. .. todo: add block with openstack security group rule example @@ -229,36 +242,52 @@ ~~~~~~~~~~~~~~ #. Creating a security group will cause the OVN mechanism driver to create - 2 new entries in the Address Set table of the northbound DB: + a port group in the Port_Group table of the northbound DB: .. code-block:: console - _uuid : 9a9d01bd-4afc-4d12-853a-cd21b547911d - addresses : [] - external_ids : {"neutron:security_group_name"=default} - name : "as_ip4_90a78a43_b549_4bee_8822_21fcccab58dc" - - _uuid : 27a91327-636e-4125-99f0-6f2937a3b6d8 - addresses : [] - external_ids : {"neutron:security_group_name"=default} - name : "as_ip6_90a78a43_b549_4bee_8822_21fcccab58dc" - - In the above entries, the address set name include the protocol (IPv4 - or IPv6, written as ip4 or ip6) and the UUID of the Openstack security - group, dashes translated to underscores. + _uuid : e96c5994-695d-4b9c-a17b-c7375ad281e2 + acls : [33c3c2d0-bc7b-421b-ace9-10884851521a, c22170ec-da5d-4a59-b118-f7f0e370ebc4] + external_ids : {"neutron:security_group_id"="ccbeffee-7b98-4b6f-adf7-d42027ca6447"} + name : pg_ccbeffee_7b98_4b6f_adf7_d42027ca6447 + ports : [] -#. In turn, these new entries will be translated by the OVN northd daemon - into entries in the southbound DB: + And it also creates the default ACLs for egress traffic in the ACL table of + the northbound DB: .. code-block:: console - _uuid : 886d7b3a-e460-470f-8af2-7c7d88ce45d2 - addresses : [] - name : "as_ip4_90a78a43_b549_4bee_8822_21fcccab58dc" - - _uuid : 355ddcba-941d-4f1c-b823-dc811cec59ca - addresses : [] - name : "as_ip6_90a78a43_b549_4bee_8822_21fcccab58dc" + _uuid : 33c3c2d0-bc7b-421b-ace9-10884851521a + action : allow-related + direction : from-lport + external_ids : {"neutron:security_group_rule_id"="655b0d7e-144e-4bd8-9243-10a261b91041"} + log : false + match : "inport == @pg_ccbeffee_7b98_4b6f_adf7_d42027ca6447 && ip4" + meter : [] + name : [] + priority : 1002 + severity : [] + + _uuid : c22170ec-da5d-4a59-b118-f7f0e370ebc4 + action : allow-related + direction : from-lport + external_ids : {"neutron:security_group_rule_id"="a303a34f-5f19-494f-a9e2-e23f246bfcad"} + log : false + match : "inport == @pg_ccbeffee_7b98_4b6f_adf7_d42027ca6447 && ip6" + meter : [] + name : [] + priority : 1002 + severity : [] + +Ports with no security groups +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a port doesn't belong to any Security Group and port security is enabled, +we, by default, drop all the traffic to/from that port. In order to implement +this through Port Groups, we'll create a special Port Group with a fixed name +(``neutron_pg_drop``) which holds the ACLs to drop all the traffic. + +This PG is created automatically once before neutron-server forks into workers. Networks -------- diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/cli/neutron-sanity-check.rst neutron-16.4.2/doc/source/cli/neutron-sanity-check.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/cli/neutron-sanity-check.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/cli/neutron-sanity-check.rst 2021-11-12 13:56:42.000000000 +0000 @@ -36,14 +36,12 @@ [--noovs_conntrack] [--noovs_geneve] [--noovs_patch] [--noovs_vxlan] [--noovsdb_native] [--noread_netns] [--nouse-syslog] [--nova_notify] - [--noverbose] [--novf_extended_management] - [--novf_management] [--nowatch-log-file] + [--noverbose] [--nowatch-log-file] [--ovs_conntrack] [--ovs_geneve] [--ovs_patch] [--ovs_vxlan] [--ovsdb_native] [--read_netns] [--state_path STATE_PATH] [--syslog-log-facility SYSLOG_LOG_FACILITY] [--use-syslog] [--verbose] [--version] - [--vf_extended_management] [--vf_management] [--watch-log-file] neutron-sanity-check optional arguments @@ -200,12 +198,6 @@ ``--noverbose`` The inverse of --verbose -``--novf_extended_management`` - The inverse of --vf_extended_management - -``--novf_management`` - The inverse of --vf_management - ``--nowatch-log-file`` The inverse of --watch-log-file @@ -244,12 +236,6 @@ ``--version`` show program's version number and exit -``--vf_extended_management`` - Check for VF extended management support - -``--vf_management`` - Check for VF management support - ``--watch-log-file`` Uses logging handler designed to watch file system. When log file is moved or removed this handler will open a new log diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/internals/openvswitch_firewall.rst neutron-16.4.2/doc/source/contributor/internals/openvswitch_firewall.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/internals/openvswitch_firewall.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/contributor/internals/openvswitch_firewall.rst 2021-11-12 13:56:42.000000000 +0000 @@ -245,16 +245,16 @@ :: - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=130 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=131 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=132 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=135 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=136 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=130 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=131 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=132 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=135 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=136 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=130 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=131 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=132 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=135 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=136 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=130 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=131 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=132 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=135 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=136 actions=resubmit(,94) Following rules implement ARP spoofing protection diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/internals/ovn/acl_optimizations.rst neutron-16.4.2/doc/source/contributor/internals/ovn/acl_optimizations.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/internals/ovn/acl_optimizations.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/contributor/internals/ovn/acl_optimizations.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,186 +0,0 @@ -.. _acl_optimizations: - -======================================== -ACL Handling optimizations in ovn driver -======================================== - -This document presents the current problem with ACLs and the design changes -proposed to core OVN as well as the necessary modifications to be made to -ovn driver to improve their usage. - -Problem description -=================== - -There is basically two problems being addressed in this spec: - -1. While in Neutron, a ``Security Group Rule`` is tied to a -``Security Group``, in OVN ``ACLs`` are created per port. Therefore, -we'll typically have *many* more ACLs than Security Group Rules, resulting -in a performance hit as the number of ports grows. - -2. An ACL in OVN is applied to a ``Logical Switch``. As a result, -``ovn driver`` has to figure out which Logical Switches to apply the -generated ACLs per each Security Rule. - -Let's highlight both problems with an example: - -- Neutron Networks: NA, NB, NC -- Neutron Security Group: SG1 -- Number of Neutron Security Group Rules in SG1: 10 -- Neutron Ports in NA: 100 -- Neutron Ports in NB: 100 -- Neutron Ports in NC: 100 -- All ports belong to SG1 - -When we implement the above scenario in OVN, this is what we'll get: - -- OVN Logical Switches: NA, NB, NC -- Number of ACL rows in Northbound DB ACL table: 3000 (10 rules * 100 ports * - 3 networks) -- Number of elements in acl column on each Logical_Switch row: 1000 (10 rules - * 100 ports). - -And this is how, for example, the ACL match fields for the default Neutron -Security Group would look like:: - - outport == && ip4 && ip4.src == $as_ip4_ - outport == && ip4 && ip4.src == $as_ip4_ - outport == && ip4 && ip4.src == $as_ip4_ - ... - outport == && ip4 && ip4.src == $as_ip4_ - -As you can see, all of them look the same except for the outport field which -is clearly redundant and makes the NB database grow a lot at scale. -Also, ``ovn driver`` had to figure out for each rule in SG1 which Logical -Switches it had to apply the ACLs on (NA, NB and NC). This can be really costly -when the number of networks and port grows. - - -Proposed optimization -===================== - -In the OpenStack context, we'll be facing this scenario most of the time -where the majority of the ACLs will look the same except for the -outport/inport fields in the match column. It would make sense to be able to -substitute all those ACLs by a single one which references all the ports -affected by that SG rule:: - - outport == @port_group1 && ip4 && ip4.src == $port_group1_ip4 - - -Implementation Details -====================== - -Core OVN --------- - -There's a series of patches in Core OVN that will enable us to achieve this -optimization: - -https://github.com/openvswitch/ovs/commit/3d2848bafa93a2b483a4504c5de801454671dccf -https://github.com/openvswitch/ovs/commit/1beb60afd25a64f1779903b22b37ed3d9956d47c -https://github.com/openvswitch/ovs/commit/689829d53612a573f810271a01561f7b0948c8c8 - - -In summary, these patches are: - -- Adding a new entity called Port_Group which will hold a list of weak - references to the Logical Switch ports that belong to it. -- Automatically creating/updating two Address Sets (_ip4 and _ip6) in - Southbound database every time a new port is added to the group. -- Support adding a list of ACLs to a Port Group. As the SG rules may - span across different Logical Switches, we used to insert the ACLs in - all the Logical Switches where we have ports in within a SG. Figuring this - out is expensive and this new feature is a huge gain in terms of - performance when creating/deleting ports. - - -ovn driver ----------- - -In the OpenStack integration driver, the following changes are required to -accomplish this optimization: - -- When a Neutron Security Group is created, create the equivalent Port Group - in OVN (pg-), instead of creating a pair of Adress Sets - for IPv4 and IPv6. This Port Group will reference Neutron SG id in its - ``external_ids`` column. - -- When a Neutron Port is created, the equivalent Logical Port in OVN will be - added to those Port Groups associated to the Neutron Security Groups this - port belongs to. - -- When a Neutron Port is deleted, we'll delete the associated Logical Port in - OVN. Since the schema includes a weak reference to the port, when the LSP - gets deleted, it will also be automatically deleted from any Port Group - entry where it was previously present. - -- Instead of handling SG rules per port, we now need to handle them per SG - referencing the associated Port Group in the outport/inport fields. This - will be the biggest gain in terms of processing since we don't need to - iterate through all the ports anymore. For example: - -.. code-block:: python - - -def acl_direction(r, port): - +def acl_direction(r): - if r['direction'] == 'ingress': - portdir = 'outport' - else: - portdir = 'inport' - - return '%s == "%s"' % (portdir, port['id']) - + return '%s == "@%s"' % (portdir, utils.ovn_name(r['security_group_id']) - -- Every time a SG rule is created, instead of figuring out the ports affected - by its SG and inserting an ACL row which will be referrenced by different - Logical Switches, we will just reference it from the associated Port Group. - -- For Neutron remote security groups, we just need to reference the - automatically created Address_Set for that Port Group. - -As a bonus, we are tackling the race conditions that could happen in -Address_Sets right now when we're deleting and creating a port at the same -time. This is thanks to the fact that the Address_Sets in the SB table are -generated automatically by ovn-northd from the Port_Group contents and -Port Group is referencing actual Logical Switch Ports. More info at: -https://bugs.launchpad.net/networking-ovn/+bug/1611852 - - -Backwards compatibility considerations --------------------------------------- - -- If the schema doesn't include the ``Port_Group`` table, keep the old - behavior(Address Sets) for backwards compatibility. - -- If the schema supports Port Groups, then a migration task will be performed - from an OvnWorker. This way we'll ensure that it'll happen only once across - the cloud thanks the OVSDB lock. This will be done right at the beginning of - the ovn_db_sync process to make sure that when neutron-server starts, - everything is in place to work with Port Groups. This migration process will - perform the following steps: - - * Create the default drop Port Group and add all ports with port - security enabled to it. - * Create a Port Group for every existing Neutron Security Group and - add all its Security Group Rules as ACLs to that Port Group. - * Delete all existing Address Sets in NorthBound database which correspond to - a Neutron Security Group. - * Delete all the ACLs in every Logical Switch (Neutron network). - -We should eventually remove the backwards compatibility and migration path. At -that point we should require OVS >= 2.10 from neutron ovn driver. - -Special cases -------------- - -Ports with no security groups -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a port doesn't belong to any Security Group and port security is enabled, -we, by default, drop all the traffic to/from that port. In order to implement -this through Port Groups, we'll create a special Port Group with a fixed name -(``neutron_pg_drop``) which holds the ACLs to drop all the traffic. - -This PG will be created automatically when we first need it, avoiding the need -to create it beforehand or during deployment. - diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/internals/ovn/index.rst neutron-16.4.2/doc/source/contributor/internals/ovn/index.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/internals/ovn/index.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/contributor/internals/ovn/index.rst 2021-11-12 13:56:42.000000000 +0000 @@ -12,7 +12,6 @@ ovn_worker metadata_api database_consistency - acl_optimizations loadbalancer distributed_ovsdb_events l3_ha_rescheduling diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/testing/ci_scenario_jobs.rst neutron-16.4.2/doc/source/contributor/testing/ci_scenario_jobs.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/contributor/testing/ci_scenario_jobs.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/contributor/testing/ci_scenario_jobs.rst 2021-11-12 13:56:42.000000000 +0000 @@ -52,6 +52,8 @@ |neutron-tempest-plugin-scenario-openvswitch-\ |neutron_tempest_plugin.scenario | 3.6 | 1 | openvswitch | iptables_hybrid | legacy | False | False | False | Yes | | iptables_hybrid | | | | | | | | | | | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ + |neutron-tempest-plugin-scenario-ovn | neutron_tempest_plugin.scenario | 3.6 | 1 | ovn | ovn | --- | False | False | False | Yes | + +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ |tempest-integrated-networking |tempest.api (without slow tests) | 3.6 | 1 | openvswitch | openvswitch | legacy | False | False | True | Yes | | |tempest.scenario | | | | | | | | | | | |(only tests related to | | | | | | | | | | @@ -69,22 +71,18 @@ |(non-voting) |tempest.scenario | | | | | dvr_snat | | | | | | | | | | | | dvr_snat | | | | | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ - |neutron-tempest-iptables_hybrid |tempest.api (without slow tests) | 3.6 | 1 | openvswitch | iptables_hybrid | legacy | False | False | True | Yes | - | |tempest.scenario | | | | | | | | | | - | |(only tests related to | | | | | | | | | | - | |Neutron and Nova) | | | | | | | | | | - +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ - |tempest-slow-py3 |tempest slow tests | 3.6 | 2 | openvswitch | openvswitch | legacy | False | False | True | Yes | + |neutron-tempest-slow-py3 |tempest slow tests | 3.6 | 2 | openvswitch | openvswitch | legacy | False | False | True | Yes | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ |neutron-tempest-with-uwsgi |tempest.api (without slow tests) | 3.6 | 1 | openvswitch | openvswitch | legacy | False | False | True | No | |(non-voting) |tempest.scenario | | | | | | | | | | | |(only tests related to | | | | | | | | | | | |Neutron and Nova) | | | | | | | | | | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ - |tempest-ipv6-only |tempest smoke + IPv6 tests | 3.6 | 1 | openvswitch | openvswitch | legacy | False | False | True | Yes | + |neutron-tempest-ipv6-only |tempest smoke + IPv6 tests | 3.6 | 1 | openvswitch | openvswitch | legacy | False | False | True | Yes | + | |(only tests related to | | | | | | | | | | + | |Neutron and Nova) | | | | | | | | | | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ |neutron-ovn-tempest-ovs-release |Various tempest api, scenario | 3.6 | 1 | ovn | ovn | --- | False | False | True | Yes | - | |and neutron_tempest_plugi tests | | | | | | | | | | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ |neutron-ovn-tempest-slow |tempest slow tests | 3.6 | 2 | ovn | ovn | --- | False | False | True | No | +----------------------------------------------+----------------------------------+---------+-------+-------------+-----------------+----------+-------+--------+------------+-------------+ diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/ovn/dhcp_opts.rst neutron-16.4.2/doc/source/ovn/dhcp_opts.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/ovn/dhcp_opts.rst 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/doc/source/ovn/dhcp_opts.rst 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,138 @@ +.. _ovn_dhcp_opts: + +OVN supported DHCP options +========================== + +This is a list of the current supported DHCP options in ML2/OVN: + +IP version 4 +~~~~~~~~~~~~ + +========================== ============================ +Option name / code OVN value +========================== ============================ +arp-timeout arp_cache_timeout +bootfile-name bootfile_name +classless-static-route classless_static_route +default-ttl default_ttl +dns-server dns_server +domain-name domain_name +ethernet-encap ethernet_encap +ip-forward-enable ip_forward_enable +lease-time lease_time +log-server log_server +lpr-server lpr_server +ms-classless-static-route ms_classless_static_route +mtu mtu +netmask netmask +nis-server nis_server +ntp-server ntp_server +path-prefix path_prefix +policy-filter policy_filter +router-discovery router_discovery +router router +router-solicitation router_solicitation +server-id server_id +server-ip-address tftp_server_address +swap-server swap_server +T1 T1 +T2 T2 +tcp-ttl tcp_ttl +tcp-keepalive tcp_keepalive_interval +tftp-server-address tftp_server_address +tftp-server tftp_server +wpad wpad +1 netmask +3 router +6 dns_server +7 log_server +9 lpr_server +15 domain_name +16 swap_server +19 ip_forward_enable +21 policy_filter +23 default_ttl +26 mtu +31 router_discovery +32 router_solicitation +35 arp_cache_timeout +36 ethernet_encap +37 tcp_ttl +38 tcp_keepalive_interval +41 nis_server +42 ntp_server +51 lease_time +54 server_id +58 T1 +59 T2 +66 tftp_server +67 bootfile_name +121 classless_static_route +150 tftp_server_address +210 path_prefix +249 ms_classless_static_route +252 wpad +========================== ============================ + +IP version 6 +~~~~~~~~~~~~ + +================== ============= +Option name / code OVN value +================== ============= +dns-server dns_server +domain-search domain_search +ia-addr ia_addr +server-id server_id +2 server_id +5 ia_addr +23 dns_server +24 domain_search +================== ============= + +OVN Database information +~~~~~~~~~~~~~~~~~~~~~~~~ + +In OVN the DHCP options are stored on a table called ``DHCP_Options`` +in the OVN Northbound database. + +Let's add a DHCP option to a Neutron port: + +.. code-block:: bash + + $ neutron port-update --extra-dhcp-opt opt_name='server-ip-address',opt_value='10.0.0.1' b4c3f265-369e-4bf5-8789-7caa9a1efb9c + Updated port: b4c3f265-369e-4bf5-8789-7caa9a1efb9c + +.. end + +To find that port in OVN we can use command below: + +.. code-block:: bash + + $ ovn-nbctl find Logical_Switch_Port name=b4c3f265-369e-4bf5-8789-7caa9a1efb9c + ... + dhcpv4_options : 5f00d1a2-c57d-4d1f-83ea-09bf8be13288 + dhcpv6_options : [] + ... + +.. end + +For DHCP, the columns that we care about are the ``dhcpv4_options`` +and ``dhcpv6_options``. These columns has the uuids of entries in the +``DHCP_Options`` table with the DHCP information for this port. + +.. code-block:: bash + + $ ovn-nbctl list DHCP_Options 5f00d1a2-c57d-4d1f-83ea-09bf8be13288 + _uuid : 5f00d1a2-c57d-4d1f-83ea-09bf8be13288 + cidr : "10.0.0.0/26" + external_ids : {"neutron:revision_number"="0", port_id="b4c3f265-369e-4bf5-8789-7caa9a1efb9c", subnet_id="5157ed8b-e7f1-4c56-b789-fa420098a687"} + options : {classless_static_route="{169.254.169.254/32,10.0.0.2, 0.0.0.0/0,10.0.0.1}", dns_server="{8.8.8.8}", domain_name="\"openstackgate.local\"", lease_time="43200", log_server="127.0.0.3", mtu="1442", router="10.0.0.1", server_id="10.0.0.1", server_mac="fa:16:3e:dc:57:22", tftp_server_address="10.0.0.1"} + +.. end + +Here you can see that the option ``tftp_server_address`` has been set in +the **options** column. Note that, the ``tftp_server_address`` option is +the OVN translated name for ``server-ip-address`` (option 150). Take a +look at the table in this document to find out more about the supported +options and their counterpart names in OVN. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/ovn/index.rst neutron-16.4.2/doc/source/ovn/index.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/ovn/index.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/ovn/index.rst 2021-11-12 13:56:42.000000000 +0000 @@ -10,4 +10,5 @@ migration.rst gaps.rst + dhcp_opts.rst faq/index.rst diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/ovn/migration.rst neutron-16.4.2/doc/source/ovn/migration.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/doc/source/ovn/migration.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/doc/source/ovn/migration.rst 2021-11-12 13:56:42.000000000 +0000 @@ -107,6 +107,14 @@ step 1. Default: ~/overcloud-deploy-ovn.sh + * UNDERCLOUD_NODE_USER - user used on the undercloud nodes + Default: heat-admin + + * STACK_NAME - Name or ID of the heat stack + Default: 'overcloud' + If the stack that is migrated differs from the default, please set this + environment variable to the stack name or ID. + * PUBLIC_NETWORK_NAME - Name of your public network. Default: 'public'. To support migration validation, this network must have available diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/etc/neutron/rootwrap.d/dhcp.filters neutron-16.4.2/etc/neutron/rootwrap.d/dhcp.filters --- neutron-16.0.0~b3~git2020041516.5f42488a9a/etc/neutron/rootwrap.d/dhcp.filters 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/etc/neutron/rootwrap.d/dhcp.filters 2021-11-12 13:56:42.000000000 +0000 @@ -10,6 +10,7 @@ # dhcp-agent dnsmasq: CommandFilter, dnsmasq, root +ethtool: CommandFilter, ethtool, root # dhcp-agent uses kill as well, that's handled by the generic KillFilter # it looks like these are the only signals needed, per # neutron/agent/linux/dhcp.py diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/etc/neutron/rootwrap.d/l3.filters neutron-16.4.2/etc/neutron/rootwrap.d/l3.filters --- neutron-16.0.0~b3~git2020041516.5f42488a9a/etc/neutron/rootwrap.d/l3.filters 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/etc/neutron/rootwrap.d/l3.filters 2021-11-12 13:56:42.000000000 +0000 @@ -12,6 +12,7 @@ arping: CommandFilter, arping, root # l3_agent +ethtool: CommandFilter, ethtool, root sysctl: CommandFilter, sysctl, root route: CommandFilter, route, root radvd: CommandFilter, radvd, root @@ -24,6 +25,7 @@ kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -15, -9, -HUP kill_radvd: KillFilter, root, /sbin/radvd, -15, -9, -HUP +kill_radvd_script: CommandFilter, radvd-kill, root # ip_lib ip: IpFilter, ip, root @@ -68,6 +70,7 @@ kill_keepalived_monitor_py3: KillFilter, root, python3, -15, -9 kill_keepalived_monitor_py36: KillFilter, root, python3.6, -15, -9 kill_keepalived_monitor_py37: KillFilter, root, python3.7, -15, -9 +kill_keepalived_monitor_py38: KillFilter, root, python3.8, -15, -9 # For e.g. RHEL8 neutron-keepalived-state-change is run by "system python" # which is /usr/libexec/platform-python3.6 so this should be in filters also. # Path /usr/libexec isn't in PATH by default so it has to be given here as diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/etc/neutron/rootwrap.d/netns-cleanup.filters neutron-16.4.2/etc/neutron/rootwrap.d/netns-cleanup.filters --- neutron-16.0.0~b3~git2020041516.5f42488a9a/etc/neutron/rootwrap.d/netns-cleanup.filters 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/etc/neutron/rootwrap.d/netns-cleanup.filters 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -# neutron-rootwrap command filters for nodes on which neutron is -# expected to control network -# -# This file should be owned by (and only-writeable by) the root user - -# format seems to be -# cmd-name: filter-name, raw-command, user, args - -[Filters] - -# netns-cleanup -netstat: CommandFilter, netstat, root diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/lower-constraints.txt neutron-16.4.2/lower-constraints.txt --- neutron-16.0.0~b3~git2020041516.5f42488a9a/lower-constraints.txt 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/lower-constraints.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -alabaster==0.7.10 -alembic==0.8.10 -amqp==2.1.1 -appdirs==1.4.3 -astroid==2.1.0 -Babel==2.3.4 -bandit==1.1.0 -bashate==0.5.1 -beautifulsoup4==4.6.0 -cachetools==2.0.0 -cffi==1.13.2 -cliff==2.8.0 -cmd2==0.8.0 -contextlib2==0.4.0 -coverage==4.0 -ddt==1.0.1 -debtcollector==1.19.0 -decorator==3.4.0 -deprecation==1.0 -doc8==0.6.0 -docutils==0.11 -dogpile.cache==0.6.2 -dulwich==0.15.0 -eventlet==0.18.2 -extras==1.0.0 -fasteners==0.7.0 -fixtures==3.0.0 -flake8-import-order==0.12 -flake8==2.6.2 -future==0.16.0 -futurist==1.2.0 -gitdb==0.6.4 -GitPython==1.0.1 -greenlet==0.4.10 -hacking==1.1.0 -httplib2==0.9.1 -imagesize==0.7.1 -iso8601==0.1.11 -Jinja2==2.10 -jmespath==0.9.0 -jsonpatch==1.16 -jsonpointer==1.13 -jsonschema==2.6.0 -keystoneauth1==3.14.0 -keystonemiddleware==4.17.0 -kombu==4.0.0 -linecache2==1.0.0 -logilab-common==1.4.1 -logutils==0.3.5 -Mako==0.4.0 -MarkupSafe==1.0 -mccabe==0.2.1 -mock==3.0.0 -monotonic==0.6;python_version<'3.3' -mox3==0.20.0 -msgpack-python==0.4.0 -munch==2.1.0 -netaddr==0.7.18 -netifaces==0.10.4 -neutron-lib==2.2.0 -openstackdocstheme==1.30.0 -openstacksdk==0.31.2 -os-client-config==1.28.0 -os-ken==0.3.0 -os-service-types==1.7.0 -os-vif==1.15.1 -os-xenapi==0.3.1 -osc-lib==1.8.0 -oslo.cache==1.26.0 -oslo.concurrency==3.26.0 -oslo.config==5.2.0 -oslo.context==2.19.2 -oslo.db==4.37.0 -oslo.i18n==3.15.3 -oslo.log==3.36.0 -oslo.messaging==5.29.0 -oslo.middleware==3.31.0 -oslo.policy==1.30.0 -oslo.privsep==1.32.0 -oslo.reports==1.18.0 -oslo.rootwrap==5.8.0 -oslo.serialization==2.18.0 -oslo.service==1.24.0 -oslo.upgradecheck==0.1.0 -oslo.utils==3.33.0 -oslo.versionedobjects==1.35.1 -oslotest==3.2.0 -osprofiler==2.3.0 -ovs==2.8.0 -ovsdbapp==1.0.0 -Paste==2.0.2 -PasteDeploy==1.5.0 -pbr==4.0.0 -pecan==1.3.2 -pep8==1.5.7 -pika-pool==0.1.3 -pika==0.10.0 -positional==1.2.1 -prettytable==0.7.2 -psutil==3.2.2 -pycadf==1.1.0 -pycodestyle==2.4.0 -pycparser==2.18 -pyflakes==0.8.1 -Pygments==2.2.0 -pyinotify==0.9.6 -pylint==2.2.0 -PyMySQL==0.7.6 -pyOpenSSL==17.1.0 -pyparsing==2.1.0 -pyperclip==1.5.27 -pyroute2==0.5.7 -python-dateutil==2.5.3 -python-designateclient==2.7.0 -python-editor==1.0.3 -python-keystoneclient==3.8.0 -python-mimeparse==1.6.0 -python-neutronclient==6.7.0 -python-novaclient==9.1.0 -python-subunit==1.0.0 -pytz==2013.6 -PyYAML==3.12 -reno==2.5.0 -repoze.lru==0.7 -requests==2.14.2 -requestsexceptions==1.2.0 -rfc3986==0.3.1 -Routes==2.3.1 -simplejson==3.5.1 -six==1.10.0 -smmap==0.9.0 -snowballstemmer==1.2.1 -Sphinx==1.6.5 -sphinxcontrib-websupport==1.0.1 -sqlalchemy-migrate==0.11.0 -SQLAlchemy==1.2.0 -sqlparse==0.2.2 -statsd==3.2.1 -stestr==1.0.0 -stevedore==1.20.0 -Tempita==0.5.2 -tenacity==4.4.0 -testrepository==0.0.18 -testresources==2.0.0 -testscenarios==0.4 -testtools==2.2.0 -tooz==1.58.0 -tinyrpc==0.6 -traceback2==1.4.0 -vine==1.1.4 -waitress==1.1.0 -WebOb==1.8.2 -WebTest==2.0.27 -wrapt==1.7.0 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/common/ovs_lib.py neutron-16.4.2/neutron/agent/common/ovs_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/common/ovs_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/common/ovs_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -74,6 +74,9 @@ CTRL_RATE_LIMIT_MIN = 100 CTRL_BURST_LIMIT_MIN = 25 +# TODO(slaweq): move this to neutron_lib.constants +TYPE_GRE_IP6 = 'ip6gre' + def _ovsdb_result_pending(result): """Return True if ovsdb indicates the result is still pending.""" @@ -100,6 +103,13 @@ return wrapped +def get_gre_tunnel_port_type(remote_ip, local_ip): + if (common_utils.get_ip_version(remote_ip) == p_const.IP_VERSION_6 or + common_utils.get_ip_version(local_ip) == p_const.IP_VERSION_6): + return TYPE_GRE_IP6 + return p_const.TYPE_GRE + + class VifPort(object): def __init__(self, port_name, ofport, vif_id, vif_mac, switch): self.port_name = port_name @@ -273,8 +283,13 @@ self._set_bridge_fail_mode(FAILMODE_STANDALONE) def add_protocols(self, *protocols): - self.ovsdb.db_add('Bridge', self.br_name, - 'protocols', *protocols).execute(check_error=True) + existing_protocols = self.db_get_val( + 'Bridge', self.br_name, 'protocols') + diff = set(protocols).difference(existing_protocols) + if diff: + self.ovsdb.db_add( + 'Bridge', self.br_name, + 'protocols', *diff).execute(check_error=True) def use_at_least_protocol(self, protocol): """Calls to ovs-ofctl will use a protocol version >= 'protocol'""" @@ -287,7 +302,7 @@ def set_igmp_snooping_state(self, state): state = bool(state) other_config = { - 'mcast-snooping-disable-flood-unregistered': str(state)} + 'mcast-snooping-disable-flood-unregistered': 'false'} with self.ovsdb.transaction() as txn: txn.add( self.ovsdb.db_set('Bridge', self.br_name, @@ -296,6 +311,16 @@ self.ovsdb.db_set('Bridge', self.br_name, ('other_config', other_config))) + def set_igmp_snooping_flood(self, port_name, state): + state = str(state) + other_config = { + 'mcast-snooping-flood-reports': state, + 'mcast-snooping-flood': state} + self.ovsdb.db_set( + 'Port', port_name, + ('other_config', other_config)).execute( + check_error=True, log_errors=True) + def create(self, secure_mode=False): other_config = { 'mac-table-size': str(cfg.CONF.OVS.bridge_mac_table_size)} @@ -509,6 +534,8 @@ dont_fragment=True, tunnel_csum=False, tos=None): + if tunnel_type == p_const.TYPE_GRE: + tunnel_type = get_gre_tunnel_port_type(remote_ip, local_ip) attrs = [('type', tunnel_type)] # TODO(twilson) This is an OrderedDict solely to make a test happy options = collections.OrderedDict() @@ -532,6 +559,10 @@ options['csum'] = str(tunnel_csum).lower() if tos: options['tos'] = str(tos) + if tunnel_type == TYPE_GRE_IP6: + # NOTE(slaweq) According to the OVS documentation L3 GRE tunnels + # over IPv6 are not supported. + options['packet_type'] = 'legacy_l2' attrs.append(('options', options)) return self.add_port(port_name, *attrs) @@ -715,8 +746,8 @@ self.set_controller_field('inactivity_probe', interval * 1000) def _set_egress_bw_limit_for_port(self, port_name, max_kbps, - max_burst_kbps): - with self.ovsdb.transaction(check_error=True) as txn: + max_burst_kbps, check_error=True): + with self.ovsdb.transaction(check_error=check_error) as txn: txn.add(self.ovsdb.db_set('Interface', port_name, ('ingress_policing_rate', max_kbps))) txn.add(self.ovsdb.db_set('Interface', port_name, @@ -743,8 +774,7 @@ def delete_egress_bw_limit_for_port(self, port_name): if not self.port_exists(port_name): return - self._set_egress_bw_limit_for_port( - port_name, 0, 0) + self._set_egress_bw_limit_for_port(port_name, 0, 0, check_error=False) def find_qos(self, port_name): qos = self.ovsdb.db_find( @@ -915,12 +945,11 @@ return max_kbps, max_burst_kbit def delete_ingress_bw_limit_for_port(self, port_name): + self.ovsdb.db_clear('Port', port_name, + 'qos').execute(check_error=False) qos = self.find_qos(port_name) queue = self.find_queue(port_name, QOS_DEFAULT_QUEUE) - does_port_exist = self.port_exists(port_name) with self.ovsdb.transaction(check_error=True) as txn: - if does_port_exist: - txn.add(self.ovsdb.db_clear("Port", port_name, 'qos')) if qos: txn.add(self.ovsdb.db_destroy('QoS', qos['_uuid'])) if queue: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/common/utils.py neutron-16.4.2/neutron/agent/common/utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/common/utils.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/common/utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -70,25 +70,55 @@ cfg.CONF.agent_down_time) +def get_hypervisor_hostname(): + """Get hypervisor hostname + + This logic is implemented following the logic of virGetHostnameImpl + in libvirt. + """ + hypervisor_hostname = socket.gethostname() + if (hypervisor_hostname.startswith('localhost') or + '.' in hypervisor_hostname): + return hypervisor_hostname + + try: + addrinfo = socket.getaddrinfo(host=hypervisor_hostname, + port=None, + family=socket.AF_UNSPEC, + flags=socket.AI_CANONNAME) + # getaddrinfo returns a list of 5-tuples with; + # (family, type, proto, canonname, sockaddr) + if (addrinfo and addrinfo[0][3] and + not addrinfo[0][3].startswith('localhost')): + return addrinfo[0][3] + except OSError: + pass + + return hypervisor_hostname + + # TODO(bence romsics): rehome this to neutron_lib.placement.utils -def default_rp_hypervisors(hypervisors, device_mappings): +def default_rp_hypervisors(hypervisors, device_mappings, + default_hypervisor=None): """Fill config option 'resource_provider_hypervisors' with defaults. - Default hypervisor names to socket.gethostname(). Since libvirt knows - itself by the same name, the default is good for libvirt. + Default hypervisor names to socket.gethostname() unless default_hypervisor + is set. :param hypervisors: Config option 'resource_provider_hypervisors' as parsed by oslo.config, that is a dict with keys of physical devices and values of hypervisor names. :param device_mappings: Device mappings standardized to the list-valued format. + :param default_hypervisor: Default hypervisor hostname. """ - default_hypervisor = socket.gethostname() + _default_hypervisor = default_hypervisor or get_hypervisor_hostname() + rv = {} for _physnet, devices in device_mappings.items(): for device in devices: if device in hypervisors: rv[device] = hypervisors[device] else: - rv[device] = default_hypervisor + rv[device] = _default_hypervisor return rv diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/dhcp/agent.py neutron-16.4.2/neutron/agent/dhcp/agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/dhcp/agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/dhcp/agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,6 +26,7 @@ from neutron_lib import rpc as n_rpc from oslo_concurrency import lockutils from oslo_config import cfg +from oslo_log import helpers as log_helpers from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall @@ -73,6 +74,35 @@ return wrapped +class DHCPResourceUpdate(queue.ResourceUpdate): + + def __init__(self, _id, priority, action=None, resource=None, + timestamp=None, tries=5, obj_type=None): + super().__init__(_id, priority, action=action, resource=resource, + timestamp=timestamp, tries=tries) + self.obj_type = obj_type + + def __lt__(self, other): + if other.obj_type == self.obj_type == 'port': + # NOTE(ralonsoh): both resources should have "fixed_ips" + # information. That key was added to the deleted ports in this + # patch but this code runs in the Neutron API (server). Both the + # server and the DHCP agent should be updated. + # This check could be removed in Y release. + if ('fixed_ips' not in self.resource or + 'fixed_ips' not in other.resource): + return super().__lt__(other) + + self_ips = set(str(fixed_ip['ip_address']) for + fixed_ip in self.resource['fixed_ips']) + other_ips = set(str(fixed_ip['ip_address']) for + fixed_ip in other.resource['fixed_ips']) + if self_ips & other_ips: + return self.timestamp < other.timestamp + + return super().__lt__(other) + + class DhcpAgent(manager.Manager): """DHCP agent service manager. @@ -151,10 +181,15 @@ def _reload_bulk_allocations(self): while True: - for network_id in self._network_bulk_allocations.keys(): + # No need to lock access to _network_bulk_allocations because + # greenthreads multi-task co-operatively. + to_reload = self._network_bulk_allocations.keys() + self._network_bulk_allocations = {} + + for network_id in to_reload: network = self.cache.get_network_by_id(network_id) - self.call_driver('bulk_reload_allocations', network) - del self._network_bulk_allocations[network_id] + if network is not None: + self.call_driver('bulk_reload_allocations', network) eventlet.greenthread.sleep(self.conf.bulk_reload_interval) def call_driver(self, action, network, **action_kwargs): @@ -423,28 +458,28 @@ def network_create_end(self, context, payload): """Handle the network.create.end notification event.""" - update = queue.ResourceUpdate(payload['network']['id'], - payload.get('priority', - DEFAULT_PRIORITY), - action='_network_create', - resource=payload) + update = DHCPResourceUpdate(payload['network']['id'], + payload.get('priority', DEFAULT_PRIORITY), + action='_network_create', + resource=payload, obj_type='network') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _network_create(self, payload): network_id = payload['network']['id'] self.enable_dhcp_helper(network_id) def network_update_end(self, context, payload): """Handle the network.update.end notification event.""" - update = queue.ResourceUpdate(payload['network']['id'], - payload.get('priority', - DEFAULT_PRIORITY), - action='_network_update', - resource=payload) + update = DHCPResourceUpdate(payload['network']['id'], + payload.get('priority', DEFAULT_PRIORITY), + action='_network_update', + resource=payload, obj_type='network') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _network_update(self, payload): network_id = payload['network']['id'] if payload['network']['admin_state_up']: @@ -454,28 +489,28 @@ def network_delete_end(self, context, payload): """Handle the network.delete.end notification event.""" - update = queue.ResourceUpdate(payload['network_id'], - payload.get('priority', - DEFAULT_PRIORITY), - action='_network_delete', - resource=payload) + update = DHCPResourceUpdate(payload['network_id'], + payload.get('priority', DEFAULT_PRIORITY), + action='_network_delete', + resource=payload, obj_type='network') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _network_delete(self, payload): network_id = payload['network_id'] self.disable_dhcp_helper(network_id) def subnet_update_end(self, context, payload): """Handle the subnet.update.end notification event.""" - update = queue.ResourceUpdate(payload['subnet']['network_id'], - payload.get('priority', - DEFAULT_PRIORITY), - action='_subnet_update', - resource=payload) + update = DHCPResourceUpdate(payload['subnet']['network_id'], + payload.get('priority', DEFAULT_PRIORITY), + action='_subnet_update', + resource=payload, obj_type='subnet') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _subnet_update(self, payload): network_id = payload['subnet']['network_id'] self.refresh_dhcp_helper(network_id) @@ -506,14 +541,14 @@ network_id = self._get_network_lock_id(payload) if not network_id: return - update = queue.ResourceUpdate(network_id, - payload.get('priority', - DEFAULT_PRIORITY), - action='_subnet_delete', - resource=payload) + update = DHCPResourceUpdate(network_id, + payload.get('priority', DEFAULT_PRIORITY), + action='_subnet_delete', + resource=payload, obj_type='subnet') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _subnet_delete(self, payload): network_id = self._get_network_lock_id(payload) if not network_id: @@ -550,17 +585,19 @@ def port_update_end(self, context, payload): """Handle the port.update.end notification event.""" updated_port = dhcp.DictModel(payload['port']) + if not dhcp.port_requires_dhcp_configuration(updated_port): + return if self.cache.is_port_message_stale(updated_port): LOG.debug("Discarding stale port update: %s", updated_port) return - update = queue.ResourceUpdate(updated_port.network_id, - payload.get('priority', - DEFAULT_PRIORITY), - action='_port_update', - resource=updated_port) + update = DHCPResourceUpdate(updated_port.network_id, + payload.get('priority', DEFAULT_PRIORITY), + action='_port_update', + resource=updated_port, obj_type='port') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _port_update(self, updated_port): if self.cache.is_port_message_stale(updated_port): LOG.debug("Discarding stale port update: %s", updated_port) @@ -572,7 +609,10 @@ self.reload_allocations(updated_port, network, prio=True) def reload_allocations(self, port, network, prio=False): - LOG.info("Trigger reload_allocations for port %s", port) + LOG.info("Trigger reload_allocations for port %s on network %s", + port, network) + if not dhcp.port_requires_dhcp_configuration(port): + return driver_action = 'reload_allocations' if self._is_port_on_this_agent(port): orig = self.cache.get_port_by_id(port['id']) @@ -611,14 +651,16 @@ def port_create_end(self, context, payload): """Handle the port.create.end notification event.""" created_port = dhcp.DictModel(payload['port']) - update = queue.ResourceUpdate(created_port.network_id, - payload.get('priority', - DEFAULT_PRIORITY), - action='_port_create', - resource=created_port) + if not dhcp.port_requires_dhcp_configuration(created_port): + return + update = DHCPResourceUpdate(created_port.network_id, + payload.get('priority', DEFAULT_PRIORITY), + action='_port_create', + resource=created_port, obj_type='port') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _port_create(self, created_port): network = self.cache.get_network_by_id(created_port.network_id) if not network: @@ -633,9 +675,18 @@ if (new_ips.intersection(cached_ips) and (created_port['id'] != port_cached['id'] or created_port['mac_address'] != port_cached['mac_address'])): - self.schedule_resync("Duplicate IP addresses found, " - "DHCP cache is out of sync", - created_port.network_id) + resync_reason = ( + "Duplicate IP addresses found, " + "Port in cache: {cache_port_id}, " + "Created port: {port_id}, " + "IPs in cache: {cached_ips}, " + "new IPs: {new_ips}." + "DHCP cache is out of sync").format( + cache_port_id=port_cached['id'], + port_id=created_port['id'], + cached_ips=cached_ips, + new_ips=new_ips) + self.schedule_resync(resync_reason, created_port.network_id) return self.reload_allocations(created_port, network, prio=True) @@ -644,14 +695,14 @@ network_id = self._get_network_lock_id(payload) if not network_id: return - update = queue.ResourceUpdate(network_id, - payload.get('priority', - DEFAULT_PRIORITY), - action='_port_delete', - resource=payload) + update = DHCPResourceUpdate(network_id, + payload.get('priority', DEFAULT_PRIORITY), + action='_port_delete', + resource=payload, obj_type='port') self._queue.add(update) @_wait_if_syncing + @log_helpers.log_method_call def _port_delete(self, payload): network_id = self._get_network_lock_id(payload) if not network_id: @@ -952,11 +1003,12 @@ "self._deleted_ports" and "self._deleted_ports_ts". """ timestamp_min = timeutils.utcnow_ts() - DELETED_PORT_MAX_AGE - idx = None - for idx, (ts, port_id) in enumerate(self._deleted_ports_ts): + idx = 0 + for (ts, port_id) in self._deleted_ports_ts: if ts > timestamp_min: break self._deleted_ports.remove(port_id) + idx += 1 if idx: self._deleted_ports_ts = self._deleted_ports_ts[idx:] diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/firewall.py neutron-16.4.2/neutron/agent/firewall.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/firewall.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/firewall.py 2021-11-12 13:56:42.000000000 +0000 @@ -36,8 +36,11 @@ # List of ICMPv6 types that should be permitted (egress) by default. ICMPV6_ALLOWED_EGRESS_TYPES = (n_const.ICMPV6_TYPE_MLD_QUERY, n_const.ICMPV6_TYPE_RS, - n_const.ICMPV6_TYPE_NS, - n_const.ICMPV6_TYPE_NA) + n_const.ICMPV6_TYPE_NS) + +# List of ICMPv6 types that should be permitted depending on payload content +# to avoid spoofing (egress) by default. +ICMPV6_RESTRICTED_EGRESS_TYPES = (n_const.ICMPV6_TYPE_NA, ) def port_sec_enabled(port): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/agent.py neutron-16.4.2/neutron/agent/l3/agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -78,6 +78,7 @@ ADD_UPDATE_ROUTER = 3 ADD_UPDATE_RELATED_ROUTER = 4 PD_UPDATE = 5 +UPDATE_NETWORK = 6 RELATED_ACTION_MAP = {DELETE_ROUTER: DELETE_RELATED_ROUTER, ADD_UPDATE_ROUTER: ADD_UPDATE_RELATED_ROUTER} @@ -450,7 +451,6 @@ if router.get('ha'): features.append('ha') - kwargs['state_change_callback'] = self.enqueue_state_change if router.get('distributed') and router.get('ha'): # Case 1: If the router contains information about the HA interface @@ -465,7 +465,6 @@ if (not router.get(lib_const.HA_INTERFACE_KEY) or self.conf.agent_mode != lib_const.L3_AGENT_MODE_DVR_SNAT): features.remove('ha') - kwargs.pop('state_change_callback') return self.router_factory.create(features, **kwargs) @@ -595,15 +594,28 @@ def network_update(self, context, **kwargs): network_id = kwargs['network']['id'] + LOG.debug("Got network %s update", network_id) for ri in self.router_info.values(): - ports = list(ri.internal_ports) - if ri.ex_gw_port: - ports.append(ri.ex_gw_port) - port_belongs = lambda p: p['network_id'] == network_id - if any(port_belongs(p) for p in ports): - update = queue.ResourceUpdate( - ri.router_id, PRIORITY_SYNC_ROUTERS_TASK) - self._resync_router(update) + update = queue.ResourceUpdate(ri.router_id, + PRIORITY_RPC, + action=UPDATE_NETWORK, + resource=network_id) + self._queue.add(update) + + def _process_network_update(self, router_id, network_id): + ri = self.router_info.get(router_id) + if not ri: + return + LOG.debug("Checking if router %s is plugged to the network %s", + ri, network_id) + ports = list(ri.internal_ports) + if ri.ex_gw_port: + ports.append(ri.ex_gw_port) + port_belongs = lambda p: p['network_id'] == network_id + if any(port_belongs(p) for p in ports): + update = queue.ResourceUpdate( + ri.router_id, PRIORITY_SYNC_ROUTERS_TASK) + self._resync_router(update) def _process_router_if_compatible(self, router): # Either ex_net_id or handle_internal_only_routers must be set @@ -626,6 +638,23 @@ def _process_updated_router(self, router): ri = self.router_info[router['id']] + + router_ha = router.get('ha') + router_distributed = router.get('distributed') + if ((router_ha is not None and ri.router.get('ha') != router_ha) or + (router_distributed is not None and + ri.router.get('distributed') != router_distributed)): + LOG.warning('Type of the router %(id)s changed. ' + 'Old type: ha=%(old_ha)s; distributed=%(old_dvr)s; ' + 'New type: ha=%(new_ha)s; distributed=%(new_dvr)s', + {'id': router['id'], + 'old_ha': ri.router.get('ha'), + 'old_dvr': ri.router.get('distributed'), + 'new_ha': router.get('ha'), + 'new_dvr': router.get('distributed')}) + ri = self._create_router(router['id'], router) + self.router_info[router['id']] = ri + is_dvr_snat_agent = (self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT) is_dvr_only_agent = (self.conf.agent_mode in @@ -666,81 +695,91 @@ if router_update.hit_retry_limit(): LOG.warning("Hit retry limit with router update for %s, action %s", router_update.id, router_update.action) - if router_update.action != DELETE_ROUTER: - LOG.debug("Deleting router %s", router_update.id) - self._safe_router_removed(router_update.id) return router_update.timestamp = timeutils.utcnow() router_update.priority = priority router_update.resource = None # Force the agent to resync the router self._queue.add(router_update) - def _process_router_update(self): + def _process_update(self): if self._exiting: return for rp, update in self._queue.each_update_to_next_resource(): - LOG.info("Starting router update for %s, action %s, priority %s, " + LOG.info("Starting processing update %s, action %s, priority %s, " "update_id %s. Wait time elapsed: %.3f", update.id, update.action, update.priority, update.update_id, update.time_elapsed_since_create) - if update.action == PD_UPDATE: - self.pd.process_prefix_update() - LOG.info("Finished a router update for %s IPv6 PD, " - "update_id. %s. Time elapsed: %.3f", - update.id, update.update_id, - update.time_elapsed_since_start) - continue + if update.action == UPDATE_NETWORK: + self._process_network_update( + router_id=update.id, + network_id=update.resource) + else: + self._process_router_update(rp, update) - routers = [update.resource] if update.resource else [] + def _process_router_update(self, rp, update): + LOG.info("Starting router update for %s, action %s, priority %s, " + "update_id %s. Wait time elapsed: %.3f", + update.id, update.action, update.priority, + update.update_id, + update.time_elapsed_since_create) + if update.action == PD_UPDATE: + self.pd.process_prefix_update() + LOG.info("Finished a router update for %s IPv6 PD, " + "update_id. %s. Time elapsed: %.3f", + update.id, update.update_id, + update.time_elapsed_since_start) + return - not_delete_no_routers = (update.action != DELETE_ROUTER and - not routers) - related_action = update.action in (DELETE_RELATED_ROUTER, - ADD_UPDATE_RELATED_ROUTER) - if not_delete_no_routers or related_action: - try: - update.timestamp = timeutils.utcnow() - routers = self.plugin_rpc.get_routers(self.context, - [update.id]) - except Exception: - msg = "Failed to fetch router information for '%s'" - LOG.exception(msg, update.id) - self._resync_router(update) - continue - - # For a related action, verify the router is still hosted here, - # since it could have just been deleted and we don't want to - # add it back. - if related_action: - routers = [r for r in routers if r['id'] == update.id] - - if not routers: - removed = self._safe_router_removed(update.id) - if not removed: - self._resync_router(update) - else: - # need to update timestamp of removed router in case - # there are older events for the same router in the - # processing queue (like events from fullsync) in order to - # prevent deleted router re-creation - rp.fetched_and_processed(update.timestamp) - LOG.info("Finished a router update for %s, update_id %s. " - "Time elapsed: %.3f", - update.id, update.update_id, - update.time_elapsed_since_start) - continue + routers = [update.resource] if update.resource else [] - if not self._process_routers_if_compatible(routers, update): + not_delete_no_routers = (update.action != DELETE_ROUTER and + not routers) + related_action = update.action in (DELETE_RELATED_ROUTER, + ADD_UPDATE_RELATED_ROUTER) + if not_delete_no_routers or related_action: + try: + update.timestamp = timeutils.utcnow() + routers = self.plugin_rpc.get_routers(self.context, + [update.id]) + except Exception: + msg = "Failed to fetch router information for '%s'" + LOG.exception(msg, update.id) self._resync_router(update) - continue + return - rp.fetched_and_processed(update.timestamp) - LOG.info("Finished a router update for %s, update_id %s. " + # For a related action, verify the router is still hosted here, + # since it could have just been deleted and we don't want to + # add it back. + if related_action: + routers = [r for r in routers if r['id'] == update.id] + + if not routers: + removed = self._safe_router_removed(update.id) + if not removed: + self._resync_router(update) + else: + # need to update timestamp of removed router in case + # there are older events for the same router in the + # processing queue (like events from fullsync) in order to + # prevent deleted router re-creation + rp.fetched_and_processed(update.timestamp) + LOG.info("Finished a router delete for %s, update_id %s. " "Time elapsed: %.3f", update.id, update.update_id, update.time_elapsed_since_start) + return + + if not self._process_routers_if_compatible(routers, update): + self._resync_router(update) + return + + rp.fetched_and_processed(update.timestamp) + LOG.info("Finished a router update for %s, update_id %s. " + "Time elapsed: %.3f", + update.id, update.update_id, + update.time_elapsed_since_start) def _process_routers_if_compatible(self, routers, update): process_result = True @@ -789,7 +828,7 @@ def _process_routers_loop(self): LOG.debug("Starting _process_routers_loop") while not self._exiting: - self._pool.spawn_n(self._process_router_update) + self._pool.spawn_n(self._process_update) # NOTE(kevinbenton): this is set to 1 second because the actual interval # is controlled by a FixedIntervalLoopingCall in neutron/service.py that @@ -935,7 +974,8 @@ 'handle_internal_only_routers': self.conf.handle_internal_only_routers, 'interface_driver': self.conf.interface_driver, - 'log_agent_heartbeats': self.conf.AGENT.log_agent_heartbeats}, + 'log_agent_heartbeats': self.conf.AGENT.log_agent_heartbeats, + 'extensions': self.l3_ext_manager.names()}, 'start_flag': True, 'agent_type': lib_const.AGENT_TYPE_L3} report_interval = self.conf.AGENT.report_interval diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_edge_ha_router.py neutron-16.4.2/neutron/agent/l3/dvr_edge_ha_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_edge_ha_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/dvr_edge_ha_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,10 +14,14 @@ # under the License. from neutron_lib import constants +from oslo_log import log as logging from neutron.agent.l3 import dvr_edge_router from neutron.agent.l3 import ha_router from neutron.agent.l3 import router_info +from neutron.common import utils as common_utils + +LOG = logging.getLogger(__name__) class DvrEdgeHaRouter(dvr_edge_router.DvrEdgeRouter, @@ -57,6 +61,22 @@ self._get_snat_int_device_name, constants.SNAT_INT_DEV_PREFIX) + def internal_network_updated(self, port): + interface_name = self.get_internal_device_name(port['id']) + ip_cidrs = common_utils.fixed_ip_cidrs(port['fixed_ips']) + mtu = port['mtu'] + self.driver.set_mtu(interface_name, mtu, namespace=self.ns_name, + prefix=router_info.INTERNAL_DEV_PREFIX) + self._clear_vips(interface_name) + # NOTE(slaweq): qr- interface is not in ha_namespace but in qrouter + # namespace in case of dvr ha ruter + self._disable_ipv6_addressing_on_interface( + interface_name, namespace=self.ns_name) + for ip_cidr in ip_cidrs: + self._add_vip(ip_cidr, interface_name) + + self._set_snat_interfce_mtu(port) + def add_centralized_floatingip(self, fip, fip_cidr): interface_name = self.get_snat_external_device_interface_name( self.get_ex_gw_port()) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_edge_router.py neutron-16.4.2/neutron/agent/l3/dvr_edge_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_edge_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/dvr_edge_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -71,8 +71,10 @@ if self.snat_namespace.exists(): LOG.debug("SNAT was rescheduled to host %s. Clearing snat " "namespace.", self.router.get('gw_port_host')) - return self.external_gateway_removed( - ex_gw_port, interface_name) + self.driver.unplug(interface_name, + namespace=self.snat_namespace.name, + prefix=router.EXTERNAL_DEV_PREFIX) + self.snat_namespace.delete() return if not self.snat_namespace.exists(): @@ -127,6 +129,24 @@ lib_constants.SNAT_INT_DEV_PREFIX, mtu=sn_port.get('mtu')) + def _set_snat_interfce_mtu(self, port): + if not self._is_this_snat_host(): + return + + sn_port = self.get_snat_port_for_internal_port(port) + if not sn_port: + return + + ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(self.router['id']) + interface_name = self._get_snat_int_device_name(sn_port['id']) + self.driver.set_mtu(interface_name, port['mtu'], namespace=ns_name, + prefix=lib_constants.SNAT_INT_DEV_PREFIX) + + def internal_network_updated(self, port): + super(DvrEdgeRouter, self).internal_network_updated(port) + if port: + self._set_snat_interfce_mtu(port) + def _dvr_internal_network_removed(self, port): super(DvrEdgeRouter, self)._dvr_internal_network_removed(port) @@ -178,8 +198,8 @@ # TODO(mlavalle): in the near future, this method should contain the # code in the L3 agent that creates a gateway for a dvr. The first step # is to move the creation of the snat namespace here - self.snat_namespace.create() - return self.snat_namespace + if self._is_this_snat_host(): + self.snat_namespace.create() def _get_snat_int_device_name(self, port_id): long_name = lib_constants.SNAT_INT_DEV_PREFIX + port_id @@ -373,7 +393,10 @@ def process_floating_ip_nat_rules(self): if self._is_this_snat_host(): - self.process_floating_ip_nat_rules_for_centralized_floatingip() + if not self.snat_iptables_manager: + LOG.debug("DVR router: no snat rules to be handled") + else: + self.process_floating_ip_nat_rules_for_centralized_floatingip() # Cover mixed dvr_snat and compute node, aka a dvr_snat node has both # centralized and distributed floating IPs. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_fip_ns.py neutron-16.4.2/neutron/agent/l3/dvr_fip_ns.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_fip_ns.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/dvr_fip_ns.py 2021-11-12 13:56:42.000000000 +0000 @@ -101,6 +101,9 @@ self._subscribers.discard(external_net_id) return not self.has_subscribers() + def lookup_rule_priority(self, floating_ip): + return self._rule_priorities.lookup(floating_ip) + def allocate_rule_priority(self, floating_ip): return self._rule_priorities.allocate(floating_ip) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_local_router.py neutron-16.4.2/neutron/agent/l3/dvr_local_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/dvr_local_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/dvr_local_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -46,6 +46,63 @@ self.rtr_fip_connect = False self.fip_ns = None self._pending_arp_set = set() + self._load_used_fip_information() + + def _load_used_fip_information(self): + """Load FIP from the FipRulePriorityAllocator state file. + + If, for any reason, the FIP is not stored in the state file, this + method reads the namespace "ip rule" list and search for the + corresponding fixed IP of the FIP. If present, this "ip rule" is + (1) deleted, (2) a new rule priority is allocated and (3) the "ip rule" + is written again with the new assigned priority. + + At the end of the method, all existing "ip rule" registers in + FIP_RT_TBL table (where FIP rules are stored) that don't match with + any register memoized in self._rule_priorities is deleted. + """ + ex_gw_port = self.get_ex_gw_port() + if not ex_gw_port: + return + + fip_ns = self.agent.get_fip_ns(ex_gw_port['network_id']) + for fip in self.get_floating_ips(): + floating_ip = fip['floating_ip_address'] + fixed_ip = fip['fixed_ip_address'] + if not fixed_ip: + continue + + rule_pr = fip_ns.lookup_rule_priority(floating_ip) + if rule_pr: + self.floating_ips_dict[floating_ip] = (fixed_ip, rule_pr) + continue + + rule_pr = fip_ns.allocate_rule_priority(floating_ip) + ip_lib.add_ip_rule(self.ns_name, fixed_ip, + table=dvr_fip_ns.FIP_RT_TBL, + priority=rule_pr) + self.floating_ips_dict[floating_ip] = (fixed_ip, rule_pr) + + self._cleanup_unused_fip_ip_rules() + + def _cleanup_unused_fip_ip_rules(self): + if not self.router_namespace.exists(): + # It could be a new router, thus the namespace is not created yet. + return + + ip_rules = ip_lib.list_ip_rules(self.ns_name, + lib_constants.IP_VERSION_4) + ip_rules = [ipr for ipr in ip_rules + if ipr['table'] == dvr_fip_ns.FIP_RT_TBL] + for ip_rule in ip_rules: + for fixed_ip, rule_pr in self.floating_ips_dict.values(): + if (ip_rule['from'] == fixed_ip and + ip_rule['priority'] == rule_pr): + break + else: + ip_lib.delete_ip_rule(self.ns_name, ip_rule['from'], + table=dvr_fip_ns.FIP_RT_TBL, + priority=ip_rule['priority']) def migrate_centralized_floating_ip(self, fip, interface_name, device): # Remove the centralized fip first and then add fip to the host @@ -160,7 +217,11 @@ table=dvr_fip_ns.FIP_RT_TBL, priority=int(str(rule_pr))) self.fip_ns.deallocate_rule_priority(floating_ip) - # TODO(rajeev): Handle else case - exception/log? + else: + LOG.error('Floating IP %s not stored in this agent. Because of ' + 'the initilization method "_load_used_fip_information", ' + 'all floating IPs should be memoized in the local ' + 'memory.', floating_ip) def floating_ip_removed_dist(self, fip_cidr): """Remove floating IP from FIP namespace.""" @@ -300,6 +361,32 @@ self._update_arp_entry(fixed_ip['ip_address'], p['mac_address'], subnet_id, + 'add', + device=device, + device_exists=device_exists) + for allowed_address_pair in p.get('allowed_address_pairs', []): + if ('/' not in str(allowed_address_pair['ip_address']) or + common_utils.is_cidr_host( + allowed_address_pair['ip_address'])): + ip_address = common_utils.cidr_to_ip( + allowed_address_pair['ip_address']) + self._update_arp_entry( + ip_address, + allowed_address_pair['mac_address'], + subnet_id, + 'add', + device=device, + device_exists=device_exists) + + # subnet_ports does not have snat port if the port is still unbound + # by the time this function is called. So ensure to add arp entry + # for snat port if port details are updated in router info. + for p in self.get_snat_interfaces(): + for fixed_ip in p['fixed_ips']: + if fixed_ip['subnet_id'] == subnet_id: + self._update_arp_entry(fixed_ip['ip_address'], + p['mac_address'], + subnet_id, 'add', device=device, device_exists=device_exists) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/extensions/port_forwarding.py neutron-16.4.2/neutron/agent/l3/extensions/port_forwarding.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/extensions/port_forwarding.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/extensions/port_forwarding.py 2021-11-12 13:56:42.000000000 +0000 @@ -72,11 +72,11 @@ if not self.managed_port_forwardings.get(port_forwarding.id): continue self.managed_port_forwardings.pop(port_forwarding.id) - self.fip_port_forwarding[port_forwarding.floatingip_id].remove( + self.fip_port_forwarding[port_forwarding.floatingip_id].discard( port_forwarding.id) if not self.fip_port_forwarding[port_forwarding.floatingip_id]: self.fip_port_forwarding.pop(port_forwarding.floatingip_id) - self.router_fip_mapping[port_forwarding.router_id].remove( + self.router_fip_mapping[port_forwarding.router_id].discard( port_forwarding.floatingip_id) if not self.router_fip_mapping[port_forwarding.router_id]: del self.router_fip_mapping[port_forwarding.router_id] @@ -88,7 +88,7 @@ @lockutils.synchronized('port-forwarding-cache') def clear_by_fip(self, fip_id, router_id): - self.router_fip_mapping[router_id].remove(fip_id) + self.router_fip_mapping[router_id].discard(fip_id) if len(self.router_fip_mapping[router_id]) == 0: del self.router_fip_mapping[router_id] for pf_id in self.fip_port_forwarding[fip_id]: @@ -100,6 +100,14 @@ old_pf = self.managed_port_forwardings.get(new_pf.id) return old_pf != new_pf + @lockutils.synchronized('port-forwarding-cache') + def clean_port_forwardings_by_router_id(self, router_id): + router_fips = self.router_fip_mapping.pop(router_id, []) + for fip_id in router_fips: + pf_ids = self.fip_port_forwarding.pop(fip_id, []) + for pf_id in pf_ids: + self.managed_port_forwardings.pop(pf_id, None) + class PortForwardingAgentExtension(l3_extension.L3AgentExtension): SUPPORTED_RESOURCE_TYPES = [resources.PORTFORWARDING] @@ -169,7 +177,7 @@ iptables_manager.ipv4['nat'].add_chain(chain) iptables_manager.ipv4['nat'].add_rule(chain, rule, tag=rule_tag) - @coordination.synchronized('port-forwarding-{namespace}') + @coordination.synchronized('router-lock-ns-{namespace}') def _process_create(self, port_forwardings, ri, interface_name, namespace, iptables_manager): if not port_forwardings: @@ -301,7 +309,7 @@ context, [port_forwarding], ri, interface_name, namespace, iptables_manager) - @coordination.synchronized('port-forwarding-{namespace}') + @coordination.synchronized('router-lock-ns-{namespace}') def _process_update(self, port_forwardings, iptables_manager, interface_name, namespace): if not port_forwardings: @@ -326,7 +334,7 @@ iptables_manager.apply() self._store_local(port_forwardings, events.UPDATED) - @coordination.synchronized('port-forwarding-{namespace}') + @coordination.synchronized('router-lock-ns-{namespace}') def _process_delete(self, context, port_forwardings, ri, interface_name, namespace, iptables_manager): if not port_forwardings: @@ -344,8 +352,8 @@ iptables_manager.apply() fip_id_cidrs = set([(pf.floatingip_id, - str(pf.floating_ip_address)) for pf in - port_forwardings]) + str(netaddr.IPNetwork(pf.floating_ip_address))) + for pf in port_forwardings]) self._sync_and_remove_fip(context, fip_id_cidrs, device, ri) self._store_local(port_forwardings, events.DELETED) @@ -458,7 +466,7 @@ :param context: RPC context. :param data: Router data. """ - pass + self.mapping.clean_port_forwardings_by_router_id(data['id']) def ha_state_change(self, context, data): pass diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/fip_rule_priority_allocator.py neutron-16.4.2/neutron/agent/l3/fip_rule_priority_allocator.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/fip_rule_priority_allocator.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/fip_rule_priority_allocator.py 2021-11-12 13:56:42.000000000 +0000 @@ -31,6 +31,9 @@ else: return False + def __int__(self): + return int(self.index) + class FipRulePriorityAllocator(ItemAllocator): """Manages allocation of floating ips rule priorities. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/ha_router.py neutron-16.4.2/neutron/agent/l3/ha_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/ha_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/ha_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -30,6 +30,7 @@ from neutron.common import utils as common_utils from neutron.extensions import revisions from neutron.extensions import timestamp +from neutron.ipam import utils as ipam_utils LOG = logging.getLogger(__name__) HA_DEV_PREFIX = 'ha-' @@ -65,12 +66,11 @@ class HaRouter(router.RouterInfo): - def __init__(self, state_change_callback, *args, **kwargs): + def __init__(self, *args, **kwargs): super(HaRouter, self).__init__(*args, **kwargs) self.ha_port = None self.keepalived_manager = None - self.state_change_callback = state_change_callback self._ha_state = None self._ha_state_path = None @@ -150,7 +150,6 @@ self._init_keepalived_manager(process_monitor) self._check_and_set_real_state() self.ha_network_added() - self.update_initial_state(self.state_change_callback) self.spawn_state_change_monitor(process_monitor) def _init_keepalived_manager(self, process_monitor): @@ -199,7 +198,10 @@ return self.keepalived_manager.disable() conf_dir = self.keepalived_manager.get_conf_dir() - shutil.rmtree(conf_dir) + try: + shutil.rmtree(conf_dir) + except FileNotFoundError: + pass def _get_keepalived_instance(self): return self.keepalived_manager.config.get_instance(self.ha_vr_id) @@ -271,6 +273,14 @@ default_gw_rts = [] instance = self._get_keepalived_instance() + for subnet in ex_gw_port.get('subnets', []): + is_gateway_not_in_subnet = (subnet['gateway_ip'] and + not ipam_utils.check_subnet_ip( + subnet['cidr'], + subnet['gateway_ip'])) + if is_gateway_not_in_subnet: + default_gw_rts.append(keepalived.KeepalivedVirtualRoute( + subnet['gateway_ip'], None, interface_name, scope='link')) for gw_ip in gateway_ips: # TODO(Carl) This is repeated everywhere. A method would # be nice. @@ -305,21 +315,23 @@ return False return True - def _disable_ipv6_addressing_on_interface(self, interface_name): + def _disable_ipv6_addressing_on_interface(self, interface_name, + namespace=None): """Disable IPv6 link local addressing on the device and add it as a VIP to keepalived. This means that the IPv6 link local address will only be present on the master. """ - device = ip_lib.IPDevice(interface_name, namespace=self.ha_namespace) + namespace = namespace or self.ha_namespace + device = ip_lib.IPDevice(interface_name, namespace=namespace) ipv6_lladdr = ip_lib.get_ipv6_lladdr(device.link.address) if self._should_delete_ipv6_lladdr(ipv6_lladdr): - self.driver.configure_ipv6_ra(self.ha_namespace, interface_name, + self.driver.configure_ipv6_ra(namespace, interface_name, n_consts.ACCEPT_RA_DISABLED) device.addr.flush(n_consts.IP_VERSION_6) else: self.driver.configure_ipv6_ra( - self.ha_namespace, interface_name, + namespace, interface_name, n_consts.ACCEPT_RA_WITHOUT_FORWARDING) self._remove_vip(ipv6_lladdr) @@ -342,7 +354,10 @@ if device.addr.list(to=to): super(HaRouter, self).remove_floating_ip(device, ip_cidr) - def internal_network_updated(self, interface_name, ip_cidrs, mtu): + def internal_network_updated(self, port): + interface_name = self.get_internal_device_name(port['id']) + ip_cidrs = common_utils.fixed_ip_cidrs(port['fixed_ips']) + mtu = port['mtu'] self.driver.set_mtu(interface_name, mtu, namespace=self.ns_name, prefix=router.INTERNAL_DEV_PREFIX) self._clear_vips(interface_name) @@ -432,15 +447,6 @@ except common_utils.WaitTimeout: pm.disable(sig=str(int(signal.SIGKILL))) - def update_initial_state(self, callback): - addresses = ip_lib.get_devices_with_ip(self.ha_namespace, - name=self.get_ha_device_name()) - cidrs = (address['cidr'] for address in addresses) - ha_cidr = self._get_primary_vip() - state = 'master' if ha_cidr in cidrs else 'backup' - self.ha_state = state - callback(self.router_id, state) - @staticmethod def _gateway_ports_equal(port1, port2): def _get_filtered_dict(d, ignore): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/keepalived_state_change.py neutron-16.4.2/neutron/agent/l3/keepalived_state_change.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/keepalived_state_change.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/keepalived_state_change.py 2021-11-12 13:56:42.000000000 +0000 @@ -28,11 +28,13 @@ from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as agent_utils from neutron.common import config +from neutron.common import utils as common_utils from neutron.conf.agent.l3 import keepalived from neutron import privileged LOG = logging.getLogger(__name__) +INITIAL_STATE_READ_TIMEOUT = 10 class KeepalivedUnixDomainConnection(agent_utils.UnixDomainHTTPConnection): @@ -57,10 +59,24 @@ self.event_stop = threading.Event() self.event_started = threading.Event() self.queue = queue.Queue() + self._initial_state = None super(MonitorDaemon, self).__init__(pidfile, uuid=router_id, user=user, group=group) + @property + def initial_state(self): + return self._initial_state + + @initial_state.setter + def initial_state(self, state): + if not self._initial_state: + LOG.debug('Initial status of router %s is %s', self.router_id, + state) + self._initial_state = state + def run(self): + self._thread_initial_state = threading.Thread( + target=self.handle_initial_state) self._thread_ip_monitor = threading.Thread( target=ip_lib.ip_monitor, args=(self.namespace, self.queue, self.event_stop, @@ -68,9 +84,19 @@ self._thread_read_queue = threading.Thread( target=self.read_queue, args=(self.queue, self.event_stop, self.event_started)) + self._thread_initial_state.start() self._thread_ip_monitor.start() self._thread_read_queue.start() - self.handle_initial_state() + + # NOTE(ralonsoh): if the initial status is not read in a defined + # timeout, "backup" state is set. + self._thread_initial_state.join(timeout=INITIAL_STATE_READ_TIMEOUT) + if not self.initial_state: + LOG.warning('Timeout reading the initial status of router %s, ' + 'state is set to "backup".', self.router_id) + self.write_state_change('backup') + self.notify_agent('backup') + self._thread_read_queue.join() def read_queue(self, _queue, event_stop, event_started): @@ -100,21 +126,26 @@ def handle_initial_state(self): try: state = 'backup' - ip = ip_lib.IPDevice(self.interface, self.namespace) - for address in ip.addr.list(): - if address.get('cidr') == self.cidr: + cidr = common_utils.ip_to_cidr(self.cidr) + # NOTE(ralonsoh): "get_devices_with_ip" without passing an IP + # address performs one single pyroute2 command. Because the number + # of interfaces in the namespace is reduced, this is faster. + for address in ip_lib.get_devices_with_ip(self.namespace): + if (address['name'] == self.interface and + address['cidr'] == cidr): state = 'master' - self.write_state_change(state) - self.notify_agent(state) break - LOG.debug('Initial status of router %s is %s', - self.router_id, state) + if not self.initial_state: + self.write_state_change(state) + self.notify_agent(state) except Exception: - LOG.exception('Failed to get initial status of router %s', - self.router_id) + if not self.initial_state: + LOG.exception('Failed to get initial status of router %s', + self.router_id) def write_state_change(self, state): + self.initial_state = state with open(os.path.join( self.conf_dir, 'state'), 'w') as state_file: state_file.write(state) @@ -126,7 +157,8 @@ # the URL doesn't matter. 'http://127.0.0.1/', headers={'X-Neutron-Router-Id': self.router_id, - 'X-Neutron-State': state}, + 'X-Neutron-State': state, + 'Connection': 'close'}, connection_type=KeepalivedUnixDomainConnection) if resp.status != 200: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/router_info.py neutron-16.4.2/neutron/agent/l3/router_info.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/l3/router_info.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/l3/router_info.py 2021-11-12 13:56:42.000000000 +0000 @@ -27,6 +27,7 @@ from neutron.agent.linux import ip_lib from neutron.agent.linux import iptables_manager from neutron.agent.linux import ra +from neutron.common import coordination from neutron.common import ipv6_utils from neutron.common import utils as common_utils from neutron.ipam import utils as ipam_utils @@ -586,7 +587,10 @@ self.router_id) self.radvd.disable() - def internal_network_updated(self, interface_name, ip_cidrs, mtu): + def internal_network_updated(self, port): + interface_name = self.get_internal_device_name(port['id']) + ip_cidrs = common_utils.fixed_ip_cidrs(port['fixed_ips']) + mtu = port['mtu'] self.driver.set_mtu(interface_name, mtu, namespace=self.ns_name, prefix=INTERNAL_DEV_PREFIX) self.driver.init_router_port( @@ -624,7 +628,7 @@ for subnet in p['subnets']: if ipv6_utils.is_ipv6_pd_enabled(subnet): self.agent.pd.disable_subnet(self.router_id, subnet['id']) - del self.pd_subnets[subnet['id']] + self.pd_subnets.pop(subnet['id'], None) for p in new_ports: self.internal_network_added(p) @@ -645,12 +649,8 @@ updated_cidrs = [] for p in updated_ports: self._update_internal_ports_cache(p) - interface_name = self.get_internal_device_name(p['id']) - ip_cidrs = common_utils.fixed_ip_cidrs(p['fixed_ips']) - LOG.debug("updating internal network for port %s", p) - updated_cidrs += ip_cidrs - self.internal_network_updated( - interface_name, ip_cidrs, p['mtu']) + updated_cidrs += common_utils.fixed_ip_cidrs(p['fixed_ips']) + self.internal_network_updated(p) enable_ra = enable_ra or self._port_has_ipv6_subnet(p) # Check if there is any pd prefix update @@ -983,6 +983,7 @@ finally: self.update_fip_statuses(fip_statuses) + @coordination.synchronized('router-lock-ns-{self.ns_name}') def process_external(self): fip_statuses = {} try: @@ -1191,6 +1192,7 @@ self.get_address_scope_mark_mask(address_scope)) iptables_manager.ipv4['nat'].add_rule('snat', rule) + @coordination.synchronized('router-lock-ns-{self.ns_name}') def process_address_scope(self): with self.iptables_manager.defer_apply(): self.process_ports_address_scope_iptables() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/dhcp.py neutron-16.4.2/neutron/agent/linux/dhcp.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/dhcp.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/dhcp.py 2021-11-12 13:56:42.000000000 +0000 @@ -60,11 +60,28 @@ DNSMASQ_SERVICE_NAME = 'dnsmasq' DHCP_RELEASE_TRIES = 3 DHCP_RELEASE_TRIES_SLEEP = 0.3 +HOST_DHCPV6_TAG = 'tag:dhcpv6,' # this variable will be removed when neutron-lib is updated with this value DHCP_OPT_CLIENT_ID_NUM = 61 +def port_requires_dhcp_configuration(port): + if not getattr(port, 'device_owner', None): + # We can't check if port needs dhcp entry, so it will be better + # to create one + return True + # TODO(slaweq): define this list as a constant in neutron_lib.constants + # NOTE(slaweq): Not all port types which belongs e.g. to the routers can be + # excluded from that list. For some of them, like router interfaces used to + # plug subnet to the router should be configured in dnsmasq to provide DNS + # naming resolution. Otherwise it may slowdown e.g. traceroutes from the VM + return port.device_owner not in [ + constants.DEVICE_OWNER_ROUTER_HA_INTF, + constants.DEVICE_OWNER_FLOATINGIP, + constants.DEVICE_OWNER_DHCP] + + class DictModel(collections.abc.MutableMapping): """Convert dict into an object that provides attribute access to values.""" @@ -292,7 +309,7 @@ common_utils.wait_until_true(lambda: not self.active) if not retain_port: self._destroy_namespace_and_port() - self._remove_config_files() + self._remove_config_files() def _destroy_namespace_and_port(self): try: @@ -358,6 +375,7 @@ _ID = 'id:' _IS_DHCP_RELEASE6_SUPPORTED = None + _IS_HOST_TAG_SUPPORTED = None @classmethod def check_version(cls): @@ -461,7 +479,8 @@ cmd.append('--dhcp-option-force=option:T2,%ds' % self.conf.dhcp_rebinding_time) - cmd.append('--conf-file=%s' % self.conf.dnsmasq_config_file) + cmd.append('--conf-file=%s' % + (self.conf.dnsmasq_config_file.strip() or '/dev/null')) for server in self.conf.dnsmasq_dns_servers: cmd.append('--server=%s' % server) @@ -521,6 +540,12 @@ "will not call it again.") return self._IS_DHCP_RELEASE6_SUPPORTED + def _is_dnsmasq_host_tag_supported(self): + if self._IS_HOST_TAG_SUPPORTED is None: + self._IS_HOST_TAG_SUPPORTED = checks.dnsmasq_host_tag_support() + + return self._IS_HOST_TAG_SUPPORTED + def _release_lease(self, mac_address, ip, ip_version, client_id=None, server_id=None, iaid=None): """Release a DHCP lease.""" @@ -703,6 +728,7 @@ no_dhcp, # A flag indicating that the address doesn't need a DHCP # IP address. no_opts, # A flag indication that options shouldn't be written + tag, # A dhcp-host tag to add to the configuration if supported ) """ v6_nets = dict((subnet.id, subnet) for subnet in @@ -710,6 +736,9 @@ if subnet.ip_version == 6) for port in self.network.ports: + if not port_requires_dhcp_configuration(port): + continue + fixed_ips = self._sort_fixed_ips_for_dnsmasq(port.fixed_ips, v6_nets) # TODO(hjensas): Drop this conditional and option once distros @@ -722,10 +751,13 @@ for alloc in fixed_ips: no_dhcp = False no_opts = False + tag = '' if alloc.subnet_id in v6_nets: addr_mode = v6_nets[alloc.subnet_id].ipv6_address_mode no_dhcp = addr_mode in (constants.IPV6_SLAAC, constants.DHCPV6_STATELESS) + if self._is_dnsmasq_host_tag_supported(): + tag = HOST_DHCPV6_TAG # we don't setup anything for SLAAC. It doesn't make sense # to provide options for a client that won't use DHCP no_opts = addr_mode == constants.IPV6_SLAAC @@ -733,7 +765,7 @@ hostname, fqdn = self._get_dns_assignment(alloc.ip_address, dns_assignment) - yield (port, alloc, hostname, fqdn, no_dhcp, no_opts) + yield (port, alloc, hostname, fqdn, no_dhcp, no_opts, tag) def _get_port_extra_dhcp_opts(self, port): return getattr(port, edo_ext.EXTRADHCPOPTS, False) @@ -766,17 +798,50 @@ dhcpv4_enabled_subnet_ids = [ s.id for s in self._get_all_subnets(self.network) if s.enable_dhcp and s.ip_version == constants.IP_VERSION_4] + dhcpv6_enabled_subnet_ids = [ + s.id for s in self._get_all_subnets(self.network) + if s.enable_dhcp and s.ip_version == constants.IP_VERSION_6] + + existing_ipv6_leases = {} + if os.path.isfile(filename): + # The IPv6 leases can't be generated as their IAID is unknown. To + # not loose active leases, read the existing leases and add them to + # the generated file. + LOG.debug('Reading IPv6 leases from existing lease file.') + with open(filename) as leasefile: + for line in leasefile: + if line.startswith('duid '): + # Keep the DUID + buf.write(line) + continue + try: + ts, mac, ip, host, iaid = line.split(' ') + except ValueError: + # not the correct format for a lease, skip this line + continue + + if netaddr.valid_ipv6(ip): + existing_ipv6_leases[netaddr.IPAddress(ip)] = line + for host_tuple in self._iter_hosts(): - port, alloc, hostname, name, no_dhcp, no_opts = host_tuple - # don't write ip address which belongs to a dhcp disabled subnet - # or an IPv6 subnet. - if no_dhcp or alloc.subnet_id not in dhcpv4_enabled_subnet_ids: + port, alloc, hostname, name, no_dhcp, no_opts, tag = host_tuple + + if no_dhcp: continue - # all that matters is the mac address and IP. the hostname and - # client ID will be overwritten on the next renewal. - buf.write('%s %s %s * *\n' % - (timestamp, port.mac_address, alloc.ip_address)) + if alloc.subnet_id in dhcpv4_enabled_subnet_ids: + # all that matters is the mac address and IP. the hostname and + # client ID will be overwritten on the next renewal. + buf.write('%s %s %s * *\n' % + (timestamp, port.mac_address, alloc.ip_address)) + elif (alloc.subnet_id in dhcpv6_enabled_subnet_ids and + netaddr.IPAddress(alloc.ip_address) in existing_ipv6_leases): + # Keep the existing IPv6 lease if the port still exists and is + # still configured for DHCPv6 + buf.write( + existing_ipv6_leases[netaddr.IPAddress(alloc.ip_address)] + ) + contents = buf.getvalue() file_utils.replace_file(filename, contents) LOG.debug('Done building initial lease file %s with contents:\n%s', @@ -818,11 +883,11 @@ # NOTE(ihrachyshka): the loop should not log anything inside it, to # avoid potential performance drop when lots of hosts are dumped for host_tuple in self._iter_hosts(merge_addr6_list=True): - port, alloc, hostname, name, no_dhcp, no_opts = host_tuple + port, alloc, hostname, name, no_dhcp, no_opts, tag = host_tuple if no_dhcp: if not no_opts and self._get_port_extra_dhcp_opts(port): - buf.write('%s,%s%s\n' % ( - port.mac_address, + buf.write('%s,%s%s%s\n' % ( + port.mac_address, tag, 'set:', self._PORT_TAG_PREFIX % port.id)) continue @@ -835,21 +900,21 @@ if self._get_port_extra_dhcp_opts(port): client_id = self._get_client_id(port) if client_id and len(port.extra_dhcp_opts) > 1: - buf.write('%s,%s%s,%s,%s,%s%s\n' % - (port.mac_address, self._ID, client_id, name, - ip_address, 'set:', + buf.write('%s,%s%s%s,%s,%s,%s%s\n' % + (port.mac_address, tag, self._ID, client_id, + name, ip_address, 'set:', self._PORT_TAG_PREFIX % port.id)) elif client_id and len(port.extra_dhcp_opts) == 1: - buf.write('%s,%s%s,%s,%s\n' % - (port.mac_address, self._ID, client_id, name, - ip_address)) + buf.write('%s,%s%s%s,%s,%s\n' % + (port.mac_address, tag, self._ID, client_id, + name, ip_address)) else: - buf.write('%s,%s,%s,%s%s\n' % - (port.mac_address, name, ip_address, + buf.write('%s,%s%s,%s,%s%s\n' % + (port.mac_address, tag, name, ip_address, 'set:', self._PORT_TAG_PREFIX % port.id)) else: - buf.write('%s,%s,%s\n' % - (port.mac_address, name, ip_address)) + buf.write('%s,%s%s,%s\n' % + (port.mac_address, tag, name, ip_address)) file_utils.replace_file(filename, buf.getvalue()) LOG.debug('Done building host file %s', filename) @@ -863,6 +928,11 @@ str(DHCP_OPT_CLIENT_ID_NUM)): return opt.opt_value + @staticmethod + def _parse_ip_addresses(ip_list): + ip_list = [ip.strip('[]') for ip in ip_list] + return [ip for ip in ip_list if netutils.is_valid_ip(ip)] + def _read_hosts_file_leases(self, filename): leases = set() try: @@ -874,11 +944,14 @@ if host[1].startswith('set:'): continue if host[1].startswith(self._ID): - ip = host[3].strip('[]') + ips = self._parse_ip_addresses(host[3:]) client_id = host[1][len(self._ID):] + elif host[1].startswith('tag:'): + ips = self._parse_ip_addresses(host[3:]) else: - ip = host[2].strip('[]') - leases.add((ip, mac, client_id)) + ips = self._parse_ip_addresses(host[2:]) + for ip in ips: + leases.add((ip, mac, client_id)) except (OSError, IOError): LOG.debug('Error while reading hosts file %s', filename) return leases @@ -1045,7 +1118,7 @@ """ buf = six.StringIO() for host_tuple in self._iter_hosts(): - port, alloc, hostname, fqdn, no_dhcp, no_opts = host_tuple + port, alloc, hostname, fqdn, no_dhcp, no_opts, tag = host_tuple # It is compulsory to write the `fqdn` before the `hostname` in # order to obtain it in PTR responses. if alloc: @@ -1239,6 +1312,7 @@ def _format_option(self, ip_version, tag, option, *args): """Format DHCP option by option name or code.""" option = str(option) + option = option.split("\n", 1)[0] pattern = "(tag:(.*),)?(.*)$" matches = re.match(pattern, option) extra_tag = matches.groups()[0] @@ -1254,10 +1328,11 @@ elif not option.isdigit(): option = 'option:%s' % option if extra_tag: - tags = ('tag:' + tag, extra_tag[:-1], '%s' % option) + tags = ['tag:' + tag, extra_tag[:-1], '%s' % option] else: - tags = ('tag:' + tag, '%s' % option) - return ','.join(tags + args) + tags = ['tag:' + tag, '%s' % option] + + return ','.join(tags + [v.split("\n", 1)[0] for v in args]) @staticmethod def _convert_to_literal_addrs(ip_version, ips): @@ -1726,9 +1801,9 @@ def fill_dhcp_udp_checksums(self, namespace): """Ensure DHCP reply packets always have correct UDP checksums.""" - iptables_mgr = iptables_manager.IptablesManager(use_ipv6=True, - nat=False, - namespace=namespace) + iptables_mgr = iptables_manager.IptablesManager( + use_ipv6=netutils.is_ipv6_enabled(), nat=False, + namespace=namespace) ipv4_rule = ('-p udp -m udp --dport %d -j CHECKSUM --checksum-fill' % constants.DHCP_RESPONSE_PORT) ipv6_rule = ('-p udp -m udp --dport %d -j CHECKSUM --checksum-fill' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ethtool.py neutron-16.4.2/neutron/agent/linux/ethtool.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ethtool.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/ethtool.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,34 @@ +# Copyright 2020 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.agent.linux import ip_lib + + +class Ethtool(object): + + COMMAND = 'ethtool' + + @staticmethod + def _cmd(cmd, namespace, **kwargs): + ip_wrapper = ip_lib.IPWrapper(namespace) + return ip_wrapper.netns.execute(cmd, run_as_root=True, **kwargs) + + @classmethod + def offload(cls, device, rx, tx, namespace=None): + rx = 'on' if rx else 'off' + tx = 'on' if tx else 'off' + cmd = ['--offload', device, 'rx', rx, 'tx', tx] + cmd = [cls.COMMAND] + cmd + cls._cmd(cmd, namespace) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/external_process.py neutron-16.4.2/neutron/agent/linux/external_process.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/external_process.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/external_process.py 2021-11-12 13:56:42.000000000 +0000 @@ -113,9 +113,10 @@ utils.execute(cmd, run_as_root=self.run_as_root) # In the case of shutting down, remove the pid file if sig == '9': - fileutils.delete_if_exists(self.get_pid_file_name()) + utils.delete_if_exists(self.get_pid_file_name(), + run_as_root=self.run_as_root) elif pid: - LOG.debug('%{service}s process for %(uuid)s pid %(pid)d is stale, ' + LOG.debug('%(service)s process for %(uuid)s pid %(pid)d is stale, ' 'ignoring signal %(signal)s', {'service': self.service, 'uuid': self.uuid, 'pid': pid, 'signal': sig}) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/interface.py neutron-16.4.2/neutron/agent/linux/interface.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/interface.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/interface.py 2021-11-12 13:56:42.000000000 +0000 @@ -25,8 +25,12 @@ import six from neutron.agent.common import ovs_lib +from neutron.agent.linux import ethtool from neutron.agent.linux import ip_lib from neutron.common import utils +from neutron.conf.plugins.ml2.drivers import ovs_conf +from neutron.plugins.ml2.drivers.openvswitch.agent.common \ + import constants as ovs_const LOG = logging.getLogger(__name__) @@ -264,8 +268,9 @@ bridge=None, namespace=None, prefix=None, mtu=None, link_up=True): if not ip_lib.device_exists(device_name, namespace=namespace): - self.plug_new(network_id, port_id, device_name, mac_address, - bridge, namespace, prefix, mtu, link_up) + self._safe_plug_new( + network_id, port_id, device_name, mac_address, bridge, + namespace, prefix, mtu, link_up) else: LOG.info("Device %s already exists", device_name) if mtu: @@ -274,6 +279,22 @@ else: LOG.warning("No MTU configured for port %s", port_id) + def _safe_plug_new(self, network_id, port_id, device_name, mac_address, + bridge=None, namespace=None, prefix=None, mtu=None, link_up=True): + try: + self.plug_new( + network_id, port_id, device_name, mac_address, bridge, + namespace, prefix, mtu, link_up) + except TypeError: + LOG.warning("Interface driver's plug_new() method should now " + "accept additional optional parameter 'link_up'. " + "Usage of plug_new() method which takes from 5 to 9 " + "positional arguments is now deprecated and will not " + "be possible in W release.") + self.plug_new( + network_id, port_id, device_name, mac_address, bridge, + namespace, prefix, mtu) + @abc.abstractmethod def unplug(self, device_name, bridge=None, namespace=None, prefix=None): """Unplug the interface.""" @@ -329,6 +350,7 @@ def __init__(self, conf, **kwargs): super(OVSInterfaceDriver, self).__init__(conf, **kwargs) + ovs_conf.register_ovs_agent_opts(self.conf) if self.conf.ovs_use_veth: self.DEV_NAME_PREFIX = 'ns-' @@ -416,6 +438,11 @@ if link_up: ns_dev.link.set_up() if self.conf.ovs_use_veth: + # ovs-dpdk does not do checksum calculations for veth interface + # (bug 1832021) + if self.conf.OVS.datapath_type == ovs_const.OVS_DATAPATH_NETDEV: + ethtool.Ethtool.offload(ns_dev.name, rx=False, tx=False, + namespace=namespace) root_dev.link.set_up() def unplug(self, device_name, bridge=None, namespace=None, prefix=None): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ip_conntrack.py neutron-16.4.2/neutron/agent/linux/ip_conntrack.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ip_conntrack.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/ip_conntrack.py 2021-11-12 13:56:42.000000000 +0000 @@ -116,6 +116,7 @@ ethertype = rule.get('ethertype') protocol = rule.get('protocol') direction = rule.get('direction') + mark = rule.get('mark') cmd = ['conntrack', '-D'] if protocol is not None: # 0 is IP in /etc/protocols, but conntrack will throw an error @@ -123,6 +124,8 @@ protocol = 'ip' cmd.extend(['-p', str(protocol)]) cmd.extend(['-f', str(ethertype).lower()]) + if mark is not None: + cmd.extend(['-m', str(mark)]) cmd.append('-d' if direction == 'ingress' else '-s') cmd_ns = [] if namespace: @@ -173,10 +176,12 @@ self._process(device_info_list, rule) def delete_conntrack_state_by_remote_ips(self, device_info_list, - ethertype, remote_ips): + ethertype, remote_ips, mark=None): for direction in ['ingress', 'egress']: rule = {'ethertype': str(ethertype).lower(), 'direction': direction} + if mark: + rule['mark'] = mark self._process(device_info_list, rule, remote_ips) def _populate_initial_zone_map(self): @@ -254,3 +259,21 @@ return index + ZONE_START # conntrack zones exhausted :( :( raise exceptions.CTZoneExhaustedError() + + +class OvsIpConntrackManager(IpConntrackManager): + + def __init__(self, execute=None): + super(OvsIpConntrackManager, self).__init__( + get_rules_for_table_func=None, + filtered_ports={}, unfiltered_ports={}, + execute=execute, namespace=None, zone_per_port=False) + + def _populate_initial_zone_map(self): + self._device_zone_map = {} + + def get_device_zone(self, port, create=False): + of_port = port.get('of_port') + if of_port is None: + return + return of_port.vlan_tag diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ip_lib.py neutron-16.4.2/neutron/agent/linux/ip_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ip_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/ip_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. import errno +from os import path import re import threading import time @@ -29,13 +30,13 @@ from pyroute2.netlink import rtnl from pyroute2.netlink.rtnl import ifaddrmsg from pyroute2.netlink.rtnl import ifinfmsg -from pyroute2 import NetlinkError from pyroute2 import netns from neutron._i18n import _ from neutron.agent.common import utils from neutron.common import utils as common_utils from neutron.privileged.agent.linux import ip_lib as privileged +from neutron.privileged.agent.linux import utils as priv_utils LOG = logging.getLogger(__name__) @@ -100,9 +101,7 @@ "become ready: %(reason)s") -class InvalidArgument(exceptions.NeutronException): - message = _("Invalid value %(value)s for parameter %(parameter)s " - "provided.") +InvalidArgument = privileged.InvalidArgument class SubProcessBase(object): @@ -442,13 +441,8 @@ self.name, self._parent.namespace, ifinfmsg.IFF_ALLMULTI) def set_mtu(self, mtu_size): - try: - privileged.set_link_attribute( - self.name, self._parent.namespace, mtu=mtu_size) - except NetlinkError as e: - if e.code == errno.EINVAL: - raise InvalidArgument(parameter="MTU", value=mtu_size) - raise + privileged.set_link_attribute( + self.name, self._parent.namespace, mtu=mtu_size) def set_up(self): privileged.set_link_attribute( @@ -520,6 +514,13 @@ def exists(self): return privileged.interface_exists(self.name, self._parent.namespace) + def get_vfs(self): + return privileged.get_link_vfs(self.name, self._parent.namespace) + + def set_vf_feature(self, vf_config): + return privileged.set_link_vf_feature( + self.name, self._parent.namespace, vf_config) + class IpAddrCommand(IpDeviceCommandBase): COMMAND = 'addr' @@ -552,7 +553,7 @@ if not common_utils.is_cidr_host(cidr): kwargs['mask'] = common_utils.cidr_mask_length(cidr) if scope: - kwargs['scope'] = IP_ADDRESS_SCOPE_NAME[scope] + kwargs['scope'] = scope if ip_version: kwargs['family'] = common_utils.get_socket_address_family( ip_version) @@ -932,9 +933,16 @@ is ready to be operated. :param kwargs: Callers add any filters they use as kwargs """ + if not namespace: + return False + if not try_is_ready: - output = list_network_namespaces(**kwargs) - return namespace in output + nspath = kwargs.get('nspath') or netns.NETNS_RUN_DIR + nspath += '/' + namespace + if cfg.CONF.AGENT.use_helper_for_ns_read: + return priv_utils.path_exists(nspath) + else: + return path.exists(nspath) try: privileged.open_namespace(namespace) @@ -1012,7 +1020,7 @@ arping_cmd = ['arping', arg, '-I', iface_name, '-c', 1, # Pass -w to set timeout to ensure exit if interface # removed while running - '-w', 1.5, address] + '-w', 2, address] try: ip_wrapper.netns.execute(arping_cmd, extra_ok_codes=extra_ok_codes) @@ -1341,32 +1349,37 @@ 'event': event} -def _parse_link_device(namespace, device, **kwargs): - """Parse pytoute2 link device information - - For each link device, the IP address information is retrieved and returned - in a dictionary. - IP address scope: http://linux-ip.net/html/tools-ip-address.html - """ - retval = [] - name = get_attr(device, 'IFLA_IFNAME') - ip_addresses = privileged.get_ip_addresses(namespace, - index=device['index'], - **kwargs) - for ip_address in ip_addresses: - retval.append(_parse_ip_address(ip_address, name)) - return retval - - def get_devices_with_ip(namespace, name=None, **kwargs): + retval = [] link_args = {} if name: link_args['ifname'] = name - devices = privileged.get_link_devices(namespace, **link_args) - retval = [] - for parsed_ips in (_parse_link_device(namespace, device, **kwargs) - for device in devices): - retval += parsed_ips + scope = kwargs.pop('scope', None) + if scope: + kwargs['scope'] = IP_ADDRESS_SCOPE_NAME[scope] + + if not link_args: + ip_addresses = privileged.get_ip_addresses(namespace, **kwargs) + else: + device = get_devices_info(namespace, **link_args) + if not device: + return retval + ip_addresses = privileged.get_ip_addresses( + namespace, index=device[0]['index'], **kwargs) + + devices = {} # {device index: name} + for ip_address in ip_addresses: + index = ip_address['index'] + name = get_attr(ip_address, 'IFA_LABEL') or devices.get(index) + if not name: + device = get_devices_info(namespace, index=index) + if not device: + continue + name = device[0]['name'] + + retval.append(_parse_ip_address(ip_address, name)) + devices[index] = name + return retval diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ip_link_support.py neutron-16.4.2/neutron/agent/linux/ip_link_support.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ip_link_support.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/ip_link_support.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -# Copyright 2014 Mellanox Technologies, Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re - -from neutron_lib import exceptions as n_exc -from oslo_log import log as logging - -from neutron._i18n import _ -from neutron.agent.linux import utils - - -LOG = logging.getLogger(__name__) - - -class IpLinkSupportError(n_exc.NeutronException): - pass - - -class UnsupportedIpLinkCommand(IpLinkSupportError): - message = _("ip link command is not supported: %(reason)s") - - -class InvalidIpLinkCapability(IpLinkSupportError): - message = _("ip link capability %(capability)s is not supported") - - -class IpLinkConstants(object): - IP_LINK_CAPABILITY_STATE = "state" - IP_LINK_CAPABILITY_VLAN = "vlan" - IP_LINK_CAPABILITY_RATE = "rate" - IP_LINK_CAPABILITY_MIN_TX_RATE = "min_tx_rate" - IP_LINK_CAPABILITY_SPOOFCHK = "spoofchk" - IP_LINK_SUB_CAPABILITY_QOS = "qos" - - -class IpLinkSupport(object): - VF_BLOCK_REGEX = r"\[ vf NUM(?P.*) \] \]" - - CAPABILITY_REGEX = r"\[ %s (.*)" - SUB_CAPABILITY_REGEX = r"\[ %(cap)s (.*) \[ %(subcap)s (.*)" - - @classmethod - def get_vf_mgmt_section(cls): - """Parses ip link help output, and gets vf block""" - - output = cls._get_ip_link_output() - vf_block_pattern = re.search(cls.VF_BLOCK_REGEX, - output, - re.DOTALL | re.MULTILINE) - if vf_block_pattern: - return vf_block_pattern.group("vf_block") - - @classmethod - def vf_mgmt_capability_supported(cls, vf_section, capability, - subcapability=None): - """Validate vf capability support - - Checks if given vf capability (and sub capability - if given) supported - :param vf_section: vf Num block content - :param capability: for example: vlan, rate, spoofchk, state - :param subcapability: for example: qos - """ - if not vf_section: - return False - if subcapability: - regex = cls.SUB_CAPABILITY_REGEX % {"cap": capability, - "subcap": subcapability} - else: - regex = cls.CAPABILITY_REGEX % capability - pattern_match = re.search(regex, vf_section, - re.DOTALL | re.MULTILINE) - return pattern_match is not None - - @classmethod - def _get_ip_link_output(cls): - """Gets the output of the ip link help command - - Runs ip link help command and stores its output - Note: ip link help return error and writes its output to stderr - so we get the output from there. however, if this issue - will be solved and the command will write to stdout, we - will get the output from there too. - """ - try: - ip_cmd = ['ip', 'link', 'help'] - _stdout, _stderr = utils.execute( - ip_cmd, - check_exit_code=False, - return_stderr=True, - log_fail_as_error=False) - except Exception as e: - LOG.exception("Failed executing ip command") - raise UnsupportedIpLinkCommand(reason=e) - return _stdout or _stderr diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ipset_manager.py neutron-16.4.2/neutron/agent/linux/ipset_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/ipset_manager.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/ipset_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -44,7 +44,7 @@ /1's to represent the /0. """ sanitized_addresses = [] - for ip in addresses: + for ip, _mac in addresses: ip = netaddr.IPNetwork(ip) if ip.prefixlen == 0: if ip.version == 4: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/iptables_firewall.py neutron-16.4.2/neutron/agent/linux/iptables_firewall.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/iptables_firewall.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/iptables_firewall.py 2021-11-12 13:56:42.000000000 +0000 @@ -387,6 +387,11 @@ def _get_br_device_name(self, port): return ('brq' + port['network_id'])[:constants.LINUX_DEV_LEN] + def _get_port_device_name(self, port): + if port['device'].startswith(constants.TAP_DEVICE_PREFIX): + return port['device'][4:] + return port['device'] + def _get_jump_rules(self, port, create=True): zone = self.ipconntrack.get_device_zone(port, create=create) if not zone: @@ -400,10 +405,10 @@ if self._are_sg_rules_stateful(port_sg_rules): # comment to prevent duplicate warnings for different devices using # same bridge. truncate start to remove prefixes - comment = 'Set zone for %s' % port['device'][4:] + comment = 'Set zone for %s' % self._get_port_device_name(port) conntrack = '--zone %s' % self.ipconntrack.get_device_zone(port) else: - comment = 'Make %s stateless' % port['device'][4:] + comment = 'Make %s stateless' % self._get_port_device_name(port) conntrack = '--notrack' rules = [] for dev, match in ((br_dev, match_physdev), (br_dev, match_interface), @@ -601,7 +606,7 @@ ethertype = rule['ethertype'] port_ips = port.get('fixed_ips', []) - for ip in self.sg_members[remote_group_id][ethertype]: + for ip, _mac in self.sg_members[remote_group_id][ethertype]: if ip not in port_ips: ip_rule = rule.copy() direction_ip_prefix = firewall.DIRECTION_IP_PREFIX[ @@ -989,7 +994,8 @@ self._clean_deleted_remote_sg_members_conntrack_entries() def _get_sg_members(self, sg_info, sg_id, ethertype): - return set(sg_info.get(sg_id, {}).get(ethertype, [])) + ip_mac_addresses = sg_info.get(sg_id, {}).get(ethertype, []) + return set([ip_mac[0] for ip_mac in ip_mac_addresses]) def filter_defer_apply_off(self): if self._defer_apply: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/iptables_manager.py neutron-16.4.2/neutron/agent/linux/iptables_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/iptables_manager.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/iptables_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -308,7 +308,8 @@ _random_fully = None def __init__(self, _execute=None, state_less=False, use_ipv6=False, - nat=True, namespace=None, binary_name=binary_name): + nat=True, namespace=None, binary_name=binary_name, + external_lock=True): if _execute: self.execute = _execute else: @@ -318,6 +319,7 @@ self.namespace = namespace self.iptables_apply_deferred = False self.wrap_name = binary_name[:16] + self.external_lock = external_lock self.ipv4 = {'filter': IptablesTable(binary_name=self.wrap_name)} self.ipv6 = {'filter': IptablesTable(binary_name=self.wrap_name)} @@ -460,7 +462,8 @@ # NOTE(ihrachys) we may get rid of the lock once all supported # platforms get iptables with 999eaa241212d3952ddff39a99d0d55a74e3639e # ("iptables-restore: support acquiring the lock.") - with lockutils.lock(lock_name, runtime.SYNCHRONIZED_PREFIX, True): + with lockutils.lock(lock_name, runtime.SYNCHRONIZED_PREFIX, + external=self.external_lock): first = self._apply_synchronized() if not cfg.CONF.AGENT.debug_iptables_rules: return first diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/keepalived.py neutron-16.4.2/neutron/agent/linux/keepalived.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/keepalived.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/keepalived.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,6 +15,7 @@ import errno import itertools import os +import signal import netaddr from neutron_lib import constants @@ -39,6 +40,7 @@ HEALTH_CHECK_NAME = 'ha_health_check' LOG = logging.getLogger(__name__) +SIGTERM_TIMEOUT = 5 def get_free_range(parent_range, excluded_ranges, size=PRIMARY_VIP_RANGE_SIZE): @@ -85,24 +87,28 @@ class KeepalivedVipAddress(object): """A virtual address entry of a keepalived configuration.""" - def __init__(self, ip_address, interface_name, scope=None): + def __init__(self, ip_address, interface_name, scope=None, track=True): self.ip_address = ip_address self.interface_name = interface_name self.scope = scope + self.track = track def __eq__(self, other): return (isinstance(other, KeepalivedVipAddress) and self.ip_address == other.ip_address) def __str__(self): - return '[%s, %s, %s]' % (self.ip_address, - self.interface_name, - self.scope) + return '[%s, %s, %s, %s]' % (self.ip_address, + self.interface_name, + self.scope, + self.track) def build_config(self): result = '%s dev %s' % (self.ip_address, self.interface_name) if self.scope: result += ' scope %s' % self.scope + if cfg.CONF.keepalived_use_no_track and not self.track: + result += ' no_track' return result @@ -124,6 +130,8 @@ output += ' dev %s' % self.interface_name if self.scope: output += ' scope %s' % self.scope + if cfg.CONF.keepalived_use_no_track: + output += ' no_track' return output @@ -200,7 +208,8 @@ self.authentication = (auth_type, password) def add_vip(self, ip_cidr, interface_name, scope): - vip = KeepalivedVipAddress(ip_cidr, interface_name, scope) + track = interface_name in self.track_interfaces + vip = KeepalivedVipAddress(ip_cidr, interface_name, scope, track=track) if vip not in self.vips: self.vips.append(vip) else: @@ -450,7 +459,15 @@ service_name=KEEPALIVED_SERVICE_NAME) pm = self.get_process() - pm.disable(sig='15') + pm.disable(sig=str(int(signal.SIGTERM))) + try: + utils.wait_until_true(lambda: not pm.active, + timeout=SIGTERM_TIMEOUT) + except utils.WaitTimeout: + LOG.warning('Keepalived process %s did not finish after SIGTERM ' + 'signal in %s seconds, sending SIGKILL signal', + pm.pid, SIGTERM_TIMEOUT) + pm.disable(sig=str(int(signal.SIGKILL))) def check_processes(self): keepalived_pm = self.get_process() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/openvswitch_firewall/firewall.py neutron-16.4.2/neutron/agent/linux/openvswitch_firewall/firewall.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/openvswitch_firewall/firewall.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/openvswitch_firewall/firewall.py 2021-11-12 13:56:42.000000000 +0000 @@ -32,6 +32,7 @@ from neutron._i18n import _ from neutron.agent.common import ovs_lib from neutron.agent import firewall +from neutron.agent.linux import ip_conntrack from neutron.agent.linux.openvswitch_firewall import constants as ovsfw_consts from neutron.agent.linux.openvswitch_firewall import exceptions from neutron.agent.linux.openvswitch_firewall import iptables @@ -268,6 +269,10 @@ def __init__(self): self.id_map = collections.defaultdict(self._conj_id_factory) + # Stores the set of conjuntion IDs used for each unique tuple + # (sg_id, remote_sg_id, direction, ethertype). Each tuple can have up + # to 8 conjuntion IDs (see ConjIPFlowManager.add()). + self.id_map_group = collections.defaultdict(set) self.id_free = collections.deque() self.max_id = 0 @@ -298,13 +303,22 @@ return a list of (remote_sg_id, conj_id), which are no longer in use. """ - result = [] + result = set([]) for k in list(self.id_map.keys()): if sg_id in k[0:2]: conj_id = self.id_map.pop(k) - result.append((k[1], conj_id)) + result.add((k[1], conj_id)) self.id_free.append(conj_id) + # If the remote_sg_id is removed, the tuple (sg_id, remote_sg_id, + # direction, ethertype) no longer exists; the conjunction IDs assigned + # to this tuple should be removed too. + for k in list(self.id_map_group.keys()): + if sg_id in k[0:2]: + conj_id_groups = self.id_map_group.pop(k, []) + for conj_id in conj_id_groups: + result.add((k[1], conj_id)) + return result @@ -346,12 +360,22 @@ return addr_to_conj def _update_flows_for_vlan_subr(self, direction, ethertype, vlan_tag, - flow_state, addr_to_conj): + flow_state, addr_to_conj, + conj_id_to_remove): """Do the actual flow updates for given direction and ethertype.""" - current_ips = set(flow_state.keys()) - self.driver.delete_flows_for_ip_addresses( - current_ips - set(addr_to_conj.keys()), - direction, ethertype, vlan_tag) + conj_id_to_remove = conj_id_to_remove or [] + # Delete any current flow related to any deleted IP address, before + # creating the flows for the current IPs. + self.driver.delete_flows_for_flow_state( + flow_state, addr_to_conj, direction, ethertype, vlan_tag) + for conj_id_set in conj_id_to_remove: + # Remove any remaining flow with remote SG ID conj_id_to_remove + for current_ip, conj_ids in flow_state.items(): + conj_ids_to_remove = conj_id_set & set(conj_ids) + self.driver.delete_flow_for_ip( + current_ip, direction, ethertype, vlan_tag, + conj_ids_to_remove) + for addr, conj_ids in addr_to_conj.items(): conj_ids.sort() if flow_state.get(addr) == conj_ids: @@ -360,7 +384,7 @@ addr, direction, ethertype, vlan_tag, conj_ids): self.driver._add_flow(**flow) - def update_flows_for_vlan(self, vlan_tag): + def update_flows_for_vlan(self, vlan_tag, conj_id_to_remove=None): """Install action=conjunction(conj_id, 1/2) flows, which depend on IP addresses of remote_group_id. """ @@ -373,7 +397,7 @@ self._update_flows_for_vlan_subr( direction, ethertype, vlan_tag, self.flow_state[vlan_tag][(direction, ethertype)], - addr_to_conj) + addr_to_conj, conj_id_to_remove) self.flow_state[vlan_tag][(direction, ethertype)] = addr_to_conj def add(self, vlan_tag, sg_id, remote_sg_id, direction, ethertype, @@ -394,32 +418,43 @@ collections.defaultdict(set)) self.conj_ids[vlan_tag][(direction, ethertype)][remote_sg_id].add( conj_id) + + conj_id_tuple = (sg_id, remote_sg_id, direction, ethertype) + self.conj_id_map.id_map_group[conj_id_tuple].add(conj_id) return conj_id def sg_removed(self, sg_id): """Handle SG removal events. - Free all conj_ids associated with the sg_id and clean up + Free all conj_ids associated with the sg_id removed and clean up obsolete entries from the self.conj_ids map. Unlike the add method, it also updates flows. + If a SG is removed, both sg_id and remote_sg_id should be removed from + the "vlan_conj_id_map". """ - id_list = self.conj_id_map.delete_sg(sg_id) + id_set = self.conj_id_map.delete_sg(sg_id) unused_dict = collections.defaultdict(set) - for remote_sg_id, conj_id in id_list: + for remote_sg_id, conj_id in id_set: unused_dict[remote_sg_id].add(conj_id) for vlan_tag, vlan_conj_id_map in self.conj_ids.items(): update = False + conj_id_to_remove = [] for sg_conj_id_map in vlan_conj_id_map.values(): for remote_sg_id, unused in unused_dict.items(): if (remote_sg_id in sg_conj_id_map and sg_conj_id_map[remote_sg_id] & unused): + if remote_sg_id == sg_id: + conj_id_to_remove.append( + sg_conj_id_map[remote_sg_id] & unused) sg_conj_id_map[remote_sg_id] -= unused if not sg_conj_id_map[remote_sg_id]: del sg_conj_id_map[remote_sg_id] update = True + if update: - self.update_flows_for_vlan(vlan_tag) + self.update_flows_for_vlan(vlan_tag, + conj_id_to_remove=conj_id_to_remove) class OVSFirewallDriver(firewall.FirewallDriver): @@ -442,13 +477,12 @@ """ self.permitted_ethertypes = cfg.CONF.SECURITYGROUP.permitted_ethertypes self.int_br = self.initialize_bridge(integration_bridge) - self.sg_port_map = SGPortMap() - self.conj_ip_manager = ConjIPFlowManager(self) - self.sg_to_delete = set() + self._initialize_sg() self._update_cookie = None self._deferred = False self.iptables_helper = iptables.Helper(self.int_br.br) self.iptables_helper.load_driver_if_needed() + self.ipconntrack = ip_conntrack.OvsIpConntrackManager() self._initialize_firewall() callbacks_registry.subscribe( @@ -458,8 +492,14 @@ def _init_firewall_callback(self, resource, event, trigger, payload=None): LOG.info("Reinitialize Openvswitch firewall after OVS restart.") + self._initialize_sg() self._initialize_firewall() + def _initialize_sg(self): + self.sg_port_map = SGPortMap() + self.conj_ip_manager = ConjIPFlowManager(self) + self.sg_to_delete = set() + def _initialize_firewall(self): self._drop_all_unmatched_flows() self._initialize_common_flows() @@ -500,7 +540,8 @@ def _delete_flows(self, **kwargs): create_reg_numbers(kwargs) - if self._deferred: + deferred = kwargs.pop('deferred', self._deferred) + if deferred: self.int_br.delete_flows(**kwargs) else: self.int_br.br.delete_flows(**kwargs) @@ -572,6 +613,12 @@ return get_physical_network_from_other_config( self.int_br.br, port_name) + def _delete_invalid_conntrack_entries_for_port(self, port, of_port): + port['of_port'] = of_port + for ethertype in [lib_const.IPv4, lib_const.IPv6]: + self.ipconntrack.delete_conntrack_state_by_remote_ips( + [port], ethertype, set(), mark=ovsfw_consts.CT_MARK_INVALID) + def get_ofport(self, port): port_id = port['device'] return self.sg_port_map.ports.get(port_id) @@ -626,6 +673,7 @@ self._update_flows_for_port(of_port, old_of_port) else: self._set_port_filters(of_port) + self._delete_invalid_conntrack_entries_for_port(port, of_port) except exceptions.OVSFWPortNotFound as not_found_error: LOG.info("port %(port_id)s does not exist in ovsdb: %(err)s.", {'port_id': port['device'], @@ -665,6 +713,8 @@ else: self._set_port_filters(of_port) + self._delete_invalid_conntrack_entries_for_port(port, of_port) + except exceptions.OVSFWPortNotFound as not_found_error: LOG.info("port %(port_id)s does not exist in ovsdb: %(err)s.", {'port_id': port['device'], @@ -771,32 +821,47 @@ return {id_: port.neutron_port_dict for id_, port in self.sg_port_map.ports.items()} - def install_vlan_direct_flow(self, mac, segment_id, ofport, local_vlan): - # If the port segment_id is not None/0, the - # port's network type must be VLAN type. - if segment_id: + def install_physical_direct_flow(self, mac, segment_id, + ofport, local_vlan, network_type): + actions = ('set_field:{:d}->reg{:d},' + 'set_field:{:d}->reg{:d},').format( + ofport, + ovsfw_consts.REG_PORT, + # This always needs the local vlan. + local_vlan, + ovsfw_consts.REG_NET) + if network_type == lib_const.TYPE_VLAN: + actions += 'strip_vlan,resubmit(,{:d})'.format( + ovs_consts.BASE_INGRESS_TABLE) self._add_flow( table=ovs_consts.TRANSIENT_TABLE, priority=90, dl_dst=mac, dl_vlan='0x%x' % segment_id, - actions='set_field:{:d}->reg{:d},' - 'set_field:{:d}->reg{:d},' - 'strip_vlan,resubmit(,{:d})'.format( - ofport, - ovsfw_consts.REG_PORT, - # This always needs the local vlan. - local_vlan, - ovsfw_consts.REG_NET, - ovs_consts.BASE_INGRESS_TABLE) - ) + actions=actions) + elif network_type == lib_const.TYPE_FLAT: + # If the port belong to flat network, we need match vlan_tci and + # needn't pop vlan + actions += 'resubmit(,{:d})'.format( + ovs_consts.BASE_INGRESS_TABLE) + self._add_flow( + table=ovs_consts.TRANSIENT_TABLE, + priority=90, + dl_dst=mac, + vlan_tci=ovs_consts.FLAT_VLAN_TCI, + actions=actions) - def delete_vlan_direct_flow(self, mac, segment_id): + def delete_physical_direct_flow(self, mac, segment_id): if segment_id: self._strict_delete_flow(priority=90, table=ovs_consts.TRANSIENT_TABLE, dl_dst=mac, dl_vlan=segment_id) + else: + self._strict_delete_flow(priority=90, + table=ovs_consts.TRANSIENT_TABLE, + dl_dst=mac, + vlan_tci=ovs_consts.FLAT_VLAN_TCI) def initialize_port_flows(self, port): """Set base flows for port @@ -821,8 +886,9 @@ # Identify ingress flows for mac_addr in port.all_allowed_macs: - self.install_vlan_direct_flow( - mac_addr, port.segment_id, port.ofport, port.vlan_tag) + self.install_physical_direct_flow( + mac_addr, port.segment_id, port.ofport, + port.vlan_tag, port.network_type) self._add_flow( table=ovs_consts.TRANSIENT_TABLE, @@ -842,19 +908,36 @@ self._initialize_egress(port) self._initialize_ingress(port) - def _initialize_egress_ipv6_icmp(self, port): - for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES: - self._add_flow( - table=ovs_consts.BASE_EGRESS_TABLE, - priority=95, - in_port=port.ofport, - reg_port=port.ofport, - dl_type=lib_const.ETHERTYPE_IPV6, - nw_proto=lib_const.PROTO_NUM_IPV6_ICMP, - icmp_type=icmp_type, - actions='resubmit(,%d)' % ( - ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE) - ) + def _initialize_egress_ipv6_icmp(self, port, allowed_pairs): + allowed_pairs = allowed_pairs.union({(port.mac, port.lla_address)}) + for mac_addr, ip_addr in allowed_pairs: + for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES: + self._add_flow( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=port.ofport, + reg_port=port.ofport, + dl_type=lib_const.ETHERTYPE_IPV6, + nw_proto=lib_const.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + dl_src=mac_addr, + ipv6_src=ip_addr, + actions='resubmit(,%d)' % ( + ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE) + ) + for icmp_type in firewall.ICMPV6_RESTRICTED_EGRESS_TYPES: + self._add_flow( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=port.ofport, + reg_port=port.ofport, + dl_type=lib_const.ETHERTYPE_IPV6, + nw_proto=lib_const.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + nd_target=ip_addr, + actions='resubmit(,%d)' % ( + ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE) + ) def _initialize_egress_no_port_security(self, port_id, ovs_ports=None): try: @@ -927,12 +1010,11 @@ def _initialize_egress(self, port): """Identify egress traffic and send it to egress base""" - self._initialize_egress_ipv6_icmp(port) # Apply mac/ip pairs for IPv4 - allowed_pairs = port.allowed_pairs_v4.union( + allowed_mac_ipv4_pairs = port.allowed_pairs_v4.union( {(port.mac, ip_addr) for ip_addr in port.ipv4_addresses}) - for mac_addr, ip_addr in allowed_pairs: + for mac_addr, ip_addr in allowed_mac_ipv4_pairs: self._add_flow( table=ovs_consts.BASE_EGRESS_TABLE, priority=95, @@ -958,9 +1040,10 @@ ) # Apply mac/ip pairs for IPv6 - allowed_pairs = port.allowed_pairs_v6.union( + allowed_mac_ipv6_pairs = port.allowed_pairs_v6.union( {(port.mac, ip_addr) for ip_addr in port.ipv6_addresses}) - for mac_addr, ip_addr in allowed_pairs: + self._initialize_egress_ipv6_icmp(port, allowed_mac_ipv6_pairs) + for mac_addr, ip_addr in allowed_mac_ipv6_pairs: self._add_flow( table=ovs_consts.BASE_EGRESS_TABLE, priority=65, @@ -975,21 +1058,30 @@ ) # DHCP discovery - for dl_type, src_port, dst_port in ( - (lib_const.ETHERTYPE_IP, 68, 67), - (lib_const.ETHERTYPE_IPV6, 546, 547)): - self._add_flow( - table=ovs_consts.BASE_EGRESS_TABLE, - priority=80, - reg_port=port.ofport, - in_port=port.ofport, - dl_type=dl_type, - nw_proto=lib_const.PROTO_NUM_UDP, - tp_src=src_port, - tp_dst=dst_port, - actions='resubmit(,{:d})'.format( - ovs_consts.ACCEPT_OR_INGRESS_TABLE) - ) + additional_ipv4_filters = [ + {"dl_src": mac, "nw_src": ip} + for mac, ip in (*allowed_mac_ipv4_pairs, + (port.mac, '0.0.0.0'),)] + additional_ipv6_filters = [ + {"dl_src": mac, "ipv6_src": ip} + for mac, ip in allowed_mac_ipv6_pairs] + for dl_type, src_port, dst_port, filters_list in ( + (lib_const.ETHERTYPE_IP, 68, 67, additional_ipv4_filters), + (lib_const.ETHERTYPE_IPV6, 546, 547, additional_ipv6_filters)): + for additional_filters in filters_list: + self._add_flow( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=80, + reg_port=port.ofport, + in_port=port.ofport, + dl_type=dl_type, + **additional_filters, + nw_proto=lib_const.PROTO_NUM_UDP, + tp_src=src_port, + tp_dst=dst_port, + actions='resubmit(,{:d})'.format( + ovs_consts.ACCEPT_OR_INGRESS_TABLE) + ) # Ban dhcp service running on an instance for dl_type, src_port, dst_port in ( (lib_const.ETHERTYPE_IP, 67, 68), @@ -1275,6 +1367,18 @@ actions='resubmit(,%d)' % ovs_consts.DROPPED_TRAFFIC_TABLE ) + # NOTE: The OUTPUT action is used instead of NORMAL action to reduce + # cpu utilization, but it causes the datapath rule to be flood rule. + # This is due to mac learning not happened on ingress traffic. + # While this is ok for no offload case, in ovs offload flood rule + # is not offloaded. Therefore, we change the action to be NORMAL in + # offload case. In case the explicitly_egress_direct is used the + # pipeline don't contain action NORMAL so we don't have flood rule + # issue. + actions = 'output:{:d}'.format(port.ofport) + if (self.int_br.br.is_hw_offload_enabled and + not cfg.CONF.AGENT.explicitly_egress_direct): + actions = 'mod_vlan_vid:{:d},normal'.format(port.vlan_tag) # Allow established and related connections for state in (ovsfw_consts.OF_STATE_ESTABLISHED_REPLY, ovsfw_consts.OF_STATE_RELATED): @@ -1285,7 +1389,7 @@ ct_state=state, ct_mark=ovsfw_consts.CT_MARK_NORMAL, ct_zone=port.vlan_tag, - actions='output:{:d}'.format(port.ofport) + actions=actions ) self._add_flow( table=ovs_consts.RULES_INGRESS_TABLE, @@ -1406,7 +1510,7 @@ table=ovs_consts.TRANSIENT_TABLE, dl_dst=mac_addr, dl_vlan=port.vlan_tag) - self.delete_vlan_direct_flow(mac_addr, port.segment_id) + self.delete_physical_direct_flow(mac_addr, port.segment_id) self._delete_flows(table=ovs_consts.ACCEPT_OR_INGRESS_TABLE, dl_dst=mac_addr, reg_net=port.vlan_tag) @@ -1417,20 +1521,36 @@ in_port=port.ofport) self._delete_flows(reg_port=port.ofport) - def delete_flows_for_ip_addresses( - self, ip_addresses, direction, ethertype, vlan_tag): + def delete_flows_for_flow_state( + self, flow_state, addr_to_conj, direction, ethertype, vlan_tag): + # Remove rules for deleted IPs and action=conjunction(conj_id, 1/2) + removed_ips = set(flow_state.keys()) - set(addr_to_conj.keys()) + for removed_ip in removed_ips: + conj_ids = flow_state[removed_ip] + self.delete_flow_for_ip(removed_ip, direction, ethertype, vlan_tag, + conj_ids) + if not cfg.CONF.AGENT.explicitly_egress_direct: return - for ip_addr in ip_addresses: + for ip_addr in removed_ips: # Generate deletion template with bogus conj_id. - flows = rules.create_flows_for_ip_address( - ip_addr, direction, ethertype, vlan_tag, [0]) - for f in flows: - # The following del statements are partly for - # complying the OpenFlow spec. It forbids the use of - # these field in non-strict delete flow messages, and - # the actions field is bogus anyway. - del f['actions'] - del f['priority'] - self._delete_flows(**f) + self.delete_flow_for_ip(ip_addr, direction, ethertype, vlan_tag, + [0]) + + def delete_flow_for_ip(self, ip_address, direction, ethertype, + vlan_tag, conj_ids): + for flow in rules.create_flows_for_ip_address( + ip_address, direction, ethertype, vlan_tag, conj_ids): + # The following del statements are partly for + # complying the OpenFlow spec. It forbids the use of + # these field in non-strict delete flow messages, and + # the actions field is bogus anyway. + del flow['actions'] + del flow['priority'] + # NOTE(hangyang) If cookie is not set then _delete_flows will + # use the OVSBridge._default_cookie to filter the flows but that + # will not match with the ip flow's cookie so OVS won't actually + # delete the flow + flow['cookie'] = ovs_lib.COOKIE_ANY + self._delete_flows(deferred=False, **flow) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/openvswitch_firewall/rules.py neutron-16.4.2/neutron/agent/linux/openvswitch_firewall/rules.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/openvswitch_firewall/rules.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/openvswitch_firewall/rules.py 2021-11-12 13:56:42.000000000 +0000 @@ -297,6 +297,9 @@ """Create flows from a rule and an ip_address derived from remote_group_id """ + ip_address, mac_address = ip_address + net = netaddr.IPNetwork(str(ip_address)) + any_src_ip = net.prefixlen == 0 # Group conj_ids per priority. conj_id_lists = [[] for i in range(4)] @@ -321,6 +324,9 @@ flow_template[FLOW_FIELD_FOR_IPVER_AND_DIRECTION[( ip_ver, direction)]] = ip_prefix + if any_src_ip: + flow_template['dl_src'] = mac_address + result = [] for offset, conj_id_list in enumerate(conj_id_lists): if not conj_id_list: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/pd.py neutron-16.4.2/neutron/agent/linux/pd.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/pd.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/pd.py 2021-11-12 13:56:42.000000000 +0000 @@ -27,6 +27,7 @@ import six from stevedore import driver +from neutron.agent.linux import ip_lib from neutron.common import utils LOG = logging.getLogger(__name__) @@ -209,16 +210,20 @@ def _add_lla(self, router, lla_with_mask): if router['gw_interface']: - self.intf_driver.add_ipv6_addr(router['gw_interface'], - lla_with_mask, - router['ns_name'], - 'link') - # There is a delay before the LLA becomes active. - # This is because the kernel runs DAD to make sure LLA uniqueness - # Spawn a thread to wait for the interface to be ready - self._spawn_lla_thread(router['gw_interface'], - router['ns_name'], - lla_with_mask) + try: + self.intf_driver.add_ipv6_addr(router['gw_interface'], + lla_with_mask, + router['ns_name'], + 'link') + # There is a delay before the LLA becomes active. + # This is because the kernel runs DAD to make sure LLA + # uniqueness + # Spawn a thread to wait for the interface to be ready + self._spawn_lla_thread(router['gw_interface'], + router['ns_name'], + lla_with_mask) + except ip_lib.IpAddressAlreadyExists: + pass def _spawn_lla_thread(self, gw_ifname, ns_name, lla_with_mask): eventlet.spawn_n(self._ensure_lla_task, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/tc_lib.py neutron-16.4.2/neutron/agent/linux/tc_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/tc_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/tc_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -345,8 +345,8 @@ qdisc_type=qdisc_type, needed_arguments=['latency_ms', 'max_kbps', 'kernel_hz']) args['burst'] = int( - _get_tbf_burst_value(max_kbps, burst_kb, kernel_hz) * 1024 / 8) - args['rate'] = int(max_kbps * 1024 / 8) + _get_tbf_burst_value(max_kbps, burst_kb, kernel_hz) * 1000 / 8) + args['rate'] = int(max_kbps * 1000 / 8) args['latency'] = latency_ms * 1000 if parent: args['parent'] = rtnl.TC_H_ROOT if parent == 'root' else parent @@ -371,10 +371,10 @@ if qdisc_attrs['qdisc_type'] == 'tbf': tca_options = _get_attr(qdisc, 'TCA_OPTIONS') tca_tbf_parms = _get_attr(tca_options, 'TCA_TBF_PARMS') - qdisc_attrs['max_kbps'] = int(tca_tbf_parms['rate'] * 8 / 1024) + qdisc_attrs['max_kbps'] = int(tca_tbf_parms['rate'] * 8 / 1000) burst_bytes = _calc_burst(tca_tbf_parms['rate'], tca_tbf_parms['buffer']) - qdisc_attrs['burst_kb'] = int(burst_bytes * 8 / 1024) + qdisc_attrs['burst_kb'] = int(burst_bytes * 8 / 1000) qdisc_attrs['latency_ms'] = _calc_latency_ms( tca_tbf_parms['limit'], burst_bytes, tca_tbf_parms['rate']) retval.append(qdisc_attrs) @@ -427,10 +427,10 @@ # - ceil (max bw): bytes/second # - burst: bytes # [1] https://www.systutorials.com/docs/linux/man/8-tc/ - kwargs = {'ceil': int(max_kbps * 1024 / 8), - 'burst': int(burst_kb * 1024 / 8)} + kwargs = {'ceil': int(max_kbps * 1000 / 8), + 'burst': int(burst_kb * 1000 / 8)} - rate = int((min_kbps or 0) * 1024 / 8) + rate = int((min_kbps or 0) * 1000 / 8) min_rate = _calc_min_rate(kwargs['burst']) if min_rate > rate: LOG.warning('TC HTB class policy rate %(rate)s (bytes/second) is ' @@ -460,9 +460,9 @@ tca_params = _get_attr(tca_options, 'TCA_' + qdisc_type.upper() + '_PARMS') burst_kb = int( - _calc_burst(tca_params['rate'], tca_params['buffer']) * 8 / 1024) - max_kbps = int(tca_params['ceil'] * 8 / 1024) - min_kbps = int(tca_params['rate'] * 8 / 1024) + _calc_burst(tca_params['rate'], tca_params['buffer']) * 8 / 1000) + max_kbps = int(tca_params['ceil'] * 8 / 1000) + min_kbps = int(tca_params['rate'] * 8 / 1000) return max_kbps, min_kbps, burst_kb tc_classes = priv_tc_lib.list_tc_policy_classes(device, @@ -562,8 +562,8 @@ :param namespace: (string) (optional) namespace name """ - rate = int(rate_kbps * 1024 / 8) - burst = int(burst_kb * 1024 / 8) + rate = int(rate_kbps * 1000 / 8) + burst = int(burst_kb * 1000 / 8) priv_tc_lib.add_tc_filter_policy(device, parent, priority, rate, burst, mtu, action, protocol=protocol, namespace=namespace) @@ -604,10 +604,10 @@ if tca_u32_police: tca_police_tbf = _get_attr(tca_u32_police, 'TCA_POLICE_TBF') if tca_police_tbf: - value['rate_kbps'] = int(tca_police_tbf['rate'] * 8 / 1024) + value['rate_kbps'] = int(tca_police_tbf['rate'] * 8 / 1000) value['burst_kb'] = int( _calc_burst(tca_police_tbf['rate'], - tca_police_tbf['burst']) * 8 / 1024) + tca_police_tbf['burst']) * 8 / 1000) value['mtu'] = tca_police_tbf['mtu'] retval.append(value) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/utils.py neutron-16.4.2/neutron/agent/linux/utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/linux/utils.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/linux/utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -38,6 +38,7 @@ from neutron.agent.linux import xenapi_root_helper from neutron.common import utils from neutron.conf.agent import common as config +from neutron.privileged.agent.linux import utils as priv_utils from neutron import wsgi @@ -394,6 +395,14 @@ return group_id_or_name == effective_group_name +def delete_if_exists(path, run_as_root=False): + """Delete a path, executed as normal user or with elevated privileges""" + if run_as_root: + priv_utils.delete_if_exists(path) + else: + fileutils.delete_if_exists(path) + + class UnixDomainHTTPConnection(httplib.HTTPConnection): """Connection class for HTTP over UNIX domain socket.""" def __init__(self, host, port=None, strict=None, timeout=None, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/metadata/driver.py neutron-16.4.2/neutron/agent/metadata/driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/metadata/driver.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/metadata/driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,10 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -import errno import grp import os import pwd +import signal from neutron_lib.callbacks import events from neutron_lib.callbacks import registry @@ -30,10 +30,15 @@ from neutron.agent.l3 import ha_router from neutron.agent.l3 import namespaces from neutron.agent.linux import external_process +from neutron.agent.linux import utils as linux_utils +from neutron.common import coordination +from neutron.common import utils as common_utils LOG = logging.getLogger(__name__) +SIGTERM_TIMEOUT = 5 + METADATA_SERVICE_NAME = 'metadata-proxy' HAPROXY_SERVICE = 'haproxy' @@ -163,13 +168,7 @@ cfg_path = os.path.join( HaproxyConfigurator.get_config_path(state_path), "%s.conf" % uuid) - try: - os.unlink(cfg_path) - except OSError as ex: - # It can happen that this function is called but metadata proxy - # was never spawned so its config file won't exist - if ex.errno != errno.ENOENT: - raise + linux_utils.delete_if_exists(cfg_path, run_as_root=True) class MetadataDriver(object): @@ -252,10 +251,19 @@ monitor.unregister(uuid, METADATA_SERVICE_NAME) pm = cls._get_metadata_proxy_process_manager(uuid, conf, ns_name=ns_name) - pm.disable() + pm.disable(sig=str(int(signal.SIGTERM))) + try: + common_utils.wait_until_true(lambda: not pm.active, + timeout=SIGTERM_TIMEOUT) + except common_utils.WaitTimeout: + LOG.warning('Metadata process %s did not finish after SIGTERM ' + 'signal in %s seconds, sending SIGKILL signal', + pm.pid, SIGTERM_TIMEOUT) + pm.disable(sig=str(int(signal.SIGKILL))) - # Delete metadata proxy config file + # Delete metadata proxy config and PID files. HaproxyConfigurator.cleanup_config_file(uuid, cfg.CONF.state_path) + linux_utils.delete_if_exists(pm.get_pid_file_name(), run_as_root=True) cls.monitors.pop(uuid, None) @@ -273,13 +281,7 @@ def after_router_added(resource, event, l3_agent, **kwargs): router = kwargs['router'] proxy = l3_agent.metadata_driver - for c, r in proxy.metadata_filter_rules(proxy.metadata_port, - proxy.metadata_access_mark): - router.iptables_manager.ipv4['filter'].add_rule(c, r) - for c, r in proxy.metadata_nat_rules(proxy.metadata_port): - router.iptables_manager.ipv4['nat'].add_rule(c, r) - router.iptables_manager.apply() - + apply_metadata_nat_rules(router, proxy) if not isinstance(router, ha_router.HaRouter): proxy.spawn_monitored_metadata_proxy( l3_agent.process_monitor, @@ -310,3 +312,13 @@ router.router['id'], l3_agent.conf, router.ns_name) + + +@coordination.synchronized('router-lock-ns-{router.ns_name}') +def apply_metadata_nat_rules(router, proxy): + for c, r in proxy.metadata_filter_rules(proxy.metadata_port, + proxy.metadata_access_mark): + router.iptables_manager.ipv4['filter'].add_rule(c, r) + for c, r in proxy.metadata_nat_rules(proxy.metadata_port): + router.iptables_manager.ipv4['nat'].add_rule(c, r) + router.iptables_manager.apply() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/metadata_agent.py neutron-16.4.2/neutron/agent/metadata_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/metadata_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/metadata_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,6 +23,7 @@ from neutron.common import utils from neutron.conf.agent import common as agent_conf from neutron.conf.agent.metadata import config as meta +from neutron.conf import service as service_conf LOG = logging.getLogger(__name__) @@ -33,6 +34,8 @@ meta.register_meta_conf_opts(meta.METADATA_PROXY_HANDLER_OPTS) cache.register_oslo_configs(cfg.CONF) agent_conf.register_agent_state_opts_helper(cfg.CONF) + service_conf.register_service_opts(service_conf.RPC_EXTRA_OPTS, cfg.CONF) + config.init(sys.argv[1:]) config.setup_logging() utils.log_opt_values(LOG) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovn/metadata/agent.py neutron-16.4.2/neutron/agent/ovn/metadata/agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovn/metadata/agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/ovn/metadata/agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,21 +15,26 @@ import collections import re +from neutron_lib import constants as n_const +from oslo_concurrency import lockutils +from oslo_log import log +from oslo_utils import netutils +from oslo_utils import uuidutils +from ovsdbapp.backend.ovs_idl import event as row_event +from ovsdbapp.backend.ovs_idl import vlog +import six +import tenacity + from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib +from neutron.agent.linux import iptables_manager from neutron.agent.ovn.metadata import driver as metadata_driver from neutron.agent.ovn.metadata import ovsdb from neutron.agent.ovn.metadata import server as metadata_server from neutron.common.ovn import constants as ovn_const +from neutron.common.ovn import utils as ovn_utils from neutron.common import utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config -from neutron_lib import constants as n_const -from oslo_concurrency import lockutils -from oslo_log import log -from oslo_utils import uuidutils -from ovsdbapp.backend.ovs_idl import event as row_event -from ovsdbapp.backend.ovs_idl import vlog -import six LOG = log.getLogger(__name__) @@ -78,9 +83,10 @@ return with _SYNC_STATE_LOCK.read_lock(): try: - LOG.info(self.LOG_MSG, row.logical_port, - str(row.datapath.uuid)) - self.agent.update_datapath(str(row.datapath.uuid)) + net_name = ovn_utils.get_network_name_from_datapath( + row.datapath) + LOG.info(self.LOG_MSG, row.logical_port, net_name) + self.agent.update_datapath(str(row.datapath.uuid), net_name) except ConfigException: # We're now in the reader lock mode, we need to exit the # context and then use writer lock @@ -111,7 +117,7 @@ return False -class ChassisCreateEvent(row_event.RowEvent): +class ChassisCreateEventBase(row_event.RowEvent): """Row create event - Chassis name == our_chassis. On connection, we get a dump of all chassis so if we catch a creation @@ -119,14 +125,14 @@ to do a full sync to make sure that we capture all changes while the connection to OVSDB was down. """ + table = None def __init__(self, metadata_agent): self.agent = metadata_agent self.first_time = True - table = 'Chassis' events = (self.ROW_CREATE,) - super(ChassisCreateEvent, self).__init__( - events, table, (('name', '=', self.agent.chassis),)) + super(ChassisCreateEventBase, self).__init__( + events, self.table, (('name', '=', self.agent.chassis),)) self.event_name = self.__class__.__name__ def run(self, event, row, old): @@ -141,6 +147,14 @@ self.agent.sync() +class ChassisCreateEvent(ChassisCreateEventBase): + table = 'Chassis' + + +class ChassisPrivateCreateEvent(ChassisCreateEventBase): + table = 'Chassis_Private' + + class SbGlobalUpdateEvent(row_event.RowEvent): """Row update event on SB_Global table.""" @@ -152,8 +166,12 @@ self.event_name = self.__class__.__name__ def run(self, event, row, old): - self.agent.sb_idl.update_metadata_health_status( - self.agent.chassis, row.nb_cfg).execute() + table = ('Chassis_Private' if self.agent.has_chassis_private + else 'Chassis') + self.agent.sb_idl.db_set( + table, self.agent.chassis, ('external_ids', { + ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: + str(row.nb_cfg)})).execute() class MetadataAgent(object): @@ -186,16 +204,30 @@ self._load_config() # Launch the server that will act as a proxy between the VM's and Nova. - proxy = metadata_server.UnixDomainMetadataProxy(self.conf) + proxy = metadata_server.UnixDomainMetadataProxy(self.conf, + self.chassis) proxy.run() + tables = ('Encap', 'Port_Binding', 'Datapath_Binding', 'SB_Global', + 'Chassis') + events = (PortBindingChassisCreatedEvent(self), + PortBindingChassisDeletedEvent(self), + SbGlobalUpdateEvent(self)) + + # TODO(lucasagomes): Remove this in the future. Try to register + # the Chassis_Private table, if not present, fallback to the normal + # Chassis table. # Open the connection to OVN SB database. - self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( - chassis=self.chassis, - events=[PortBindingChassisCreatedEvent(self), - PortBindingChassisDeletedEvent(self), - ChassisCreateEvent(self), - SbGlobalUpdateEvent(self)]).start() + self.has_chassis_private = False + try: + self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( + chassis=self.chassis, tables=tables + ('Chassis_Private', ), + events=events + (ChassisPrivateCreateEvent(self), )).start() + self.has_chassis_private = True + except AssertionError: + self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( + chassis=self.chassis, tables=tables, + events=events + (ChassisCreateEvent(self), )).start() # Do the initial sync. self.sync() @@ -205,12 +237,17 @@ proxy.wait() + @tenacity.retry( + wait=tenacity.wait_exponential( + max=config.get_ovn_ovsdb_retry_max_interval()), + reraise=True) def register_metadata_agent(self): # NOTE(lucasagomes): db_add() will not overwrite the UUID if # it's already set. + table = ('Chassis_Private' if self.has_chassis_private else 'Chassis') ext_ids = { ovn_const.OVN_AGENT_METADATA_ID_KEY: uuidutils.generate_uuid()} - self.sb_idl.db_add('Chassis', self.chassis, 'external_ids', + self.sb_idl.db_add(table, self.chassis, 'external_ids', ext_ids).execute(check_error=True) def _get_own_chassis_name(self): @@ -272,14 +309,20 @@ def _vif_ports(self, ports): return (p for p in ports if p.type in OVN_VIF_PORT_TYPES) - def teardown_datapath(self, datapath): + def teardown_datapath(self, datapath, net_name=None): """Unprovision this datapath to stop serving metadata. This function will shutdown metadata proxy if it's running and delete the VETH pair, the OVS port and the namespace. """ self.update_chassis_metadata_networks(datapath, remove=True) - namespace = self._get_namespace_name(datapath) + + # TODO(dalvarez): Remove this in Y cycle when we are sure that all + # namespaces will be created with the Neutron network UUID and not + # anymore with the OVN datapath UUID. + dp = net_name or datapath + + namespace = self._get_namespace_name(dp) ip = ip_lib.IPWrapper(namespace) # If the namespace doesn't exist, return if not ip.netns.exists(namespace): @@ -289,16 +332,16 @@ namespace) metadata_driver.MetadataDriver.destroy_monitored_metadata_proxy( - self._process_monitor, datapath, self.conf, namespace) + self._process_monitor, dp, self.conf, namespace) - veth_name = self._get_veth_name(datapath) + veth_name = self._get_veth_name(dp) self.ovs_idl.del_port(veth_name[0]).execute() if ip_lib.device_exists(veth_name[0]): ip_lib.IPWrapper().del_veth(veth_name[0]) ip.garbage_collect_namespace() - def update_datapath(self, datapath): + def update_datapath(self, datapath, net_name): """Update the metadata service for this datapath. This function will: @@ -313,11 +356,30 @@ datapath_ports = [p for p in self._vif_ports(ports) if str(p.datapath.uuid) == datapath] if datapath_ports: - self.provision_datapath(datapath) + self.provision_datapath(datapath, net_name) else: - self.teardown_datapath(datapath) + self.teardown_datapath(datapath, net_name) - def provision_datapath(self, datapath): + def _ensure_datapath_checksum(self, namespace): + """Ensure the correct checksum in the metadata packets in DPDK bridges + + (LP#1904871) In DPDK deployments (integration bridge datapath_type == + "netdev"), the checksum between the metadata namespace and OVS is not + correctly populated. + """ + if (self.ovs_idl.db_get( + 'Bridge', self.ovn_bridge, 'datapath_type').execute() != + ovn_const.CHASSIS_DATAPATH_NETDEV): + return + + iptables_mgr = iptables_manager.IptablesManager( + use_ipv6=netutils.is_ipv6_enabled(), nat=False, + namespace=namespace, external_lock=False) + rule = '-p tcp -m tcp -j CHECKSUM --checksum-fill' + iptables_mgr.ipv4['mangle'].add_rule('POSTROUTING', rule, wrap=False) + iptables_mgr.apply() + + def provision_datapath(self, datapath, net_name): """Provision the datapath so that it can serve metadata. This function will create the namespace and VETH pair if needed @@ -327,7 +389,7 @@ :return: The metadata namespace name of this datapath """ - LOG.debug("Provisioning datapath %s", datapath) + LOG.debug("Provisioning metadata for network %s", net_name) port = self.sb_idl.get_metadata_port_network(datapath) # If there's no metadata port or it doesn't have a MAC or IP # addresses, then tear the namespace down if needed. This might happen @@ -335,10 +397,10 @@ # an IP address. if not (port and port.mac and port.external_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, None)): - LOG.debug("There is no metadata port for datapath %s or it has no " + LOG.debug("There is no metadata port for network %s or it has no " "MAC or IP addresses configured, tearing the namespace " - "down if needed", datapath) - self.teardown_datapath(datapath) + "down if needed", net_name) + self.teardown_datapath(datapath, net_name) return # First entry of the mac field must be the MAC address. @@ -346,9 +408,9 @@ # If it is not, we can't provision the namespace. Tear it down if # needed and log the error. if not match: - LOG.error("Metadata port for datapath %s doesn't have a MAC " + LOG.error("Metadata port for network %s doesn't have a MAC " "address, tearing the namespace down if needed", - datapath) + net_name) self.teardown_datapath(datapath) return @@ -360,8 +422,8 @@ # Create the VETH pair if it's not created. Also the add_veth function # will create the namespace for us. - namespace = self._get_namespace_name(datapath) - veth_name = self._get_veth_name(datapath) + namespace = self._get_namespace_name(net_name) + veth_name = self._get_veth_name(net_name) ip1 = ip_lib.IPDevice(veth_name[0]) if ip_lib.device_exists(veth_name[1], namespace): @@ -424,13 +486,16 @@ 'Interface', veth_name[0], ('external_ids', {'iface-id': port.logical_port})).execute() + # Ensure the correct checksum in the metadata traffic. + self._ensure_datapath_checksum(namespace) + # Spawn metadata proxy if it's not already running. metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy( self._process_monitor, namespace, ovn_const.METADATA_PORT, self.conf, bind_address=ovn_const.METADATA_DEFAULT_IP, - network_id=datapath) + network_id=net_name) - self.update_chassis_metadata_networks(datapath) + self.update_chassis_metadata_networks(net_name) return namespace def ensure_all_networks_provisioned(self): @@ -445,11 +510,13 @@ """ # Retrieve all VIF ports in our Chassis ports = self.sb_idl.get_ports_on_chassis(self.chassis) - datapaths = {str(p.datapath.uuid) for p in self._vif_ports(ports)} + nets = {(str(p.datapath.uuid), + ovn_utils.get_network_name_from_datapath(p.datapath)) + for p in self._vif_ports(ports)} namespaces = [] # Make sure that all those datapaths are serving metadata - for datapath in datapaths: - netns = self.provision_datapath(datapath) + for datapath, net_name in nets: + netns = self.provision_datapath(datapath, net_name) if netns: namespaces.append(netns) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovn/metadata/ovsdb.py neutron-16.4.2/neutron/agent/ovn/metadata/ovsdb.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovn/metadata/ovsdb.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/ovn/metadata/ovsdb.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from oslo_log import log from ovs.db import idl from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils @@ -23,6 +24,9 @@ from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor +LOG = log.getLogger(__name__) + + class MetadataAgentOvnSbIdl(ovsdb_monitor.OvnIdl): SCHEMA = 'OVN_Southbound' @@ -38,8 +42,10 @@ helper.register_table(table) super(MetadataAgentOvnSbIdl, self).__init__( None, connection_string, helper) - if chassis and 'Chassis' in tables: - self.tables['Chassis'].condition = [['name', '==', chassis]] + if chassis: + for table in set(tables).intersection({'Chassis', + 'Chassis_Private'}): + self.tables[table].condition = [['name', '==', chassis]] if events: self.notify_handler.watch_events(events) @@ -49,7 +55,12 @@ def _get_ovsdb_helper(self, connection_string): return idlutils.get_schema_helper(connection_string, self.SCHEMA) + @tenacity.retry( + wait=tenacity.wait_exponential( + max=config.get_ovn_ovsdb_retry_max_interval()), + reraise=True) def start(self): + LOG.info('Getting OvsdbSbOvnIdl for MetadataAgent with retry') conn = connection.Connection( self, timeout=config.get_ovn_ovsdb_timeout()) return impl_idl_ovn.OvsdbSbOvnIdl(conn) @@ -64,9 +75,9 @@ tables = ('Open_vSwitch', 'Bridge', 'Port', 'Interface') for table in tables: helper.register_table(table) - ovs_idl = idl.Idl(connection_string, helper) - ovs_idl._session.reconnect.set_probe_interval( - config.get_ovn_ovsdb_probe_interval()) + ovs_idl = idl.Idl( + connection_string, helper, + probe_interval=config.get_ovn_ovsdb_probe_interval()) conn = connection.Connection( ovs_idl, timeout=config.cfg.CONF.ovs.ovsdb_connection_timeout) return idl_ovs.OvsdbIdl(conn) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovn/metadata/server.py neutron-16.4.2/neutron/agent/ovn/metadata/server.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovn/metadata/server.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/ovn/metadata/server.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ import hashlib import hmac +import threading from neutron._i18n import _ from neutron.agent.linux import utils as agent_utils @@ -44,10 +45,24 @@ class MetadataProxyHandler(object): - def __init__(self, conf): + def __init__(self, conf, chassis): self.conf = conf + self.chassis = chassis + self._sb_idl = None + self._post_fork_event = threading.Event() self.subscribe() + @property + def sb_idl(self): + if not self._sb_idl: + self._post_fork_event.wait() + + return self._sb_idl + + @sb_idl.setter + def sb_idl(self, val): + self._sb_idl = val + def subscribe(self): registry.subscribe(self.post_fork_initialize, resources.PROCESS, @@ -56,8 +71,13 @@ def post_fork_initialize(self, resource, event, trigger, payload=None): # We need to open a connection to OVN SouthBound database for # each worker so that we can process the metadata requests. + self._post_fork_event.clear() self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( - tables=('Port_Binding', 'Datapath_Binding')).start() + tables=('Port_Binding', 'Datapath_Binding', 'Chassis'), + chassis=self.chassis).start() + + # Now IDL connections can be safely used. + self._post_fork_event.set() @webob.dec.wsgify(RequestClass=webob.Request) def __call__(self, req): @@ -83,10 +103,22 @@ ports = self.sb_idl.get_network_port_bindings_by_ip(network_id, remote_address) - if len(ports) == 1: + num_ports = len(ports) + if num_ports == 1: external_ids = ports[0].external_ids return (external_ids[ovn_const.OVN_DEVID_EXT_ID_KEY], external_ids[ovn_const.OVN_PROJID_EXT_ID_KEY]) + elif num_ports == 0: + LOG.error("No port found in network %s with IP address %s", + network_id, remote_address) + elif num_ports > 1: + port_uuids = ', '.join([str(port.uuid) for port in ports]) + LOG.error("More than one port found in network %s with IP address " + "%s. Please run the neutron-ovn-db-sync-util script as " + "there seems to be inconsistent data between Neutron " + "and OVN databases. OVN Port uuids: %s", network_id, + remote_address, port_uuids) + return None, None def _proxy_request(self, instance_id, tenant_id, req): @@ -162,8 +194,9 @@ class UnixDomainMetadataProxy(object): - def __init__(self, conf): + def __init__(self, conf, chassis): self.conf = conf + self.chassis = chassis agent_utils.ensure_directory_exists_without_file( cfg.CONF.metadata_proxy_socket) @@ -188,7 +221,7 @@ def run(self): self.server = agent_utils.UnixDomainWSGIServer( 'networking-ovn-metadata-agent') - self.server.start(MetadataProxyHandler(self.conf), + self.server.start(MetadataProxyHandler(self.conf, self.chassis), self.conf.metadata_proxy_socket, workers=self.conf.metadata_workers, backlog=self.conf.metadata_backlog, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovsdb/impl_idl.py neutron-16.4.2/neutron/agent/ovsdb/impl_idl.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/ovsdb/impl_idl.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/ovsdb/impl_idl.py 2021-11-12 13:56:42.000000000 +0000 @@ -22,6 +22,7 @@ from ovsdbapp.schema.open_vswitch import impl_idl from neutron.agent.ovsdb.native import connection as n_connection +from neutron.common import utils from neutron.conf.agent import ovs_conf from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants @@ -83,12 +84,13 @@ return True +@utils.SingletonDecorator class NeutronOvsdbIdl(impl_idl.OvsdbIdl): def __init__(self, connection, idl_monitor): max_level = None if cfg.CONF.OVS.ovsdb_debug else vlog.INFO vlog.use_python_logger(max_level=max_level) self.idl_monitor = idl_monitor - super(NeutronOvsdbIdl, self).__init__(connection) + super().__init__(connection) def ovs_cleanup(self, bridges, all_ports=False): return OvsCleanup(self, bridges, all_ports) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/securitygroups_rpc.py neutron-16.4.2/neutron/agent/securitygroups_rpc.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/securitygroups_rpc.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/securitygroups_rpc.py 2021-11-12 13:56:42.000000000 +0000 @@ -18,6 +18,7 @@ from neutron_lib.api.definitions import rbac_security_groups as rbac_sg_apidef from neutron_lib.api.definitions import stateful_security_group as stateful_sg +from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import log as logging import oslo_messaging @@ -60,6 +61,9 @@ self.context = context self.plugin_rpc = plugin_rpc self.init_firewall(defer_refresh_firewall, integration_bridge) + # _latest_port_filter_lock will point to the lock created for the + # most recent thread to enter _apply_port_filters(). + self._latest_port_filter_lock = lockutils.ReaderWriterLock() def _get_trusted_devices(self, device_ids, devices): trusted_devices = [] @@ -75,6 +79,27 @@ trusted_devices.append(device_id) return trusted_devices + def _port_filter_lock(func): + """Decorator to acquire a new lock while applying port filters""" + @functools.wraps(func) + def decorated_function(self, *args, **kwargs): + lock = lockutils.ReaderWriterLock() + # Tracking the most recent lock at the instance level allows + # waiters to only wait for the most recent lock to be released + # instead of waiting until all locks have been released. + self._latest_port_filter_lock = lock + with lock.write_lock(): + return func(self, *args, **kwargs) + return decorated_function + + def _port_filter_wait(func): + """Decorator to wait for the latest port filter lock to be released""" + @functools.wraps(func) + def decorated_function(self, *args, **kwargs): + with self._latest_port_filter_lock.read_lock(): + return func(self, *args, **kwargs) + return decorated_function + def init_firewall(self, defer_refresh_firewall=False, integration_bridge=None): firewall_driver = cfg.CONF.SECURITYGROUP.firewall_driver or 'noop' @@ -136,6 +161,7 @@ LOG.info("Preparing filters for devices %s", device_ids) self._apply_port_filter(device_ids) + @_port_filter_lock def _apply_port_filter(self, device_ids, update_filter=False): step = common_constants.AGENT_RES_PROCESSING_STEP if self.use_enhanced_rpc: @@ -193,6 +219,7 @@ 'security_group_source_groups', 'sg_member') + @_port_filter_wait def _security_group_updated(self, security_groups, attribute, action_type): devices = [] sec_grp_set = set(security_groups) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/windows/utils.py neutron-16.4.2/neutron/agent/windows/utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/agent/windows/utils.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/agent/windows/utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ctypes import io import os @@ -38,6 +39,8 @@ subprocess = eventlet.patcher.original('subprocess') subprocess.threading = eventlet.patcher.original('threading') +ERROR_KEY_DELETED = 0x03FA + def create_process(cmd, run_as_root=False, addl_env=None, tpool_proxy=True): @@ -71,8 +74,16 @@ if not pid: return None - conn = wmi.WMI() - processes = conn.Win32_Process(ProcessId=pid) + try: + conn = wmi.WMI() + processes = conn.Win32_Process(ProcessId=pid) + except wmi.x_wmi as exc: + hresult = exc.com_error.hresult + err_code = ctypes.c_uint(hresult).value & 0xFFFF + if err_code == ERROR_KEY_DELETED: + return None + raise + if processes: return processes[0] return None diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/api/extensions.py neutron-16.4.2/neutron/api/extensions.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/api/extensions.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/api/extensions.py 2021-11-12 13:56:42.000000000 +0000 @@ -197,8 +197,13 @@ controller = req_controllers[request_ext.key] controller.add_handler(request_ext.handler) + # NOTE(slaweq): It seems that using singleton=True in conjunction + # with eventlet monkey patching of the threading library doesn't work + # well and there is memory leak. See + # https://bugs.launchpad.net/neutron/+bug/1942179 for details self._router = routes.middleware.RoutesMiddleware(self._dispatch, - mapper) + mapper, + singleton=False) super(ExtensionMiddleware, self).__init__(application) @classmethod diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py neutron-16.4.2/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py 2021-11-12 13:56:42.000000000 +0000 @@ -143,11 +143,11 @@ network['id']) return new_agents + existing_agents - def _get_enabled_agents(self, context, network, agents, method, payload): + def _get_enabled_agents( + self, context, network_id, network, agents, method, payload): """Get the list of agents who can provide services.""" if not agents: return [] - network_id = network['id'] enabled_agents = agents if not cfg.CONF.enable_services_on_agents_with_admin_state_down: enabled_agents = [x for x in agents if x.admin_state_up] @@ -165,6 +165,10 @@ if not enabled_agents: num_ports = self.plugin.get_ports_count( context, {'network_id': [network_id]}) + if not network: + admin_ctx = (context if context.is_admin else + context.elevated()) + network = self.plugin.get_network(admin_ctx, network_id) notification_required = ( num_ports > 0 and len(network['subnets']) >= 1) if notification_required: @@ -179,7 +183,8 @@ def _is_reserved_dhcp_port(self, port): return port.get('device_id') == constants.DEVICE_ID_RESERVED_DHCP_PORT - def _notify_agents(self, context, method, payload, network_id): + def _notify_agents( + self, context, method, payload, network_id, network=None): """Notify all the agents that are hosting the network.""" payload['priority'] = METHOD_PRIORITY_MAP.get(method) # fanout is required as we do not know who is "listening" @@ -194,31 +199,35 @@ if fanout_required: self._fanout_message(context, method, payload) elif cast_required: - admin_ctx = (context if context.is_admin else context.elevated()) - network = self.plugin.get_network(admin_ctx, network_id) + candidate_hosts = None if 'subnet' in payload and payload['subnet'].get('segment_id'): # if segment_id exists then the segment service plugin # must be loaded segment_plugin = directory.get_plugin('segments') segment = segment_plugin.get_segment( context, payload['subnet']['segment_id']) - network['candidate_hosts'] = segment['hosts'] + candidate_hosts = segment['hosts'] agents = self.plugin.get_dhcp_agents_hosting_networks( - context, [network_id], hosts=network.get('candidate_hosts')) + context, [network_id], hosts=candidate_hosts) # schedule the network first, if needed schedule_required = ( method == 'subnet_create_end' or method == 'port_create_end' and not self._is_reserved_dhcp_port(payload['port'])) if schedule_required: + admin_ctx = context if context.is_admin else context.elevated() + network = network or self.plugin.get_network( + admin_ctx, network_id) + if candidate_hosts: + network['candidate_hosts'] = candidate_hosts agents = self._schedule_network(admin_ctx, network, agents) if not agents: LOG.debug("Network %s is not hosted by any dhcp agent", network_id) return enabled_agents = self._get_enabled_agents( - context, network, agents, method, payload) + context, network_id, network, agents, method, payload) if method == 'port_create_end': high_agent = enabled_agents.pop( @@ -268,7 +277,8 @@ def _after_router_interface_deleted(self, resource, event, trigger, **kwargs): self._notify_agents(kwargs['context'], 'port_delete_end', - {'port_id': kwargs['port']['id']}, + {'port_id': kwargs['port']['id'], + 'fixed_ips': kwargs['port']['fixed_ips']}, kwargs['port']['network_id']) def _native_event_send_dhcp_notification(self, resource, event, trigger, @@ -343,6 +353,10 @@ payload = {obj_type + '_id': obj_value['id']} if obj_type != 'network': payload['network_id'] = network_id - self._notify_agents(context, method_name, payload, network_id) + if obj_type == 'port': + payload['fixed_ips'] = obj_value['fixed_ips'] + self._notify_agents(context, method_name, payload, network_id, + obj_value.get('network')) else: - self._notify_agents(context, method_name, data, network_id) + self._notify_agents(context, method_name, data, network_id, + obj_value.get('network')) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/api/rpc/handlers/securitygroups_rpc.py neutron-16.4.2/neutron/api/rpc/handlers/securitygroups_rpc.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/api/rpc/handlers/securitygroups_rpc.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/api/rpc/handlers/securitygroups_rpc.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,6 +23,7 @@ from neutron_lib.utils import net from oslo_log import log as logging import oslo_messaging +from oslo_utils import versionutils from neutron.api.rpc.callbacks import resources from neutron.api.rpc.handlers import resources_rpc @@ -56,9 +57,11 @@ def security_group_info_for_devices(self, context, devices): LOG.debug("Get security group information for devices via rpc %r", devices) - cctxt = self.client.prepare(version='1.2') + call_version = '1.3' + cctxt = self.client.prepare(version=call_version) return cctxt.call(context, 'security_group_info_for_devices', - devices=devices) + devices=devices, + call_version=call_version) class SecurityGroupServerRpcCallback(object): @@ -72,10 +75,12 @@ # API version history: # 1.1 - Initial version # 1.2 - security_group_info_for_devices introduced as an optimization + # 1.3 - security_group_info_for_devices returns member_ips with new + # structure. # NOTE: target must not be overridden in subclasses # to keep RPC API version consistent across plugins. - target = oslo_messaging.Target(version='1.2', + target = oslo_messaging.Target(version='1.3', namespace=constants.RPC_NAMESPACE_SECGROUP) @property @@ -115,9 +120,34 @@ Note that sets are serialized into lists by rpc code. """ + # The original client RPC version was 1.2 before this change. + call_version = kwargs.pop("call_version", '1.2') + _target_version = versionutils.convert_version_to_tuple(call_version) devices_info = kwargs.get('devices') ports = self._get_devices_info(context, devices_info) - return self.plugin.security_group_info_for_ports(context, ports) + sg_info = self.plugin.security_group_info_for_ports(context, ports) + if _target_version < (1, 3): + LOG.warning("RPC security_group_info_for_devices call has " + "inconsistent version between server and agents. " + "The server supports RPC version is 1.3 while " + "the agent is %s.", call_version) + return self.make_compatible_sg_member_ips(sg_info) + return sg_info + + def make_compatible_sg_member_ips(self, sg_info): + sg_member_ips = sg_info.get('sg_member_ips', {}) + sg_ids = sg_member_ips.keys() + for sg_id in sg_ids: + member_ips = sg_member_ips.get(sg_id, {}) + ipv4_ips = member_ips.get("IPv4", set()) + comp_ipv4_ips = set([ip for ip, _mac in ipv4_ips]) + ipv6_ips = member_ips.get("IPv6", set()) + comp_ipv6_ips = set([ip for ip, _mac in ipv6_ips]) + comp_ips = {"IPv4": comp_ipv4_ips, + "IPv6": comp_ipv6_ips} + sg_member_ips[sg_id] = comp_ips + sg_info['sg_member_ips'] = sg_member_ips + return sg_info class SecurityGroupAgentRpcApiMixin(object): @@ -327,8 +357,10 @@ filters = {'security_group_ids': tuple(remote_group_ids)} for p in self.rcache.get_resources('Port', filters): - port_ips = [str(addr.ip_address) - for addr in p.fixed_ips + p.allowed_address_pairs] + allowed_ips = [(str(addr.ip_address), str(addr.mac_address)) + for addr in p.allowed_address_pairs] + port_ips = [(str(addr.ip_address), str(p.mac_address)) + for addr in p.fixed_ips] + allowed_ips for sg_id in p.security_group_ids: if sg_id in ips_by_group: ips_by_group[sg_id].update(set(port_ips)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/ipset_cleanup.py neutron-16.4.2/neutron/cmd/ipset_cleanup.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/ipset_cleanup.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/ipset_cleanup.py 2021-11-12 13:56:42.000000000 +0000 @@ -19,6 +19,7 @@ from neutron.agent.linux import utils from neutron.common import config from neutron.conf.agent import cmd as command +from neutron.conf.agent import common as agent_config LOG = logging.getLogger(__name__) @@ -31,6 +32,7 @@ from the main config that do not apply during clean-up. """ conf = cfg.CONF + agent_config.register_root_helper(conf=conf) command.register_cmd_opts(command.ip_opts, conf) return conf diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/netns_cleanup.py neutron-16.4.2/neutron/cmd/netns_cleanup.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/netns_cleanup.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/netns_cleanup.py 2021-11-12 13:56:42.000000000 +0000 @@ -35,6 +35,7 @@ from neutron.conf.agent import cmd from neutron.conf.agent import common as agent_config from neutron.conf.agent import dhcp as dhcp_config +from neutron.privileged.agent.linux import utils as priv_utils LOG = logging.getLogger(__name__) NS_PREFIXES = { @@ -43,7 +44,6 @@ dvr_fip_ns.FIP_NS_PREFIX], } SIGTERM_WAITTIME = 10 -NETSTAT_PIDS_REGEX = re.compile(r'.* (?P\d{2,6})/.*') class PidsInNamespaceException(Exception): @@ -134,22 +134,6 @@ device.set_log_fail_as_error(orig_log_fail_as_error) -def find_listen_pids_namespace(namespace): - """Retrieve a list of pids of listening processes within the given netns. - - It executes netstat -nlp and returns a set of unique pairs - """ - ip = ip_lib.IPWrapper(namespace=namespace) - pids = set() - cmd = ['netstat', '-nlp'] - output = ip.netns.execute(cmd, run_as_root=True) - for line in output.splitlines(): - m = NETSTAT_PIDS_REGEX.match(line) - if m: - pids.add(m.group('pid')) - return pids - - def wait_until_no_listen_pids_namespace(namespace, timeout=SIGTERM_WAITTIME): """Poll listening processes within the given namespace. @@ -164,7 +148,7 @@ # previous command start = end = time.time() while end - start < timeout: - if not find_listen_pids_namespace(namespace): + if not priv_utils.find_listen_pids_namespace(namespace): return time.sleep(1) end = time.time() @@ -179,7 +163,7 @@ then a SIGKILL will be issued to all parents and all their children. Also, this function returns the number of listening processes. """ - pids = find_listen_pids_namespace(namespace) + pids = priv_utils.find_listen_pids_namespace(namespace) pids_to_kill = {utils.find_fork_top_parent(pid) for pid in pids} kill_signal = signal.SIGTERM if force: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/ovn/migration_mtu.py neutron-16.4.2/neutron/cmd/ovn/migration_mtu.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/ovn/migration_mtu.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/ovn/migration_mtu.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,21 +16,64 @@ import os import sys +from neutron_lib import constants from openstack import connection -# TODO(dalvarez): support also GRE -GENEVE_TO_VXLAN_OVERHEAD = 8 + +# Overhead size of Geneve is configurable, use the recommended value +GENEVE_ENCAP_OVERHEAD = 38 +# map of network types to migrate and the difference in overhead size when +# converted to Geneve. +NETWORK_TYPE_OVERHEAD_DIFF = { + 'vxlan': GENEVE_ENCAP_OVERHEAD - constants.VXLAN_ENCAP_OVERHEAD, + 'gre': GENEVE_ENCAP_OVERHEAD - constants.GRE_ENCAP_OVERHEAD, +} def get_connection(): - user_domain_id = os.environ.get('OS_USER_DOMAIN_ID', 'default') - project_domain_id = os.environ.get('OS_PROJECT_DOMAIN_ID', 'default') + """Get OpenStack SDK Connection object with parameters from environment. + + Project scoped authorization is used and the following environment + variables are required: + + OS_AUTH_URL URL to OpenStack Identity service + OS_PROJECT_NAME Name of project for authorization + OS_USERNAME Username for authentication + OS_PASSWORD Password for authentication + + Which domain to use for authentication and authorization may be specified + by domain name or domain ID. If none of the domain selection variables are + set the tool will default to use the domain with literal ID of 'default'. + + To select domain by name set both of these envornment variables: + + OS_USER_DOMAIN_NAME Name of domain to authenticate to + OS_PROJECT_DOMAIN_NAME Name of domain for authorization + + To select domain by ID set both of these environment variables: + + OS_USER_DOMAIN_ID ID of domain to authenticate to + OS_PROJECT_DOMAIN_ID ID of domain for authorization + + NOTE: If both OS_*_DOMAIN_NAME and OS_*_DOMAIN_ID variables are present in + the environment the OS_*_DOMAIN_NAME variables will be used. + """ + user_domain_name = os.environ.get('OS_USER_DOMAIN_NAME') + project_domain_name = os.environ.get('OS_PROJECT_DOMAIN_NAME') + user_domain_id = os.environ.get( + 'OS_USER_DOMAIN_ID', + 'default') if not user_domain_name else None + project_domain_id = os.environ.get( + 'OS_PROJECT_DOMAIN_ID', + 'default') if not project_domain_name else None conn = connection.Connection(auth_url=os.environ['OS_AUTH_URL'], project_name=os.environ['OS_PROJECT_NAME'], username=os.environ['OS_USERNAME'], password=os.environ['OS_PASSWORD'], user_domain_id=user_domain_id, - project_domain_id=project_domain_id) + project_domain_id=project_domain_id, + user_domain_name=user_domain_name, + project_domain_name=project_domain_name) return conn @@ -40,7 +83,7 @@ success = True for network in conn.network.networks(): if network.provider_physical_network is None and ( - network.provider_network_type == 'vxlan') and ( + network.provider_network_type in NETWORK_TYPE_OVERHEAD_DIFF) and ( 'adapted_mtu' not in network.tags): print("adapted_mtu tag is not set for the Network " "[" + str(network.name) + "]") @@ -60,7 +103,8 @@ for network in conn.network.networks(): try: if network.provider_physical_network is None and ( - network.provider_network_type == 'vxlan') and ( + network.provider_network_type in + NETWORK_TYPE_OVERHEAD_DIFF) and ( 'adapted_mtu' not in network.tags): print("Updating the mtu and the tag 'adapted_mtu" " of the network - " + str(network.name)) @@ -68,7 +112,8 @@ new_tags.append('adapted_mtu') conn.network.update_network( network, - mtu=int(network.mtu) - GENEVE_TO_VXLAN_OVERHEAD) + mtu=int(network.mtu) - NETWORK_TYPE_OVERHEAD_DIFF[ + network.provider_network_type]) conn.network.set_tags(network, new_tags) except Exception as e: print("Exception occured while updating the MTU:" + str(e)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/ovn/neutron_ovn_db_sync_util.py neutron-16.4.2/neutron/cmd/ovn/neutron_ovn_db_sync_util.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/ovn/neutron_ovn_db_sync_util.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/ovn/neutron_ovn_db_sync_util.py 2021-11-12 13:56:42.000000000 +0000 @@ -27,6 +27,7 @@ from neutron.plugins.ml2.drivers.ovn.mech_driver import mech_driver from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker from neutron.plugins.ml2 import plugin as ml2_plugin LOG = logging.getLogger(__name__) @@ -63,7 +64,7 @@ def create_port_postcommit(self, context): port = context.current - self.ovn_client.create_port(context, port) + self.ovn_client.create_port(context._plugin_context, port) def update_port_precommit(self, context): pass @@ -71,7 +72,8 @@ def update_port_postcommit(self, context): port = context.current original_port = context.original - self.ovn_client.update_port(context, port, original_port) + self.ovn_client.update_port(context._plugin_context, port, + original_port) def delete_port_precommit(self, context): pass @@ -81,8 +83,7 @@ port['network'] = context.network.current # FIXME(lucasagomes): PortContext does not have a session, therefore # we need to use the _plugin_context attribute. - self.ovn_client.delete_port(context._plugin_context, port['id'], - port_object=port) + self.ovn_client.delete_port(context._plugin_context, port['id']) class AgentNotifierApi(object): @@ -135,6 +136,9 @@ cfg.CONF.register_cli_opts(ovn_opts, group=ovn_group) db_group, neutron_db_opts = db_options.list_opts()[0] cfg.CONF.register_cli_opts(neutron_db_opts, db_group) + # Override Nova notify configuration LP: #1882020 + cfg.CONF.set_override('notify_nova_on_port_status_changes', False) + cfg.CONF.set_override('notify_nova_on_port_data_changes', False) return conf @@ -179,28 +183,32 @@ return cfg.CONF.set_override('mechanism_drivers', ['ovn-sync'], 'ml2') conf.service_plugins = [ - 'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin'] + 'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin', + 'neutron.services.segments.plugin.Plugin'] else: LOG.error('Invalid core plugin : ["%s"].', cfg.CONF.core_plugin) return + mech_worker = worker.MaintenanceWorker try: - conn = impl_idl_ovn.get_connection(impl_idl_ovn.OvsdbNbOvnIdl) - ovn_api = impl_idl_ovn.OvsdbNbOvnIdl(conn) + ovn_api = impl_idl_ovn.OvsdbNbOvnIdl.from_worker(mech_worker) except RuntimeError: LOG.error('Invalid --ovn-ovn_nb_connection parameter provided.') return try: - sb_conn = impl_idl_ovn.get_connection(impl_idl_ovn.OvsdbSbOvnIdl) - ovn_sb_api = impl_idl_ovn.OvsdbSbOvnIdl(sb_conn) + ovn_sb_api = impl_idl_ovn.OvsdbSbOvnIdl.from_worker(mech_worker) except RuntimeError: LOG.error('Invalid --ovn-ovn_sb_connection parameter provided.') return manager.init() core_plugin = directory.get_plugin() - ovn_driver = core_plugin.mechanism_manager.mech_drivers['ovn-sync'].obj + driver = core_plugin.mechanism_manager.mech_drivers['ovn-sync'] + # The L3 code looks for the OVSDB connection on the 'ovn' driver + # and will fail with a KeyError if it isn't there + core_plugin.mechanism_manager.mech_drivers['ovn'] = driver + ovn_driver = driver.obj ovn_driver._nb_ovn = ovn_api ovn_driver._sb_ovn = ovn_sb_api diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/runtime_checks.py neutron-16.4.2/neutron/cmd/runtime_checks.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/runtime_checks.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/runtime_checks.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import exceptions from oslo_log import log as logging from neutron.agent.linux import utils as agent_utils @@ -35,3 +36,13 @@ "Exception: %s", e) return False return True + + +def dnsmasq_host_tag_support(): + cmd = ['dnsmasq', '--test', '--dhcp-host=tag:foo'] + env = {'LC_ALL': 'C', 'PATH': '/sbin:/usr/sbin'} + try: + agent_utils.execute(cmd, addl_env=env, log_fail_as_error=False) + except exceptions.ProcessExecutionError: + return False + return True diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/sanity/checks.py neutron-16.4.2/neutron/cmd/sanity/checks.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/sanity/checks.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/sanity/checks.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,6 +21,7 @@ import netaddr from neutron_lib import constants as n_consts from neutron_lib import exceptions +from neutron_lib.utils import helpers from oslo_config import cfg from oslo_log import log as logging from oslo_utils import uuidutils @@ -31,11 +32,11 @@ from neutron.agent.l3 import namespaces from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib -from neutron.agent.linux import ip_link_support from neutron.agent.linux import keepalived from neutron.agent.linux import utils as agent_utils from neutron.cmd import runtime_checks from neutron.common import utils as common_utils +from neutron.conf.agent.l3 import config as l3_config from neutron.plugins.ml2.drivers.openvswitch.agent.common \ import constants as ovs_const @@ -51,16 +52,26 @@ def ovs_vxlan_supported(from_ip='192.0.2.1', to_ip='192.0.2.2'): - name = common_utils.get_rand_device_name(prefix='vxlantest-') - with ovs_lib.OVSBridge(name) as br: - port = br.add_tunnel_port(from_ip, to_ip, n_consts.TYPE_VXLAN) + br_name = common_utils.get_rand_device_name(prefix='vxlantest-') + port_name = common_utils.get_rand_device_name(prefix='vxlantest-') + with ovs_lib.OVSBridge(br_name) as br: + port = br.add_tunnel_port( + port_name=port_name, + remote_ip=from_ip, + local_ip=to_ip, + tunnel_type=n_consts.TYPE_VXLAN) return port != ovs_lib.INVALID_OFPORT def ovs_geneve_supported(from_ip='192.0.2.3', to_ip='192.0.2.4'): - name = common_utils.get_rand_device_name(prefix='genevetest-') - with ovs_lib.OVSBridge(name) as br: - port = br.add_tunnel_port(from_ip, to_ip, n_consts.TYPE_GENEVE) + br_name = common_utils.get_rand_device_name(prefix='genevetest-') + port_name = common_utils.get_rand_device_name(prefix='genevetest-') + with ovs_lib.OVSBridge(br_name) as br: + port = br.add_tunnel_port( + port_name=port_name, + remote_ip=from_ip, + local_ip=to_ip, + tunnel_type=n_consts.TYPE_GENEVE) return port != ovs_lib.INVALID_OFPORT @@ -149,41 +160,6 @@ actions="NORMAL") -def _vf_management_support(required_caps): - is_supported = True - try: - vf_section = ip_link_support.IpLinkSupport.get_vf_mgmt_section() - for cap in required_caps: - if not ip_link_support.IpLinkSupport.vf_mgmt_capability_supported( - vf_section, cap): - is_supported = False - LOG.debug("ip link command does not support " - "vf capability '%(cap)s'", {'cap': cap}) - except ip_link_support.UnsupportedIpLinkCommand: - LOG.exception("Unexpected exception while checking supported " - "ip link command") - return False - return is_supported - - -def vf_management_supported(): - required_caps = ( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_SPOOFCHK, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE) - return _vf_management_support(required_caps) - - -def vf_extended_management_supported(): - required_caps = ( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_SPOOFCHK, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, - ) - return _vf_management_support(required_caps) - - def netns_read_requires_helper(): nsname = "netnsreadtest-" + uuidutils.generate_uuid() ip_lib.create_network_namespace(nsname) @@ -295,6 +271,7 @@ class KeepalivedIPv6Test(object): def __init__(self, ha_port, gw_port, gw_vip, default_gw): + l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF) self.ha_port = ha_port self.gw_port = gw_port self.gw_vip = gw_vip @@ -539,3 +516,36 @@ return agent_utils.execute(cmd, log_fail_as_error=False) except exceptions.ProcessExecutionError: return False + + +def min_tx_rate_support(): + device_mappings = helpers.parse_mappings( + cfg.CONF.SRIOV_NIC.physical_device_mappings, unique_keys=False) + devices_to_test = set() + for devices_in_physnet in device_mappings.values(): + for device in devices_in_physnet: + devices_to_test.add(device) + + # NOTE(ralonsoh): the VF used by default is 0. Each SR-IOV configured + # NIC should have configured at least 1 VF. + VF_NUM = 0 + devices_without_support = set() + for device in devices_to_test: + try: + ip_link = ip_lib.IpLinkCommand(device) + # NOTE(ralonsoh): to set min_tx_rate, first is needed to set + # max_tx_rate and max_tx_rate >= min_tx_rate. + vf_config = {'vf': VF_NUM, 'rate': {'min_tx_rate': int(400), + 'max_tx_rate': int(500)}} + ip_link.set_vf_feature(vf_config) + vf_config = {'vf': VF_NUM, 'rate': {'min_tx_rate': 0, + 'max_tx_rate': 0}} + ip_link.set_vf_feature(vf_config) + except ip_lib.InvalidArgument: + devices_without_support.add(device) + + if devices_without_support: + LOG.debug('The following NICs do not support "min_tx_rate": %s', + devices_without_support) + return False + return True diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/sanity_check.py neutron-16.4.2/neutron/cmd/sanity_check.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/cmd/sanity_check.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/cmd/sanity_check.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,9 +23,12 @@ from neutron.cmd.sanity import checks from neutron.common import config from neutron.conf.agent import securitygroups_rpc +from neutron.conf import common as common_config from neutron.conf.db import l3_hamode_db from neutron.conf.plugins.ml2 import config as ml2_conf from neutron.conf.plugins.ml2.drivers import linuxbridge as lb_conf +from neutron.conf.plugins.ml2.drivers.mech_sriov import agent_common as \ + sriov_conf from neutron.conf.plugins.ml2.drivers import ovs_conf @@ -35,10 +38,12 @@ def setup_conf(): ovs_conf.register_ovs_agent_opts(cfg.CONF) lb_conf.register_linuxbridge_opts(cfg.CONF) + sriov_conf.register_agent_sriov_nic_opts(cfg.CONF) ml2_conf.register_ml2_plugin_opts(cfg.CONF) securitygroups_rpc.register_securitygroups_opts(cfg.CONF) dhcp_agent.register_options(cfg.CONF) l3_hamode_db.register_db_l3_hamode_opts(cfg.CONF) + common_config.register_core_common_config_opts(cfg.CONF) class BoolOptCallback(cfg.BoolOpt): @@ -192,25 +197,6 @@ return result -def check_vf_management(): - result = checks.vf_management_supported() - if not result: - LOG.error('Check for VF management support failed. ' - 'Please ensure that the version of ip link ' - 'being used has VF support.') - return result - - -def check_vf_extended_management(): - result = checks.vf_extended_management_supported() - if not result: - LOG.error('Check for VF extended management support failed. ' - 'Please ensure that the version of ip link ' - 'being used has VF extended support: version ' - '"iproute2-ss140804", git tag "v3.16.0"') - return result - - def check_ovsdb_native(): result = checks.ovsdb_native_supported() if not result: @@ -307,6 +293,15 @@ return result +def check_min_tx_rate_support(): + result = checks.min_tx_rate_support() + if not result: + LOG.warning('There are SR-IOV network interfaces that do not support ' + 'setting the minimum TX rate (dataplane enforced minimum ' + 'guaranteed bandwidth) "ip-link vf min_tx_rate".') + return result + + # Define CLI opts to test specific features, with a callback for the test OPTS = [ BoolOptCallback('ovs_vxlan', check_ovs_vxlan, default=False, @@ -325,10 +320,6 @@ help=_('Check for ARP header match support')), BoolOptCallback('icmpv6_header_match', check_icmpv6_header_match, help=_('Check for ICMPv6 header match support')), - BoolOptCallback('vf_management', check_vf_management, - help=_('Check for VF management support')), - BoolOptCallback('vf_extended_management', check_vf_extended_management, - help=_('Check for VF extended management support')), BoolOptCallback('read_netns', check_read_netns, help=_('Check netns permission settings')), BoolOptCallback('dnsmasq_local_service_supported', @@ -371,6 +362,10 @@ help=_('Check ip_nonlocal_bind kernel option works with ' 'network namespaces.'), default=False), + BoolOptCallback('check_min_tx_rate_support', check_min_tx_rate_support, + help=_('Check if the configured SR-IOV NICs support ' + 'the "ip-link vf min_tx_rate" parameter.'), + default=False), ] @@ -380,7 +375,6 @@ run all necessary tests, just by passing in the appropriate configs. """ - cfg.CONF.set_default('vf_management', True) cfg.CONF.set_default('arp_header_match', True) cfg.CONF.set_default('icmpv6_header_match', True) if 'vxlan' in cfg.CONF.AGENT.tunnel_types: @@ -411,9 +405,6 @@ cfg.CONF.set_default('ipset_installed', True) if cfg.CONF.SECURITYGROUP.enable_security_group: cfg.CONF.set_default('ip6tables_installed', True) - if ('sriovnicswitch' in cfg.CONF.ml2.mechanism_drivers and - 'qos' in cfg.CONF.ml2.extension_drivers): - cfg.CONF.set_default('vf_extended_management', True) if cfg.CONF.SECURITYGROUP.firewall_driver in ( 'iptables', 'iptables_hybrid', @@ -423,6 +414,8 @@ 'OVSHybridIptablesFirewallDriver'), ): cfg.CONF.set_default('bridge_firewalling', True) + if cfg.CONF.SRIOV_NIC.physical_device_mappings: + cfg.CONF.set_default('check_min_tx_rate_support', True) def all_tests_passed(): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/_constants.py neutron-16.4.2/neutron/common/_constants.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/_constants.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/_constants.py 2021-11-12 13:56:42.000000000 +0000 @@ -67,3 +67,13 @@ # Segmentation ID pool; DB select limit to improve the performace. IDPOOL_SELECT_SIZE = 100 + +# Ports with the following 'device_owner' values will not prevent +# network deletion. If delete_network() finds that all ports on a +# network have these owners, it will explicitly delete each port +# and allow network deletion to continue. Similarly, if delete_subnet() +# finds out that all existing IP Allocations are associated with ports +# with these owners, it will allow subnet deletion to proceed with the +# IP allocations being cleaned up by cascade. +AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP, + constants.DEVICE_OWNER_AGENT_GW] diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/eventlet_utils.py neutron-16.4.2/neutron/common/eventlet_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/eventlet_utils.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/eventlet_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -37,3 +37,9 @@ # fail on Windows when using pipes due to missing non-blocking IO # support. eventlet.monkey_patch(os=False) + # Monkey patch the original current_thread to use the up-to-date _active + # global variable. See https://bugs.launchpad.net/bugs/1863021 and + # https://github.com/eventlet/eventlet/issues/592 + import __original_module_threading as orig_threading + import threading # noqa + orig_threading.current_thread.__globals__['_active'] = threading._active diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/acl.py neutron-16.4.2/neutron/common/ovn/acl.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/acl.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/ovn/acl.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,15 +13,18 @@ # import netaddr - from neutron_lib import constants as const from neutron_lib import exceptions as n_exceptions from oslo_config import cfg +from oslo_log import log as logging from neutron._i18n import _ from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils + +LOG = logging.getLogger(__name__) + # Convert the protocol number from integer to strings because that's # how Neutron will pass it to us PROTOCOL_NAME_TO_NUM_MAP = {k: str(v) for k, v in @@ -86,9 +89,17 @@ def acl_remote_ip_prefix(r, ip_version): if not r['remote_ip_prefix']: return '' + cidr = netaddr.IPNetwork(r['remote_ip_prefix']) + normalized_ip_prefix = "%s/%s" % (cidr.network, cidr.prefixlen) + if r['remote_ip_prefix'] != normalized_ip_prefix: + LOG.info("remote_ip_prefix %(remote_ip_prefix)s configured in " + "rule %(rule_id)s is not normalized. Normalized CIDR " + "%(normalized_ip_prefix)s will be used instead.", + {'remote_ip_prefix': r['remote_ip_prefix'], + 'rule_id': r['id'], + 'normalized_ip_prefix': normalized_ip_prefix}) src_or_dst = 'src' if r['direction'] == const.INGRESS_DIRECTION else 'dst' - return ' && %s.%s == %s' % (ip_version, src_or_dst, - r['remote_ip_prefix']) + return ' && %s.%s == %s' % (ip_version, src_or_dst, normalized_ip_prefix) def _get_protocol_number(protocol): @@ -274,44 +285,17 @@ return sg -def acl_remote_group_id(r, ip_version, ovn=None): +def acl_remote_group_id(r, ip_version): if not r['remote_group_id']: return '' src_or_dst = 'src' if r['direction'] == const.INGRESS_DIRECTION else 'dst' - if (ovn and ovn.is_port_groups_supported()): - addrset_name = utils.ovn_pg_addrset_name(r['remote_group_id'], - ip_version) - else: - addrset_name = utils.ovn_addrset_name(r['remote_group_id'], - ip_version) + addrset_name = utils.ovn_pg_addrset_name(r['remote_group_id'], + ip_version) return ' && %s.%s == $%s' % (ip_version, src_or_dst, addrset_name) -def _add_sg_rule_acl_for_port(port, r): - # Update the match based on which direction this rule is for (ingress - # or egress). - match = acl_direction(r, port) - - # Update the match for IPv4 vs IPv6. - ip_match, ip_version, icmp = acl_ethertype(r) - match += ip_match - - # Update the match if an IPv4 or IPv6 prefix was specified. - match += acl_remote_ip_prefix(r, ip_version) - - # Update the match if remote group id was specified. - match += acl_remote_group_id(r, ip_version) - - # Update the match for the protocol (tcp, udp, icmp) and port/type - # range if specified. - match += acl_protocol_and_ports(r, icmp) - - # Finally, create the ACL entry for the direction specified. - return add_sg_rule_acl_for_port(port, r, match) - - -def _add_sg_rule_acl_for_port_group(port_group, r, ovn): +def _add_sg_rule_acl_for_port_group(port_group, r): # Update the match based on which direction this rule is for (ingress # or egress). match = acl_direction(r, port_group=port_group) @@ -324,7 +308,7 @@ match += acl_remote_ip_prefix(r, ip_version) # Update the match if remote group id was specified. - match += acl_remote_group_id(r, ip_version, ovn) + match += acl_remote_group_id(r, ip_version) # Update the match for the protocol (tcp, udp, icmp) and port/type # range if specified. @@ -342,8 +326,8 @@ def add_acls_for_sg_port_group(ovn, security_group, txn): for r in security_group['security_group_rules']: acl = _add_sg_rule_acl_for_port_group( - utils.ovn_port_group_name(security_group['id']), r, ovn) - txn.add(ovn.pg_acl_add(**acl)) + utils.ovn_port_group_name(security_group['id']), r) + txn.add(ovn.pg_acl_add(**acl, may_exist=True)) def update_acls_for_security_group(plugin, @@ -351,7 +335,6 @@ ovn, security_group_id, security_group_rule, - sg_ports_cache=None, is_add_acl=True): # Skip ACLs if security groups aren't enabled @@ -361,135 +344,19 @@ # Check if ACL log name and severity supported or not keep_name_severity = _acl_columns_name_severity_supported(ovn) - # If we're using a Port Group for this SG, just update it. - # Otherwise, keep the old behavior. - if (ovn.is_port_groups_supported() and - not ovn.get_address_set(security_group_id)): - acl = _add_sg_rule_acl_for_port_group( - utils.ovn_port_group_name(security_group_id), - security_group_rule, ovn) - # Remove ACL log name and severity if not supported - if is_add_acl: - if not keep_name_severity: - acl.pop('name') - acl.pop('severity') - ovn.pg_acl_add(**acl).execute(check_error=True) - else: - ovn.pg_acl_del(acl['port_group'], acl['direction'], - acl['priority'], acl['match']).execute( - check_error=True) - return - - # Get the security group ports. - sg_ports_cache = sg_ports_cache or {} - sg_ports = _get_sg_ports_from_cache(plugin, - admin_context, - sg_ports_cache, - security_group_id) - - # ACLs associated with a security group may span logical switches - sg_port_ids = [binding['port_id'] for binding in sg_ports] - sg_port_ids = list(set(sg_port_ids)) - port_list = plugin.get_ports(admin_context, - filters={'id': sg_port_ids}) - if not port_list: - return - - acl_new_values_dict = {} - update_port_list = [] - - # NOTE(lizk): We can directly locate the affected acl records, - # so no need to compare new acl values with existing acl objects. - for port in port_list: - # Skip trusted port - if utils.is_lsp_trusted(port): - continue - - update_port_list.append(port) - acl = _add_sg_rule_acl_for_port(port, security_group_rule) - # Remove lport and lswitch since we don't need them - acl.pop('lport') - acl.pop('lswitch') - # Remove ACL log name and severity if not supported, + acl = _add_sg_rule_acl_for_port_group( + utils.ovn_port_group_name(security_group_id), + security_group_rule) + # Remove ACL log name and severity if not supported + if is_add_acl: if not keep_name_severity: acl.pop('name') acl.pop('severity') - acl_new_values_dict[port['id']] = acl - - if not update_port_list: - return - lswitch_names = {p['network_id'] for p in update_port_list} - - ovn.update_acls(list(lswitch_names), - iter(update_port_list), - acl_new_values_dict, - need_compare=False, - is_add_acl=is_add_acl).execute(check_error=True) - - -def add_acls(plugin, admin_context, port, sg_cache, subnet_cache, ovn): - acl_list = [] - - # Skip ACLs if security groups aren't enabled - if not is_sg_enabled(): - return acl_list - - sec_groups = utils.get_lsp_security_groups(port) - if not sec_groups: - # If it is a trusted port or port security is disabled, allow all - # traffic. So don't add any ACLs. - if utils.is_lsp_trusted(port) or not utils.is_port_security_enabled( - port): - return acl_list - - # if port security is enabled, drop all traffic. - return drop_all_ip_traffic_for_port(port) - - # Drop all IP traffic to and from the logical port by default. - acl_list += drop_all_ip_traffic_for_port(port) - - # Add DHCP ACLs. - port_subnet_ids = set() - for ip in port['fixed_ips']: - if netaddr.IPNetwork(ip['ip_address']).version != 4: - continue - subnet = _get_subnet_from_cache(plugin, - admin_context, - subnet_cache, - ip['subnet_id']) - # Ignore duplicate DHCP ACLs for the subnet. - if subnet['id'] not in port_subnet_ids: - acl_list += add_acl_dhcp(port, subnet, True) - port_subnet_ids.add(subnet['id']) - - # We create an ACL entry for each rule on each security group applied - # to this port. - for sg_id in sec_groups: - sg = _get_sg_from_cache(plugin, - admin_context, - sg_cache, - sg_id) - for r in sg['security_group_rules']: - acl = _add_sg_rule_acl_for_port(port, r) - acl_list.append(acl) - - # Remove ACL log name and severity if not supported, - if not _acl_columns_name_severity_supported(ovn): - for acl in acl_list: - acl.pop('name') - acl.pop('severity') - - return acl_list - - -def acl_port_ips(port): - # Skip ACLs if security groups aren't enabled - if not is_sg_enabled(): - return {'ip4': [], 'ip6': []} - - ip_list = [x['ip_address'] for x in port.get('fixed_ips', [])] - ip_list.extend(utils.get_allowed_address_pairs_ip_addresses(port)) - return utils.sort_ips_by_version(ip_list) + ovn.pg_acl_add(**acl, may_exist=True).execute(check_error=True) + else: + ovn.pg_acl_del(acl['port_group'], acl['direction'], + acl['priority'], acl['match']).execute( + check_error=True) def filter_acl_dict(acl, extra_fields=None): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/constants.py neutron-16.4.2/neutron/common/ovn/constants.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/constants.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/ovn/constants.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,6 +26,7 @@ OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name' OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip' OVN_ROUTER_NAME_EXT_ID_KEY = 'neutron:router_name' +OVN_ROUTER_AZ_HINTS_EXT_ID_KEY = 'neutron:availability_zone_hints' OVN_ROUTER_IS_EXT_GW = 'neutron:is_ext_gw' OVN_GW_PORT_EXT_ID_KEY = 'neutron:gw_port_id' OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id' @@ -87,14 +88,88 @@ # unhosted router gateways to schedule. OVN_GATEWAY_INVALID_CHASSIS = 'neutron-ovn-invalid-chassis' -SUPPORTED_DHCP_OPTS = { - 4: ['netmask', 'router', 'dns-server', 'log-server', - 'lpr-server', 'swap-server', 'ip-forward-enable', - 'policy-filter', 'default-ttl', 'mtu', 'router-discovery', - 'router-solicitation', 'arp-timeout', 'ethernet-encap', - 'tcp-ttl', 'tcp-keepalive', 'nis-server', 'ntp-server', - 'tftp-server'], - 6: ['server-id', 'dns-server', 'domain-search']} +# NOTE(lucasagomes): These options were last synced from +# https://github.com/ovn-org/ovn/blob/feb5d6e81d5a0290aa3618a229c860d01200422e/lib/ovn-l7.h +# +# NOTE(lucasagomes): Whenever we update these lists please also update +# the related documentation at doc/source/ovn/dhcp_opts.rst +# +# Mappping between Neutron option names and OVN ones +SUPPORTED_DHCP_OPTS_MAPPING = { + 4: {'arp-timeout': 'arp_cache_timeout', + 'tcp-keepalive': 'tcp_keepalive_interval', + 'netmask': 'netmask', + 'router': 'router', + 'dns-server': 'dns_server', + 'log-server': 'log_server', + 'lpr-server': 'lpr_server', + 'domain-name': 'domain_name', + 'swap-server': 'swap_server', + 'policy-filter': 'policy_filter', + 'router-solicitation': 'router_solicitation', + 'nis-server': 'nis_server', + 'ntp-server': 'ntp_server', + 'server-id': 'server_id', + 'tftp-server': 'tftp_server', + 'classless-static-route': 'classless_static_route', + 'ms-classless-static-route': 'ms_classless_static_route', + 'ip-forward-enable': 'ip_forward_enable', + 'router-discovery': 'router_discovery', + 'ethernet-encap': 'ethernet_encap', + 'default-ttl': 'default_ttl', + 'tcp-ttl': 'tcp_ttl', + 'mtu': 'mtu', + 'lease-time': 'lease_time', + 'T1': 'T1', + 'T2': 'T2', + 'bootfile-name': 'bootfile_name', + 'wpad': 'wpad', + 'path-prefix': 'path_prefix', + 'tftp-server-address': 'tftp_server_address', + 'server-ip-address': 'tftp_server_address', + '1': 'netmask', + '3': 'router', + '6': 'dns_server', + '7': 'log_server', + '9': 'lpr_server', + '15': 'domain_name', + '16': 'swap_server', + '21': 'policy_filter', + '32': 'router_solicitation', + '35': 'arp_cache_timeout', + '38': 'tcp_keepalive_interval', + '41': 'nis_server', + '42': 'ntp_server', + '54': 'server_id', + '66': 'tftp_server', + '121': 'classless_static_route', + '249': 'ms_classless_static_route', + '19': 'ip_forward_enable', + '31': 'router_discovery', + '36': 'ethernet_encap', + '23': 'default_ttl', + '37': 'tcp_ttl', + '26': 'mtu', + '51': 'lease_time', + '58': 'T1', + '59': 'T2', + '67': 'bootfile_name', + '252': 'wpad', + '210': 'path_prefix', + '150': 'tftp_server_address'}, + 6: {'server-id': 'server_id', + 'dns-server': 'dns_server', + 'domain-search': 'domain_search', + 'ia-addr': 'ia_addr', + '2': 'server_id', + '5': 'ia_addr', + '24': 'domain_search', + '23': 'dns_server'}, +} + +# Special option for disabling DHCP via extra DHCP options +DHCP_DISABLED_OPT = 'dhcp_disabled' + DHCPV6_STATELESS_OPT = 'dhcpv6_stateless' # When setting global DHCP options, these options will be ignored @@ -189,12 +264,19 @@ PORT_CAP_SWITCHDEV = 'switchdev' +# The name of the port security group attribute is currently not in neutron nor +# neutron-lib api definitions or constants. To avoid importing the extension +# code directly we keep a copy here. +PORT_SECURITYGROUPS = 'security_groups' + # TODO(lucasagomes): Create constants for other LSP types LSP_TYPE_LOCALNET = 'localnet' LSP_TYPE_VIRTUAL = 'virtual' LSP_TYPE_EXTERNAL = 'external' LSP_OPTIONS_VIRTUAL_PARENTS_KEY = 'virtual-parents' LSP_OPTIONS_VIRTUAL_IP_KEY = 'virtual-ip' +LSP_OPTIONS_MCAST_FLOOD_REPORTS = 'mcast_flood_reports' +LSP_OPTIONS_MCAST_FLOOD = 'mcast_flood' HA_CHASSIS_GROUP_DEFAULT_NAME = 'default_ha_chassis_group' HA_CHASSIS_GROUP_HIGHEST_PRIORITY = 32767 @@ -210,3 +292,12 @@ # OVN igmp options MCAST_SNOOP = 'mcast_snoop' MCAST_FLOOD_UNREGISTERED = 'mcast_flood_unregistered' + +EXTERNAL_PORT_TYPES = (portbindings.VNIC_DIRECT, + portbindings.VNIC_DIRECT_PHYSICAL, + portbindings.VNIC_MACVTAP) + +NEUTRON_AVAILABILITY_ZONES = 'neutron-availability-zones' +OVN_CMS_OPTIONS = 'ovn-cms-options' +CMS_OPT_CHASSIS_AS_GW = 'enable-chassis-as-gw' +CMS_OPT_AVAILABILITY_ZONES = 'availability-zones' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/extensions.py neutron-16.4.2/neutron/common/ovn/extensions.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/extensions.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/ovn/extensions.py 2021-11-12 13:56:42.000000000 +0000 @@ -11,6 +11,11 @@ # License for the specific language governing permissions and limitations # under the License. + +from neutron_lib.api.definitions import agent as agent_def +from neutron_lib.api.definitions import availability_zone as az_def +from neutron_lib.api.definitions import router_availability_zone as raz_def + # NOTE(russellb) This remains in its own file (vs constants.py) because we want # to be able to easily import it and export the info without any dependencies # on external imports. @@ -26,13 +31,15 @@ 'sorting', 'project-id', 'dns-integration', + agent_def.ALIAS, + az_def.ALIAS, + raz_def.ALIAS, ] ML2_SUPPORTED_API_EXTENSIONS = [ 'address-scope', 'agent', 'allowed-address-pairs', 'auto-allocated-topology', - 'availability_zone', 'binding', 'default-subnetpools', 'external-net', @@ -44,6 +51,7 @@ 'port-security', 'provider', 'quotas', + 'rbac-address-scope', 'rbac-policies', 'standard-attr-revisions', 'security-group', diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/hash_ring_manager.py neutron-16.4.2/neutron/common/ovn/hash_ring_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/hash_ring_manager.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/ovn/hash_ring_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,6 +23,7 @@ from neutron.common.ovn import constants from neutron.common.ovn import exceptions from neutron.db import ovn_hash_ring_db as db_hash_ring +from neutron import service from neutron_lib import context LOG = log.getLogger(__name__) @@ -33,7 +34,7 @@ def __init__(self, group_name): self._hash_ring = None self._last_time_loaded = None - self._cache_startup_timeout = True + self._check_hashring_startup = True self._group = group_name self.admin_ctx = context.get_admin_context() @@ -42,24 +43,26 @@ # NOTE(lucasagomes): Some events are processed at the service's # startup time and since many services may be started concurrently # we do not want to use a cached hash ring at that point. This - # method checks if the created_at and updated_at columns from the - # nodes in the ring from this host is equal, and if so it means - # that the service just started. + # method ensures that we start allowing the use of cached HashRings + # once the number of HashRing nodes >= the number of api workers. # If the startup timeout already expired, there's no reason to # keep reading from the DB. At this point this will always # return False - if not self._cache_startup_timeout: + if not self._check_hashring_startup: return False + api_workers = service._get_api_workers() nodes = db_hash_ring.get_active_nodes( self.admin_ctx, constants.HASH_RING_CACHE_TIMEOUT, self._group, from_host=True) - dont_cache = nodes and nodes[0].created_at == nodes[0].updated_at - if not dont_cache: - self._cache_startup_timeout = False - return dont_cache + if len(nodes) >= api_workers: + LOG.debug("Allow caching, nodes %s>=%s", len(nodes), api_workers) + self._check_hashring_startup = False + return False + LOG.debug("Disallow caching, nodes %s<%s", len(nodes), api_workers) + return True def _load_hash_ring(self, refresh=False): cache_timeout = timeutils.utcnow() - datetime.timedelta( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/utils.py neutron-16.4.2/neutron/common/ovn/utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/ovn/utils.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/ovn/utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,6 +16,7 @@ import re import netaddr +from neutron_lib.api.definitions import availability_zone as az_def from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext from neutron_lib.api.definitions import l3 @@ -27,6 +28,8 @@ from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from neutron_lib.utils import net as n_utils +from oslo_config import cfg +from oslo_log import log from oslo_utils import netutils from oslo_utils import strutils from ovs.db import idl @@ -37,12 +40,18 @@ from neutron.common.ovn import constants from neutron.common.ovn import exceptions as ovn_exc +LOG = log.getLogger(__name__) + +CONF = cfg.CONF DNS_RESOLVER_FILE = "/etc/resolv.conf" AddrPairsDiff = collections.namedtuple( 'AddrPairsDiff', ['added', 'removed', 'changed']) +PortExtraDHCPValidation = collections.namedtuple( + 'PortExtraDHCPValidation', ['failed', 'invalid_ipv4', 'invalid_ipv6']) + def ovn_name(id): # The name of the OVN entry will be neutron- @@ -109,6 +118,39 @@ const.DEVICE_OWNER_PREFIXES) +def _is_dhcp_disabled(dhcp_opt): + return (dhcp_opt['opt_name'] == constants.DHCP_DISABLED_OPT and + dhcp_opt.get('opt_value', '').lower() == 'true') + + +def validate_port_extra_dhcp_opts(port): + """Validate port's extra DHCP options. + + :param port: A neutron port. + :returns: A PortExtraDHCPValidation object. + """ + invalid = {const.IP_VERSION_4: [], const.IP_VERSION_6: []} + failed = False + for edo in port.get(edo_ext.EXTRADHCPOPTS, []): + ip_version = edo['ip_version'] + opt_name = edo['opt_name'] + + # If DHCP is disabled for this port via this special option, + # always succeed the validation + if _is_dhcp_disabled(edo): + failed = False + break + + if opt_name not in constants.SUPPORTED_DHCP_OPTS_MAPPING[ip_version]: + invalid[ip_version].append(opt_name) + failed = True + + return PortExtraDHCPValidation( + failed=failed, + invalid_ipv4=invalid[const.IP_VERSION_4] if failed else [], + invalid_ipv6=invalid[const.IP_VERSION_6] if failed else []) + + def get_lsp_dhcp_opts(port, ip_version): # Get dhcp options from Neutron port, for setting DHCP_Options row # in OVN. @@ -117,12 +159,12 @@ if is_network_device_port(port): lsp_dhcp_disabled = True else: + mapping = constants.SUPPORTED_DHCP_OPTS_MAPPING[ip_version] for edo in port.get(edo_ext.EXTRADHCPOPTS, []): if edo['ip_version'] != ip_version: continue - if edo['opt_name'] == 'dhcp_disabled' and ( - edo['opt_value'] in ['True', 'true']): + if _is_dhcp_disabled(edo): # OVN native DHCP is disabled on this port lsp_dhcp_disabled = True # Make sure return value behavior not depends on the order and @@ -130,11 +172,13 @@ lsp_dhcp_opts.clear() break - if edo['opt_name'] not in ( - constants.SUPPORTED_DHCP_OPTS[ip_version]): + if edo['opt_name'] not in mapping: + LOG.warning('The DHCP option %(opt_name)s on port %(port)s ' + 'is not suppported by OVN, ignoring it', + {'opt_name': edo['opt_name'], 'port': port['id']}) continue - opt = edo['opt_name'].replace('-', '_') + opt = mapping[edo['opt_name']] lsp_dhcp_opts[opt] = edo['opt_value'] return (lsp_dhcp_disabled, lsp_dhcp_opts) @@ -166,6 +210,10 @@ return port.get(psec.PORTSECURITY) +def is_security_groups_enabled(port): + return port.get(constants.PORT_SECURITYGROUPS) + + def validate_and_get_data_from_binding_profile(port): if (constants.OVN_PORT_BINDING_PROFILE not in port or not validators.is_attr_set( @@ -312,8 +360,7 @@ def is_lsp_router_port(port): - return port.get('device_owner') in [const.DEVICE_OWNER_ROUTER_INTF, - const.DEVICE_OWNER_ROUTER_GW] + return port.get('device_owner') in const.ROUTER_PORT_OWNERS def get_lrouter_ext_gw_static_route(ovn_router): @@ -424,7 +471,7 @@ def is_provider_network(network): - return external_net.EXTERNAL in network + return network.get(external_net.EXTERNAL, False) def is_neutron_dhcp_agent_port(port): @@ -448,11 +495,15 @@ return AddrPairsDiff(added, removed, changed=any(added or removed)) +def get_ovn_cms_options(chassis): + """Return the list of CMS options in a Chassis.""" + return [opt.strip() for opt in chassis.external_ids.get( + constants.OVN_CMS_OPTIONS, '').split(',')] + + def is_gateway_chassis(chassis): """Check if the given chassis is a gateway chassis""" - external_ids = getattr(chassis, 'external_ids', {}) - return ('enable-chassis-as-gw' in external_ids.get( - 'ovn-cms-options', '').split(',')) + return constants.CMS_OPT_CHASSIS_AS_GW in get_ovn_cms_options(chassis) def get_port_capabilities(port): @@ -472,3 +523,48 @@ :returns: String containing router port_id. """ return constants.RE_PORT_FROM_GWC.search(row.name).group(2) + + +def get_az_hints(resource): + """Return the availability zone hints from a given resource.""" + return (resource.get(az_def.AZ_HINTS) or CONF.default_availability_zones) + + +def get_chassis_availability_zones(chassis): + """Return a list of availability zones from a given OVN Chassis.""" + azs = [] + if not chassis: + return azs + + opt_key = constants.CMS_OPT_AVAILABILITY_ZONES + '=' + for opt in get_ovn_cms_options(chassis): + if not opt.startswith(opt_key): + continue + values = opt.split('=')[1] + azs = [az.strip() for az in values.split(':') if az.strip()] + break + return azs + + +def get_network_name_from_datapath(datapath): + return datapath.external_ids['name'].replace('neutron-', '') + + +def connection_config_to_target_string(connection_config): + """Converts the Neutron NB/SB connection parameter to the OVN target string + + :param connection_config: Neutron OVN config parameter for the OVN NB or SB + database. See "ovn_sb_connection" or + "ovn_nb_connection" params. + :returns: (String) OVN NB/SB ``connection.target`` column value. + """ + regex = re.compile(r'^(?P\w+)\:((?P.+)\:(?P\d+)|' + r'(?P[\w\/\.]+))') + m = regex.match(connection_config) + if m: + _dict = m.groupdict() + if _dict['ip'] and _dict['port']: + return ('p' + _dict['proto'] + ':' + _dict['port'] + ':' + + _dict['ip']) + elif _dict['file']: + return 'p' + _dict['proto'] + ':' + _dict['file'] diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/utils.py neutron-16.4.2/neutron/common/utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/common/utils.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/common/utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -18,7 +18,6 @@ """Utilities and helper functions.""" -import datetime import functools import importlib import os @@ -37,7 +36,6 @@ import netaddr from neutron_lib import constants as n_const from neutron_lib.db import api as db_api -from neutron_lib import exceptions as n_exc from neutron_lib.services.trunk import constants as trunk_constants from neutron_lib.utils import helpers from oslo_config import cfg @@ -48,6 +46,10 @@ from oslo_utils import uuidutils from osprofiler import profiler import pkg_resources +from sqlalchemy.dialects.mysql import dialect as mysql_dialect +from sqlalchemy.dialects.postgresql import dialect as postgresql_dialect +from sqlalchemy.dialects.sqlite import dialect as sqlite_dialect +from sqlalchemy.sql.expression import func as sql_func import neutron from neutron._i18n import _ @@ -65,10 +67,6 @@ """Default exception coming from wait_until_true() function.""" -class TimerTimeout(n_exc.NeutronException): - message = _('Timer timeout expired after %(timeout)s second(s).') - - class LockWithTimer(object): def __init__(self, threshold): self._threshold = threshold @@ -865,72 +863,6 @@ "device mappings") % {'dev_name': dev_name}) -class Timer(object): - """Timer context manager class - - This class creates a context that: - - Triggers a timeout exception if the timeout is set. - - Returns the time elapsed since the context was initialized. - - Returns the time spent in the context once it's closed. - - The timeout exception can be suppressed; when the time expires, the context - finishes without rising TimerTimeout. - - NOTE(ralonsoh): this class, when a timeout is defined, cannot be used in - other than the main thread. When a timeout is defined, an alarm signal is - set. Only the main thread is allowed to set a signal handler and the signal - handlers are always executed in this main thread [1]. - [1] https://docs.python.org/3/library/signal.html#signals-and-threads - """ - def __init__(self, timeout=None, raise_exception=True): - self.start = self.delta = None - self._timeout = int(timeout) if timeout else None - self._timeout_flag = False - self._raise_exception = raise_exception - - def _timeout_handler(self, *_): - self._timeout_flag = True - if self._raise_exception: - raise TimerTimeout(timeout=self._timeout) - self.__exit__() - - def __enter__(self): - self.start = datetime.datetime.now() - if self._timeout: - signal.signal(signal.SIGALRM, self._timeout_handler) - signal.alarm(self._timeout) - return self - - def __exit__(self, *_): - if self._timeout: - signal.alarm(0) - self.delta = datetime.datetime.now() - self.start - - def __getattr__(self, item): - return getattr(self.delta, item) - - def __iter__(self): - self._raise_exception = False - return self.__enter__() - - def next(self): # pragma: no cover - # NOTE(ralonsoh): Python 2 support. - if not self._timeout_flag: - return datetime.datetime.now() - raise StopIteration() - - def __next__(self): # pragma: no cover - # NOTE(ralonsoh): Python 3 support. - return self.next() - - def __del__(self): - signal.alarm(0) - - @property - def delta_time_sec(self): - return (datetime.datetime.now() - self.start).total_seconds() - - def collect_profiler_info(): p = profiler.get() if p: @@ -987,3 +919,50 @@ ret = f(*args, **kwargs) return ret return wrapper + + +class SingletonDecorator(object): + + def __init__(self, klass): + self._klass = klass + self._instance = None + + def __call__(self, *args, **kwargs): + if self._instance is None: + self._instance = self._klass(*args, **kwargs) + return self._instance + + +def skip_exceptions(exceptions): + """Decorator to catch and hide any provided exception in the argument""" + + # NOTE(ralonsoh): could be rehomed to neutron-lib. + if not isinstance(exceptions, list): + exceptions = [exceptions] + + def decorator(function): + @functools.wraps(function) + def wrapper(*args, **kwargs): + try: + return function(*args, **kwargs) + except Exception as exc: + with excutils.save_and_reraise_exception() as ctx: + if issubclass(type(exc), tuple(exceptions)): + LOG.info('Skipped exception %s when calling method %s', + ctx.value.__repr__(), function.__repr__()) + ctx.reraise = False + return wrapper + return decorator + + +def get_sql_random_method(sql_dialect_name): + """Return the SQL random method supported depending on the dialect.""" + # NOTE(ralonsoh): this method is a good candidate to be implemented in + # oslo.db. + # https://www.postgresql.org/docs/8.2/functions-math.html + # https://www.sqlite.org/c3ref/randomness.html + if sql_dialect_name in (postgresql_dialect.name, sqlite_dialect.name): + return sql_func.random + # https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html + elif sql_dialect_name == mysql_dialect.name: + return sql_func.rand diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/agent/l3/config.py neutron-16.4.2/neutron/conf/agent/l3/config.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/agent/l3/config.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/conf/agent/l3/config.py 2021-11-12 13:56:42.000000000 +0000 @@ -106,6 +106,12 @@ 'the state change monitor. NOTE: Setting to True ' 'could affect the data plane when stopping or ' 'restarting the L3 agent.')), + cfg.BoolOpt('keepalived_use_no_track', + default=True, + help=_('If keepalived without support for "no_track" option ' + 'is used, this should be set to False. ' + 'Support for this option was introduced in keepalived ' + '2.x')) ] diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/common.py neutron-16.4.2/neutron/conf/common.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/common.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/conf/common.py 2021-11-12 13:56:42.000000000 +0000 @@ -137,7 +137,15 @@ 'this value without modification. For overlay networks ' 'such as VXLAN, neutron automatically subtracts the ' 'overlay protocol overhead from this value. Defaults ' - 'to 1500, the standard value for Ethernet.')) + 'to 1500, the standard value for Ethernet.')), + cfg.IntOpt('http_retries', default=3, min=0, + help=_("Number of times client connections (nova, ironic) " + "should be retried on a failed HTTP call. 0 (zero) means" + "connection is attempted only once (not retried). " + "Setting to any positive integer means that on failure " + "the connection is retried that many times. " + "For example, setting to 3 means total attempts to " + "connect will be 4.")) ] core_cli_opts = [ diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py neutron-16.4.2/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py 2021-11-12 13:56:42.000000000 +0000 @@ -65,8 +65,13 @@ "hypervisor name is used to locate the parent of the " "resource provider tree. Only needs to be set in the " "rare case when the hypervisor name is different from " - "the DEFAULT.host config option value as known by the " - "nova-compute managing that hypervisor.")), + "the resource_provider_default_hypervisor config " + "option value as known by the nova-compute managing " + "that hypervisor.")), + cfg.StrOpt('resource_provider_default_hypervisor', + help=_("The default hypervisor name used to locate the parent " + "of the resource provider. If this option is not set, " + "canonical name is used")), cfg.DictOpt('resource_provider_inventory_defaults', default={'allocation_ratio': 1.0, 'min_unit': 1, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/plugins/ml2/drivers/ovs_conf.py neutron-16.4.2/neutron/conf/plugins/ml2/drivers/ovs_conf.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/conf/plugins/ml2/drivers/ovs_conf.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/conf/plugins/ml2/drivers/ovs_conf.py 2021-11-12 13:56:42.000000000 +0000 @@ -84,8 +84,13 @@ "hypervisor name is used to locate the parent of the " "resource provider tree. Only needs to be set in the " "rare case when the hypervisor name is different from " - "the DEFAULT.host config option value as known by the " - "nova-compute managing that hypervisor.")), + "the resource_provider_default_hypervisor config " + "option value as known by the nova-compute managing " + "that hypervisor.")), + cfg.StrOpt('resource_provider_default_hypervisor', + help=_("The default hypervisor name used to locate the parent " + "of the resource provider. If this option is not set, " + "canonical name is used")), cfg.DictOpt('resource_provider_inventory_defaults', default={'allocation_ratio': 1.0, 'min_unit': 1, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/agentschedulers_db.py neutron-16.4.2/neutron/db/agentschedulers_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/agentschedulers_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/agentschedulers_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -17,12 +17,16 @@ import random import time +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context as ncontext from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc from neutron_lib.exceptions import agent as agent_exc from neutron_lib.exceptions import dhcpagentscheduler as das_exc +from neutron_lib.plugins import directory from oslo_config import cfg from oslo_log import log as logging import oslo_messaging @@ -36,6 +40,7 @@ from neutron.db.availability_zone import network as network_az from neutron.extensions import dhcpagentscheduler from neutron.objects import network +from neutron.objects import subnet as subnet_obj from neutron import worker as neutron_worker @@ -227,12 +232,15 @@ additional_time) return agent_expected_up > timeutils.utcnow() - def _schedule_network(self, context, network_id, dhcp_notifier): + def _schedule_network(self, context, network_id, dhcp_notifier, + candidate_hosts=None): LOG.info("Scheduling unhosted network %s", network_id) try: # TODO(enikanorov): have to issue redundant db query # to satisfy scheduling interface network = self.get_network(context, network_id) + if candidate_hosts: + network['candidate_hosts'] = candidate_hosts agents = self.schedule_network(context, network) if not agents: LOG.info("Failed to schedule network %s, " @@ -483,6 +491,25 @@ if self.network_scheduler: self.network_scheduler.auto_schedule_networks(self, context, host) + @registry.receives(resources.SEGMENT_HOST_MAPPING, [events.AFTER_CREATE]) + def auto_schedule_new_network_segments(self, resource, event, trigger, + payload=None): + if not cfg.CONF.network_auto_schedule: + return + segment_plugin = directory.get_plugin('segments') + dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP) + segment_ids = payload.metadata.get('current_segment_ids') + segments = segment_plugin.get_segments( + payload.context, filters={'id': segment_ids}) + subnets = subnet_obj.Subnet.get_objects( + payload.context, segment_id=segment_ids) + network_ids = {s.network_id for s in subnets} + for network_id in network_ids: + for segment in segments: + self._schedule_network( + payload.context, network_id, dhcp_notifier, + candidate_hosts=segment['hosts']) + class AZDhcpAgentSchedulerDbMixin(DhcpAgentSchedulerDbMixin, network_az.NetworkAvailabilityZoneMixin): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/agents_db.py neutron-16.4.2/neutron/db/agents_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/agents_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/agents_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -270,6 +270,9 @@ payload=events.DBEventPayload( context, states=(agent,), resource_id=id)) agent.delete() + registry.publish(resources.AGENT, events.AFTER_DELETE, self, + payload=events.DBEventPayload( + context, states=(agent,), resource_id=id)) @db_api.retry_if_session_inactive() def update_agent(self, context, id, agent): @@ -431,6 +434,7 @@ agent_state['agent_status'] = status agent_state['admin_state_up'] = agent.admin_state_up + agent_state['id'] = agent.id registry.publish(resources.AGENT, event_type, self, payload=events.DBEventPayload( context=context, metadata={ diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/allowedaddresspairs_db.py neutron-16.4.2/neutron/db/allowedaddresspairs_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/allowedaddresspairs_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/allowedaddresspairs_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # +import collections from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import port as port_def @@ -65,6 +66,17 @@ return [self._make_allowed_address_pairs_dict(pair.db_obj) for pair in pairs] + def get_allowed_address_pairs_for_ports(self, context, port_ids): + pairs = ( + obj_addr_pair.AllowedAddressPair. + get_allowed_address_pairs_for_ports( + context, port_ids=port_ids)) + result = collections.defaultdict(list) + for pair in pairs: + result[pair.port_id].append( + self._make_allowed_address_pairs_dict(pair.db_obj)) + return result + @staticmethod @resource_extend.extends([port_def.COLLECTION_NAME]) def _extend_port_dict_allowed_address_pairs(port_res, port_db): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/db_base_plugin_common.py neutron-16.4.2/neutron/db/db_base_plugin_common.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/db_base_plugin_common.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/db_base_plugin_common.py 2021-11-12 13:56:42.000000000 +0000 @@ -218,7 +218,8 @@ def _make_port_dict(self, port, fields=None, process_extensions=True, - with_fixed_ips=True): + with_fixed_ips=True, + bulk=False): mac = port["mac_address"] if isinstance(mac, netaddr.EUI): mac.dialect = netaddr.mac_unix_expanded @@ -241,8 +242,10 @@ port_data = port if isinstance(port, port_obj.Port): port_data = port.db_obj + res['bulk'] = bulk resource_extend.apply_funcs( port_def.COLLECTION_NAME, res, port_data) + res.pop('bulk') return db_utils.resource_fields(res, fields) def _get_network(self, context, id): @@ -261,6 +264,11 @@ raise exceptions.SubnetNotFound(subnet_id=id) return subnet + def _network_exists(self, context, network_id): + query = model_query.query_with_hooks( + context, models_v2.Network, field='id') + return query.filter(models_v2.Network.id == network_id).first() + def _get_subnet_object(self, context, id): subnet = subnet_obj.Subnet.get_object(context, id=id) if not subnet: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/db_base_plugin_v2.py neutron-16.4.2/neutron/db/db_base_plugin_v2.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/db_base_plugin_v2.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/db_base_plugin_v2.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,8 +16,8 @@ import functools import netaddr +from neutron_lib.api.definitions import external_net as extnet_def from neutron_lib.api.definitions import ip_allocation as ipalloc_apidef -from neutron_lib.api.definitions import port as port_def from neutron_lib.api.definitions import portbindings as portbindings_def from neutron_lib.api.definitions import subnetpool as subnetpool_def from neutron_lib.api import validators @@ -47,6 +47,7 @@ from neutron._i18n import _ from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api +from neutron.common import _constants from neutron.common import ipv6_utils from neutron.common import utils from neutron.db import db_base_plugin_common @@ -69,17 +70,10 @@ LOG = logging.getLogger(__name__) -# Ports with the following 'device_owner' values will not prevent -# network deletion. If delete_network() finds that all ports on a -# network have these owners, it will explicitly delete each port -# and allow network deletion to continue. Similarly, if delete_subnet() -# finds out that all existing IP Allocations are associated with ports -# with these owners, it will allow subnet deletion to proceed with the -# IP allocations being cleaned up by cascade. -AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP] - -def _check_subnet_not_used(context, subnet_id): +def _ensure_subnet_not_used(context, subnet_id): + models_v2.Subnet.lock_register( + context, exc.SubnetInUse(subnet_id=subnet_id), id=subnet_id) try: registry.publish( resources.SUBNET, events.BEFORE_DELETE, None, @@ -423,10 +417,12 @@ return network @db_api.retry_if_session_inactive() - def update_network(self, context, id, network): + def update_network(self, context, id, network, db_network=None): n = network['network'] + # we dont't use DB objects not belonging to the current active session + db_network = db_network if context.session.is_active else None with db_api.CONTEXT_WRITER.using(context): - network = self._get_network(context, id) + network = db_network or self._get_network(context, id) # validate 'shared' parameter if 'shared' in n: entry = None @@ -471,7 +467,8 @@ def _ensure_network_not_in_use(self, context, net_id): non_auto_ports = context.session.query( models_v2.Port.id).filter_by(network_id=net_id).filter( - ~models_v2.Port.device_owner.in_(AUTO_DELETE_PORT_OWNERS)) + ~models_v2.Port.device_owner.in_( + _constants.AUTO_DELETE_PORT_OWNERS)) if non_auto_ports.count(): raise exc.NetworkInUse(net_id=net_id) @@ -484,7 +481,8 @@ with db_api.CONTEXT_READER.using(context): auto_delete_port_ids = [p.id for p in context.session.query( models_v2.Port.id).filter_by(network_id=id).filter( - models_v2.Port.device_owner.in_(AUTO_DELETE_PORT_OWNERS))] + models_v2.Port.device_owner.in_( + _constants.AUTO_DELETE_PORT_OWNERS))] for port_id in auto_delete_port_ids: try: self.delete_port(context.elevated(), port_id) @@ -499,11 +497,16 @@ # cleanup if a network-owned port snuck in without failing for subnet in subnets: self._delete_subnet(context, subnet) + # TODO(ralonsoh): use payloads + registry.notify(resources.SUBNET, events.AFTER_DELETE, + self, context=context, subnet=subnet.to_dict(), + for_net_delete=True) with db_api.CONTEXT_WRITER.using(context): network_db = self._get_network(context, id) network = self._make_network_dict(network_db, context=context) registry.notify(resources.NETWORK, events.PRECOMMIT_DELETE, - self, context=context, network_id=id) + self, context=context, network_id=id, + network=network) # We expire network_db here because precommit deletion # might have left the relationship stale, for example, # if we deleted a segment. @@ -735,11 +738,12 @@ @db_api.retry_if_session_inactive() def _create_subnet_postcommit(self, context, result, network=None, ipam_subnet=None): - if not network: + # need to get db net obj with full subnet info either if no network + # is passed or if passed network is an external net dict from + # ml2 plugin (with only IDs in 'subnets') + if not network or network.get(extnet_def.EXTERNAL): network = self._get_network(context, result['network_id']) - if not ipam_subnet: - ipam_subnet = self.ipam.get_subnet(context, result['id']) if hasattr(network, 'external') and network.external: self._update_router_gw_ports(context, @@ -748,6 +752,8 @@ # If this subnet supports auto-addressing, then update any # internal ports on the network with addresses for this subnet. if ipv6_utils.is_auto_address_subnet(result): + if not ipam_subnet: + ipam_subnet = self.ipam.get_subnet(context, result['id']) updated_ports = self.ipam.add_auto_addrs_on_network_ports( context, result, ipam_subnet) for port_id in updated_ports: @@ -940,8 +946,8 @@ self, **kwargs) with db_api.CONTEXT_WRITER.using(context): - subnet, changes = self.ipam.update_db_subnet(context, id, s, - db_pools) + subnet, changes = self.ipam.update_db_subnet( + context, id, s, db_pools, subnet_obj=subnet_obj) return self._make_subnet_dict(subnet, context=context), orig @property @@ -994,13 +1000,11 @@ **kwargs) return result - @db_api.CONTEXT_READER def _subnet_get_user_allocation(self, context, subnet_id): """Check if there are any user ports on subnet and return first.""" return port_obj.IPAllocation.get_alloc_by_subnet_id( - context, subnet_id, AUTO_DELETE_PORT_OWNERS) + context, subnet_id, _constants.AUTO_DELETE_PORT_OWNERS) - @db_api.CONTEXT_READER def _subnet_check_ip_allocations_internal_router_ports(self, context, subnet_id): # Do not delete the subnet if IP allocations for internal @@ -1012,23 +1016,6 @@ "cannot delete", subnet_id) raise exc.SubnetInUse(subnet_id=subnet_id) - @db_api.retry_if_session_inactive() - def _remove_subnet_from_port(self, context, sub_id, port_id, auto_subnet): - try: - fixed = [f for f in self.get_port(context, port_id)['fixed_ips'] - if f['subnet_id'] != sub_id] - if auto_subnet: - # special flag to avoid re-allocation on auto subnets - fixed.append({'subnet_id': sub_id, 'delete_subnet': True}) - data = {port_def.RESOURCE_NAME: {'fixed_ips': fixed}} - self.update_port(context, port_id, data) - except exc.PortNotFound: - # port is gone - return - except exc.SubnetNotFound as e: - # another subnet in the fixed ips was concurrently removed. retry - raise os_db_exc.RetryRequest(e) - def _ensure_no_user_ports_on_subnet(self, context, id): alloc = self._subnet_get_user_allocation(context, id) if alloc: @@ -1040,48 +1027,43 @@ 'subnet': id}) raise exc.SubnetInUse(subnet_id=id) - @db_api.retry_if_session_inactive() def _remove_subnet_ip_allocations_from_ports(self, context, subnet): # Do not allow a subnet to be deleted if a router is attached to it + sid = subnet['id'] self._subnet_check_ip_allocations_internal_router_ports( - context, subnet.id) + context, sid) is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet) if not is_auto_addr_subnet: # we only automatically remove IP addresses from user ports if # the IPs come from auto allocation subnets. - self._ensure_no_user_ports_on_subnet(context, subnet.id) - net_allocs = (context.session.query(models_v2.IPAllocation.port_id). - filter_by(subnet_id=subnet.id)) - port_ids_on_net = [ipal.port_id for ipal in net_allocs] - for port_id in port_ids_on_net: - self._remove_subnet_from_port(context, subnet.id, port_id, - auto_subnet=is_auto_addr_subnet) + self._ensure_no_user_ports_on_subnet(context, sid) + + port_obj.IPAllocation.delete_alloc_by_subnet_id(context, sid) @db_api.retry_if_session_inactive() def delete_subnet(self, context, id): LOG.debug("Deleting subnet %s", id) - # Make sure the subnet isn't used by other resources - _check_subnet_not_used(context, id) - subnet = self._get_subnet_object(context, id) - registry.publish(resources.SUBNET, - events.PRECOMMIT_DELETE_ASSOCIATIONS, - self, - payload=events.DBEventPayload(context, - resource_id=subnet.id)) - self._remove_subnet_ip_allocations_from_ports(context, subnet) - self._delete_subnet(context, subnet) + with db_api.CONTEXT_WRITER.using(context): + _ensure_subnet_not_used(context, id) + subnet = self._get_subnet_object(context, id) + registry.publish( + resources.SUBNET, events.PRECOMMIT_DELETE_ASSOCIATIONS, self, + payload=events.DBEventPayload(context, resource_id=subnet.id)) + self._remove_subnet_ip_allocations_from_ports(context, subnet) + self._delete_subnet(context, subnet) + registry.notify(resources.SUBNET, events.AFTER_DELETE, + self, context=context, subnet=subnet.to_dict()) def _delete_subnet(self, context, subnet): with db_api.exc_to_retry(sql_exc.IntegrityError), \ db_api.CONTEXT_WRITER.using(context): registry.notify(resources.SUBNET, events.PRECOMMIT_DELETE, - self, context=context, subnet_id=subnet.id) + self, context=context, subnet_id=subnet.id, + subnet_obj=subnet) subnet.delete() # Delete related ipam subnet manually, # since there is no FK relationship self.ipam.delete_subnet(context, subnet.id) - registry.notify(resources.SUBNET, events.AFTER_DELETE, - self, context=context, subnet=subnet.to_dict()) @db_api.retry_if_session_inactive() def get_subnet(self, context, id, fields=None): @@ -1335,7 +1317,7 @@ if not validators.is_attr_set(network_id): msg = _("network_id must be specified.") raise exc.InvalidInput(error_message=msg) - if not network_obj.Network.objects_exist(context, id=network_id): + if not self._network_exists(context, network_id): raise exc.NetworkNotFound(net_id=network_id) subnetpool = subnetpool_obj.SubnetPool.get_object(context, @@ -1445,7 +1427,8 @@ port_data['mac_address'] = p.get('mac_address') with db_api.CONTEXT_WRITER.using(context): # Ensure that the network exists. - self._get_network(context, network_id) + if not self._network_exists(context, network_id): + raise exc.NetworkNotFound(net_id=network_id) # Create the port db_port = self._create_db_port_obj(context, port_data) @@ -1483,11 +1466,13 @@ new_mac, current_owner) @db_api.retry_if_session_inactive() - def update_port(self, context, id, port): + def update_port(self, context, id, port, db_port=None): + # we dont't use DB objects not belonging to the current active session + db_port = db_port if context.session.is_active else None new_port = port['port'] with db_api.CONTEXT_WRITER.using(context): - db_port = self._get_port(context, id) + db_port = db_port or self._get_port(context, id) new_mac = new_port.get('mac_address') self._validate_port_for_update(context, db_port, new_port, new_mac) # Note: _make_port_dict is called here to load extension data @@ -1587,7 +1572,9 @@ sorts=sorts, limit=limit, marker_obj=marker_obj, page_reverse=page_reverse) - items = [self._make_port_dict(c, fields) for c in query] + items = [self._make_port_dict(c, fields, bulk=True) for c in query] + # TODO(obondarev): use neutron_lib constant + resource_extend.apply_funcs('ports_bulk', items, None) if limit and page_reverse: items.reverse() return items diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/ipam_backend_mixin.py neutron-16.4.2/neutron/db/ipam_backend_mixin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/ipam_backend_mixin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/ipam_backend_mixin.py 2021-11-12 13:56:42.000000000 +0000 @@ -81,6 +81,11 @@ raise exc.InvalidAllocationPool(pool=ip_pool) return ip_range_pools + @staticmethod + def _is_distributed_service(port): + return (port.get('device_owner') == const.DEVICE_OWNER_DHCP and + port.get('device_id').startswith('ovn')) + def delete_subnet(self, context, subnet_id): pass @@ -195,7 +200,8 @@ new_type.create() return updated_types - def update_db_subnet(self, context, subnet_id, s, oldpools): + def update_db_subnet(self, context, subnet_id, s, oldpools, + subnet_obj=None): changes = {} if "dns_nameservers" in s: changes['dns_nameservers'] = ( @@ -213,7 +219,7 @@ changes['service_types'] = ( self._update_subnet_service_types(context, subnet_id, s)) - subnet_obj = self._get_subnet_object(context, subnet_id) + subnet_obj = subnet_obj or self._get_subnet_object(context, subnet_id) subnet_obj.update_fields(s) subnet_obj.update() return subnet_obj, changes @@ -646,7 +652,8 @@ return fixed_ip_list def _ipam_get_subnets(self, context, network_id, host, service_type=None, - fixed_configured=False, fixed_ips=None): + fixed_configured=False, fixed_ips=None, + distributed_service=False): """Return eligible subnets If no eligible subnets are found, determine why and potentially raise @@ -654,8 +661,14 @@ """ subnets = subnet_obj.Subnet.find_candidate_subnets( context, network_id, host, service_type, fixed_configured, - fixed_ips) + fixed_ips, distributed_service=distributed_service) if subnets: + msg = ('This subnet is being modified by another concurrent ' + 'operation') + for subnet in subnets: + subnet.lock_register( + context, exc.SubnetInUse(subnet_id=subnet.id, reason=msg), + id=subnet.id) subnet_dicts = [self._make_subnet_dict(subnet, context=context) for subnet in subnets] # Give priority to subnets with service_types @@ -716,7 +729,8 @@ if old_ips and new_host_requested and not fixed_ips_requested: valid_subnets = self._ipam_get_subnets( context, old_port['network_id'], host, - service_type=old_port.get('device_owner')) + service_type=old_port.get('device_owner'), + distributed_service=self._is_distributed_service(old_port)) valid_subnet_ids = {s['id'] for s in valid_subnets} for fixed_ip in old_ips: if fixed_ip['subnet_id'] not in valid_subnet_ids: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/ipam_pluggable_backend.py neutron-16.4.2/neutron/db/ipam_pluggable_backend.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/ipam_pluggable_backend.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/ipam_pluggable_backend.py 2021-11-12 13:56:42.000000000 +0000 @@ -231,12 +231,14 @@ p = port['port'] fixed_configured = p['fixed_ips'] is not constants.ATTR_NOT_SPECIFIED fixed_ips = p['fixed_ips'] if fixed_configured else [] - subnets = self._ipam_get_subnets(context, - network_id=p['network_id'], - host=p.get(portbindings.HOST_ID), - service_type=p.get('device_owner'), - fixed_configured=fixed_configured, - fixed_ips=fixed_ips) + subnets = self._ipam_get_subnets( + context, + network_id=p['network_id'], + host=p.get(portbindings.HOST_ID), + service_type=p.get('device_owner'), + fixed_configured=fixed_configured, + fixed_ips=fixed_ips, + distributed_service=self._is_distributed_service(p)) v4, v6_stateful, v6_stateless = self._classify_subnets( context, subnets) @@ -348,7 +350,8 @@ subnets = self._ipam_get_subnets( context, network_id=port['network_id'], host=host, service_type=port.get('device_owner'), fixed_configured=True, - fixed_ips=changes.add + changes.original) + fixed_ips=changes.add + changes.original, + distributed_service=self._is_distributed_service(port)) except ipam_exc.DeferIpam: subnets = [] @@ -491,8 +494,8 @@ self._ipam_deallocate_ips(context, ipam_driver, port, port['fixed_ips']) - def update_db_subnet(self, context, id, s, old_pools): - subnet = obj_subnet.Subnet.get_object(context, id=id) + def update_db_subnet(self, context, id, s, old_pools, subnet_obj=None): + subnet = subnet_obj or obj_subnet.Subnet.get_object(context, id=id) old_segment_id = subnet.segment_id if subnet else None if 'segment_id' in s: self._validate_segment( @@ -503,7 +506,7 @@ # so create unchanged copy for ipam driver subnet_copy = copy.deepcopy(s) subnet, changes = super(IpamPluggableBackend, self).update_db_subnet( - context, id, s, old_pools) + context, id, s, old_pools, subnet_obj=subnet_obj) ipam_driver = driver.Pool.get_instance(None, context) # Set old allocation pools if no new pools are provided by user. @@ -542,7 +545,8 @@ p.get(portbindings.HOST_ID), p.get('device_owner'), fixed_configured, - p.get('fixed_ips')) + p.get('fixed_ips'), + distributed_service=self._is_distributed_service(p)) if subnet['id'] not in [s['id'] for s in subnet_candidates]: continue diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_agentschedulers_db.py neutron-16.4.2/neutron/db/l3_agentschedulers_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_agentschedulers_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/l3_agentschedulers_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -34,6 +34,7 @@ from neutron.objects import base as base_obj from neutron.objects import l3agent as rb_obj from neutron.objects import router as l3_objs +from neutron.scheduler import base_scheduler LOG = logging.getLogger(__name__) @@ -520,21 +521,9 @@ pager = base_obj.Pager(sorts=[('binding_index', True)]) bindings = rb_obj.RouterL3AgentBinding.get_objects( context, _pager=pager, router_id=router_id) - binding_indices = [b.binding_index for b in bindings] - all_indicies = set(range(rb_model.LOWEST_BINDING_INDEX, - num_agents + 1)) - open_slots = sorted(list(all_indicies - set(binding_indices))) - - if open_slots: - return open_slots[0] - - # Last chance: if this is a manual scheduling, we're gonna allow - # creation of a binding_index even if it will exceed - # max_l3_agents_per_router. - if is_manual_scheduling: - return max(all_indicies) + 1 - - return -1 + return base_scheduler.get_vacant_binding_index( + num_agents, bindings, rb_model.LOWEST_BINDING_INDEX, + force_scheduling=is_manual_scheduling) class AZL3AgentSchedulerDbMixin(L3AgentSchedulerDbMixin, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_db.py neutron-16.4.2/neutron/db/l3_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/l3_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -106,6 +106,15 @@ l3plugin.prevent_l3_port_deletion( payload.context, payload.resource_id) + @staticmethod + def _validate_subnet_address_mode(subnet): + if (subnet['ip_version'] == 6 and subnet['ipv6_ra_mode'] is None and + subnet['ipv6_address_mode'] is not None): + msg = (_('IPv6 subnet %s configured to receive RAs from an ' + 'external router cannot be added to Neutron Router.') % + subnet['id']) + raise n_exc.BadRequest(resource='router', msg=msg) + @property def _is_dns_integration_supported(self): if self._dns_integration is None: @@ -418,8 +427,7 @@ self._check_for_dup_router_subnets(context, router, new_network_id, - subnets, - include_gateway=True) + subnets) self._create_router_gw_port(context, router, new_network_id, ext_ips) @@ -544,8 +552,7 @@ filters=filters) def _check_for_dup_router_subnets(self, context, router, - network_id, new_subnets, - include_gateway=False): + network_id, new_subnets): # It's possible these ports are on the same network, but # different subnets. new_subnet_ids = {s['id'] for s in new_subnets} @@ -556,8 +563,13 @@ msg = (_("Router already has a port on subnet %s") % ip['subnet_id']) raise n_exc.BadRequest(resource='router', msg=msg) - gw_owner = (p.get('device_owner') == DEVICE_OWNER_ROUTER_GW) - if include_gateway == gw_owner: + if p.get('device_owner') == DEVICE_OWNER_ROUTER_GW: + ext_subts = self._core_plugin.get_subnets( + context.elevated(), + filters={'network_id': [p['network_id']]}) + for sub in ext_subts: + router_subnets.append(sub['id']) + else: router_subnets.append(ip['subnet_id']) # Ignore temporary Prefix Delegation CIDRs @@ -627,6 +639,13 @@ if not port['fixed_ips']: msg = _('Router port must have at least one fixed IP') raise n_exc.BadRequest(resource='router', msg=msg) + + fixed_ips = [ip for ip in port['fixed_ips']] + for fixed_ip in fixed_ips: + subnet = self._core_plugin.get_subnet( + context, fixed_ip['subnet_id']) + self._validate_subnet_address_mode(subnet) + return port def _validate_port_in_range_or_admin(self, context, subnets, port): @@ -751,12 +770,7 @@ msg = (_('Cannot add interface to router because subnet %s is not ' 'owned by project making the request') % subnet_id) raise n_exc.BadRequest(resource='router', msg=msg) - if (subnet['ip_version'] == 6 and subnet['ipv6_ra_mode'] is None and - subnet['ipv6_address_mode'] is not None): - msg = (_('IPv6 subnet %s configured to receive RAs from an ' - 'external router cannot be added to Neutron Router.') % - subnet['id']) - raise n_exc.BadRequest(resource='router', msg=msg) + self._validate_subnet_address_mode(subnet) self._check_for_dup_router_subnets(context, router, subnet['network_id'], [subnet]) fixed_ip = {'ip_address': subnet['gateway_ip'], @@ -1505,12 +1519,11 @@ @db_api.retry_if_session_inactive() def delete_disassociated_floatingips(self, context, network_id): - fip_objs = l3_obj.FloatingIP.get_objects( - context, - floating_network_id=network_id, router_id=None, fixed_port_id=None) + fip_ids = l3_obj.FloatingIP.get_disassociated_ids_for_net( + context, network_id) - for fip in fip_objs: - self.delete_floatingip(context, fip.id) + for fip_id in fip_ids: + self.delete_floatingip(context, fip_id) @db_api.retry_if_session_inactive() def get_floatingips_count(self, context, filters=None): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_dvr_db.py neutron-16.4.2/neutron/db/l3_dvr_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_dvr_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/l3_dvr_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,8 @@ # under the License. import collections +import netaddr +from neutron_lib.api.definitions import external_net as extnet_apidef from neutron_lib.api.definitions import l3 as l3_apidef from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings_extended @@ -49,7 +51,9 @@ from neutron.extensions import _admin_state_down_before_update_lib from neutron.ipam import utils as ipam_utils from neutron.objects import agent as ag_obj +from neutron.objects import base as base_obj from neutron.objects import l3agent as rb_obj +from neutron.objects import ports as port_obj from neutron.objects import router as l3_obj @@ -372,6 +376,11 @@ # to clean up the fip namespace as it is no longer required. self.l3plugin.l3_rpc_notifier.delete_fipnamespace_for_ext_net( payload.context, network_id) + # Delete the Floating IP agent gateway port + # bindings on all hosts + l3_obj.DvrFipGatewayPortAgentBinding.delete_objects( + payload.context, + network_id=network_id) def _delete_fip_agent_port(self, context, network_id, host_id): try: @@ -405,6 +414,15 @@ if host_id: return + @registry.receives(resources.NETWORK, [events.AFTER_DELETE]) + def delete_fip_namespaces_for_ext_net(self, rtype, event, trigger, + context, network, **kwargs): + if network.get(extnet_apidef.EXTERNAL): + # Send the information to all the L3 Agent hosts + # to clean up the fip namespace as it is no longer required. + self.l3plugin.l3_rpc_notifier.delete_fipnamespace_for_ext_net( + context, network['id']) + def _get_ports_for_allowed_address_pair_ip(self, context, network_id, fixed_ip): """Return all active ports associated with the allowed_addr_pair ip.""" @@ -465,6 +483,17 @@ fixed_ip_address)) if not addr_pair_active_service_port_list: return + self._inherit_service_port_and_arp_update( + context, addr_pair_active_service_port_list[0]) + + def _inherit_service_port_and_arp_update(self, context, service_port): + """Function inherits port host bindings for allowed_address_pair.""" + service_port_dict = self.l3plugin._core_plugin._make_port_dict( + service_port) + address_pair_list = service_port_dict.get('allowed_address_pairs') + for address_pair in address_pair_list: + self.update_arp_entry_for_dvr_service_port(context, + service_port_dict) @registry.receives(resources.ROUTER_INTERFACE, [events.BEFORE_CREATE]) @db_api.retry_if_session_inactive() @@ -741,6 +770,61 @@ p['id'], l3_port_check=False) + def _get_ext_nets_by_host(self, context, host): + ext_nets = set() + ports_ids = port_obj.Port.get_ports_by_host(context, host) + for port_id in ports_ids: + fips = self._get_floatingips_by_port_id(context, port_id) + for fip in fips: + ext_nets.add(fip.floating_network_id) + return ext_nets + + @registry.receives(resources.AGENT, [events.AFTER_CREATE]) + def create_fip_agent_gw_ports(self, resource, event, + trigger, payload=None): + """Create floating agent gw ports for DVR L3 agent. + + Create floating IP Agent gateway ports when an L3 agent is created. + """ + if not payload: + return + agent = payload.latest_state + if agent.get('agent_type') != const.AGENT_TYPE_L3: + return + # NOTE(slaweq) agent is passed in payload as dict so to avoid getting + # again from db, lets just get configuration from this dict directly + l3_agent_mode = agent.get('configurations', {}).get('agent_mode') + if l3_agent_mode not in [const.L3_AGENT_MODE_DVR, + const.L3_AGENT_MODE_DVR_SNAT]: + return + + host = agent['host'] + context = payload.context.elevated() + for ext_net in self._get_ext_nets_by_host(context, host): + self.create_fip_agent_gw_port_if_not_exists( + context, ext_net, host) + + @registry.receives(resources.AGENT, [events.AFTER_DELETE]) + def delete_fip_agent_gw_ports(self, resource, event, + trigger, payload=None): + """Delete floating agent gw ports for DVR. + + Delete floating IP Agent gateway ports from host when an L3 agent is + deleted. + """ + if not payload: + return + agent = payload.latest_state + if agent.get('agent_type') != const.AGENT_TYPE_L3: + return + if self._get_agent_mode(agent) not in [const.L3_AGENT_MODE_DVR, + const.L3_AGENT_MODE_DVR_SNAT]: + return + + agent_gw_ports = self._get_agent_gw_ports(payload.context, agent['id']) + for gw_port in agent_gw_ports: + self._core_plugin.delete_port(payload.context, gw_port['id']) + class _DVRAgentInterfaceMixin(object): """Contains calls made by the DVR scheduler and RPC interface. @@ -1017,6 +1101,14 @@ if ports: return ports[0] + def _get_agent_gw_ports(self, context, agent_id): + """Return agent gw ports.""" + filters = { + 'device_id': [agent_id], + 'device_owner': [const.DEVICE_OWNER_AGENT_GW] + } + return self._core_plugin.get_ports(context, filters) + def check_for_fip_and_create_agent_gw_port_on_host_if_not_exists( self, context, port, host): """Create fip agent_gw_port on host if not exists""" @@ -1068,13 +1160,14 @@ try: fip_agent_port_obj.create() except o_exc.NeutronDbObjectDuplicateEntry: - LOG.debug("Floating IP Agent Gateway port for network " + LOG.debug("Floating IP Gateway port agent binding for network " "%(network)s already exists on host %(host)s. " "Probably it was just created by other worker.", {'network': network_id, 'host': host}) agent_port = self._get_agent_gw_ports_exist_for_network( context, network_id, host, l3_agent_db['id']) + if agent_port: LOG.debug("Floating IP Agent Gateway port %(gw)s found " "for the destination host: %(dest_host)s", {'gw': agent_port, @@ -1101,6 +1194,21 @@ self._populate_mtu_and_subnets_for_ports(context, [agent_port]) return agent_port + def _generate_arp_table_and_notify_agent(self, context, fixed_ip, + mac_address, notifier): + """Generates the arp table entry and notifies the l3 agent.""" + ip_address = fixed_ip['ip_address'] + subnet = fixed_ip['subnet_id'] + arp_table = {'ip_address': ip_address, + 'mac_address': mac_address, + 'subnet_id': subnet} + filters = {'fixed_ips': {'subnet_id': [subnet]}, + 'device_owner': [const.DEVICE_OWNER_DVR_INTERFACE]} + ports = self._core_plugin.get_ports(context, filters=filters) + routers = [port['device_id'] for port in ports] + for router_id in routers: + notifier(context, router_id, arp_table) + def _get_subnet_id_for_given_fixed_ip(self, context, fixed_ip, port_dict): """Returns the subnet_id that matches the fixedip on a network.""" filters = {'network_id': [port_dict['network_id']]} @@ -1109,6 +1217,78 @@ if ipam_utils.check_subnet_ip(subnet['cidr'], fixed_ip): return subnet['id'] + def _get_allowed_address_pair_fixed_ips(self, context, port_dict): + """Returns all fixed_ips associated with the allowed_address_pair.""" + aa_pair_fixed_ips = [] + if port_dict.get('allowed_address_pairs'): + for address_pair in port_dict['allowed_address_pairs']: + aap_ip_cidr = address_pair['ip_address'].split("/") + if len(aap_ip_cidr) == 1 or int(aap_ip_cidr[1]) == 32: + subnet_id = self._get_subnet_id_for_given_fixed_ip( + context, aap_ip_cidr[0], port_dict) + if subnet_id is not None: + fixed_ip = {'subnet_id': subnet_id, + 'ip_address': aap_ip_cidr[0]} + aa_pair_fixed_ips.append(fixed_ip) + else: + LOG.debug("Subnet does not match for the given " + "fixed_ip %s for arp update", aap_ip_cidr[0]) + return aa_pair_fixed_ips + + def update_arp_entry_for_dvr_service_port(self, context, port_dict): + """Notify L3 agents of ARP table entry for dvr service port. + + When a dvr service port goes up, look for the DVR router on + the port's subnet, and send the ARP details to all + L3 agents hosting the router to add it. + If there are any allowed_address_pairs associated with the port + those fixed_ips should also be updated in the ARP table. + """ + fixed_ips = port_dict['fixed_ips'] + if not fixed_ips: + return + allowed_address_pair_fixed_ips = ( + self._get_allowed_address_pair_fixed_ips(context, port_dict)) + changed_fixed_ips = fixed_ips + allowed_address_pair_fixed_ips + for fixed_ip in changed_fixed_ips: + self._generate_arp_table_and_notify_agent( + context, fixed_ip, port_dict['mac_address'], + self.l3_rpc_notifier.add_arp_entry) + + def delete_arp_entry_for_dvr_service_port(self, context, port_dict, + fixed_ips_to_delete=None): + """Notify L3 agents of ARP table entry for dvr service port. + + When a dvr service port goes down, look for the DVR + router on the port's subnet, and send the ARP details to all + L3 agents hosting the router to delete it. + If there are any allowed_address_pairs associated with the + port, those fixed_ips should be removed from the ARP table. + """ + fixed_ips = port_dict['fixed_ips'] + if not fixed_ips: + return + if not fixed_ips_to_delete: + allowed_address_pair_fixed_ips = ( + self._get_allowed_address_pair_fixed_ips(context, port_dict)) + fixed_ips_to_delete = fixed_ips + allowed_address_pair_fixed_ips + for fixed_ip in fixed_ips_to_delete: + self._generate_arp_table_and_notify_agent( + context, fixed_ip, port_dict['mac_address'], + self.l3_rpc_notifier.del_arp_entry) + + def _get_address_pair_active_port_with_fip( + self, context, port_dict, port_addr_pair_ip): + port_valid_state = (port_dict['admin_state_up'] or + port_dict['status'] == const.PORT_STATUS_ACTIVE) + if not port_valid_state: + return + fips = l3_obj.FloatingIP.get_objects( + context, _pager=base_obj.Pager(limit=1), + fixed_ip_address=netaddr.IPAddress(port_addr_pair_ip)) + return self._core_plugin.get_port( + context, fips[0].fixed_port_id) if fips else None + class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin, DVRResourceOperationHandler, @@ -1218,11 +1398,23 @@ def get_ports_under_dvr_connected_subnet(self, context, subnet_id): query = dvr_mac_db.get_ports_query_by_subnet_and_ip(context, subnet_id) ports = [p for p in query.all() if is_port_bound(p)] - return [ + # TODO(slaweq): if there would be way to pass to neutron-lib only + # list of extensions which actually should be processed, than setting + # process_extensions=True below could avoid that second loop and + # getting allowed_address_pairs for each of the ports + ports_list = [ self.l3plugin._core_plugin._make_port_dict( port, process_extensions=False) for port in ports ] + ports_ids = [p['id'] for p in ports_list] + allowed_address_pairs = ( + self._core_plugin.get_allowed_address_pairs_for_ports( + context, ports_ids)) + for port in ports_list: + port['allowed_address_pairs'] = allowed_address_pairs.get( + port['id'], []) + return ports_list def is_distributed_router(router): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_dvrscheduler_db.py neutron-16.4.2/neutron/db/l3_dvrscheduler_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/l3_dvrscheduler_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/l3_dvrscheduler_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -440,20 +440,24 @@ for router_id in (router_ids - result_set): subnet_ids = self.get_subnet_ids_on_router( context, router_id, keep_gateway_port=False) - if (subnet_ids and + if (subnet_ids and ( self._check_dvr_serviceable_ports_on_host( context, agent_db['host'], - list(subnet_ids))): + list(subnet_ids)) or + self._is_router_related_to_dvr_routers( + context, router_id, dvr_routers))): result_set.add(router_id) LOG.debug("Routers %(router_ids)s are scheduled or have " "serviceable ports in host %(host)s", {'router_ids': result_set, 'host': agent_db['host']}) - for router_id in router_ids: - result_set |= set( + related_routers = set() + for router_id in result_set: + related_routers |= set( self._get_other_dvr_router_ids_connected_router( context, router_id)) + result_set |= related_routers LOG.debug("Router IDs %(router_ids)s for agent in host %(host)s", {'router_ids': result_set, @@ -506,6 +510,26 @@ query = query.filter(host_filter) return query.first() is not None + @log_helpers.log_method_call + def _is_router_related_to_dvr_routers(self, context, router_id, + dvr_routers): + related_routers = self._get_other_dvr_router_ids_connected_router( + context, router_id) + return any([r in dvr_routers for r in related_routers]) + + +def _dvr_handle_unbound_allowed_addr_pair_add( + plugin, context, port, allowed_address_pair): + plugin.update_arp_entry_for_dvr_service_port(context, port) + + +def _dvr_handle_unbound_allowed_addr_pair_del( + plugin, context, port, allowed_address_pair): + aa_fixed_ips = plugin._get_allowed_address_pair_fixed_ips(context, port) + if aa_fixed_ips: + plugin.delete_arp_entry_for_dvr_service_port( + context, port, fixed_ips_to_delete=aa_fixed_ips) + def _notify_l3_agent_new_port(resource, event, trigger, **kwargs): LOG.debug('Received %(resource)s %(event)s', { @@ -519,6 +543,7 @@ l3plugin = directory.get_plugin(plugin_constants.L3) context = kwargs['context'] l3plugin.dvr_handle_new_service_port(context, port) + l3plugin.update_arp_entry_for_dvr_service_port(context, port) def _notify_port_delete(event, resource, trigger, **kwargs): @@ -526,6 +551,14 @@ port = kwargs['port'] get_related_hosts_info = kwargs.get("get_related_hosts_info", True) l3plugin = directory.get_plugin(plugin_constants.L3) + if port: + port_host = port.get(portbindings.HOST_ID) + allowed_address_pairs_list = port.get('allowed_address_pairs') + if allowed_address_pairs_list and port_host: + for address_pair in allowed_address_pairs_list: + _dvr_handle_unbound_allowed_addr_pair_del( + l3plugin, context, port, address_pair) + l3plugin.delete_arp_entry_for_dvr_service_port(context, port) removed_routers = l3plugin.get_dvr_routers_to_remove( context, port, get_related_hosts_info) for info in removed_routers: @@ -614,7 +647,32 @@ context, new_port, dest_host=dest_host, router_id=fip_router_id) + l3plugin.update_arp_entry_for_dvr_service_port( + context, new_port) return + # Check for allowed_address_pairs and port state + new_port_host = new_port.get(portbindings.HOST_ID) + allowed_address_pairs_list = new_port.get('allowed_address_pairs') + if allowed_address_pairs_list and new_port_host: + new_port_state = new_port.get('admin_state_up') + original_port_state = original_port.get('admin_state_up') + if new_port_state: + # Case were we activate the port from inactive state, + # or the same port has additional address_pairs added. + for address_pair in allowed_address_pairs_list: + _dvr_handle_unbound_allowed_addr_pair_add( + l3plugin, context, new_port, address_pair) + return + elif original_port_state: + # Case were we deactivate the port from active state. + for address_pair in allowed_address_pairs_list: + _dvr_handle_unbound_allowed_addr_pair_del( + l3plugin, context, original_port, address_pair) + return + + if kwargs.get('mac_address_updated') or is_fixed_ips_changed: + l3plugin.update_arp_entry_for_dvr_service_port( + context, new_port) def subscribe(): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD neutron-16.4.2/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD 2021-11-12 13:56:42.000000000 +0000 @@ -1 +1 @@ -e88badaa9591 +d8bdf05313f4 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/migration/alembic_migrations/versions/rocky/expand/867d39095bf4_port_forwarding.py neutron-16.4.2/neutron/db/migration/alembic_migrations/versions/rocky/expand/867d39095bf4_port_forwarding.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/migration/alembic_migrations/versions/rocky/expand/867d39095bf4_port_forwarding.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/migration/alembic_migrations/versions/rocky/expand/867d39095bf4_port_forwarding.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,10 +26,15 @@ from neutron_lib.db import constants +from neutron.db import migration + # revision identifiers, used by Alembic. revision = '867d39095bf4' down_revision = '61663558142c' +# milestone identifier, used by neutron-db-manage +neutron_milestone = [migration.ROCKY] + def upgrade(): op.create_table( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/migration/alembic_migrations/versions/ussuri/expand/d8bdf05313f4_add_in_use_to_subnet.py neutron-16.4.2/neutron/db/migration/alembic_migrations/versions/ussuri/expand/d8bdf05313f4_add_in_use_to_subnet.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/migration/alembic_migrations/versions/ussuri/expand/d8bdf05313f4_add_in_use_to_subnet.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/db/migration/alembic_migrations/versions/ussuri/expand/d8bdf05313f4_add_in_use_to_subnet.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,53 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.engine import reflection + + +"""add in_use to subnet + +Revision ID: d8bdf05313f4 +Revises: e88badaa9591 +Create Date: 2020-03-13 17:15:38.462751 + +""" + +# revision identifiers, used by Alembic. +revision = 'd8bdf05313f4' +down_revision = 'e88badaa9591' + +TABLE = 'subnets' +COLUMN_IN_USE = 'in_use' + + +def upgrade(): + inspector = reflection.Inspector.from_engine(op.get_bind()) + # NOTE(ralonsoh): bug #1865891 is present in stable releases. Although is + # not possible to backport a patch implementing a DB change [1], we are + # planning to migrate this patch to a stable branch in a private + # repository. This check will not affect the current revision (this table + # column does not exist) and help us in the maintenance of our stable + # branches. + # [1] https://docs.openstack.org/project-team-guide/ + # stable-branches.html#review-guidelines + if COLUMN_IN_USE not in [column['name'] for column + in inspector.get_columns(TABLE)]: + op.add_column(TABLE, + sa.Column(COLUMN_IN_USE, + sa.Boolean(), + server_default=sa.sql.false(), + nullable=False)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/models/l3.py neutron-16.4.2/neutron/db/models/l3.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/models/l3.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/models/l3.py 2021-11-12 13:56:42.000000000 +0000 @@ -88,11 +88,11 @@ port = orm.relationship(models_v2.Port, backref=orm.backref('floating_ips', cascade='all,delete-orphan'), - foreign_keys='FloatingIP.floating_port_id') + foreign_keys=floating_port_id) fixed_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id')) fixed_port = orm.relationship(models_v2.Port, - foreign_keys='FloatingIP.fixed_port_id', - lazy='joined') + foreign_keys=fixed_port_id, + lazy='joined', viewonly=True) fixed_ip_address = sa.Column(sa.String(64)) router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id')) # Additional attribute for keeping track of the router where the floating diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/models/ovn.py neutron-16.4.2/neutron/db/models/ovn.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/models/ovn.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/models/ovn.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. from neutron_lib.db import model_base +from oslo_utils import timeutils import sqlalchemy as sa from sqlalchemy.dialects import sqlite @@ -51,9 +52,9 @@ node_uuid = sa.Column(sa.String(36), nullable=False, index=True) group_name = sa.Column(sa.String(256), nullable=False, index=True) hostname = sa.Column(sa.String(256), nullable=False) - created_at = sa.Column(sa.DateTime(), default=sa.func.now(), + created_at = sa.Column(sa.DateTime(), default=timeutils.utcnow, nullable=False) - updated_at = sa.Column(sa.DateTime(), default=sa.func.now(), + updated_at = sa.Column(sa.DateTime(), default=timeutils.utcnow, nullable=False) __table_args__ = ( sa.PrimaryKeyConstraint( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/models_v2.py neutron-16.4.2/neutron/db/models_v2.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/models_v2.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/models_v2.py 2021-11-12 13:56:42.000000000 +0000 @@ -29,6 +29,32 @@ from neutron.db import standard_attr +# NOTE(ralonsoh): move to neutron_lib.db.model_base +class HasInUse(object): + """NeutronBaseV2 mixin, to add the flag "in_use" to a DB model. + + The content of this flag (boolean) parameter is not relevant. The goal of + this field is to be used in a write transaction to mark a DB register as + "in_use". Writing any value on this DB parameter will lock the container + register. At the end of the DB transaction, the DB engine will check if + this register was modified or deleted. In such case, the transaction will + fail and won't be commited. + + "lock_register" is the method to write the register "in_use" column. + Because the lifespan of this DB lock is the DB transaction, there isn't an + unlock method. The lock will finish once the transaction ends. + """ + in_use = sa.Column(sa.Boolean(), nullable=False, + server_default=sql.false(), default=False) + + @classmethod + def lock_register(cls, context, exception, **filters): + num_reg = context.session.query( + cls).filter_by(**filters).update({'in_use': True}) + if num_reg != 1: + raise exception + + class IPAllocationPool(model_base.BASEV2, model_base.HasId): """Representation of an allocation pool in a Neutron subnet.""" @@ -147,7 +173,7 @@ class Subnet(standard_attr.HasStandardAttributes, model_base.BASEV2, - model_base.HasId, model_base.HasProject): + model_base.HasId, model_base.HasProject, HasInUse): """Represents a neutron subnet. When a subnet is created the first and last entries will be created. These diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/securitygroups_db.py neutron-16.4.2/neutron/db/securitygroups_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/securitygroups_db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/securitygroups_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ import netaddr from neutron_lib.api.definitions import port as port_def +from neutron_lib.api import extensions from neutron_lib.api import validators from neutron_lib.callbacks import events from neutron_lib.callbacks import exceptions @@ -26,6 +27,7 @@ from neutron_lib.db import resource_extend from neutron_lib.db import utils as db_utils from neutron_lib import exceptions as n_exc +from neutron_lib.objects import exceptions as obj_exc from neutron_lib.utils import helpers from neutron_lib.utils import net from oslo_log import log as logging @@ -46,6 +48,8 @@ LOG = logging.getLogger(__name__) +DEFAULT_SG_DESCRIPTION = _('Default security group') + @resource_extend.has_resource_extenders @registry.has_registry_receivers @@ -272,6 +276,7 @@ sg.delete() kwargs.pop('security_group') + kwargs['name'] = sg['name'] registry.notify(resources.SECURITY_GROUP, events.AFTER_DELETE, self, **kwargs) @@ -857,6 +862,8 @@ :returns: the default security group id for given tenant. """ + if not extensions.is_extension_supported(self, 'security-group'): + return default_group_id = self._get_default_sg_id(context, tenant_id) if default_group_id: return default_group_id @@ -865,10 +872,13 @@ 'security_group': {'name': 'default', 'tenant_id': tenant_id, - 'description': _('Default security group')} + 'description': DEFAULT_SG_DESCRIPTION} } - return self.create_security_group(context, security_group, - default_sg=True)['id'] + try: + return self.create_security_group(context, security_group, + default_sg=True)['id'] + except obj_exc.NeutronDbObjectDuplicateEntry: + return self._get_default_sg_id(context, tenant_id) def _get_security_groups_on_port(self, context, port): """Check that all security groups on port belong to tenant. @@ -889,7 +899,8 @@ valid_groups = set( g.id for g in sg_objs - if (not tenant_id or g.tenant_id == tenant_id or + if (context.is_admin or not tenant_id or + g.tenant_id == tenant_id or sg_obj.SecurityGroup.is_shared_with_tenant( context, g.id, tenant_id)) ) @@ -911,7 +922,8 @@ port_project = port.get('tenant_id') default_sg = self._ensure_default_security_group(context, port_project) - port[ext_sg.SECURITYGROUPS] = [default_sg] + if default_sg: + port[ext_sg.SECURITYGROUPS] = [default_sg] def _check_update_deletes_security_groups(self, port): """Return True if port has as a security group and it's value diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/securitygroups_rpc_base.py neutron-16.4.2/neutron/db/securitygroups_rpc_base.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/db/securitygroups_rpc_base.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/db/securitygroups_rpc_base.py 2021-11-12 13:56:42.000000000 +0000 @@ -228,7 +228,7 @@ context, sg_info['sg_member_ips'].keys()) for sg_id, member_ips in ips.items(): for ip in member_ips: - ethertype = 'IPv%d' % netaddr.IPNetwork(ip).version + ethertype = 'IPv%d' % netaddr.IPNetwork(ip[0]).version if ethertype in sg_info['sg_member_ips'][sg_id]: sg_info['sg_member_ips'][sg_id][ethertype].add(ip) return sg_info @@ -257,7 +257,8 @@ port['security_group_source_groups'].append(remote_group_id) base_rule = rule - for ip in ips[remote_group_id]: + ip_list = [ip[0] for ip in ips[remote_group_id]] + for ip in ip_list: if ip in port.get('fixed_ips', []): continue ip_rule = base_rule.copy() @@ -408,9 +409,11 @@ # Join the security group binding table directly to the IP allocation # table instead of via the Port table skip an unnecessary intermediary - query = context.session.query(sg_binding_sgid, - models_v2.IPAllocation.ip_address, - aap_models.AllowedAddressPair.ip_address) + query = context.session.query( + sg_binding_sgid, + models_v2.IPAllocation.ip_address, + aap_models.AllowedAddressPair.ip_address, + aap_models.AllowedAddressPair.mac_address) query = query.join(models_v2.IPAllocation, ip_port == sg_binding_port) # Outerjoin because address pairs may be null and we still want the @@ -422,10 +425,14 @@ # Each allowed address pair IP record for a port beyond the 1st # will have a duplicate regular IP in the query response since # the relationship is 1-to-many. Dedup with a set - for security_group_id, ip_address, allowed_addr_ip in query: - ips_by_group[security_group_id].add(ip_address) + for security_group_id, ip_address, allowed_addr_ip, mac in query: + # Since port mac will not be used further, but in order to align + # the data structure we directly set None to it to avoid bother + # the ports table. + ips_by_group[security_group_id].add((ip_address, None)) if allowed_addr_ip: - ips_by_group[security_group_id].add(allowed_addr_ip) + ips_by_group[security_group_id].add( + (allowed_addr_ip, mac)) return ips_by_group @db_api.retry_if_session_inactive() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/de/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/de/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/de/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/de/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -7,15 +7,16 @@ # Andreas Jaeger , 2016. #zanata # Frank Kloeker , 2016. #zanata # Andreas Jaeger , 2019. #zanata +# Andreas Jaeger , 2020. #zanata msgid "" msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2019-09-26 12:47+0000\n" +"PO-Revision-Date: 2020-04-25 10:33+0000\n" "Last-Translator: Andreas Jaeger \n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -363,6 +364,11 @@ "Der verteilte Router %(router_id)s kann am traditionellen L3-Agenten " "%(agent_id)s nicht gehostet werden." +msgid "Cannot match priority on flow deletion or modification without 'strict'" +msgstr "" +"Abgleichen von Priorität bei Ablauflöschung oder Änderung nicht möglich ohne " +"'strict'" + msgid "Cannot specify both subnet-id and port-id" msgstr "Angabe sowohl von Teilnetz-ID als auch von Port-ID nicht möglich" @@ -1191,6 +1197,10 @@ msgstr "Ungültiger Benutzer/Benutzer ID: '%s'" #, python-format +msgid "Invalid value %(value)s for parameter %(parameter)s provided." +msgstr "Ungültiger Wert %(value)s für Parameter '%(parameter)s' angegeben." + +#, python-format msgid "" "Invalid value for ICMP %(field)s (%(attr)s) %(value)s. It must be 0 to 255." msgstr "" @@ -1491,10 +1501,6 @@ "Netz der Größe %(size)s, aus IP-Bereich %(parent_range)s ausschließlich der " "IP-Bereiche %(excluded_ranges)s wurde nicht gefunden." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "Netztypwert '%s' wird nicht unterstützt" - msgid "Network type value needed by the ML2 plugin" msgstr "Netztypwert für ML2-Plug-in erforderlich" @@ -2271,14 +2277,6 @@ "Zeitlimit in Sekunden für die Wartezeit, in der der lokale Switch die " "Verbindung mit dem Controller hergestellt haben muss." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"'True' zum Löschen aller Ports auf den OpenvSwitch-Brücken. 'False' zum " -"Löschen von Ports, die von Neutron auf Integrationsbrücken und externen " -"Netzbrücken erstellt wurden." - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "Tunnel-IP-Wert für ML2-Plug-in erforderlich" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/es/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/es/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/es/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/es/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1397,10 +1397,6 @@ "No se ha encontrado la red de tamaño %(size)s, de rango de IP " "%(parent_range)s, excluyendo los rangos %(excluded_ranges)s." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "No hay soporte para el valor de tipo de red '%s'" - msgid "Network type value needed by the ML2 plugin" msgstr "El plugin ML2 necesita el valor de tipo de red" @@ -2132,14 +2128,6 @@ "Hay direccionadores conectados a esta red que dependen de esta política para " "su acceso." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"Verdadero para suprimir todos los puertos en todos los puentes OpenvSwitch. " -"Falso para suprimir puertos creados por Neutron por los puentes de red " -"externos y de integración." - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "El plugin ML2 necesita el valor de IP de túnel" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/fr/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/fr/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/fr/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/fr/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1402,10 +1402,6 @@ "Le réseau de taille %(size)s, de plage IP %(parent_range)s (hors plages IP " "%(excluded_ranges)s) est introuvable." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "Valeur de type de réseau '%s' non prise en charge" - msgid "Network type value needed by the ML2 plugin" msgstr "Valeur de type de réseau requise par le plug-in ML2" @@ -2146,14 +2142,6 @@ "Certains routeurs connectés à ce réseau dépendent de cette stratégie pour " "l'accès." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"La valeur est vraie pour la suppression de tous les ports sur tous les ponts " -"OpenvSwitch. Elle est fausse pour la suppression des ports créés par Neutron " -"lors de l'intégration et des ponts de réseau externes." - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "Valeur IP de tunnel requise par le plug-in ML2" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/it/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/it/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/it/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/it/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1383,10 +1383,6 @@ "Rete di dimensione %(size)s, dall'intervallo IP %(parent_range)s esclusi gli " "intervalli IP %(excluded_ranges)s non trovata." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "Valore del tipo di rete '%s' non supportato" - msgid "Network type value needed by the ML2 plugin" msgstr "Valore Tipo di rete richiesto dal plugin ML2" @@ -2101,14 +2097,6 @@ "Sono presenti router collegati a questa rete che dipendono da questa " "politica per l'accesso." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"True per eliminare tutte le porte su tutti i bridge OpenvSwitch. False per " -"eliminare le porte create da Neutron nell'integrazione e i bridge di reti " -"esterne." - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "Valore IP tunnel IP richiesto dal plugin ML2" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/ja/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/ja/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/ja/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/ja/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -17,7 +17,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1378,10 +1378,6 @@ "IP 範囲 %(parent_range)s (IP 範囲 %(excluded_ranges)s を除く) からのサイズ " "%(size)s のネットワークが見つかりませんでした。" -#, python-format -msgid "Network type value '%s' not supported" -msgstr "ネットワークタイプ値 '%s' はサポートされていません" - msgid "Network type value needed by the ML2 plugin" msgstr "ネットワークタイプ値が ML2 プラグインに必要です" @@ -2073,14 +2069,6 @@ "このネットワークにはルーターが存在し、ルーターはアクセスの際にこのポリシーを" "使用します。" -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"すべての OpenvSwitch ブリッジですべてのポートを削除する場合は True。統合およ" -"び外部ネットワークブリッジで Neutron によって作成されたポートを削除する場合" -"は False。" - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "トンネル IP 値が ML2 プラグインに必要です" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/ko_KR/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/ko_KR/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/ko_KR/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/ko_KR/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -15,7 +15,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1324,10 +1324,6 @@ "IP 범위가 %(parent_range)s이고 크기가 %(size)s인(IP 범위 %(excluded_ranges)s " "제외) 네트워크를 발견하지 못했습니다." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "네트워크 유형 값 '%s'이(가) 지원되지 않음" - msgid "Network type value needed by the ML2 plugin" msgstr "ML2 플러그인에 네트워크 유형 값이 필요함" @@ -1992,13 +1988,6 @@ "이 네트워크에 연결된 라우터가 있으며, 해당 라우터에는 액세스를 위해 이 정책" "이 필요합니다." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"모든 OpenvSwitch 브릿지의 모든 포트를 삭제하려면 true입니다. 통합 및 외부 네" -"트워크 브릿지에 Neutron이 작성한 포트를 삭제하려면 false입니다. " - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "ML2 플러그인에 터널 IP 값이 필요함" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/pt_BR/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/pt_BR/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/pt_BR/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/pt_BR/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1373,10 +1373,6 @@ "Rede de tamanho %(size)s, do intervalo de IP %(parent_range)s, excluindo " "intervalos de IP %(excluded_ranges)s não foi localizada." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "Valor do tipo de rede '%s' não suportado" - msgid "Network type value needed by the ML2 plugin" msgstr "Valor de tipo de rede necessário pelo plug-in ML2" @@ -2086,13 +2082,6 @@ msgstr "" "Há roteadores conectados a essa rede que dependem dessa política para acesso." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"True para excluir todas as portas em todas as pontes OpenvSwitch. False para " -"excluir portas criadas pelo Neutron na integração e pontes de rede externa." - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "Valor do IP do túnel necessário pelo plug-in ML2" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/ru/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/ru/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/ru/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/ru/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1369,10 +1369,6 @@ "Сеть размера %(size)s из диапазона IP-адресов %(parent_range)s, кроме " "диапазонов IP-адресов %(excluded_ranges)s, не найдена." -#, python-format -msgid "Network type value '%s' not supported" -msgstr "Значение типа сети '%s' не поддерживается" - msgid "Network type value needed by the ML2 plugin" msgstr "Для модуля ML2 требуется значение типа сети" @@ -2075,13 +2071,6 @@ msgstr "" "К сети подключены маршрутизаторы, доступ к которым зависит от этой стратегии." -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"True - удалить все порты для всех мостов OpenvSwitch. False - удалить порты, " -"созданные Neutron для мостов интеграции и внешних сетей." - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "Для модуля ML2 требуется значение IP-адреса туннеля" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/zh_CN/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/zh_CN/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/zh_CN/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/zh_CN/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -17,7 +17,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1257,10 +1257,6 @@ "从 IP 范围 %(parent_range)s(排除 IP 范围%(excluded_ranges)s)中找不到大小为 " "%(size)s 的网络。" -#, python-format -msgid "Network type value '%s' not supported" -msgstr "网络类型值“%s”不受支持" - msgid "Network type value needed by the ML2 plugin" msgstr "ML2 插件需要网络类型值" @@ -1887,13 +1883,6 @@ "access." msgstr "根据此策略,有一些路由器附加至此网络以用于访问。" -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"True 表示删除所有 OpenvSwitch 网桥上的所有端口。False 表示删除集成和外部网络" -"网桥上由 Neutron 创建的端口。" - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "ML2 插件需要隧道 IP 值" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/zh_TW/LC_MESSAGES/neutron.po neutron-16.4.2/neutron/locale/zh_TW/LC_MESSAGES/neutron.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/locale/zh_TW/LC_MESSAGES/neutron.po 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/locale/zh_TW/LC_MESSAGES/neutron.po 2021-11-12 13:56:42.000000000 +0000 @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: neutron VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-12-20 15:01+0000\n" +"POT-Creation-Date: 2020-04-25 10:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1252,10 +1252,6 @@ "在 IP 範圍 %(parent_range)s(排除 IP 範圍 %(excluded_ranges)s)中找不到大小" "為 %(size)s 的網路。" -#, python-format -msgid "Network type value '%s' not supported" -msgstr "不支援網路類型值 '%s'" - msgid "Network type value needed by the ML2 plugin" msgstr "ML2 外掛程式需要的網路類型值" @@ -1885,13 +1881,6 @@ "access." msgstr "有依賴於此存取原則的路由器已連接至此網路。" -msgid "" -"True to delete all ports on all the OpenvSwitch bridges. False to delete " -"ports created by Neutron on integration and external network bridges." -msgstr "" -"如果為 True,則刪除所有 OpenvSwitch 橋接器上的所有埠。如果為 False,則刪除" -"Neutron 在整合及外部網路橋接器上建立的埠。" - msgid "Tunnel IP value needed by the ML2 plugin" msgstr "ML2 外掛程式需要的通道 IP 值" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/notifiers/ironic.py neutron-16.4.2/neutron/notifiers/ironic.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/notifiers/ironic.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/notifiers/ironic.py 2021-11-12 13:56:42.000000000 +0000 @@ -67,7 +67,8 @@ IRONIC_SESSION = self._get_session(IRONIC_CONF_SECTION) return connection.Connection( - session=IRONIC_SESSION, oslo_conf=cfg.CONF).baremetal + session=IRONIC_SESSION, oslo_conf=cfg.CONF, + connect_retries=cfg.CONF.http_retries).baremetal def send_events(self, batched_events): try: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/notifiers/nova.py neutron-16.4.2/neutron/notifiers/nova.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/notifiers/nova.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/notifiers/nova.py 2021-11-12 13:56:42.000000000 +0000 @@ -75,6 +75,7 @@ region_name=cfg.CONF.nova.region_name, endpoint_type=cfg.CONF.nova.endpoint_type, extensions=self.extensions, + connect_retries=cfg.CONF.http_retries, global_request_id=global_id) def _is_compute_port(self, port): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/network_segment_range.py neutron-16.4.2/neutron/objects/network_segment_range.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/network_segment_range.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/network_segment_range.py 2021-11-12 13:56:42.000000000 +0000 @@ -187,15 +187,20 @@ _filters = copy.deepcopy(filters) project_id = _filters.pop('project_id', None) with cls.db_context_reader(context): - # Retrieve default segment ID range. - default_range = context.session.query(cls.db_model).filter( + # Retrieve all network segment ranges shared. + shared_ranges = context.session.query(cls.db_model).filter( and_(cls.db_model.network_type == network_type, - cls.db_model.default == sql.expression.true())) - if network_type == constants.TYPE_VLAN: - default_range.filter(cls.db_model.physical_network == + cls.db_model.shared == sql.expression.true())) + if (network_type == constants.TYPE_VLAN and + 'physical_network' in _filters): + shared_ranges.filter(cls.db_model.physical_network == _filters['physical_network']) - segment_ids = set(range(default_range.all()[0].minimum, - default_range.all()[0].maximum + 1)) + segment_ids = set([]) + for shared_range in shared_ranges.all(): + segment_ids.update(set(range(shared_range.minimum, + shared_range.maximum + 1))) + if not segment_ids: + return [] # Retrieve other project segment ID ranges (not own project, not # default range). @@ -203,7 +208,8 @@ and_(cls.db_model.project_id != project_id, cls.db_model.project_id.isnot(None), cls.db_model.network_type == network_type)) - if network_type == constants.TYPE_VLAN: + if (network_type == constants.TYPE_VLAN and + 'physical_network' in _filters): other_project_ranges = other_project_ranges.filter( cls.db_model.physical_network == _filters['physical_network']) @@ -225,8 +231,8 @@ # assigned to other projects. query = cls._build_query_segments(context, model, network_type, **_filters) - clauses = [and_(model_segmentation_id >= range[0], - model_segmentation_id <= range[1]) - for range in segment_ranges] + clauses = [and_(model_segmentation_id >= _range[0], + model_segmentation_id <= _range[1]) + for _range in segment_ranges] query = query.filter(or_(*clauses)) return query.limit(common_constants.IDPOOL_SELECT_SIZE).all() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/plugins/ml2/base.py neutron-16.4.2/neutron/objects/plugins/ml2/base.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/plugins/ml2/base.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/plugins/ml2/base.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,7 +16,7 @@ import netaddr -from neutron.common import _constants as common_constants +from neutron.common import utils as n_utils from neutron.objects import base @@ -42,14 +42,18 @@ class SegmentAllocation(object, metaclass=abc.ABCMeta): @classmethod - def get_unallocated_segments(cls, context, **filters): + def get_random_unallocated_segment(cls, context, **filters): with cls.db_context_reader(context): columns = set(dict(cls.db_model.__table__.columns)) model_filters = dict((k, filters[k]) for k in columns & set(filters.keys())) query = context.session.query(cls.db_model).filter_by( allocated=False, **model_filters) - return query.limit(common_constants.IDPOOL_SELECT_SIZE).all() + rand_func = n_utils.get_sql_random_method( + context.session.bind.dialect.name) + if rand_func: + query = query.order_by(rand_func()) + return query.first() @classmethod def allocate(cls, context, **segment): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/port/extensions/allowedaddresspairs.py neutron-16.4.2/neutron/objects/port/extensions/allowedaddresspairs.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/port/extensions/allowedaddresspairs.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/port/extensions/allowedaddresspairs.py 2021-11-12 13:56:42.000000000 +0000 @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib.db import api as db_api from neutron_lib.objects import common_types from neutron_lib.utils import net as net_utils @@ -61,3 +62,12 @@ fields['mac_address'] = net_utils.AuthenticEUI( fields['mac_address']) return fields + + @classmethod + def get_allowed_address_pairs_for_ports(cls, context, port_ids): + with db_api.CONTEXT_READER.using(context): + query = context.session.query(models.AllowedAddressPair).filter( + models.AllowedAddressPair.port_id.in_(port_ids)) + pairs = [cls._load_object(context, db_obj) + for db_obj in query.all()] + return pairs diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/ports.py neutron-16.4.2/neutron/objects/ports.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/ports.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/ports.py 2021-11-12 13:56:42.000000000 +0000 @@ -20,6 +20,7 @@ from oslo_utils import versionutils from oslo_versionedobjects import fields as obj_fields +from neutron.common import _constants from neutron.db.models import dns as dns_models from neutron.db.models import l3 from neutron.db.models import securitygroup as sg_models @@ -214,6 +215,14 @@ if alloc_db: return True + @classmethod + def delete_alloc_by_subnet_id(cls, context, subnet_id): + allocs = context.session.query(models_v2.IPAllocation).filter_by( + subnet_id=subnet_id).all() + for alloc in allocs: + alloc_obj = super(IPAllocation, cls)._load_object(context, alloc) + alloc_obj.delete() + @base.NeutronObjectRegistry.register class PortDNS(base.NeutronDbObject): @@ -415,14 +424,25 @@ **kwargs) @classmethod - def get_port_ids_filter_by_segment_id(cls, context, segment_id): + def get_auto_deletable_port_ids_and_proper_port_count_by_segment( + cls, context, segment_id): + query = context.session.query(models_v2.Port.id) query = query.join( ml2_models.PortBindingLevel, ml2_models.PortBindingLevel.port_id == models_v2.Port.id) query = query.filter( ml2_models.PortBindingLevel.segment_id == segment_id) - return [p.id for p in query] + + q_delete = query.filter( + models_v2.Port.device_owner.in_( + _constants.AUTO_DELETE_PORT_OWNERS)) + + q_proper = query.filter( + ~models_v2.Port.device_owner.in_( + _constants.AUTO_DELETE_PORT_OWNERS)) + + return ([r.id for r in q_delete.all()], q_proper.count()) @classmethod def modify_fields_to_db(cls, fields): @@ -585,6 +605,14 @@ return [port_binding['port_id'] for port_binding in query.all()] @classmethod + def get_ports_by_host(cls, context, host): + query = context.session.query(models_v2.Port.id).join( + ml2_models.PortBinding) + query = query.filter( + ml2_models.PortBinding.host == host) + return [port_id[0] for port_id in query.all()] + + @classmethod def get_ports_by_binding_type_and_host(cls, context, binding_type, host): query = context.session.query(models_v2.Port).join( @@ -627,3 +655,10 @@ query = query.filter( ml2_models.PortBinding.vif_type.in_(binding_types)) return bool(query.count()) + + @classmethod + def get_ports_allocated_by_subnet_id(cls, context, subnet_id): + """Return ports with fixed IPs in a subnet""" + return context.session.query(models_v2.Port).filter( + models_v2.IPAllocation.port_id == models_v2.Port.id).filter( + models_v2.IPAllocation.subnet_id == subnet_id).all() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/qos/binding.py neutron-16.4.2/neutron/objects/qos/binding.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/qos/binding.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/qos/binding.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,7 +14,10 @@ # under the License. from neutron_lib.objects import common_types +from sqlalchemy import and_ +from sqlalchemy import exists +from neutron.db import models_v2 from neutron.db.qos import models as qos_db_model from neutron.objects import base @@ -34,6 +37,20 @@ primary_keys = ['port_id'] fields_no_update = ['policy_id', 'port_id'] + @classmethod + def get_ports_by_network_id(cls, context, network_id, policy_id=None): + query = context.session.query(models_v2.Port).filter( + models_v2.Port.network_id == network_id) + if policy_id: + query = query.filter(exists().where(and_( + cls.db_model.port_id == models_v2.Port.id, + cls.db_model.policy_id == policy_id))) + else: + query = query.filter(~exists().where( + cls.db_model.port_id == models_v2.Port.id)).filter( + models_v2.Port.network_id == network_id) + return query.all() + @base.NeutronObjectRegistry.register class QosPolicyNetworkBinding(base.NeutronDbObject): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/quota.py neutron-16.4.2/neutron/objects/quota.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/quota.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/quota.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,6 +16,7 @@ from oslo_versionedobjects import fields as obj_fields import sqlalchemy as sa from sqlalchemy import sql +from sqlalchemy import types as sqltypes from neutron.db.quota import models from neutron.objects import base @@ -90,7 +91,9 @@ resv_query = context.session.query( models.ResourceDelta.resource, models.Reservation.expiration, - sql.func.sum(models.ResourceDelta.amount)).join( + sql.func.cast( + sql.func.sum(models.ResourceDelta.amount), + sqltypes.Integer)).join( models.Reservation) if expired: exp_expr = (models.Reservation.expiration < now) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/router.py neutron-16.4.2/neutron/objects/router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/router.py 2021-11-12 13:56:42.000000000 +0000 @@ -310,6 +310,15 @@ row = [r for r in six.next(value)] yield (cls._load_object(context, row[0]), row[1]) + @classmethod + def get_disassociated_ids_for_net(cls, context, network_id): + query = context.session.query(cls.db_model.id) + query = query.filter_by( + floating_network_id=network_id, + router_id=None, + fixed_port_id=None) + return [f.id for f in query] + @base.NeutronObjectRegistry.register class DvrFipGatewayPortAgentBinding(base.NeutronDbObject): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/subnet.py neutron-16.4.2/neutron/objects/subnet.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/objects/subnet.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/objects/subnet.py 2021-11-12 13:56:42.000000000 +0000 @@ -321,7 +321,8 @@ @classmethod def find_candidate_subnets(cls, context, network_id, host, service_type, - fixed_configured, fixed_ips): + fixed_configured, fixed_ips, + distributed_service=False): """Find canditate subnets for the network, host, and service_type""" query = cls.query_subnets_on_network(context, network_id) query = SubnetServiceType.query_filter_service_subnets( @@ -335,7 +336,8 @@ # on port update with binding:host_id set. Allocation _cannot_ # be deferred as requested fixed_ips would then be lost. return cls._query_filter_by_fixed_ips_segment( - query, fixed_ips).all() + query, fixed_ips, + allow_multiple_segments=distributed_service).all() # If the host isn't known, we can't allocate on a routed network. # So, exclude any subnets attached to segments. return cls._query_exclude_subnets_on_segments(query).all() @@ -357,7 +359,8 @@ return [subnet for subnet, _mapping in results] @classmethod - def _query_filter_by_fixed_ips_segment(cls, query, fixed_ips): + def _query_filter_by_fixed_ips_segment(cls, query, fixed_ips, + allow_multiple_segments=False): """Excludes subnets not on the same segment as fixed_ips :raises: FixedIpsSubnetsNotOnSameSegment @@ -390,9 +393,12 @@ if subnet and subnet.segment_id not in segment_ids: segment_ids.append(subnet.segment_id) - if 1 < len(segment_ids): + if 1 < len(segment_ids) and not allow_multiple_segments: raise segment_exc.FixedIpsSubnetsNotOnSameSegment() + if allow_multiple_segments: + return query + segment_id = None if not segment_ids else segment_ids[0] return query.filter(cls.db_model.segment_id == segment_id) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/opts.py neutron-16.4.2/neutron/opts.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/opts.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/opts.py 2021-11-12 13:56:42.000000000 +0000 @@ -191,6 +191,8 @@ def list_linux_bridge_opts(): return [ + ('DEFAULT', + neutron.conf.service.RPC_EXTRA_OPTS), ('linux_bridge', neutron.conf.plugins.ml2.drivers.linuxbridge.bridge_opts), ('vxlan', @@ -242,7 +244,8 @@ itertools.chain( meta_conf.SHARED_OPTS, meta_conf.METADATA_PROXY_HANDLER_OPTS, - meta_conf.UNIX_DOMAIN_METADATA_PROXY_OPTS) + meta_conf.UNIX_DOMAIN_METADATA_PROXY_OPTS, + neutron.conf.service.RPC_EXTRA_OPTS) ), ('agent', neutron.conf.agent.common.AGENT_STATE_OPTS) ] @@ -305,6 +308,10 @@ def list_sriov_agent_opts(): return [ + ('DEFAULT', + itertools.chain( + neutron.conf.service.RPC_EXTRA_OPTS) + ), ('sriov_nic', neutron.conf.plugins.ml2.drivers.mech_sriov.agent_common. sriov_nic_opts), diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/db.py neutron-16.4.2/neutron/plugins/ml2/db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/db.py 2021-11-12 13:56:42.000000000 +0000 @@ -19,6 +19,7 @@ from neutron_lib.callbacks import resources from neutron_lib import constants as n_const from neutron_lib.db import api as db_api +from neutron_lib import exceptions as nlib_exc from neutron_lib.plugins import directory from oslo_db import exception as db_exc from oslo_log import log @@ -33,6 +34,7 @@ from neutron.objects import base as objects_base from neutron.objects import ports as port_obj from neutron.plugins.ml2 import models +from neutron.services.segments import db as seg_db from neutron.services.segments import exceptions as seg_exc LOG = log.getLogger(__name__) @@ -316,22 +318,33 @@ def _prevent_segment_delete_with_port_bound(resource, event, trigger, payload=None): """Raise exception if there are any ports bound with segment_id.""" - if payload.metadata.get('for_net_delete'): + if payload.metadata.get(seg_db.FOR_NET_DELETE): # don't check for network deletes return with db_api.CONTEXT_READER.using(payload.context): - port_ids = port_obj.Port.get_port_ids_filter_by_segment_id( - payload.context, segment_id=payload.resource_id) - - # There are still some ports in the segment, segment should not be deleted - # TODO(xiaohhui): Should we delete the dhcp port automatically here? - if port_ids: - reason = _("The segment is still bound with port(s) " - "%s") % ", ".join(port_ids) + auto_delete_port_ids, proper_port_count = port_obj.Port.\ + get_auto_deletable_port_ids_and_proper_port_count_by_segment( + payload.context, segment_id=payload.resource_id) + + if proper_port_count: + reason = (_("The segment is still bound with %s port(s)") % + (proper_port_count + len(auto_delete_port_ids))) raise seg_exc.SegmentInUse(segment_id=payload.resource_id, reason=reason) + if auto_delete_port_ids: + LOG.debug("Auto-deleting dhcp port(s) on segment %s: %s", + payload.resource_id, ", ".join(auto_delete_port_ids)) + plugin = directory.get_plugin() + for port_id in auto_delete_port_ids: + try: + plugin.delete_port(payload.context.elevated(), port_id) + except nlib_exc.PortNotFound: + # Don't raise if something else concurrently deleted the port + LOG.debug("Ignoring PortNotFound when deleting port '%s'. " + "The port has already been deleted.", port_id) + def subscribe(): registry.subscribe(_prevent_segment_delete_with_port_bound, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/helpers.py neutron-16.4.2/neutron/plugins/ml2/drivers/helpers.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/helpers.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/helpers.py 2021-11-12 13:56:42.000000000 +0000 @@ -137,17 +137,23 @@ self.model_segmentation_id, **filters)] else: calls = [functools.partial( - self.segmentation_obj.get_unallocated_segments, + self.segmentation_obj.get_random_unallocated_segment, context, **filters)] + try_to_allocate = False for call in calls: allocations = call() + if not isinstance(allocations, list): + allocations = [allocations] if allocations else [] for alloc in allocations: segment = dict((k, alloc[k]) for k in self.primary_keys) + try_to_allocate = True if self.segmentation_obj.allocate(context, **segment): LOG.debug('%(type)s segment allocate from pool success ' 'with %(segment)s ', {'type': network_type, 'segment': segment}) return alloc - raise db_exc.RetryRequest( - exceptions.NoNetworkFoundInMaximumAllowedAttempts()) + + if try_to_allocate: + raise db_exc.RetryRequest( + exceptions.NoNetworkFoundInMaximumAllowedAttempts()) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/linuxbridge/agent/arp_protect.py neutron-16.4.2/neutron/plugins/ml2/drivers/linuxbridge/agent/arp_protect.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/linuxbridge/agent/arp_protect.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/linuxbridge/agent/arp_protect.py 2021-11-12 13:56:42.000000000 +0000 @@ -73,12 +73,6 @@ _delete_arp_spoofing_protection(vifs, current_rules, table='nat', chain='PREROUTING') - # TODO(haleyb) this can go away in "R" cycle, it's here to cleanup - # old chains in the filter table - current_rules = ebtables(['-L'], table='filter').splitlines() - _delete_arp_spoofing_protection(vifs, current_rules, table='filter', - chain='FORWARD') - def _delete_arp_spoofing_protection(vifs, current_rules, table, chain): # delete the jump rule and then delete the whole chain @@ -87,16 +81,16 @@ ebtables(['-D', chain, '-i', vif, '-j', chain_name(vif), '-p', 'ARP'], table=table) for vif in vifs: - if chain_exists(chain_name(vif), current_rules): - ebtables(['-X', chain_name(vif)], table=table) + chain_delete(chain_name(vif), table, current_rules) _delete_mac_spoofing_protection(vifs, current_rules, table=table, chain=chain) -def _delete_unreferenced_arp_protection(current_vifs, table, chain): +@lockutils.synchronized('ebtables') +def delete_unreferenced_arp_protection(current_vifs): # deletes all jump rules and chains that aren't in current_vifs but match # the spoof prefix - current_rules = ebtables(['-L'], table=table).splitlines() + current_rules = ebtables(['-L'], table='nat').splitlines() to_delete = [] for line in current_rules: # we're looking to find and turn the following: @@ -108,19 +102,8 @@ to_delete.append(devname) LOG.info("Clearing orphaned ARP spoofing entries for devices %s", to_delete) - _delete_arp_spoofing_protection(to_delete, current_rules, table=table, - chain=chain) - - -@lockutils.synchronized('ebtables') -def delete_unreferenced_arp_protection(current_vifs): - _delete_unreferenced_arp_protection(current_vifs, - table='nat', chain='PREROUTING') - - # TODO(haleyb) this can go away in "R" cycle, it's here to cleanup - # old chains in the filter table - _delete_unreferenced_arp_protection(current_vifs, - table='filter', chain='FORWARD') + _delete_arp_spoofing_protection(to_delete, current_rules, table='nat', + chain='PREROUTING') @lockutils.synchronized('ebtables') @@ -134,12 +117,17 @@ vif_chain = chain_name(vif) if not chain_exists(vif_chain, current_rules): ebtables(['-N', vif_chain, '-P', 'DROP']) - # flush the chain to clear previous accepts. this will cause dropped ARP - # packets until the allows are installed, but that's better than leaked - # spoofed packets and ARP can handle losses. - ebtables(['-F', vif_chain]) + # Append a default DROP rule at the end of the chain. This will + # avoid "ebtables-nft" error when listing the chain. + ebtables(['-A', vif_chain, '-j', 'DROP']) + else: + # Flush the chain to clear previous accepts. This will cause dropped + # ARP packets until the allows are installed, but that's better than + # leaked spoofed packets and ARP can handle losses. + ebtables(['-F', vif_chain]) + ebtables(['-A', vif_chain, '-j', 'DROP']) for addr in sorted(addresses): - ebtables(['-A', vif_chain, '-p', 'ARP', '--arp-ip-src', addr, + ebtables(['-I', vif_chain, '-p', 'ARP', '--arp-ip-src', addr, '-j', 'ACCEPT']) # check if jump rule already exists, if not, install it if not vif_jump_present(vif, current_rules): @@ -154,6 +142,13 @@ return False +def chain_delete(chain, table, current_rules): + # flush and delete chain if exists + if chain_exists(chain, current_rules): + ebtables(['-F', chain], table=table) + ebtables(['-X', chain], table=table) + + def vif_jump_present(vif, current_rules): searches = (('-i %s' % vif), ('-j %s' % chain_name(vif)), ('-p ARP')) for line in current_rules: @@ -172,17 +167,22 @@ # mac filter chain for each vif which has a default deny if not chain_exists(vif_chain, current_rules): ebtables(['-N', vif_chain, '-P', 'DROP']) + # Append a default DROP rule at the end of the chain. This will + # avoid "ebtables-nft" error when listing the chain. + ebtables(['-A', vif_chain, '-j', 'DROP']) + # check if jump rule already exists, if not, install it if not _mac_vif_jump_present(vif, current_rules): - ebtables(['-A', 'PREROUTING', '-i', vif, '-j', vif_chain]) + ebtables(['-I', 'PREROUTING', '-i', vif, '-j', vif_chain]) + + _delete_vif_mac_rules(vif, current_rules) # we can't just feed all allowed macs at once because we can exceed # the maximum argument size. limit to 500 per rule. for chunk in (mac_addresses[i:i + 500] for i in range(0, len(mac_addresses), 500)): - new_rule = ['-A', vif_chain, '-i', vif, + new_rule = ['-I', vif_chain, '-i', vif, '--among-src', ','.join(sorted(chunk)), '-j', 'RETURN'] ebtables(new_rule) - _delete_vif_mac_rules(vif, current_rules) def _mac_vif_jump_present(vif, current_rules): @@ -212,9 +212,7 @@ ebtables(['-D', chain, '-i', vif, '-j', _mac_chain_name(vif)], table=table) for vif in vifs: - chain = _mac_chain_name(vif) - if chain_exists(chain, current_rules): - ebtables(['-X', chain], table=table) + chain_delete(_mac_chain_name(vif), table, current_rules) # Used to scope ebtables commands in testing @@ -223,7 +221,7 @@ @tenacity.retry( wait=tenacity.wait_exponential(multiplier=0.02), - retry=tenacity.retry_if_exception(lambda e: e.returncode == 255), + retry=tenacity.retry_if_exception(lambda e: e.returncode in [255, 4]), reraise=True ) def ebtables(comm, table='nat'): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/linuxbridge/agent/common/constants.py neutron-16.4.2/neutron/plugins/ml2/drivers/linuxbridge/agent/common/constants.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/linuxbridge/agent/common/constants.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/linuxbridge/agent/common/constants.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,9 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -FLAT_VLAN_ID = -1 -LOCAL_VLAN_ID = -2 - # Supported VXLAN features VXLAN_NONE = 'not_supported' VXLAN_MCAST = 'multicast_flooding' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py neutron-16.4.2/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -41,6 +41,7 @@ from neutron.common import profiler as setup_profiler from neutron.common import utils from neutron.conf.agent import common as agent_config +from neutron.conf import service as service_conf from neutron.plugins.ml2.drivers.agent import _agent_manager_base as amb from neutron.plugins.ml2.drivers.agent import _common_agent as ca from neutron.plugins.ml2.drivers.agent import config as cagt_config # noqa @@ -346,8 +347,12 @@ if self.vxlan_mode == lconst.VXLAN_MCAST: args['group'] = self.get_vxlan_group(segmentation_id) + if cfg.CONF.VXLAN.l2_population: args['proxy'] = cfg.CONF.VXLAN.arp_responder + # L2population should set the local ip address to handle + # a source dev with multiple ip addresses configured. + args['local'] = self.local_ip try: int_vxlan = self.ip.add_vxlan(interface, segmentation_id, @@ -1031,6 +1036,8 @@ common_config.setup_logging() agent_config.setup_privsep() + service_conf.register_service_opts(service_conf.RPC_EXTRA_OPTS, cfg.CONF) + try: interface_mappings = helpers.parse_mappings( cfg.CONF.LINUX_BRIDGE.physical_interface_mappings) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,13 +26,5 @@ message = _("Invalid Device %(dev_name)s: %(reason)s") -class IpCommandError(SriovNicError): - message = _("ip command failed on device %(dev_name)s: %(reason)s") - - -class IpCommandOperationNotSupportedError(SriovNicError): - message = _("Operation not supported on device %(dev_name)s") - - class InvalidPciSlotError(SriovNicError): message = _("Invalid pci slot %(pci_slot)s") diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,7 +21,6 @@ from oslo_log import log as logging from neutron._i18n import _ -from neutron.agent.linux import ip_link_support from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib @@ -29,6 +28,14 @@ LOG = logging.getLogger(__name__) +IP_LINK_CAPABILITY_STATE = 'state' +IP_LINK_CAPABILITY_VLAN = 'vlan' +IP_LINK_CAPABILITY_RATE = 'max_tx_rate' +IP_LINK_CAPABILITY_MIN_TX_RATE = 'min_tx_rate' +IP_LINK_CAPABILITY_SPOOFCHK = 'spoofchk' +IP_LINK_SUB_CAPABILITY_QOS = 'qos' + + class PciOsWrapper(object): """OS wrapper for checking virtual functions""" @@ -200,11 +207,11 @@ auto=propagate_uplink_state) def set_device_rate(self, pci_slot, rate_type, rate_kbps): - """Set device rate: rate (max_tx_rate), min_tx_rate + """Set device rate: max_tx_rate, min_tx_rate @param pci_slot: Virtual Function address - @param rate_type: device rate name type. Could be 'rate' and - 'min_tx_rate'. + @param rate_type: device rate name type. Could be 'max_tx_rate' and + 'min_tx_rate' ('rate' is not supported anymore). @param rate_kbps: device rate in kbps """ vf_index = self._get_vf_index(pci_slot) @@ -349,7 +356,7 @@ embedded_switch = self._get_emb_eswitch(device_mac, pci_slot) if embedded_switch: return embedded_switch.get_device_state(pci_slot) - return pci_lib.LinkState.DISABLE + return pci_lib.LinkState.disable.name def set_device_max_rate(self, device_mac, pci_slot, max_kbps): """Set device max rate @@ -362,9 +369,7 @@ embedded_switch = self._get_emb_eswitch(device_mac, pci_slot) if embedded_switch: embedded_switch.set_device_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, - max_kbps) + pci_slot, IP_LINK_CAPABILITY_RATE, max_kbps) def set_device_min_tx_rate(self, device_mac, pci_slot, min_kbps): """Set device min_tx_rate @@ -377,9 +382,7 @@ embedded_switch = self._get_emb_eswitch(device_mac, pci_slot) if embedded_switch: embedded_switch.set_device_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, - min_kbps) + pci_slot, IP_LINK_CAPABILITY_MIN_TX_RATE, min_kbps) def set_device_state(self, device_mac, pci_slot, admin_state_up, propagate_uplink_state): @@ -498,9 +501,7 @@ Clear the "rate" configuration from VF by setting it to 0. @param pci_slot: VF PCI slot """ - self._clear_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE) + self._clear_rate(pci_slot, IP_LINK_CAPABILITY_RATE) def clear_min_tx_rate(self, pci_slot): """Clear the VF "min_tx_rate" parameter @@ -508,16 +509,14 @@ Clear the "min_tx_rate" configuration from VF by setting it to 0. @param pci_slot: VF PCI slot """ - self._clear_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE) + self._clear_rate(pci_slot, IP_LINK_CAPABILITY_MIN_TX_RATE) def _clear_rate(self, pci_slot, rate_type): """Clear the VF rate parameter specified in rate_type Clear the rate configuration from VF by setting it to 0. @param pci_slot: VF PCI slot - @param rate_type: rate to clear ('rate', 'min_tx_rate') + @param rate_type: rate to clear ('max_tx_rate', 'min_tx_rate') """ # NOTE(Moshe Levi): we don't use the self._get_emb_eswitch here, # because when clearing the VF it may be not assigned. This happens diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,9 +15,8 @@ from oslo_log import log as logging from neutron.agent.l2.extensions import qos_linux as qos -from neutron.plugins.ml2.drivers.mech_sriov.agent.common import ( - exceptions as exc) from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.services.qos.drivers.sriov import driver LOG = logging.getLogger(__name__) @@ -55,9 +54,9 @@ try: self.eswitch_mgr.set_device_max_rate( device, pci_slot, max_kbps) - except exc.SriovNicError: - LOG.exception( - "Failed to set device %s max rate", device) + except (priv_ip_lib.InterfaceOperationNotSupported, + priv_ip_lib.InvalidArgument): + LOG.exception("Failed to set device %s max rate", device) else: LOG.info("No device with MAC %s defined on agent.", device) @@ -97,8 +96,8 @@ try: self.eswitch_mgr.set_device_min_tx_rate( device, pci_slot, min_tx_kbps) - except exc.SriovNicError: - LOG.exception( - "Failed to set device %s min_tx_rate", device) + except (priv_ip_lib.InterfaceOperationNotSupported, + priv_ip_lib.InvalidArgument): + LOG.exception("Failed to set device %s min_tx_rate", device) else: LOG.info("No device with MAC %s defined on agent.", device) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,86 +13,42 @@ # See the License for the specific language governing permissions and # limitations under the License. -import re +import enum from oslo_log import log as logging from neutron.agent.linux import ip_lib -from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ - import exceptions as exc + LOG = logging.getLogger(__name__) -class LinkState(object): - ENABLE = "enable" - DISABLE = "disable" - AUTO = "auto" +class LinkState(enum.Enum): + auto = 0 + enable = 1 + disable = 2 class PciDeviceIPWrapper(ip_lib.IPWrapper): - """Wrapper class for ip link commands. - - wrapper for getting/setting pci device details using ip link... - """ - VF_PATTERN = r"^vf\s+(?P\d+)\s+" - MAC_PATTERN = r"MAC\s+(?P[a-fA-F0-9:]+)," - STATE_PATTERN = r"\s+link-state\s+(?P\w+)" - ANY_PATTERN = ".*," - - VF_LINE_FORMAT = VF_PATTERN + MAC_PATTERN + ANY_PATTERN + STATE_PATTERN - VF_DETAILS_REG_EX = re.compile(VF_LINE_FORMAT) - - IP_LINK_OP_NOT_SUPPORTED = 'RTNETLINK answers: Operation not supported' + """Wrapper class for ip link commands related to virtual functions.""" def __init__(self, dev_name): super(PciDeviceIPWrapper, self).__init__() self.dev_name = dev_name - def _set_feature(self, vf_index, feature, value): - """Sets vf feature - - Checks if the feature is not supported or there's some - general error during ip link invocation and raises - exception accordingly. - - :param vf_index: vf index - :param feature: name of a feature to be passed to ip link, - such as 'state' or 'spoofchk' - :param value: value of the feature setting - """ - try: - self._as_root([], "link", ("set", self.dev_name, "vf", - str(vf_index), feature, value)) - except Exception as e: - if self.IP_LINK_OP_NOT_SUPPORTED in str(e): - raise exc.IpCommandOperationNotSupportedError( - dev_name=self.dev_name) - else: - raise exc.IpCommandError(dev_name=self.dev_name, - reason=str(e)) - def get_assigned_macs(self, vf_list): """Get assigned mac addresses for vf list. @param vf_list: list of vf indexes @return: dict mapping of vf to mac """ - try: - out = self._as_root([], "link", ("show", self.dev_name)) - except Exception as e: - LOG.exception("Failed executing ip command") - raise exc.IpCommandError(dev_name=self.dev_name, - reason=e) + ip = self.device(self.dev_name) + vfs = ip.link.get_vfs() vf_to_mac_mapping = {} - vf_lines = self._get_vf_link_show(vf_list, out) - if vf_lines: - for vf_line in vf_lines: - vf_details = self._parse_vf_link_show(vf_line) - if vf_details: - vf_num = vf_details.get('vf') - vf_mac = vf_details.get("MAC") - vf_to_mac_mapping[vf_num] = vf_mac + for vf_num in vf_list: + if vfs.get(vf_num): + vf_to_mac_mapping[vf_num] = vfs[vf_num]['mac'] + return vf_to_mac_mapping def get_vf_state(self, vf_index): @@ -100,88 +56,55 @@ @param vf_index: vf index """ - try: - out = self._as_root([], "link", ("show", self.dev_name)) - except Exception as e: - LOG.exception("Failed executing ip command") - raise exc.IpCommandError(dev_name=self.dev_name, - reason=e) - vf_lines = self._get_vf_link_show([vf_index], out) - if vf_lines: - vf_details = self._parse_vf_link_show(vf_lines[0]) - if vf_details: - state = vf_details.get("link-state", - LinkState.DISABLE) - if state in (LinkState.AUTO, LinkState.ENABLE): - return state - return LinkState.DISABLE + ip = self.device(self.dev_name) + vfs = ip.link.get_vfs() + vf = vfs.get(vf_index) + if vf: + return LinkState(int(vf['link_state'])).name + + return LinkState.disable.name def set_vf_state(self, vf_index, state, auto=False): """sets vf state. @param vf_index: vf index - @param state: required state {True/False} + @param state: required state {True: enable (1) + False: disable (2)} + @param auto: set link_state to auto (0) """ + ip = self.device(self.dev_name) if auto: - status_str = LinkState.AUTO + link_state = 0 else: - status_str = LinkState.ENABLE if state else \ - LinkState.DISABLE - self._set_feature(vf_index, "state", status_str) + link_state = 1 if state else 2 + vf_config = {'vf': vf_index, 'link_state': link_state} + ip.link.set_vf_feature(vf_config) def set_vf_spoofcheck(self, vf_index, enabled): """sets vf spoofcheck @param vf_index: vf index - @param enabled: True to enable spoof checking, - False to disable + @param enabled: True to enable (1) spoof checking, + False to disable (0) """ - setting = "on" if enabled else "off" - self._set_feature(vf_index, "spoofchk", setting) + ip = self.device(self.dev_name) + vf_config = {'vf': vf_index, 'spoofchk': int(enabled)} + ip.link.set_vf_feature(vf_config) def set_vf_rate(self, vf_index, rate_type, rate_value): """sets vf rate. @param vf_index: vf index - @param rate_type: vf rate type ('rate', 'min_tx_rate') + @param rate_type: vf rate type ('max_tx_rate', 'min_tx_rate') @param rate_value: vf rate in Mbps """ - self._set_feature(vf_index, rate_type, str(rate_value)) - - def _get_vf_link_show(self, vf_list, link_show_out): - """Get link show output for VFs - - get vf link show command output filtered by given vf list - @param vf_list: list of vf indexes - @param link_show_out: link show command output - @return: list of output rows regarding given vf_list - """ - vf_lines = [] - for line in link_show_out.split("\n"): - line = line.strip() - if line.startswith("vf"): - details = line.split() - index = int(details[1]) - if index in vf_list: - vf_lines.append(line) - if not vf_lines: - LOG.warning("Cannot find vfs %(vfs)s in device %(dev_name)s", - {'vfs': vf_list, 'dev_name': self.dev_name}) - return vf_lines - - def _parse_vf_link_show(self, vf_line): - """Parses vf link show command output line. - - @param vf_line: link show vf line - """ - vf_details = {} - pattern_match = self.VF_DETAILS_REG_EX.match(vf_line) - if pattern_match: - vf_details["vf"] = int(pattern_match.group("vf_index")) - vf_details["MAC"] = pattern_match.group("mac") - vf_details["link-state"] = pattern_match.group("state") - else: - LOG.warning("failed to parse vf link show line %(line)s: " - "for %(device)s", - {'line': vf_line, 'device': self.dev_name}) - return vf_details + ip = self.device(self.dev_name) + vf_config = {'vf': vf_index, 'rate': {rate_type: int(rate_value)}} + try: + ip.link.set_vf_feature(vf_config) + except ip_lib.InvalidArgument: + # NOTE(ralonsoh): some NICs do not support "min_tx_rate" parameter. + # https://bugs.launchpad.net/neutron/+bug/1918464 + LOG.error('Device %(device)s does not support ip-link vf ' + '"%(rate_type)s" parameter.', + {'device': self.dev_name, 'rate_type': rate_type}) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -31,6 +31,7 @@ import oslo_messaging from oslo_service import loopingcall from osprofiler import profiler +import pyroute2 import six from neutron._i18n import _ @@ -43,10 +44,13 @@ from neutron.common import config as common_config from neutron.common import profiler as setup_profiler from neutron.common import utils as n_utils +from neutron.conf.agent import common as agent_config +from neutron.conf import service as service_conf from neutron.plugins.ml2.drivers.mech_sriov.agent.common import config from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib LOG = logging.getLogger(__name__) @@ -291,10 +295,9 @@ self.eswitch_mgr.set_device_state(device, pci_slot, admin_state_up, propagate_uplink_state) - except exc.IpCommandOperationNotSupportedError: - LOG.warning("Device %s does not support state change", - device) - except exc.SriovNicError: + except priv_ip_lib.InterfaceOperationNotSupported: + LOG.warning("Device %s does not support state change", device) + except pyroute2.NetlinkError: LOG.warning("Failed to set device %s state", device) return False else: @@ -519,7 +522,8 @@ cfg.CONF.SRIOV_NIC.resource_provider_inventory_defaults) self.rp_hypervisors = utils.default_rp_hypervisors( cfg.CONF.SRIOV_NIC.resource_provider_hypervisors, - self.device_mappings + self.device_mappings, + cfg.CONF.SRIOV_NIC.resource_provider_default_hypervisor, ) self._validate() @@ -546,6 +550,9 @@ common_config.init(sys.argv[1:]) common_config.setup_logging() + agent_config.setup_privsep() + service_conf.register_service_opts(service_conf.RPC_EXTRA_OPTS, cfg.CONF) + try: config_parser = SriovNicAgentConfigParser() config_parser.parse() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,8 +16,8 @@ from neutron_lib import constants as p_const -# Special vlan_id value in ovs_vlan_allocations table indicating flat network -FLAT_VLAN_ID = -1 +# Special vlan_tci value indicating flat network +FLAT_VLAN_TCI = '0x0000/0x1fff' # Topic for tunnel notifications between the plugin and agent TUNNEL = 'tunnel' @@ -41,11 +41,14 @@ LOCAL_SWITCHING = 0 +# The pyhsical network types of support DVR router +DVR_PHYSICAL_NETWORK_TYPES = [p_const.TYPE_VLAN, p_const.TYPE_FLAT] + # Various tables for DVR use of integration bridge flows DVR_TO_SRC_MAC = 1 -DVR_TO_SRC_MAC_VLAN = 2 +DVR_TO_SRC_MAC_PHYSICAL = 2 ARP_DVR_MAC_TO_DST_MAC = 3 -ARP_DVR_MAC_TO_DST_MAC_VLAN = 4 +ARP_DVR_MAC_TO_DST_MAC_PHYSICAL = 4 CANARY_TABLE = 23 # Table for ARP poison/spoofing prevention rules @@ -56,7 +59,8 @@ # Table to decide whether further filtering is needed TRANSIENT_TABLE = 60 -TRANSIENT_EGRESS_TABLE = 61 +LOCAL_MAC_DIRECT = 61 +TRANSIENT_EGRESS_TABLE = 62 # Tables used for ovs firewall BASE_EGRESS_TABLE = 71 @@ -82,10 +86,11 @@ INT_BR_ALL_TABLES = ( LOCAL_SWITCHING, DVR_TO_SRC_MAC, - DVR_TO_SRC_MAC_VLAN, + DVR_TO_SRC_MAC_PHYSICAL, CANARY_TABLE, ARP_SPOOF_TABLE, MAC_SPOOF_TABLE, + LOCAL_MAC_DIRECT, TRANSIENT_TABLE, TRANSIENT_EGRESS_TABLE, BASE_EGRESS_TABLE, @@ -128,15 +133,15 @@ # --- Physical Bridges (phys_brs) # Various tables for DVR use of physical bridge flows -DVR_PROCESS_VLAN = 1 +DVR_PROCESS_PHYSICAL = 1 LOCAL_VLAN_TRANSLATION = 2 -DVR_NOT_LEARN_VLAN = 3 +DVR_NOT_LEARN_PHYSICAL = 3 PHY_BR_ALL_TABLES = ( LOCAL_SWITCHING, - DVR_PROCESS_VLAN, + DVR_PROCESS_PHYSICAL, LOCAL_VLAN_TRANSLATION, - DVR_NOT_LEARN_VLAN) + DVR_NOT_LEARN_PHYSICAL) # --- end of OpenFlow table IDs diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,7 +21,6 @@ import netaddr -from neutron_lib import constants as p_const from os_ken.lib.packet import ether_types from os_ken.lib.packet import icmpv6 from os_ken.lib.packet import in_proto @@ -103,13 +102,15 @@ def _arp_dvr_dst_mac_match(ofp, ofpp, vlan, dvr_mac): # If eth_dst is equal to the dvr mac of this host, then # flag it as matched. + if not vlan: + return ofpp.OFPMatch(vlan_vid=ofp.OFPVID_NONE, eth_dst=dvr_mac) return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT, eth_dst=dvr_mac) @staticmethod def _dvr_dst_mac_table_id(network_type): - if network_type == p_const.TYPE_VLAN: - return constants.ARP_DVR_MAC_TO_DST_MAC_VLAN + if network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: + return constants.ARP_DVR_MAC_TO_DST_MAC_PHYSICAL else: return constants.ARP_DVR_MAC_TO_DST_MAC @@ -137,13 +138,16 @@ @staticmethod def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac): + if not vlan_tag: + # When the network is flat type, the vlan_tag will be None. + return ofpp.OFPMatch(vlan_vid=ofp.OFPVID_NONE, eth_dst=dst_mac) return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT, eth_dst=dst_mac) @staticmethod def _dvr_to_src_mac_table_id(network_type): - if network_type == p_const.TYPE_VLAN: - return constants.DVR_TO_SRC_MAC_VLAN + if network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: + return constants.DVR_TO_SRC_MAC_PHYSICAL else: return constants.DVR_TO_SRC_MAC @@ -164,10 +168,10 @@ priority=20, match=match, instructions=instructions) - actions = [ - ofpp.OFPActionPopVlan(), - ofpp.OFPActionOutput(dst_port, 0), - ] + actions = [] + if vlan_tag: + actions.append(ofpp.OFPActionPopVlan()) + actions.append(ofpp.OFPActionOutput(dst_port, 0)) self.install_apply_actions(table_id=constants.TRANSIENT_TABLE, priority=20, match=match, @@ -182,12 +186,12 @@ self.uninstall_flows( strict=True, priority=20, table_id=table, match=match) - def add_dvr_mac_vlan(self, mac, port): + def add_dvr_mac_physical(self, mac, port): self.install_goto(table_id=constants.LOCAL_SWITCHING, priority=4, in_port=port, eth_src=mac, - dest_table_id=constants.DVR_TO_SRC_MAC_VLAN) + dest_table_id=constants.DVR_TO_SRC_MAC_PHYSICAL) def remove_dvr_mac_vlan(self, mac): # REVISIT(yamamoto): match in_port as well? @@ -214,11 +218,12 @@ strict=True, priority=5, table_id=table_id, match=match) def add_dvr_gateway_mac_arp_vlan(self, mac, port): - self.install_goto(table_id=constants.LOCAL_SWITCHING, - priority=5, - in_port=port, - eth_dst=mac, - dest_table_id=constants.ARP_DVR_MAC_TO_DST_MAC_VLAN) + self.install_goto( + table_id=constants.LOCAL_SWITCHING, + priority=5, + in_port=port, + eth_dst=mac, + dest_table_id=constants.ARP_DVR_MAC_TO_DST_MAC_PHYSICAL) def remove_dvr_gateway_mac_arp_vlan(self, mac, port): self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,7 +26,7 @@ """openvswitch agent physical bridge specific logic.""" # Used by OVSDVRProcessMixin - dvr_process_table_id = constants.DVR_PROCESS_VLAN + dvr_process_table_id = constants.DVR_PROCESS_PHYSICAL dvr_process_next_table_id = constants.LOCAL_VLAN_TRANSLATION of_tables = constants.PHY_BR_ALL_TABLES @@ -57,12 +57,12 @@ match = self._local_vlan_match(ofp, ofpp, port, lvid) self.uninstall_flows(match=match) - def add_dvr_mac_vlan(self, mac, port): - self.install_output(table_id=constants.DVR_NOT_LEARN_VLAN, + def add_dvr_mac_physical(self, mac, port): + self.install_output(table_id=constants.DVR_NOT_LEARN_PHYSICAL, priority=2, eth_src=mac, port=port) def remove_dvr_mac_vlan(self, mac): # REVISIT(yamamoto): match in_port as well? self.uninstall_flows( - table_id=constants.DVR_NOT_LEARN_VLAN, + table_id=constants.DVR_NOT_LEARN_PHYSICAL, eth_src=mac) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py 2021-11-12 13:56:42.000000000 +0000 @@ -73,10 +73,14 @@ def setup_controllers(self, conf): url = ipv6_utils.valid_ipv6_url(conf.OVS.of_listen_address, conf.OVS.of_listen_port) - controllers = ["tcp:" + url] - self.add_protocols(ovs_consts.OPENFLOW13) - self.set_controller(controllers) + controller = "tcp:" + url + existing_controllers = self.get_controller() + if controller not in existing_controllers: + LOG.debug("Setting controller %s for bridge %s.", + controller, self.br_name) + self.set_controller([controller]) + self.add_protocols(ovs_consts.OPENFLOW13) # NOTE(ivc): Force "out-of-band" controller connection mode (see # "In-Band Control" [1]). # diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import collections import sys import netaddr @@ -36,6 +37,8 @@ def __init__(self, subnet, csnat_ofport=constants.OFPORT_INVALID): # set of compute ports on this dvr subnet self.compute_ports = {} + # set of dvr router interfaces on this subnet + self.dvr_ports = {} self.subnet = subnet self.csnat_ofport = csnat_ofport self.dvr_owned = False @@ -73,6 +76,15 @@ def get_csnat_ofport(self): return self.csnat_ofport + def add_dvr_ofport(self, vif_id, ofport): + self.dvr_ports[vif_id] = ofport + + def remove_dvr_ofport(self, vif_id): + self.dvr_ports.pop(vif_id, 0) + + def get_dvr_ofports(self): + return self.dvr_ports + class OVSPort(object): def __init__(self, id, ofport, mac, device_owner): @@ -81,21 +93,30 @@ self.ofport = ofport self.subnets = set() self.device_owner = device_owner + # Currently, this is updated only for DVR router interfaces + self.ips = collections.defaultdict(list) def __str__(self): return ("OVSPort: id = %s, ofport = %s, mac = %s, " - "device_owner = %s, subnets = %s" % + "device_owner = %s, subnets = %s, ips = %s" % (self.id, self.ofport, self.mac, - self.device_owner, self.subnets)) + self.device_owner, self.subnets, + self.ips)) - def add_subnet(self, subnet_id): + def add_subnet(self, subnet_id, fixed_ip=None): self.subnets.add(subnet_id) + if fixed_ip is None: + return + + self.ips[subnet_id].append(fixed_ip) def remove_subnet(self, subnet_id): self.subnets.remove(subnet_id) + self.ips.pop(subnet_id, None) def remove_all_subnets(self): self.subnets.clear() + self.ips.clear() def get_subnets(self): return self.subnets @@ -109,6 +130,9 @@ def get_ofport(self): return self.ofport + def get_ip(self, subnet_id): + return self.ips.get(subnet_id) + @profiler.trace_cls("ovs_dvr_agent") class OVSDVRNeutronAgent(object): @@ -128,10 +152,9 @@ self.enable_tunneling = enable_tunneling self.enable_distributed_routing = enable_distributed_routing self.bridge_mappings = bridge_mappings - self.phys_brs = phys_brs self.int_ofports = int_ofports self.phys_ofports = phys_ofports - self.reset_ovs_parameters(integ_br, tun_br, + self.reset_ovs_parameters(integ_br, tun_br, phys_brs, patch_int_ofport, patch_tun_ofport) self.reset_dvr_parameters() self.dvr_mac_address = None @@ -143,17 +166,19 @@ def set_firewall(self, firewall=None): self.firewall = firewall - def setup_dvr_flows(self): + def setup_dvr_flows(self, bridge_mappings=None): + bridge_mappings = bridge_mappings or self.bridge_mappings self.setup_dvr_flows_on_integ_br() self.setup_dvr_flows_on_tun_br() - self.setup_dvr_flows_on_phys_br() + self.setup_dvr_flows_on_phys_br(bridge_mappings) self.setup_dvr_mac_flows_on_all_brs() - def reset_ovs_parameters(self, integ_br, tun_br, + def reset_ovs_parameters(self, integ_br, tun_br, phys_brs, patch_int_ofport, patch_tun_ofport): '''Reset the openvswitch parameters''' self.int_br = integ_br self.tun_br = tun_br + self.phys_brs = phys_brs self.patch_int_ofport = patch_int_ofport self.patch_tun_ofport = patch_tun_ofport @@ -164,6 +189,15 @@ self.local_ports = {} self.registered_dvr_macs = set() + def reset_dvr_flows(self, integ_br, tun_br, phys_brs, + patch_int_ofport, patch_tun_ofport, + bridge_mappings=None): + '''Reset the openvswitch and DVR parameters and DVR flows''' + self.reset_ovs_parameters( + integ_br, tun_br, phys_brs, patch_int_ofport, patch_tun_ofport) + self.reset_dvr_parameters() + self.setup_dvr_flows(bridge_mappings) + def get_dvr_mac_address(self): try: self.get_dvr_mac_address_with_retry() @@ -214,7 +248,7 @@ # Insert 'drop' action as the default for Table DVR_TO_SRC_MAC self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC, priority=1) - self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC_VLAN, + self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC_PHYSICAL, priority=1) for physical_network in self.bridge_mappings: @@ -239,19 +273,19 @@ self.tun_br.install_goto(table_id=constants.DVR_PROCESS, dest_table_id=constants.PATCH_LV_TO_TUN) - def setup_dvr_flows_on_phys_br(self): + def setup_dvr_flows_on_phys_br(self, bridge_mappings=None): '''Setup up initial dvr flows into br-phys''' - - for physical_network in self.bridge_mappings: + bridge_mappings = bridge_mappings or self.bridge_mappings + for physical_network in bridge_mappings: self.phys_brs[physical_network].install_goto( in_port=self.phys_ofports[physical_network], priority=2, - dest_table_id=constants.DVR_PROCESS_VLAN) + dest_table_id=constants.DVR_PROCESS_PHYSICAL) self.phys_brs[physical_network].install_goto( priority=1, - dest_table_id=constants.DVR_NOT_LEARN_VLAN) + dest_table_id=constants.DVR_NOT_LEARN_PHYSICAL) self.phys_brs[physical_network].install_goto( - table_id=constants.DVR_PROCESS_VLAN, + table_id=constants.DVR_PROCESS_PHYSICAL, priority=0, dest_table_id=constants.LOCAL_VLAN_TRANSLATION) self.phys_brs[physical_network].install_drop( @@ -259,15 +293,15 @@ in_port=self.phys_ofports[physical_network], priority=2) self.phys_brs[physical_network].install_normal( - table_id=constants.DVR_NOT_LEARN_VLAN, + table_id=constants.DVR_NOT_LEARN_PHYSICAL, priority=1) def _add_dvr_mac_for_phys_br(self, physical_network, mac): - self.int_br.add_dvr_mac_vlan(mac=mac, - port=self.int_ofports[physical_network]) + self.int_br.add_dvr_mac_physical( + mac=mac, port=self.int_ofports[physical_network]) phys_br = self.phys_brs[physical_network] - phys_br.add_dvr_mac_vlan(mac=mac, - port=self.phys_ofports[physical_network]) + phys_br.add_dvr_mac_physical( + mac=mac, port=self.phys_ofports[physical_network]) def _add_arp_dvr_mac_for_phys_br(self, physical_network, mac): self.int_br.add_dvr_gateway_mac_arp_vlan( @@ -392,7 +426,7 @@ ldm.set_dvr_owned(True) vlan_to_use = lvm.vlan - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: vlan_to_use = lvm.segmentation_id subnet_info = ldm.get_subnet_info() @@ -446,7 +480,7 @@ dvr_mac=self.dvr_mac_address, rtr_port=port.ofport) - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: # TODO(vivek) remove the IPv6 related flows once SNAT is not # used for IPv6 DVR. br = self.phys_brs[lvm.physical_network] @@ -469,8 +503,9 @@ # a router interface on any given router ovsport = OVSPort(port.vif_id, port.ofport, port.vif_mac, device_owner) - ovsport.add_subnet(subnet_uuid) + ovsport.add_subnet(subnet_uuid, fixed_ip['ip_address']) self.local_ports[port.vif_id] = ovsport + ldm.add_dvr_ofport(port.vif_id, port.ofport) def _bind_port_on_dvr_subnet(self, port, lvm, fixed_ips, device_owner): @@ -507,7 +542,7 @@ ovsport.add_subnet(subnet_uuid) self.local_ports[port.vif_id] = ovsport vlan_to_use = lvm.vlan - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: vlan_to_use = lvm.segmentation_id # create a rule for this vm port self.int_br.install_dvr_to_src_mac( @@ -567,7 +602,7 @@ ovsport.add_subnet(subnet_uuid) self.local_ports[port.vif_id] = ovsport vlan_to_use = lvm.vlan - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: vlan_to_use = lvm.segmentation_id self.int_br.install_dvr_to_src_mac( network_type=lvm.network_type, @@ -581,8 +616,9 @@ if not self.in_distributed_mode(): return - if local_vlan_map.network_type not in (constants.TUNNEL_NETWORK_TYPES + - [n_const.TYPE_VLAN]): + if (local_vlan_map.network_type not in + (constants.TUNNEL_NETWORK_TYPES + + constants.DVR_PHYSICAL_NETWORK_TYPES)): LOG.debug("DVR: Port %s is with network_type %s not supported" " for dvr plumbing", port.vif_id, local_vlan_map.network_type) @@ -619,7 +655,7 @@ network_type = lvm.network_type physical_network = lvm.physical_network vlan_to_use = lvm.vlan - if network_type == n_const.TYPE_VLAN: + if network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: vlan_to_use = lvm.segmentation_id # ensure we process for all the subnets laid on this removed port for sub_uuid in subnet_set: @@ -628,29 +664,45 @@ ldm = self.local_dvr_map[sub_uuid] subnet_info = ldm.get_subnet_info() ip_version = subnet_info['ip_version'] - # DVR is no more owner - ldm.set_dvr_owned(False) - # remove all vm rules for this dvr subnet - # clear of compute_ports altogether - compute_ports = ldm.get_compute_ofports() - for vif_id in compute_ports: - comp_port = self.local_ports[vif_id] - self.int_br.delete_dvr_to_src_mac( - network_type=network_type, - vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac()) - ldm.remove_all_compute_ofports() + + fixed_ip = ovsport.get_ip(sub_uuid) + is_dvr_gateway_port = False + subnet_gateway = subnet_info.get('gateway_ip') + # since distributed router port must have only one fixed IP, + # directly use fixed_ip[0] + if fixed_ip and fixed_ip[0] == subnet_gateway: + is_dvr_gateway_port = True + + # remove vm dvr src mac rules only if the ovsport + # is gateway for the subnet or if the gateway is + # not set on the subnet + if is_dvr_gateway_port or not subnet_gateway: + # DVR is no more owner + ldm.set_dvr_owned(False) + # remove all vm rules for this dvr subnet + # clear of compute_ports altogether + compute_ports = ldm.get_compute_ofports() + for vif_id in compute_ports: + comp_port = self.local_ports[vif_id] + self.int_br.delete_dvr_to_src_mac( + network_type=network_type, + vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac()) + ldm.remove_all_compute_ofports() + self.int_br.delete_dvr_dst_mac_for_arp( network_type=network_type, vlan_tag=vlan_to_use, gateway_mac=port.vif_mac, dvr_mac=self.dvr_mac_address, rtr_port=port.ofport) - if ldm.get_csnat_ofport() == constants.OFPORT_INVALID: - # if there is no csnat port for this subnet, remove - # this subnet from local_dvr_map, as no dvr (or) csnat - # ports available on this agent anymore + if (ldm.get_csnat_ofport() == constants.OFPORT_INVALID and + len(ldm.get_dvr_ofports()) <= 1): + # if there is no csnat port for this subnet and if this is + # the last dvr port in the subnet, remove this subnet from + # local_dvr_map, as no dvr (or) csnat ports available on this + # agent anymore self.local_dvr_map.pop(sub_uuid, None) - if network_type == n_const.TYPE_VLAN: + if network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: br = self.phys_brs[physical_network] if network_type in constants.TUNNEL_NETWORK_TYPES: br = self.tun_br @@ -663,13 +715,14 @@ br.delete_dvr_process_ipv6( vlan_tag=lvm.vlan, gateway_mac=subnet_info['gateway_mac']) ovsport.remove_subnet(sub_uuid) + ldm.remove_dvr_ofport(port.vif_id) if self.firewall and isinstance(self.firewall, ovs_firewall.OVSFirewallDriver): self.firewall.delete_accepted_egress_direct_flow( subnet_info['gateway_mac'], lvm.vlan) - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: br = self.phys_brs[physical_network] if lvm.network_type in constants.TUNNEL_NETWORK_TYPES: br = self.tun_br @@ -691,7 +744,7 @@ ldm = self.local_dvr_map[sub_uuid] ldm.remove_compute_ofport(port.vif_id) vlan_to_use = lvm.vlan - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: vlan_to_use = lvm.segmentation_id # first remove this vm port rule self.int_br.delete_dvr_to_src_mac( @@ -712,7 +765,7 @@ ldm = self.local_dvr_map[sub_uuid] ldm.set_csnat_ofport(constants.OFPORT_INVALID) vlan_to_use = lvm.vlan - if lvm.network_type == n_const.TYPE_VLAN: + if lvm.network_type in constants.DVR_PHYSICAL_NETWORK_TYPES: vlan_to_use = lvm.segmentation_id # then remove csnat port rule self.int_br.delete_dvr_to_src_mac( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -228,7 +228,8 @@ ovs_conf.resource_provider_inventory_defaults) self.rp_hypervisors = utils.default_rp_hypervisors( ovs_conf.resource_provider_hypervisors, - {k: [v] for k, v in self.bridge_mappings.items()} + {k: [v] for k, v in self.bridge_mappings.items()}, + ovs_conf.resource_provider_default_hypervisor ) self.setup_physical_bridges(self.bridge_mappings) @@ -369,6 +370,8 @@ self.quitting_rpc_timeout = agent_conf.quitting_rpc_timeout + self.install_ingress_direct_goto_flows() + def _parse_bridge_mappings(self, bridge_mappings): try: return helpers.parse_mappings(bridge_mappings) @@ -547,7 +550,8 @@ local_link[0]['port_id'], port_data['id'], port_binding['vif_type'], - port_data['device_id']) + port_data['device_id'], + self._get_network_mtu(port_data['network_id'])) elif (not port_binding['host'] and port_binding['vif_type'] == portbindings.VIF_TYPE_UNBOUND and port['id'] in self.current_smartnic_ports_map.keys()): @@ -572,9 +576,10 @@ rep_port = smartnic_port_data['vif_name'] iface_id = smartnic_port_data['iface_id'] vif_type = smartnic_port_data['vif_type'] + mtu = smartnic_port_data['mtu'] instance_info = vif_instance_object.InstanceInfo(uuid=vm_uuid) - vif = self._get_vif_object(iface_id, rep_port, mac) + vif = self._get_vif_object(iface_id, rep_port, mac, mtu) try: if vif_type == portbindings.VIF_TYPE_OVS: os_vif.plug(vif, instance_info) @@ -599,9 +604,9 @@ 'port_id': iface_id, 'error': e}) - def _get_vif_object(self, iface_id, rep_port, mac): + def _get_vif_object(self, iface_id, rep_port, mac, mtu): network = vif_network_object.Network( - bridge=self.conf.OVS.integration_bridge) + bridge=self.conf.OVS.integration_bridge, mtu=mtu) port_profile = vif_obj.VIFPortProfileOpenVSwitch( interface_id=iface_id, create_port=True) return vif_obj.VIFOpenVSwitch( @@ -609,13 +614,15 @@ network=network, address=str(mac)) def _add_port_to_updated_smartnic_ports(self, mac, vif_name, iface_id, - vif_type, vm_uuid=''): + vif_type, vm_uuid='', + mtu=plugin_utils.get_deployment_physnet_mtu()): self.updated_smartnic_ports.append({ 'mac': mac, 'vm_uuid': vm_uuid, 'vif_name': vif_name, 'iface_id': iface_id, - 'vif_type': vif_type}) + 'vif_type': vif_type, + 'mtu': mtu}) @profiler.trace("rpc") def port_delete(self, context, **kwargs): @@ -665,6 +672,23 @@ lvm = self.vlan_manager.get(network_id) return lvm.vlan + def _deferred_delete_direct_flows(self, ports): + if not self.direct_for_non_openflow_firewall: + return + with self.int_br.deferred(full_ordered=True, + use_bundle=True) as int_br: + for port_id in ports: + lvm, vif_port, _net_id = self._get_port_lvm_and_vif(port_id) + try: + self.delete_accepted_egress_direct_flow( + int_br, + vif_port.ofport, + vif_port.vif_mac, + lvm.vlan) + except Exception as err: + LOG.debug("Failed to remove accepted egress flows " + "for port %s, error: %s", port_id, err) + def process_deleted_ports(self, port_info): # don't try to process removed ports as deleted ports since # they are already gone @@ -672,35 +696,23 @@ self.deleted_ports -= port_info['removed'] deleted_ports = list(self.deleted_ports) - with self.int_br.deferred(full_ordered=True, - use_bundle=True) as int_br: - while self.deleted_ports: - port_id = self.deleted_ports.pop() - port = self.int_br.get_vif_port_by_id(port_id) - - if (isinstance(self.sg_agent.firewall, - agent_firewall.NoopFirewallDriver) or - not agent_sg_rpc.is_firewall_enabled()): - try: - self.delete_accepted_egress_direct_flow( - int_br, - port.ofport, - port.mac, self._get_port_local_vlan(port_id)) - except Exception as err: - LOG.debug("Failed to remove accepted egress flows " - "for port %s, error: %s", port_id, err) + self._deferred_delete_direct_flows(self.deleted_ports) + + while self.deleted_ports: + port_id = self.deleted_ports.pop() + port = self.int_br.get_vif_port_by_id(port_id) - self._clean_network_ports(port_id) - self.ext_manager.delete_port(self.context, - {"vif_port": port, - "port_id": port_id}) - # move to dead VLAN so deleted ports no - # longer have access to the network - if port: - # don't log errors since there is a chance someone will be - # removing the port from the bridge at the same time - self.port_dead(port, log_errors=False) - self.port_unbound(port_id) + self._clean_network_ports(port_id) + self.ext_manager.delete_port(self.context, + {"vif_port": port, + "port_id": port_id}) + # move to dead VLAN so deleted ports no + # longer have access to the network + if port: + # don't log errors since there is a chance someone will be + # removing the port from the bridge at the same time + self.port_dead(port, log_errors=False) + self.port_unbound(port_id) # Flush firewall rules after ports are put on dead VLAN to be # more secure @@ -738,7 +750,8 @@ local_link[0]['port_id'], smartnic_port['id'], smartnic_port['binding:vif_type'], - smartnic_port['device_id']) + smartnic_port['device_id'], + self._get_network_mtu(smartnic_port['network_id'])) def _process_removed_ports(removed_ports): for ovs_port in removed_ports: @@ -908,18 +921,8 @@ else: LOG.warning('Action %s not supported', action) - def _local_vlan_for_flat(self, lvid, physical_network): - phys_br = self.phys_brs[physical_network] - phys_port = self.phys_ofports[physical_network] - int_br = self.int_br - int_port = self.int_ofports[physical_network] - phys_br.provision_local_vlan(port=phys_port, lvid=lvid, - segmentation_id=None, - distributed=False) - int_br.provision_local_vlan(port=int_port, lvid=lvid, - segmentation_id=None) - - def _local_vlan_for_vlan(self, lvid, physical_network, segmentation_id): + def _local_vlan_for_physical(self, lvid, physical_network, + segmentation_id=None): distributed = self.enable_distributed_routing phys_br = self.phys_brs[physical_network] phys_port = self.phys_ofports[physical_network] @@ -1000,7 +1003,7 @@ 'net_uuid': net_uuid}) elif network_type == n_const.TYPE_FLAT: if physical_network in self.phys_brs: - self._local_vlan_for_flat(lvid, physical_network) + self._local_vlan_for_physical(lvid, physical_network) else: LOG.error("Cannot provision flat network for " "net-id=%(net_uuid)s - no bridge for " @@ -1009,8 +1012,8 @@ 'physical_network': physical_network}) elif network_type == n_const.TYPE_VLAN: if physical_network in self.phys_brs: - self._local_vlan_for_vlan(lvid, physical_network, - segmentation_id) + self._local_vlan_for_physical(lvid, physical_network, + segmentation_id) else: LOG.error("Cannot provision VLAN network for " "net-id=%(net_uuid)s - no bridge for " @@ -1291,6 +1294,22 @@ else: bridge.delete_arp_spoofing_protection(port=vif.ofport) + def _get_port_lvm_and_vif(self, vif_id, net_uuid=None): + try: + net_uuid = net_uuid or self.vlan_manager.get_net_uuid(vif_id) + except vlanmanager.VifIdNotFound: + LOG.info('net_uuid %s not managed by VLAN manager', + net_uuid) + return None, None, None + + lvm = self.vlan_manager.get(net_uuid) + + if vif_id in lvm.vif_ports: + vif_port = lvm.vif_ports[vif_id] + return lvm, vif_port, net_uuid + + return lvm, None, net_uuid + def port_unbound(self, vif_id, net_uuid=None): '''Unbind port. @@ -1300,18 +1319,11 @@ :param vif_id: the id of the vif :param net_uuid: the net_uuid this port is associated with. ''' - try: - net_uuid = net_uuid or self.vlan_manager.get_net_uuid(vif_id) - except vlanmanager.VifIdNotFound: - LOG.info( - 'port_unbound(): net_uuid %s not managed by VLAN manager', - net_uuid) + lvm, vif_port, net_uuid = self._get_port_lvm_and_vif(vif_id, net_uuid) + if not lvm: return - lvm = self.vlan_manager.get(net_uuid) - - if vif_id in lvm.vif_ports: - vif_port = lvm.vif_ports[vif_id] + if vif_port and vif_id in lvm.vif_ports: self.dvr_agent.unbind_port_from_dvr(vif_port, lvm) lvm.vif_ports.pop(vif_id, None) @@ -1412,6 +1424,9 @@ "version of OVS does not support tunnels or patch " "ports. Agent terminated!") sys.exit(1) + self.int_br.set_igmp_snooping_flood( + self.conf.OVS.int_peer_patch_port, + self.conf.OVS.igmp_snooping_enable) if self.conf.AGENT.drop_flows_on_start: self.tun_br.uninstall_flows(cookie=ovs_lib.COOKIE_ANY) @@ -1449,6 +1464,11 @@ if bridge_mappings: sync = True self.setup_physical_bridges(bridge_mappings) + if self.enable_distributed_routing: + self.dvr_agent.reset_dvr_flows( + self.int_br, self.tun_br, self.phys_brs, + self.patch_int_ofport, self.patch_tun_ofport, + bridge_mappings) return sync def _check_bridge_datapath_id(self, bridge, datapath_ids_set): @@ -1560,23 +1580,19 @@ phys_ofport = br.add_patch_port( phys_if_name, constants.NONEXISTENT_PEER) + self.int_br.set_igmp_snooping_flood( + int_if_name, self.conf.OVS.igmp_snooping_enable) self.int_ofports[physical_network] = int_ofport self.phys_ofports[physical_network] = phys_ofport - # These two drop flows are the root cause for the bug #1803919. - # And now we add a rpc check during agent start procedure. If - # ovs agent can not reach any neutron server, or all neutron - # servers are down, these flows will not be installed anymore. - # Bug #1803919 was fixed in that way. - # And as a reminder, we can not do much work on this. Because - # the bridge mappings can be varied. Provider (external) network - # can be implicitly set on any physical bridge due to the basic - # NORMAL flow. Different vlan range networks can also have many - # bridge map settings, these tenant network traffic can also be - # blocked by the following drop flows. - # block all untranslated traffic between bridges + # Drop packets from physical bridges that have not matched a higher + # priority flow to set a local vlan. This prevents these stray + # packets from being forwarded to other physical bridges which + # could cause a network loop in the physical network. self.int_br.drop_port(in_port=int_ofport) - br.drop_port(in_port=phys_ofport) + + if not self.enable_distributed_routing: + br.drop_port(in_port=phys_ofport) if self.use_veth_interconnection: # enable veth to pass traffic @@ -1790,9 +1806,11 @@ ): LOG.info( "Port '%(port_name)s' has lost " - "its vlan tag '%(vlan_tag)d'!", + "its vlan tag '%(vlan_tag)d'! " + "Current vlan tag on this port is '%(new_vlan_tag)d'.", {'port_name': port.port_name, - 'vlan_tag': lvm.vlan} + 'vlan_tag': lvm.vlan, + 'new_vlan_tag': port_tags[port.port_name]} ) changed_ports.add(port.vif_id) if changed_ports: @@ -2014,6 +2032,7 @@ self.conf.host) failed_devices = set(devices_down.get('failed_devices_down')) LOG.debug("Port removal failed for %s", failed_devices) + self._deferred_delete_direct_flows(devices) for device in devices: self.ext_manager.delete_port(self.context, {'port_id': device}) self.port_unbound(device) @@ -2124,13 +2143,37 @@ 'elapsed': time.time() - start}) return failed_devices - def process_install_ports_egress_flows(self, ports): - if not self.conf.AGENT.explicitly_egress_direct: - return + @property + def direct_for_non_openflow_firewall(self): + return ((isinstance(self.sg_agent.firewall, + agent_firewall.NoopFirewallDriver) or + getattr(self.sg_agent.firewall, + 'OVS_HYBRID_PLUG_REQUIRED', False) or + not agent_sg_rpc.is_firewall_enabled()) and + self.conf.AGENT.explicitly_egress_direct) + + def install_ingress_direct_goto_flows(self): + if self.direct_for_non_openflow_firewall: + for physical_network in self.bridge_mappings: + self.int_br.install_goto( + table_id=constants.TRANSIENT_TABLE, + dest_table_id=constants.LOCAL_MAC_DIRECT, + priority=4, # a bit higher than NORMAL + in_port=self.int_ofports[physical_network]) + + if self.enable_tunneling: + self.int_br.install_goto( + table_id=constants.TRANSIENT_TABLE, + dest_table_id=constants.LOCAL_MAC_DIRECT, + priority=4, # a bit higher than NORMAL + in_port=self.patch_tun_ofport) + + self.int_br.install_goto( + table_id=constants.LOCAL_MAC_DIRECT, + dest_table_id=constants.TRANSIENT_EGRESS_TABLE) - if (isinstance(self.sg_agent.firewall, - agent_firewall.NoopFirewallDriver) or - not agent_sg_rpc.is_firewall_enabled()): + def process_install_ports_egress_flows(self, ports): + if self.direct_for_non_openflow_firewall: with self.int_br.deferred(full_ordered=True, use_bundle=True) as int_br: for port in ports: @@ -2147,17 +2190,31 @@ lvm = self.vlan_manager.get(port_detail['network_id']) port = port_detail['vif_port'] + # Adding the local vlan to register 6 in case of MAC overlapping + # in different networks. br_int.add_flow( table=constants.TRANSIENT_TABLE, priority=9, in_port=port.ofport, dl_src=port_detail['mac_address'], - actions='resubmit(,{:d})'.format( - constants.TRANSIENT_EGRESS_TABLE)) + actions='set_field:{:d}->reg6,' + 'resubmit(,{:d})'.format( + lvm.vlan, + constants.LOCAL_MAC_DIRECT)) + # For packets from patch ports. br_int.add_flow( - table=constants.TRANSIENT_EGRESS_TABLE, + table=constants.LOCAL_MAC_DIRECT, + priority=12, + dl_vlan=lvm.vlan, + dl_dst=port_detail['mac_address'], + actions='strip_vlan,output:{:d}'.format(port.ofport)) + + # For packets from internal ports or VM ports. + br_int.add_flow( + table=constants.LOCAL_MAC_DIRECT, priority=12, + reg6=lvm.vlan, dl_dst=port_detail['mac_address'], actions='output:{:d}'.format(port.ofport)) @@ -2185,18 +2242,23 @@ patch_ofport)) def delete_accepted_egress_direct_flow(self, br_int, ofport, mac, vlan): - if not self.conf.AGENT.explicitly_egress_direct: + if not self.direct_for_non_openflow_firewall: return br_int.delete_flows( table=constants.TRANSIENT_TABLE, in_port=ofport, dl_src=mac) - self.delete_flows( - table=constants.TRANSIENT_EGRESS_TABLE, + br_int.delete_flows( + table=constants.LOCAL_MAC_DIRECT, + dl_vlan=vlan, + dl_dst=mac) + br_int.delete_flows( + table=constants.LOCAL_MAC_DIRECT, + reg6=vlan, dl_dst=mac) - self.delete_flows( + br_int.delete_flows( table=constants.TRANSIENT_EGRESS_TABLE, dl_src=mac, in_port=ofport) @@ -2483,6 +2545,7 @@ def _handle_ovs_restart(self, polling_manager): self.setup_integration_br() + self.install_ingress_direct_goto_flows() self.setup_physical_bridges(self.bridge_mappings) if self.enable_tunneling: self._reset_tunnel_ofports() @@ -2493,12 +2556,9 @@ # with l2pop fdb entries update self._report_state() if self.enable_distributed_routing: - self.dvr_agent.reset_ovs_parameters(self.int_br, - self.tun_br, - self.patch_int_ofport, - self.patch_tun_ofport) - self.dvr_agent.reset_dvr_parameters() - self.dvr_agent.setup_dvr_flows() + self.dvr_agent.reset_dvr_flows( + self.int_br, self.tun_br, self.phys_brs, + self.patch_int_ofport, self.patch_tun_ofport) # notify that OVS has restarted registry.publish( callback_resources.AGENT, @@ -2539,6 +2599,7 @@ LOG.info("Agent rpc_loop - iteration:%d started", self.iter_num) ovs_status = self.check_ovs_status() + bridges_recreated = False if ovs_status == constants.OVS_RESTARTED: self._handle_ovs_restart(polling_manager) tunnel_sync = self.enable_tunneling or tunnel_sync @@ -2549,11 +2610,17 @@ port_stats = self.get_port_stats({}, {}) self.loop_count_and_wait(start, port_stats) continue - # Check if any physical bridge wasn't recreated recently - added_bridges = idl_monitor.bridges_added + self.added_bridges - bridges_recreated = self._reconfigure_physical_bridges( - added_bridges) - sync |= bridges_recreated + else: + # Check if any physical bridge wasn't recreated recently, + # in case when openvswitch was restarted, it's not needed + added_bridges = idl_monitor.bridges_added + self.added_bridges + bridges_recreated = self._reconfigure_physical_bridges( + added_bridges) + if bridges_recreated: + # In case when any bridge was "re-created", we need to + # ensure that there is no any stale flows in bridges left + need_clean_stale_flow = True + sync |= bridges_recreated # Notify the plugin of tunnel IP if self.enable_tunneling and tunnel_sync: try: @@ -2621,14 +2688,19 @@ ovs_restarted or bridges_recreated) failed_devices = self.process_network_ports( port_info, provisioning_needed) - if need_clean_stale_flow: - self.cleanup_stale_flows() - need_clean_stale_flow = False LOG.info("Agent rpc_loop - iteration:%(iter_num)d - " "ports processed. Elapsed:%(elapsed).3f", {'iter_num': self.iter_num, 'elapsed': time.time() - start}) + if need_clean_stale_flow: + self.cleanup_stale_flows() + need_clean_stale_flow = False + LOG.info("Agent rpc_loop - iteration:%(iter_num)d - " + "cleanup stale flows. Elapsed:%(elapsed).3f", + {'iter_num': self.iter_num, + 'elapsed': time.time() - start}) + ports = port_info['current'] if self.ancillary_brs: @@ -2712,6 +2784,11 @@ "underlays require L2-pop to be enabled, " "in both the Agent and Server side.")) + def _get_network_mtu(self, network_id): + port_network = self.plugin_rpc.get_network_details(self.context, + network_id, self.agent_id, self.conf.host) + return port_network['mtu'] + def validate_local_ip(local_ip): """Verify if the ip exists on the agent's host.""" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,124 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import abc + +from oslo_utils import timeutils + +from neutron.common.ovn import constants as ovn_const +from neutron.common.ovn import utils as ovn_utils + + +class NeutronAgent(abc.ABC): + types = {} + + def __init_subclass__(cls): + # Register the subclasses to be looked up by their type + for _type in cls.types: + NeutronAgent.types[_type] = cls + + def __init__(self, chassis_private): + self.chassis_private = chassis_private + try: + self.chassis = self.chassis_private.chassis[0] + except (AttributeError, IndexError): + # No Chassis_Private support, just use Chassis + self.chassis = self.chassis_private + + @property + def updated_at(self): + try: + return timeutils.parse_isotime(self.chassis.external_ids[self.key]) + except KeyError: + return timeutils.utcnow(with_timezone=True) + + def as_dict(self, alive): + return { + 'binary': self.binary, + 'host': self.chassis.hostname, + 'heartbeat_timestamp': timeutils.utcnow(), + 'availability_zone': ', '.join( + ovn_utils.get_chassis_availability_zones(self.chassis)), + 'topic': 'n/a', + 'description': self.description, + 'configurations': { + 'chassis_name': self.chassis.name, + 'bridge-mappings': + self.chassis.external_ids.get('ovn-bridge-mappings', '')}, + 'start_flag': True, + 'agent_type': self.agent_type, + 'id': self.agent_id, + 'alive': alive, + 'admin_state_up': True} + + @classmethod + def from_type(cls, _type, chassis_private): + return cls.types[_type](chassis_private) + + @staticmethod + def agent_types(): + return NeutronAgent.__subclasses__() + + @property + @abc.abstractmethod + def agent_type(self): + pass + + +class ControllerAgent(NeutronAgent): + types = [ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_CONTROLLER_GW_AGENT] + binary = 'ovn-controller' + key = ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY + + @property + def agent_type(self): + if ('enable-chassis-as-gw' in + self.chassis.external_ids.get('ovn-cms-options', [])): + return ovn_const.OVN_CONTROLLER_GW_AGENT + return ovn_const.OVN_CONTROLLER_AGENT + + @property + def nb_cfg(self): + return self.chassis_private.nb_cfg + + @property + def agent_id(self): + return self.chassis_private.name + + @property + def description(self): + return self.chassis_private.external_ids.get( + ovn_const.OVN_AGENT_DESC_KEY, '') + + +class MetadataAgent(NeutronAgent): + agent_type = ovn_const.OVN_METADATA_AGENT + types = [agent_type] + binary = 'networking-ovn-metadata-agent' + key = ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY + + @property + def nb_cfg(self): + return int(self.chassis_private.external_ids.get( + ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0)) + + @property + def agent_id(self): + return self.chassis_private.external_ids.get( + ovn_const.OVN_AGENT_METADATA_ID_KEY) + + @property + def description(self): + return self.chassis_private.external_ids.get( + ovn_const.OVN_AGENT_METADATA_DESC_KEY, '') diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,7 +21,9 @@ import threading import types +import netaddr from neutron_lib.api.definitions import portbindings +from neutron_lib.api.definitions import segment as segment_def from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources @@ -30,7 +32,7 @@ from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from neutron_lib.plugins.ml2 import api -from neutron_lib.services.qos import constants as qos_consts +from oslo_concurrency import processutils from oslo_config import cfg from oslo_db import exception as os_db_exc from oslo_log import log @@ -46,15 +48,20 @@ from neutron.db import ovn_hash_ring_db from neutron.db import ovn_revision_numbers_db from neutron.db import provisioning_blocks +from neutron.extensions import securitygroup as ext_sg from neutron.plugins.ml2 import db as ml2_db +from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent as n_agent from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker +from neutron import service from neutron.services.qos.drivers.ovn import driver as qos_driver from neutron.services.segments import db as segment_service_db from neutron.services.trunk.drivers.ovn import trunk_driver +import neutron.wsgi LOG = log.getLogger(__name__) @@ -90,9 +97,6 @@ update network/port case, all data validation must be done within methods that are part of the database transaction. """ - - supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT] - def initialize(self): """Perform driver initialization. @@ -118,8 +122,11 @@ LOG.warning('Firewall driver configuration is ignored') self._setup_vif_port_bindings() self.subscribe() - self.qos_driver = qos_driver.OVNQosNotificationDriver.create(self) + self.qos_driver = qos_driver.OVNQosDriver.create(self) self.trunk_driver = trunk_driver.OVNTrunkDriver.create(self) + # The agent_chassis_table will be changed to Chassis_Private if it + # exists, we need to have a connection in order to check that. + self.agent_chassis_table = 'Chassis' @property def _plugin(self): @@ -150,7 +157,9 @@ def _setup_vif_port_bindings(self): self.supported_vnic_types = [portbindings.VNIC_NORMAL, - portbindings.VNIC_DIRECT] + portbindings.VNIC_DIRECT, + portbindings.VNIC_DIRECT_PHYSICAL, + portbindings.VNIC_MACVTAP] self.vif_details = { portbindings.VIF_TYPE_OVS: { portbindings.CAP_PORT_FILTER: self.sg_enabled @@ -175,6 +184,12 @@ registry.subscribe(self._add_segment_host_mapping_for_segment, resources.SEGMENT, events.AFTER_CREATE) + registry.subscribe(self.create_segment_provnet_port, + resources.SEGMENT, + events.AFTER_CREATE) + registry.subscribe(self.delete_segment_provnet_port, + resources.SEGMENT, + events.AFTER_DELETE) # Handle security group/rule notifications if self.sg_enabled: @@ -209,22 +224,91 @@ """Pre-initialize the ML2/OVN driver.""" atexit.register(self._clean_hash_ring) signal.signal(signal.SIGTERM, self._clean_hash_ring) + self._create_neutron_pg_drop() + self._set_inactivity_probe() + + def _create_neutron_pg_drop(self): + """Create neutron_pg_drop Port Group. + + The method creates a short living connection to the Northbound + database. Because of multiple controllers can attempt to create the + Port Group at the same time the transaction can fail and raise + RuntimeError. In such case, we make sure the Port Group was created, + otherwise the error is something else and it's raised to the caller. + """ + idl = ovsdb_monitor.OvnInitPGNbIdl.from_server( + ovn_conf.get_ovn_nb_connection(), 'OVN_Northbound', self) + with ovsdb_monitor.short_living_ovsdb_api( + impl_idl_ovn.OvsdbNbOvnIdl, idl) as pre_ovn_nb_api: + try: + create_default_drop_port_group(pre_ovn_nb_api) + except KeyError: + # Due to a bug in python-ovs, we can send transactions before + # the initial OVSDB is populated in memory. This can break + # the AddCommand post_commit method which tries to return a + # row looked up by the newly commited row's uuid. Since we + # don't care about the return value from the PgAddCommand, we + # can just catch the KeyError and continue. This can be + # removed when the python-ovs bug is resolved. + pass + except RuntimeError as re: + if pre_ovn_nb_api.get_port_group( + ovn_const.OVN_DROP_PORT_GROUP_NAME): + LOG.debug( + "Port Group %(port_group)s already exists, " + "ignoring RuntimeError %(error)s", { + 'port_group': ovn_const.OVN_DROP_PORT_GROUP_NAME, + 'error': re}) + else: + raise + + def _set_inactivity_probe(self): + """Set 'connection.inactivity_probe' in NB and SB databases""" + inactivity_probe = ovn_conf.get_ovn_ovsdb_probe_interval() + dbs = [(ovn_conf.get_ovn_nb_connection(), 'OVN_Northbound', + impl_idl_ovn.OvsdbNbOvnIdl), + (ovn_conf.get_ovn_sb_connection(), 'OVN_Southbound', + impl_idl_ovn.OvsdbSbOvnIdl)] + for connection, schema, klass in dbs: + target = ovn_utils.connection_config_to_target_string(connection) + if not target: + continue + + idl = ovsdb_monitor.BaseOvnIdl.from_server(connection, schema) + with ovsdb_monitor.short_living_ovsdb_api(klass, idl) as idl_api: + conn = idlutils.row_by_value(idl_api, 'Connection', 'target', + target, None) + if conn: + idl_api.db_set( + 'Connection', target, + ('inactivity_probe', int(inactivity_probe))).execute( + check_error=True) + + @staticmethod + def should_post_fork_initialize(worker_class): + return worker_class in (neutron.wsgi.WorkerService, + worker.MaintenanceWorker, + service.RpcWorker) def post_fork_initialize(self, resource, event, trigger, payload=None): - # NOTE(rtheis): This will initialize all workers (API, RPC, - # plugin service and OVN) with OVN IDL connections. + # Initialize API/Maintenance workers with OVN IDL connections + worker_class = ovn_utils.get_method_class(trigger) + if not self.should_post_fork_initialize(worker_class): + return + self._post_fork_event.clear() + self._wait_for_pg_drop_event() self._ovn_client_inst = None - is_maintenance = (ovn_utils.get_method_class(trigger) == - worker.MaintenanceWorker) - if not is_maintenance: + if worker_class == neutron.wsgi.WorkerService: admin_context = n_context.get_admin_context() self.node_uuid = ovn_hash_ring_db.add_node(admin_context, self.hash_ring_group) - self._nb_ovn, self._sb_ovn = impl_idl_ovn.get_ovn_idls( - self, trigger, binding_events=not is_maintenance) + self._nb_ovn, self._sb_ovn = impl_idl_ovn.get_ovn_idls(self, trigger) + + if self._sb_ovn.is_table_present('Chassis_Private'): + self.agent_chassis_table = 'Chassis_Private' # AGENTS must be populated after fork so if ovn-controller is stopped # before a worker handles a get_agents request, we still show agents @@ -236,10 +320,14 @@ self.patch_plugin_choose("update_agent", update_agent) self.patch_plugin_choose("delete_agent", delete_agent) + # Override availability zone methods + self.patch_plugin_merge("get_availability_zones", + get_availability_zones) + # Now IDL connections can be safely used. self._post_fork_event.set() - if is_maintenance: + if worker_class == worker.MaintenanceWorker: # Call the synchronization task if its maintenance worker # This sync neutron DB to OVN-NB DB only in inconsistent states self.nb_synchronizer = ovn_db_sync.OvnNbSynchronizer( @@ -267,6 +355,23 @@ self.hash_ring_group)) self._maintenance_thread.start() + def _wait_for_pg_drop_event(self): + """Wait for event that occurs when neutron_pg_drop Port Group exists. + + The method creates a short living connection to the Northbound + database. It waits for CREATE event caused by the Port Group. + Such event occurs when: + 1) The Port Group doesn't exist and is created by other process. + 2) The Port Group already exists and event is emitted when DB copy + is available to the IDL. + """ + idl = ovsdb_monitor.OvnInitPGNbIdl.from_server( + ovn_conf.get_ovn_nb_connection(), 'OVN_Northbound', self, + pg_only=True) + with ovsdb_monitor.short_living_ovsdb_api( + impl_idl_ovn.OvsdbNbOvnIdl, idl) as ovn_nb_api: + ovn_nb_api.idl.neutron_pg_drop_event.wait() + def _create_security_group_precommit(self, resource, event, trigger, **kwargs): ovn_revision_numbers_db.create_initial_revision( @@ -303,12 +408,46 @@ self._ovn_client.create_security_group_rule( kwargs['context'], kwargs.get('security_group_rule')) elif event == events.BEFORE_DELETE: - sg_rule = self._plugin.get_security_group_rule( - kwargs['context'], kwargs.get('security_group_rule_id')) + try: + sg_rule = self._plugin.get_security_group_rule( + kwargs['context'], kwargs.get('security_group_rule_id')) + except ext_sg.SecurityGroupRuleNotFound: + return + + if sg_rule.get('remote_ip_prefix') is not None: + if self._sg_has_rules_with_same_normalized_cidr(sg_rule): + return self._ovn_client.delete_security_group_rule( kwargs['context'], sg_rule) + def _sg_has_rules_with_same_normalized_cidr(self, sg_rule): + compare_keys = [ + 'ethertype', 'direction', 'protocol', + 'port_range_min', 'port_range_max'] + sg_rules = self._plugin.get_security_group_rules( + n_context.get_admin_context(), + {'security_group_id': [sg_rule['security_group_id']]}) + cidr_sg_rule = netaddr.IPNetwork(sg_rule['remote_ip_prefix']) + normalized_sg_rule_prefix = "%s/%s" % (cidr_sg_rule.network, + cidr_sg_rule.prefixlen) + + def _rules_equal(rule1, rule2): + return not any( + rule1.get(key) != rule2.get(key) for key in compare_keys) + + for rule in sg_rules: + if not rule.get('remote_ip_prefix') or rule['id'] == sg_rule['id']: + continue + cidr_rule = netaddr.IPNetwork(rule['remote_ip_prefix']) + normalized_rule_prefix = "%s/%s" % (cidr_rule.network, + cidr_rule.prefixlen) + if normalized_sg_rule_prefix != normalized_rule_prefix: + continue + if _rules_equal(sg_rule, rule): + return True + return False + def _is_network_type_supported(self, network_type): return (network_type in [const.TYPE_LOCAL, const.TYPE_FLAT, @@ -331,6 +470,20 @@ msg = _('Network type %s is not supported') % network_type raise n_exc.InvalidInput(error_message=msg) + def create_segment_provnet_port(self, resource, event, trigger, + context, segment, payload=None): + if not segment.get(segment_def.PHYSICAL_NETWORK): + return + self._ovn_client.create_provnet_port(segment['network_id'], segment) + + def delete_segment_provnet_port(self, resource, event, trigger, + payload): + # NOTE(mjozefcz): Get the last state of segment resource. + segment = payload.states[-1] + if segment.get(segment_def.PHYSICAL_NETWORK): + self._ovn_client.delete_provnet_port( + segment['network_id'], segment) + def create_network_precommit(self, context): """Allocate resources for a new network. @@ -399,8 +552,9 @@ # https://bugs.launchpad.net/neutron/+bug/1739798 is fixed. if context._plugin_context.session.is_active: return - self._ovn_client.update_network(context._plugin_context, - context.current) + self._ovn_client.update_network( + context._plugin_context, context.current, + original_network=context.original) def delete_network_postcommit(self, context): """Delete a network. @@ -436,6 +590,17 @@ self._ovn_client.delete_subnet(context._plugin_context, context.current['id']) + def _validate_port_extra_dhcp_opts(self, port): + result = ovn_utils.validate_port_extra_dhcp_opts(port) + if not result.failed: + return + ipv4_opts = ', '.join(result.invalid_ipv4) + ipv6_opts = ', '.join(result.invalid_ipv6) + LOG.info('The following extra DHCP options for port %(port_id)s ' + 'are not supported by OVN. IPv4: "%(ipv4_opts)s" and ' + 'IPv6: "%(ipv6_opts)s"', {'port_id': port['id'], + 'ipv4_opts': ipv4_opts, 'ipv6_opts': ipv6_opts}) + def create_port_precommit(self, context): """Allocate resources for a new port. @@ -450,6 +615,7 @@ if ovn_utils.is_lsp_ignored(port): return ovn_utils.validate_and_get_data_from_binding_profile(port) + self._validate_port_extra_dhcp_opts(port) if self._is_port_provisioning_required(port, context.host): self._insert_port_provisioning_block(context._plugin_context, port['id']) @@ -564,6 +730,7 @@ original_port = context.original self._validate_ignored_port(port, original_port) ovn_utils.validate_and_get_data_from_binding_profile(port) + self._validate_port_extra_dhcp_opts(port) if self._is_port_provisioning_required(port, context.host, context.original_host): self._insert_port_provisioning_block(context._plugin_context, @@ -604,7 +771,9 @@ # perform live-migration with live_migration_wait_for_vif_plug=True. if ((port['status'] == const.PORT_STATUS_DOWN and ovn_const.MIGRATING_ATTR in port[portbindings.PROFILE].keys() and - port[portbindings.VIF_TYPE] == portbindings.VIF_TYPE_OVS)): + port[portbindings.VIF_TYPE] in ( + portbindings.VIF_TYPE_OVS, + portbindings.VIF_TYPE_VHOST_USER))): LOG.info("Setting port %s status from DOWN to UP in order " "to emit vif-interface-plugged event.", port['id']) @@ -690,10 +859,10 @@ return capabilities = ovn_utils.get_port_capabilities(port) - if (vnic_type == portbindings.VNIC_DIRECT and + if (vnic_type in ovn_const.EXTERNAL_PORT_TYPES and ovn_const.PORT_CAP_SWITCHDEV not in capabilities): LOG.debug("Refusing to bind port due to unsupported vnic_type: %s " - "with no switchdev capability", portbindings.VNIC_DIRECT) + "with no switchdev capability", vnic_type) return # OVN chassis information is needed to ensure a valid port bind. @@ -775,9 +944,6 @@ def _update_dnat_entry_if_needed(self, port_id, up=True): """Update DNAT entry if using distributed floating ips.""" - if not ovn_conf.is_ovn_distributed_floating_ip(): - return - if not self._nb_ovn: self._nb_ovn = self._ovn_client._nb_idl @@ -797,17 +963,20 @@ {ovn_const.OVN_FIP_EXT_MAC_KEY: nat['external_mac']})).execute() - if up: + if up and ovn_conf.is_ovn_distributed_floating_ip(): mac = nat['external_ids'][ovn_const.OVN_FIP_EXT_MAC_KEY] - LOG.debug("Setting external_mac of port %s to %s", - port_id, mac) - self._nb_ovn.db_set( - 'NAT', nat['_uuid'], - ('external_mac', mac)).execute(check_error=True) + if nat['external_mac'] != mac: + LOG.debug("Setting external_mac of port %s to %s", + port_id, mac) + self._nb_ovn.db_set( + 'NAT', nat['_uuid'], ('external_mac', mac)).execute( + check_error=True) else: - LOG.debug("Clearing up external_mac of port %s", port_id) - self._nb_ovn.db_clear( - 'NAT', nat['_uuid'], 'external_mac').execute(check_error=True) + if nat['external_mac']: + LOG.debug("Clearing up external_mac of port %s", port_id) + self._nb_ovn.db_clear( + 'NAT', nat['_uuid'], 'external_mac').execute( + check_error=True) def _should_notify_nova(self, db_port): # NOTE(twilson) It is possible for a test to override a config option @@ -886,10 +1055,18 @@ def delete_mac_binding_entries(self, external_ip): """Delete all MAC_Binding entries associated to this IP address""" - mac_binds = self._sb_ovn.db_find_rows( - 'MAC_Binding', ('ip', '=', external_ip)).execute() or [] - for entry in mac_binds: - self._sb_ovn.db_destroy('MAC_Binding', entry.uuid).execute() + cmd = ['ovsdb-client', 'transact', ovn_conf.get_ovn_sb_connection()] + + if ovn_conf.get_ovn_sb_private_key(): + cmd += ['-p', ovn_conf.get_ovn_sb_private_key(), '-c', + ovn_conf.get_ovn_sb_certificate(), '-C', + ovn_conf.get_ovn_sb_ca_cert()] + + cmd += ['["OVN_Southbound", {"op": "delete", "table": "MAC_Binding", ' + '"where": [["ip", "==", "%s"]]}]' % external_ip] + + return processutils.execute(*cmd, + log_errors=processutils.LOG_FINAL_ERROR) def update_segment_host_mapping(self, host, phy_nets): """Update SegmentHostMapping in DB""" @@ -973,84 +1150,36 @@ " networking-ovn-metadata-agent status/logs.", port_id) - def agent_alive(self, chassis, type_): - nb_cfg = chassis.nb_cfg - key = ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY - if type_ == ovn_const.OVN_METADATA_AGENT: - nb_cfg = int(chassis.external_ids.get( - ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0)) - key = ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY - - try: - updated_at = timeutils.parse_isotime(chassis.external_ids[key]) - except KeyError: - updated_at = timeutils.utcnow(with_timezone=True) - + def agent_alive(self, agent, update_db): # Allow a maximum of 1 difference between expected and read values # to avoid false positives. - if self._nb_ovn.nb_global.nb_cfg - nb_cfg <= 1: - # update the time of our successful check - value = timeutils.utcnow(with_timezone=True).isoformat() - self._sb_ovn.db_set('Chassis', chassis.uuid, - ('external_ids', {key: value})).execute( - check_error=True) + if self._nb_ovn.nb_global.nb_cfg - agent.nb_cfg <= 1: + if update_db: + self.mark_agent_alive(agent) return True - now = timeutils.utcnow(with_timezone=True) - if (now - updated_at).total_seconds() < cfg.CONF.agent_down_time: + now = timeutils.utcnow(with_timezone=True) + if (now - agent.updated_at).total_seconds() < cfg.CONF.agent_down_time: # down, but not yet timed out return True return False - def _format_agent_info(self, chassis, binary, agent_id, type_, - description, alive): - return { - 'binary': binary, - 'host': chassis.hostname, - 'heartbeat_timestamp': timeutils.utcnow(), - 'availability_zone': 'n/a', - 'topic': 'n/a', - 'description': description, - 'configurations': { - 'chassis_name': chassis.name, - 'bridge-mappings': - chassis.external_ids.get('ovn-bridge-mappings', '')}, - 'start_flag': True, - 'agent_type': type_, - 'id': agent_id, - 'alive': alive, - 'admin_state_up': True} + def mark_agent_alive(self, agent): + # Update the time of our successful check + value = timeutils.utcnow(with_timezone=True).isoformat() + self._sb_ovn.db_set( + self.agent_chassis_table, agent.chassis_private.uuid, + ('external_ids', {agent.key: value})).execute(check_error=True) - def agents_from_chassis(self, chassis): + def agents_from_chassis(self, chassis_private, update_db=True): agent_dict = {} - - # Check for ovn-controller / ovn-controller gateway - agent_type = ovn_const.OVN_CONTROLLER_AGENT - # Only the chassis name stays consistent after ovn-controller restart - agent_id = chassis.name - if ('enable-chassis-as-gw' in - chassis.external_ids.get('ovn-cms-options', [])): - agent_type = ovn_const.OVN_CONTROLLER_GW_AGENT - - alive = self.agent_alive(chassis, agent_type) - description = chassis.external_ids.get( - ovn_const.OVN_AGENT_DESC_KEY, '') - agent_dict[agent_id] = self._format_agent_info( - chassis, 'ovn-controller', agent_id, agent_type, description, - alive) - - # Check for the metadata agent - metadata_agent_id = chassis.external_ids.get( - ovn_const.OVN_AGENT_METADATA_ID_KEY) - if metadata_agent_id: - agent_type = ovn_const.OVN_METADATA_AGENT - alive = self.agent_alive(chassis, agent_type) - description = chassis.external_ids.get( - ovn_const.OVN_AGENT_METADATA_DESC_KEY, '') - agent_dict[metadata_agent_id] = self._format_agent_info( - chassis, 'networking-ovn-metadata-agent', - metadata_agent_id, agent_type, description, alive) - + # Iterate over each unique Agent subclass + for agent in [a(chassis_private) + for a in n_agent.NeutronAgent.agent_types()]: + if not agent.agent_id: + continue + alive = self.agent_alive(agent, update_db) + agent_dict[agent.agent_id] = agent.as_dict(alive) return agent_dict def patch_plugin_merge(self, method_name, new_fn, op=operator.add): @@ -1072,13 +1201,17 @@ new_method = types.MethodType(new_fn, self._plugin) try: return new_method(*args, _driver=self, **kwargs) - except KeyError: + except n_exc.NotFound: return old_method(*args, **kwargs) setattr(self._plugin, method_name, types.MethodType(fn, self._plugin)) - def ping_chassis(self): - """Update NB_Global.nb_cfg so that Chassis.nb_cfg will increment""" + def ping_all_chassis(self): + """Update NB_Global.nb_cfg so that Chassis.nb_cfg will increment + + :returns: (bool) True if nb_cfg was updated. False if it was updated + recently and this call didn't trigger any update. + """ last_ping = self._nb_ovn.nb_global.external_ids.get( ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY) if last_ping: @@ -1086,27 +1219,49 @@ next_ping = (timeutils.parse_isotime(last_ping) + datetime.timedelta(seconds=interval)) if timeutils.utcnow(with_timezone=True) < next_ping: - return + return False with self._nb_ovn.create_transaction(check_error=True, bump_nb_cfg=True) as txn: txn.add(self._nb_ovn.check_liveness()) + return True + + def list_availability_zones(self, context, filters=None): + """List all availability zones from gateway chassis.""" + azs = {} + # TODO(lucasagomes): In the future, once the agents API in OVN + # gets more stable we should consider getting the information from + # the availability zones from the agents API itself. That would + # allow us to do things like: Do not schedule router ports on + # chassis that are offline (via the "alive" attribute for agents). + for ch in self._sb_ovn.chassis_list().execute(check_error=True): + # Only take in consideration gateway chassis because that's where + # the router ports are scheduled on + if not ovn_utils.is_gateway_chassis(ch): + continue + + azones = ovn_utils.get_chassis_availability_zones(ch) + for azone in azones: + azs[azone] = {'name': azone, 'resource': 'router', + 'state': 'available', + 'tenant_id': context.project_id} + return azs def populate_agents(driver): - for ch in driver._sb_ovn.tables['Chassis'].rows.values(): + for ch in driver._sb_ovn.tables[driver.agent_chassis_table].rows.values(): # update the cache, rows are hashed on uuid but it is the name that # stays consistent across ovn-controller restarts AGENTS.update({ch.name: ch}) def get_agents(self, context, filters=None, fields=None, _driver=None): - _driver.ping_chassis() + update_db = _driver.ping_all_chassis() filters = filters or {} agent_list = [] populate_agents(_driver) for ch in AGENTS.values(): - for agent in _driver.agents_from_chassis(ch).values(): + for agent in _driver.agents_from_chassis(ch, update_db).values(): if all(agent[k] in v for k, v in filters.items()): agent_list.append(agent) return agent_list @@ -1115,11 +1270,12 @@ def get_agent(self, context, id, fields=None, _driver=None): chassis = None try: - # look up Chassis by *name*, which the id attribte is - chassis = _driver._sb_ovn.lookup('Chassis', id) + # look up Chassis by *name*, which the id attribute is + chassis = _driver._sb_ovn.lookup(_driver.agent_chassis_table, id) except idlutils.RowNotFound: # If the UUID is not found, check for the metadata agent ID - for ch in _driver._sb_ovn.tables['Chassis'].rows.values(): + for ch in _driver._sb_ovn.tables[ + _driver.agent_chassis_table].rows.values(): metadata_agent_id = ch.external_ids.get( ovn_const.OVN_AGENT_METADATA_ID_KEY) if id == metadata_agent_id: @@ -1156,3 +1312,31 @@ get_agent(self, None, id, _driver=_driver) raise n_exc.BadRequest(resource='agent', msg='OVN agents cannot be deleted') + + +def get_availability_zones(cls, context, _driver, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + return list(_driver.list_availability_zones(context, filters).values()) + + +def create_default_drop_port_group(nb_idl): + pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME + if nb_idl.get_port_group(pg_name): + LOG.debug("Port Group %s already exists", pg_name) + return + with nb_idl.transaction(check_error=True) as txn: + # If drop Port Group doesn't exist yet, create it. + txn.add(nb_idl.pg_add(pg_name, acls=[], may_exist=True)) + # Add ACLs to this Port Group so that all traffic is dropped. + acls = ovn_acl.add_acls_for_drop_port_group(pg_name) + for acl in acls: + txn.add(nb_idl.pg_acl_add(may_exist=True, **acl)) + + ports_with_pg = set() + for pg in nb_idl.get_port_groups().values(): + ports_with_pg.update(pg['ports']) + + if ports_with_pg: + # Add the ports to the default Port Group + txn.add(nb_idl.pg_add_ports(pg_name, list(ports_with_pg))) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py 2021-11-12 13:56:42.000000000 +0000 @@ -258,20 +258,6 @@ """ @abc.abstractmethod - def create_address_set(self, name, may_exist=True, **columns): - """Create an address set - - :param name: The name of the address set - :type name: string - :param may_exist: Do not fail if address set already exists - :type may_exist: bool - :param columns: Dictionary of address set columns - Supported columns: external_ids, addresses - :type columns: dictionary - :returns: :class:`Command` with no result - """ - - @abc.abstractmethod def delete_address_set(self, name, if_exists=True): """Delete an address set @@ -283,35 +269,6 @@ """ @abc.abstractmethod - def update_address_set(self, name, addrs_add, addrs_remove, - if_exists=True): - """Updates addresses in an address set - - :param name: The name of the address set - :type name: string - :param addrs_add: The addresses to be added - :type addrs_add: [] - :param addrs_remove: The addresses to be removed - :type addrs_remove: [] - :param if_exists: Do not fail if the address set does not exist - :type if_exists: bool - :returns: :class:`Command` with no result - """ - - @abc.abstractmethod - def update_address_set_ext_ids(self, name, external_ids, if_exists=True): - """Update external IDs for an address set - - :param name: The name of the address set - :type name: string - :param external_ids: The external IDs for the address set - :type external_ids: dict - :param if_exists: Do not fail if the address set does not exist - :type if_exists: bool - :returns: :class:`Command` with no result - """ - - @abc.abstractmethod def get_all_chassis_gateway_bindings(self, chassis_candidate_list=None): """Return a dictionary of chassis name:list of gateways @@ -580,17 +537,6 @@ """ @abc.abstractmethod - def get_address_set(self, addrset_id, ip_version='ip4'): - """Get a Address Set by its ID. - - :param addrset_id: The Address Set ID - :type addrset_id: string - :param ip_version: Either "ip4" or "ip6". Defaults to "ip4" - :type addr_name: string - :returns: The Address Set row or None - """ - - @abc.abstractmethod def set_lswitch_port_to_virtual_type(self, lport_name, vip, virtual_parent, if_exists=True): """Set the type of a given port to "virtual". diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/backports.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/backports.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/backports.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/backports.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,64 @@ +# Copyright 2021 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# We don't technically require ovs 2.10 or ovsdbapp that has these fixes so +# just include them here for stable releases +try: + from ovs.db import custom_index + + IndexEntryClass = custom_index.IndexEntryClass +except ImportError: + import collections + + from ovs.db import data + + def IndexEntryClass(table): + def defaults_uuid_to_row(atom, base): + return atom.value + + columns = ['uuid'] + list(table.columns.keys()) + cls = collections.namedtuple(table.name, columns) + cls._table = table + cls.__new__.__defaults__ = (None,) + tuple( + data.Datum.default(c.type).to_python(defaults_uuid_to_row) + for c in table.columns.values()) + return cls + + +try: + from ovsdbapp.backend.ovs_idl import idlutils + + frozen_row = idlutils.frozen_row +except AttributeError: + def frozen_row(row): + try: + IndexEntry = row._table.rows.IndexEntry + except AttributeError: + row._table.rows = IndexEntryClass(row._table) + return IndexEntry( + uuid=row.uuid, + **{col: getattr(row, col) + for col in row._table.columns if hasattr(row, col)}) + + +try: + from ovsdbapp.backend.ovs_idl import event as row_event + from ovsdbapp import event as ovsdb_event + + RowEventHandler = row_event.RowEventHandler +except AttributeError: + class RowEventHandler(ovsdb_event.RowEventHandler): + def notify(self, event, row, updates=None): + row = frozen_row(row) + super().notify(event, row, updates) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py 2021-11-12 13:56:42.000000000 +0000 @@ -125,6 +125,7 @@ port = txn.insert(self.api._tables['Logical_Switch_Port']) port.name = self.lport + port.tag = self.columns.pop('tag', []) or [] dhcpv4_options = self.columns.pop('dhcpv4_options', []) if isinstance(dhcpv4_options, list): port.dhcpv4_options = dhcpv4_options @@ -675,25 +676,6 @@ break -class AddAddrSetCommand(command.BaseCommand): - def __init__(self, api, name, may_exist, **columns): - super(AddAddrSetCommand, self).__init__(api) - self.name = name - self.columns = columns - self.may_exist = may_exist - - def run_idl(self, txn): - if self.may_exist: - addrset = idlutils.row_by_value(self.api.idl, 'Address_Set', - 'name', self.name, None) - if addrset: - return - row = txn.insert(self.api._tables['Address_Set']) - row.name = self.name - for col, val in self.columns.items(): - setattr(row, col, val) - - class DelAddrSetCommand(command.BaseCommand): def __init__(self, api, name, if_exists): super(DelAddrSetCommand, self).__init__(api) @@ -714,104 +696,40 @@ self.api._tables['Address_Set'].rows[addrset.uuid].delete() -class UpdateAddrSetCommand(command.BaseCommand): - def __init__(self, api, name, addrs_add, addrs_remove, if_exists): - super(UpdateAddrSetCommand, self).__init__(api) - self.name = name - self.addrs_add = addrs_add - self.addrs_remove = addrs_remove - self.if_exists = if_exists - - def run_idl(self, txn): - try: - addrset = idlutils.row_by_value(self.api.idl, 'Address_Set', - 'name', self.name) - except idlutils.RowNotFound: - if self.if_exists: - return - msg = _("Address set %s does not exist. " - "Can't update addresses") % self.name - raise RuntimeError(msg) - - _updatevalues_in_list( - addrset, 'addresses', - new_values=self.addrs_add, - old_values=self.addrs_remove) - - -class UpdateAddrSetExtIdsCommand(command.BaseCommand): - def __init__(self, api, name, external_ids, if_exists): - super(UpdateAddrSetExtIdsCommand, self).__init__(api) - self.name = name +class UpdateObjectExtIdsCommand(command.BaseCommand): + table = None + field = 'name' + + def __init__(self, api, record, external_ids, if_exists): + super(UpdateObjectExtIdsCommand, self).__init__(api) + self.record = record self.external_ids = external_ids self.if_exists = if_exists def run_idl(self, txn): try: - addrset = idlutils.row_by_value(self.api.idl, 'Address_Set', - 'name', self.name) + # api.lookup() would be better as it doesn't rely on hardcoded col + obj = idlutils.row_by_value(self.api.idl, self.table, self.field, + self.record) except idlutils.RowNotFound: if self.if_exists: return - msg = _("Address set %s does not exist. " - "Can't update external IDs") % self.name + msg = _("%(tbl)s %(rec)s does not exist. " + "Can't update external IDs") % { + 'tbl': self.table, 'rec': self.record} raise RuntimeError(msg) - addrset.verify('external_ids') - addrset_external_ids = getattr(addrset, 'external_ids', {}) for ext_id_key, ext_id_value in self.external_ids.items(): - addrset_external_ids[ext_id_key] = ext_id_value - addrset.external_ids = addrset_external_ids + obj.setkey('external_ids', ext_id_key, ext_id_value) -class UpdateChassisExtIdsCommand(command.BaseCommand): - def __init__(self, api, name, external_ids, if_exists): - super(UpdateChassisExtIdsCommand, self).__init__(api) - self.name = name - self.external_ids = external_ids - self.if_exists = if_exists +class UpdateChassisExtIdsCommand(UpdateObjectExtIdsCommand): + table = 'Chassis' - def run_idl(self, txn): - try: - chassis = idlutils.row_by_value(self.api.idl, 'Chassis', - 'name', self.name) - except idlutils.RowNotFound: - if self.if_exists: - return - msg = _("Chassis %s does not exist. " - "Can't update external IDs") % self.name - raise RuntimeError(msg) - chassis.verify('external_ids') - chassis_external_ids = getattr(chassis, 'external_ids', {}) - for ext_id_key, ext_id_value in self.external_ids.items(): - chassis_external_ids[ext_id_key] = ext_id_value - chassis.external_ids = chassis_external_ids - - -class UpdatePortBindingExtIdsCommand(command.BaseCommand): - def __init__(self, api, name, external_ids, if_exists): - super(UpdatePortBindingExtIdsCommand, self).__init__(api) - self.name = name - self.external_ids = external_ids - self.if_exists = if_exists - - def run_idl(self, txn): - try: - port = idlutils.row_by_value(self.api.idl, 'Port_Binding', - 'logical_port', self.name) - except idlutils.RowNotFound: - if self.if_exists: - return - msg = _("Port %s does not exist. " - "Can't update external IDs") % self.name - raise RuntimeError(msg) - - port.verify('external_ids') - port_external_ids = getattr(port, 'external_ids', {}) - for ext_id_key, ext_id_value in self.external_ids.items(): - port_external_ids[ext_id_key] = ext_id_value - port.external_ids = port_external_ids +class UpdatePortBindingExtIdsCommand(UpdateObjectExtIdsCommand): + table = 'Port_Binding' + field = 'logical_port' class AddDHCPOptionsCommand(command.BaseCommand): @@ -885,11 +803,7 @@ row = txn.insert(self.api._tables['NAT']) for col, val in self.columns.items(): setattr(row, col, val) - # TODO(chandrav): convert this to ovs transaction mutate - lrouter.verify('nat') - nat = getattr(lrouter, 'nat', []) - nat.append(row.uuid) - setattr(lrouter, 'nat', nat) + lrouter.addvalue('nat', row.uuid) class DeleteNATRuleInLRouterCommand(command.BaseCommand): @@ -913,20 +827,13 @@ msg = _("Logical Router %s does not exist") % self.lrouter raise RuntimeError(msg) - lrouter.verify('nat') - # TODO(chandrav): convert this to ovs transaction mutate - nats = getattr(lrouter, 'nat', []) - for nat in nats: - type = getattr(nat, 'type', '') - external_ip = getattr(nat, 'external_ip', '') - logical_ip = getattr(nat, 'logical_ip', '') - if (self.type == type and - self.external_ip == external_ip and - self.logical_ip == logical_ip): - nats.remove(nat) + for nat in lrouter.nat: + if (self.type == nat.type and + self.external_ip == nat.external_ip and + self.logical_ip == nat.logical_ip): + lrouter.delvalue('nat', nat) nat.delete() break - setattr(lrouter, 'nat', nats) class SetNATRuleInLRouterCommand(command.BaseCommand): @@ -944,9 +851,7 @@ msg = _("Logical Router %s does not exist") % self.lrouter raise RuntimeError(msg) - lrouter.verify('nat') - nat_rules = getattr(lrouter, 'nat', []) - for nat_rule in nat_rules: + for nat_rule in lrouter.nat: if nat_rule.uuid == self.nat_rule_uuid: for col, val in self.columns.items(): setattr(nat_rule, col, val) @@ -1054,21 +959,17 @@ msg = _("Logical Router %s does not exist") % self.lrouter raise RuntimeError(msg) - lrouter.verify('static_routes') - static_routes = getattr(lrouter, 'static_routes', []) - for route in static_routes: + for route in lrouter.static_routes: external_ids = getattr(route, 'external_ids', {}) if ovn_const.OVN_ROUTER_IS_EXT_GW in external_ids: - _delvalue_from_list(lrouter, 'static_routes', route) + lrouter.delvalue('static_routes', route) route.delete() break - lrouter.verify('nat') - nats = getattr(lrouter, 'nat', []) - for nat in nats: + for nat in lrouter.nat: if nat.type != 'snat': continue - _delvalue_from_list(lrouter, 'nat', nat) + lrouter.delvalue('nat', nat) nat.delete() lrouter_ext_ids = getattr(lrouter, 'external_ids', {}) @@ -1083,7 +984,7 @@ except idlutils.RowNotFound: return - _delvalue_from_list(lrouter, 'ports', lrouter_port) + lrouter.delvalue('ports', lrouter_port) class SetLSwitchPortToVirtualTypeCommand(command.BaseCommand): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,264 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ovsdbapp.backend.ovs_idl import idlutils + +from neutron.objects.qos import binding as qos_binding +from neutron.objects.qos import policy as qos_policy +from neutron.objects.qos import rule as qos_rule +from neutron_lib import constants +from neutron_lib import context as n_context +from neutron_lib.plugins import directory +from neutron_lib.services.qos import constants as qos_consts +from oslo_log import log as logging + +from neutron.common.ovn import constants as ovn_const +from neutron.common.ovn import utils + + +LOG = logging.getLogger(__name__) +OVN_QOS_DEFAULT_RULE_PRIORITY = 2002 + + +class OVNClientQosExtension(object): + """OVN client QoS extension""" + + def __init__(self, driver): + LOG.info('Starting OVNClientQosExtension') + super(OVNClientQosExtension, self).__init__() + self._driver = driver + self._plugin_property = None + + @property + def _plugin(self): + if self._plugin_property is None: + self._plugin_property = directory.get_plugin() + return self._plugin_property + + @staticmethod + def _qos_rules(context, policy_id): + """QoS Neutron rules classified per direction and type + + :param context: (context) Neutron request context + :param policy_id: (string) Neutron QoS policy ID + :return: (dict) nested dictionary of QoS rules, classified per + direction and rule type + {egress: {bw_limit: {max_kbps, max_burst_kbps}, + dscp: {dscp_mark} + ingress: {...} } + """ + qos_rules = {constants.EGRESS_DIRECTION: {}, + constants.INGRESS_DIRECTION: {}} + if policy_id is None: + return qos_rules + + # The policy might not have any rule + all_rules = qos_rule.get_rules(qos_policy.QosPolicy, + context, policy_id) + for rule in all_rules: + if isinstance(rule, qos_rule.QosBandwidthLimitRule): + r = {rule.rule_type: {'max_kbps': rule.max_kbps}} + if rule.max_burst_kbps: + r[rule.rule_type]['max_burst_kbps'] = rule.max_burst_kbps + qos_rules[rule.direction].update(r) + elif isinstance(rule, qos_rule.QosDscpMarkingRule): + r = {rule.rule_type: {'dscp_mark': rule.dscp_mark}} + qos_rules[constants.EGRESS_DIRECTION].update(r) + else: + LOG.warning('Rule type %(rule_type)s from QoS policy ' + '%(policy_id)s is not supported in OVN', + {'rule_type': rule.rule_type, + 'policy_id': policy_id}) + return qos_rules + + def _ovn_qos_rule(self, rules_direction, rules, port_id, network_id, + delete=False): + """Generate an OVN QoS register based on several Neutron QoS rules + + A OVN QoS register can contain "bandwidth" and "action" parameters. + "bandwidth" defines the rate speed limitation; "action" contains the + DSCP value to apply. Both are not exclusive. + Only one rule per port and direction can be applied; that's why + two rules (bandwidth limit and DSCP) in the same direction must be + combined in one OVN QoS register. + http://www.openvswitch.org/support/dist-docs/ovn-nb.5.html + + :param rules_direction: (string) rules direction (egress, ingress). + :param rules: (dict) {bw_limit: {max_kbps, max_burst_kbps}, + dscp: {dscp_mark}} + :param port_id: (string) port ID. + :param network_id: (string) network ID. + :param delete: (bool) defines if this rule if going to be a partial + one (without any bandwidth or DSCP information) to be + used only as deletion rule. + :return: (dict) OVN QoS rule register to be used with QoSAddCommand + and QoSDelCommand. + """ + if not delete and not rules: + return + + lswitch_name = utils.ovn_name(network_id) + + if rules_direction == constants.EGRESS_DIRECTION: + direction = 'from-lport' + match = 'inport == "{}"'.format(port_id) + else: + direction = 'to-lport' + match = 'outport == "{}"'.format(port_id) + + ovn_qos_rule = {'switch': lswitch_name, 'direction': direction, + 'priority': OVN_QOS_DEFAULT_RULE_PRIORITY, + 'match': match} + + if delete: + # Any specific rule parameter is left undefined. + return ovn_qos_rule + + for rule_type, rule in rules.items(): + if rule_type == qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: + ovn_qos_rule['rate'] = rule['max_kbps'] + if rule.get('max_burst_kbps'): + ovn_qos_rule['burst'] = rule['max_burst_kbps'] + elif rule_type == qos_consts.RULE_TYPE_DSCP_MARKING: + ovn_qos_rule.update({'dscp': rule['dscp_mark']}) + + return ovn_qos_rule + + def _port_effective_qos_policy_id(self, port): + """Return port effective QoS policy + + If the port does not have any QoS policy reference or is a network + device, then return None. + """ + policy_exists = bool(port.get('qos_policy_id') or + port.get('qos_network_policy_id')) + if not policy_exists or utils.is_network_device_port(port): + return None, None + + if port.get('qos_policy_id'): + return port['qos_policy_id'], 'port' + else: + return port['qos_network_policy_id'], 'network' + + def _update_port_qos_rules(self, txn, port_id, network_id, qos_policy_id, + qos_rules): + # NOTE(ralonsoh): we don't use the transaction context because the + # QoS policy could belong to another user (network QoS policy). + admin_context = n_context.get_admin_context() + + # Generate generic deletion rules for both directions. In case of + # creating deletion rules, the rule content is irrelevant. + for ovn_rule in [self._ovn_qos_rule(direction, {}, port_id, + network_id, delete=True) + for direction in constants.VALID_DIRECTIONS]: + # TODO(lucasagomes): qos_del() in ovsdbapp doesn't support + # if_exists=True + try: + txn.add(self._driver._nb_idl.qos_del(**ovn_rule)) + except idlutils.RowNotFound: + continue + + if not qos_policy_id: + return # If no QoS policy is defined, there are no QoS rules. + + # TODO(ralonsoh): for update_network and update_policy operations, + # the QoS rules can be retrieved only once. + qos_rules = qos_rules or self._qos_rules(admin_context, qos_policy_id) + for direction, rules in qos_rules.items(): + ovn_rule = self._ovn_qos_rule(direction, rules, port_id, + network_id) + if ovn_rule: + txn.add(self._driver._nb_idl.qos_add(**ovn_rule)) + + def create_port(self, txn, port, port_type=None): + self.update_port(txn, port, None, reset=True, port_type=port_type) + + def delete_port(self, txn, port): + self.update_port(txn, port, None, delete=True) + + def update_port(self, txn, port, original_port, reset=False, delete=False, + qos_rules=None, port_type=None): + if port_type == ovn_const.LSP_TYPE_EXTERNAL: + # External ports (SR-IOV) QoS is handled the SR-IOV agent QoS + # extension. + return + + if (not reset and not original_port) and not delete: + # If there is no information about the previous QoS policy, do not + # make any change, unless the port is new or the QoS information + # must be reset (delete any previous configuration and set new + # one). + return + + qos_policy_id = (None if delete else + self._port_effective_qos_policy_id(port)[0]) + if not reset and not delete: + original_qos_policy_id = self._port_effective_qos_policy_id( + original_port)[0] + if qos_policy_id == original_qos_policy_id: + return # No QoS policy change + + self._update_port_qos_rules(txn, port['id'], port['network_id'], + qos_policy_id, qos_rules) + + def update_network(self, txn, network, original_network, reset=False, + qos_rules=None): + updated_port_ids = set([]) + if not reset and not original_network: + # If there is no information about the previous QoS policy, do not + # make any change. + return updated_port_ids + + qos_policy_id = network.get('qos_policy_id') + if not reset: + original_qos_policy_id = original_network.get('qos_policy_id') + if qos_policy_id == original_qos_policy_id: + return updated_port_ids # No QoS policy change + + # NOTE(ralonsoh): we don't use the transaction context because some + # ports can belong to other projects. + admin_context = n_context.get_admin_context() + for port in qos_binding.QosPolicyPortBinding.get_ports_by_network_id( + admin_context, network['id']): + if utils.is_network_device_port(port): + continue + + self._update_port_qos_rules(txn, port['id'], network['id'], + qos_policy_id, qos_rules) + updated_port_ids.add(port['id']) + + return updated_port_ids + + def update_policy(self, context, policy): + updated_port_ids = set([]) + bound_networks = policy.get_bound_networks() + bound_ports = policy.get_bound_ports() + qos_rules = self._qos_rules(context, policy.id) + # TODO(ralonsoh): we need to benchmark this transaction in systems with + # a huge amount of ports. This can take a while and could block other + # operations. + with self._driver._nb_idl.transaction(check_error=True) as txn: + for network_id in bound_networks: + network = {'qos_policy_id': policy.id, 'id': network_id} + updated_port_ids.update( + self.update_network(txn, network, {}, reset=True, + qos_rules=qos_rules)) + + # Update each port bound to this policy, not handled previously in + # the network update loop + port_ids = [p for p in bound_ports if p not in updated_port_ids] + for port in self._plugin.get_ports(context, + filters={'id': port_ids}): + self.update_port(txn, port, {}, reset=True, + qos_rules=qos_rules) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py 2021-11-12 13:56:42.000000000 +0000 @@ -11,12 +11,16 @@ # under the License. import contextlib +import functools +import socket import uuid from neutron_lib import exceptions as n_exc from neutron_lib.utils import helpers from oslo_log import log from oslo_utils import uuidutils +from ovs import socket_util +from ovs import stream from ovsdbapp.backend import ovs_idl from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils @@ -30,14 +34,29 @@ from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import exceptions as ovn_exc from neutron.common.ovn import utils +from neutron.common import utils as n_utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as cfg from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import commands as cmd from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker LOG = log.getLogger(__name__) +# Override wait_for_change to not use a timeout so we always try to reconnect +def wait_for_change(idl_, timeout, seqno=None): + if seqno is None: + seqno = idl_.change_seqno + while idl_.change_seqno == seqno and not idl_.run(): + poller = idlutils.poller.Poller() + idl_.wait(poller) + poller.block() + + +idlutils.wait_for_change = wait_for_change + + class OvnNbTransaction(idl_trans.Transaction): def __init__(self, *args, **kwargs): @@ -52,10 +71,50 @@ self.api.nb_global.increment('nb_cfg') +def add_keepalives(fn): + @functools.wraps(fn) + def _open(*args, **kwargs): + error, sock = fn(*args, **kwargs) + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + except socket.error as e: + sock.close() + return socket_util.get_exception_errno(e), None + return error, sock + return _open + + +class NoProbesMixin(object): + @staticmethod + def needs_probes(): + # If we are using keepalives, we can force probe_interval=0 + return False + + +class TCPStream(stream.TCPStream, NoProbesMixin): + @classmethod + @add_keepalives + def _open(cls, suffix, dscp): + return super()._open(suffix, dscp) + + +class SSLStream(stream.SSLStream, NoProbesMixin): + @classmethod + @add_keepalives + def _open(cls, suffix, dscp): + return super()._open(suffix, dscp) + + +# Overwriting globals in a library is clearly a good idea +stream.Stream.register_method("tcp", TCPStream) +stream.Stream.register_method("ssl", SSLStream) + + # This version of Backend doesn't use a class variable for ovsdb_connection # and therefor allows networking-ovn to manage connection scope on its own class Backend(ovs_idl.Backend): lookup_table = {} + ovsdb_connection = None def __init__(self, connection): self.ovsdb_connection = connection @@ -80,6 +139,10 @@ _tables = tables + @n_utils.classproperty + def connection_string(cls): + raise NotImplementedError() + def is_table_present(self, table_name): return table_name in self._tables @@ -121,46 +184,38 @@ # Retry forever to get the OVN NB and SB IDLs. Wait 2^x * 1 seconds between # each retry, up to 'max_interval' seconds, then interval will be fixed # to 'max_interval' seconds afterwards. The default 'max_interval' is 180. -def get_ovn_idls(driver, trigger, binding_events=False): +def get_ovn_idls(driver, trigger): @tenacity.retry( wait=tenacity.wait_exponential( max=cfg.get_ovn_ovsdb_retry_max_interval()), reraise=True) - def get_ovn_idl_retry(cls): + def get_ovn_idl_retry(api_cls): trigger_class = utils.get_method_class(trigger) LOG.info('Getting %(cls)s for %(trigger)s with retry', - {'cls': cls.__name__, 'trigger': trigger_class.__name__}) - return cls(get_connection(cls, trigger, driver, binding_events)) + {'cls': api_cls.__name__, 'trigger': trigger_class.__name__}) + return api_cls.from_worker(trigger_class, driver) vlog.use_python_logger(max_level=cfg.get_ovn_ovsdb_log_level()) return tuple(get_ovn_idl_retry(c) for c in (OvsdbNbOvnIdl, OvsdbSbOvnIdl)) -def get_connection(db_class, trigger=None, driver=None, binding_events=False): - if db_class == OvsdbNbOvnIdl: - args = (cfg.get_ovn_nb_connection(), 'OVN_Northbound') - elif db_class == OvsdbSbOvnIdl: - args = (cfg.get_ovn_sb_connection(), 'OVN_Southbound') - - if binding_events: - if db_class == OvsdbNbOvnIdl: - idl_ = ovsdb_monitor.OvnNbIdl.from_server(*args, driver=driver) - else: - idl_ = ovsdb_monitor.OvnSbIdl.from_server(*args, driver=driver) - else: - if db_class == OvsdbNbOvnIdl: - idl_ = ovsdb_monitor.BaseOvnIdl.from_server(*args) - else: - idl_ = ovsdb_monitor.BaseOvnSbIdl.from_server(*args) - - return connection.Connection(idl_, timeout=cfg.get_ovn_ovsdb_timeout()) - - class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend): def __init__(self, connection): super(OvsdbNbOvnIdl, self).__init__(connection) - self.idl._session.reconnect.set_probe_interval( - cfg.get_ovn_ovsdb_probe_interval()) + + @n_utils.classproperty + def connection_string(cls): + return cfg.get_ovn_nb_connection() + + @classmethod + def from_worker(cls, worker_class, driver=None): + args = (cfg.get_ovn_nb_connection(), 'OVN_Northbound') + if worker_class == worker.MaintenanceWorker: + idl_ = ovsdb_monitor.BaseOvnIdl.from_server(*args) + else: + idl_ = ovsdb_monitor.OvnNbIdl.from_server(*args, driver=driver) + conn = connection.Connection(idl_, timeout=cfg.get_ovn_ovsdb_timeout()) + return cls(conn) @property def nb_global(self): @@ -210,17 +265,17 @@ lswitch.external_ids): continue ports = [] - provnet_port = None + provnet_ports = [] for lport in getattr(lswitch, 'ports', []): if ovn_const.OVN_PORT_NAME_EXT_ID_KEY in lport.external_ids: ports.append(lport.name) # Handle provider network port elif lport.name.startswith( ovn_const.OVN_PROVNET_PORT_NAME_PREFIX): - provnet_port = lport.name + provnet_ports.append(lport.name) result.append({'name': lswitch.name, 'ports': ports, - 'provnet_port': provnet_port}) + 'provnet_ports': provnet_ports}) return result def get_all_logical_routers_with_rports(self): @@ -372,21 +427,9 @@ return cmd.DelStaticRouteCommand(self, lrouter, ip_prefix, nexthop, if_exists) - def create_address_set(self, name, may_exist=True, **columns): - return cmd.AddAddrSetCommand(self, name, may_exist, **columns) - def delete_address_set(self, name, if_exists=True, **columns): return cmd.DelAddrSetCommand(self, name, if_exists) - def update_address_set(self, name, addrs_add, addrs_remove, - if_exists=True): - return cmd.UpdateAddrSetCommand(self, name, addrs_add, addrs_remove, - if_exists) - - def update_address_set_ext_ids(self, name, external_ids, if_exists=True): - return cmd.UpdateAddrSetExtIdsCommand(self, name, external_ids, - if_exists) - def _get_logical_router_port_gateway_chassis(self, lrp): """Get the list of chassis hosting this gateway port. @@ -490,7 +533,7 @@ 'external_ids': ext_ids, 'uuid': row.uuid} def get_subnet_dhcp_options(self, subnet_id, with_ports=False): - subnet = None + subnet = {} ports = [] for row in self._tables['DHCP_Options'].rows.values(): external_ids = getattr(row, 'external_ids', {}) @@ -656,14 +699,6 @@ nat['external_ip'] == external_ip): return nat - def get_address_set(self, addrset_id, ip_version='ip4'): - addr_name = utils.ovn_addrset_name(addrset_id, ip_version) - try: - return idlutils.row_by_value(self.idl, 'Address_Set', - 'name', addr_name) - except idlutils.RowNotFound: - return None - def check_revision_number(self, name, resource, resource_type, if_exists=True): return cmd.CheckRevisionNumberCommand( @@ -690,9 +725,6 @@ def delete_lrouter_ext_gw(self, lrouter_name, if_exists=True): return cmd.DeleteLRouterExtGwCommand(self, lrouter_name, if_exists) - def is_port_groups_supported(self): - return self.is_table_present('Port_Group') - def get_port_group(self, pg_name): if uuidutils.is_uuid_like(pg_name): pg_name = utils.ovn_port_group_name(pg_name) @@ -744,10 +776,20 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend): def __init__(self, connection): super(OvsdbSbOvnIdl, self).__init__(connection) - # TODO(twilson) This direct access of the idl should be removed in - # favor of a backend-agnostic method - self.idl._session.reconnect.set_probe_interval( - cfg.get_ovn_ovsdb_probe_interval()) + + @n_utils.classproperty + def connection_string(cls): + return cfg.get_ovn_sb_connection() + + @classmethod + def from_worker(cls, worker_class, driver=None): + args = (cfg.get_ovn_sb_connection(), 'OVN_Southbound') + if worker_class == worker.MaintenanceWorker: + idl_ = ovsdb_monitor.BaseOvnSbIdl.from_server(*args) + else: + idl_ = ovsdb_monitor.OvnSbIdl.from_server(*args, driver=driver) + conn = connection.Connection(idl_, timeout=cfg.get_ovn_ovsdb_timeout()) + return cls(conn) def _get_chassis_physnets(self, chassis): bridge_mappings = chassis.external_ids.get('ovn-bridge-mappings', '') @@ -831,15 +873,24 @@ rows = self.db_list_rows('Port_Binding').execute(check_error=True) # TODO(twilson) It would be useful to have a db_find that takes a # comparison function - return [r for r in rows - if (r.mac and str(r.datapath.uuid) == network) and - ip_address in r.mac[0].split(' ')] - def update_metadata_health_status(self, chassis, nb_cfg): - return cmd.UpdateChassisExtIdsCommand( - self, chassis, - {ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: str(nb_cfg)}, - if_exists=True) + def check_net_and_ip(port): + # If the port is not bound to any chassis it is not relevant + if not port.chassis: + return False + + # TODO(dalvarez): Remove the comparison to port.datapath.uuid in Y + # cycle when we are sure that all namespaces will be created with + # the Neutron network UUID and not anymore with the OVN datapath + # UUID. + is_in_network = lambda port: ( + str(port.datapath.uuid) == network or + utils.get_network_name_from_datapath(port.datapath) == network) + + return port.mac and is_in_network(port) and ( + ip_address in port.mac[0].split(' ')) + + return [r for r in rows if check_net_and_ip(r)] def set_port_cidrs(self, name, cidrs): # TODO(twilson) add if_exists to db commands diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,11 +14,13 @@ # under the License. import abc +import copy import inspect import threading from futurist import periodics from neutron_lib.api.definitions import external_net +from neutron_lib.api.definitions import segment as segment_def from neutron_lib import constants as n_const from neutron_lib import context as n_context from neutron_lib import exceptions as n_exc @@ -28,9 +30,11 @@ from ovsdbapp.backend.ovs_idl import event as row_event from neutron.common.ovn import constants as ovn_const +from neutron.common.ovn import utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import ovn_hash_ring_db as hash_ring_db from neutron.db import ovn_revision_numbers_db as revision_numbers_db +from neutron.db import segments_db from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync @@ -168,7 +172,7 @@ }, ovn_const.TYPE_SECURITY_GROUPS: { 'neutron_get': self._ovn_client._plugin.get_security_group, - 'ovn_get': self._get_security_group, + 'ovn_get': self._nb_idl.get_port_group, 'ovn_create': self._ovn_client.create_security_group, 'ovn_delete': self._ovn_client.delete_security_group, }, @@ -189,10 +193,6 @@ }, } - def _get_security_group(self, uuid): - return (self._nb_idl.get_address_set(uuid) or - self._nb_idl.get_port_group(uuid)) - @property def has_lock(self): return not self._idl.is_lock_contended @@ -287,8 +287,7 @@ # If Port Groups are not supported or we've already migrated, we don't # need to attempt to migrate again. - if (not self._nb_idl.is_port_groups_supported() or - not self._nb_idl.get_address_sets()): + if not self._nb_idl.get_address_sets(): raise periodics.NeverAgain() # Only the worker holding a valid lock within OVSDB will perform the @@ -397,8 +396,9 @@ def _create_lrouter_port(self, context, port): router_id = port['device_id'] - self._ovn_client._l3_plugin.add_router_interface( + iface_info = self._ovn_client._l3_plugin._add_neutron_router_interface( context, router_id, {'port_id': port['id']}, may_exist=True) + self._ovn_client.create_router_port(context, router_id, iface_info) def _check_subnet_global_dhcp_opts(self): inconsistent_subnets = [] @@ -550,7 +550,7 @@ 'Logical_Switch', ls.name, ('other_config', { ovn_const.MCAST_SNOOP: value, - ovn_const.MCAST_FLOOD_UNREGISTERED: value}))) + ovn_const.MCAST_FLOOD_UNREGISTERED: 'false'}))) raise periodics.NeverAgain() @@ -607,6 +607,100 @@ raise periodics.NeverAgain() + # A static spacing value is used here, but this method will only run + # once per lock due to the use of periodics.NeverAgain(). + @periodics.periodic(spacing=600, run_immediately=True) + def check_for_localnet_legacy_port_name(self): + if not self.has_lock: + return + + admin_context = n_context.get_admin_context() + cmds = [] + for ls in self._nb_idl.ls_list().execute(check_error=True): + network_id = ls.name.replace('neutron-', '') + legacy_name = utils.ovn_provnet_port_name(network_id) + legacy_port = None + segment_id = None + for lsp in ls.ports: + if legacy_name == lsp.name: + legacy_port = lsp + break + else: + continue + for segment in segments_db.get_network_segments( + admin_context, network_id): + if (segment.get(segment_def.PHYSICAL_NETWORK) == + legacy_port.options['network_name']): + segment_id = segment['id'] + break + if not segment_id: + continue + new_p_name = utils.ovn_provnet_port_name(segment_id) + cmds.append(self._nb_idl.db_set('Logical_Switch_Port', + legacy_port.uuid, + ('name', new_p_name))) + if cmds: + with self._nb_idl.transaction(check_error=True) as txn: + for cmd in cmds: + txn.add(cmd) + raise periodics.NeverAgain() + + # TODO(lucasagomes): Remove this in the Y cycle + # A static spacing value is used here, but this method will only run + # once per lock due to the use of periodics.NeverAgain(). + @periodics.periodic(spacing=600, run_immediately=True) + def check_for_mcast_flood_reports(self): + if not self.has_lock: + return + + cmds = [] + for port in self._nb_idl.lsp_list().execute(check_error=True): + port_type = port.type.strip() + if port_type in ("vtep", "localport", "router"): + continue + + options = port.options + if ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS in options: + continue + + options.update({ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true'}) + if port_type == ovn_const.LSP_TYPE_LOCALNET: + options.update({ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'}) + + cmds.append(self._nb_idl.lsp_set_options(port.name, **options)) + + if cmds: + with self._nb_idl.transaction(check_error=True) as txn: + for cmd in cmds: + txn.add(cmd) + + raise periodics.NeverAgain() + + # TODO(lucasagomes): Remove this in the Z cycle + # A static spacing value is used here, but this method will only run + # once per lock due to the use of periodics.NeverAgain(). + @periodics.periodic(spacing=600, run_immediately=True) + def check_router_mac_binding_options(self): + if not self.has_lock: + return + + cmds = [] + for router in self._nb_idl.lr_list().execute(check_error=True): + if (router.options.get('always_learn_from_arp_request') and + router.options.get('dynamic_neigh_routers')): + continue + + opts = copy.deepcopy(router.options) + opts.update({'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'}) + cmds.append(self._nb_idl.update_lrouter(router.name, options=opts)) + + if cmds: + with self._nb_idl.transaction(check_error=True) as txn: + for cmd in cmds: + txn.add(cmd) + raise periodics.NeverAgain() + class HashRingHealthCheckPeriodics(object): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,9 +21,11 @@ from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net as pnet +from neutron_lib.api.definitions import segment as segment_def from neutron_lib import constants as const from neutron_lib import context as n_context from neutron_lib import exceptions as n_exc +from neutron_lib.exceptions import l3 as l3_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_lib.plugins import utils as p_utils @@ -39,8 +41,11 @@ from neutron.common.ovn import utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import ovn_revision_numbers_db as db_rev +from neutron.db import segments_db +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ + import qos as qos_extension from neutron.scheduler import l3_ovn_scheduler -from neutron.services.qos.drivers.ovn import driver as qos_driver + LOG = log.getLogger(__name__) @@ -65,7 +70,8 @@ self._plugin_property = None self._l3_plugin_property = None - self._qos_driver = qos_driver.OVNQosDriver(self) + # TODO(ralonsoh): handle the OVN client extensions with an ext. manager + self._qos_driver = qos_extension.OVNClientQosExtension(self) self._ovn_scheduler = l3_ovn_scheduler.get_scheduler() @property @@ -202,8 +208,9 @@ def get_virtual_port_parents(self, virtual_ip, port): ls = self._nb_idl.ls_get(utils.ovn_name(port['network_id'])).execute( check_error=True) - return [lsp.name for lsp in ls.ports for ps in lsp.port_security - if lsp.name != port['id'] and virtual_ip in ps] + return [lsp.name for lsp in ls.ports + if lsp.name != port['id'] and + virtual_ip in utils.get_ovn_port_addresses(lsp)] def _get_port_options(self, port): context = n_context.get_admin_context() @@ -212,6 +219,8 @@ port_type = '' cidrs = '' + dhcpv4_options = self._get_port_dhcp_options(port, const.IP_VERSION_4) + dhcpv6_options = self._get_port_dhcp_options(port, const.IP_VERSION_6) if vtep_physical_switch: vtep_logical_switch = binding_prof.get('vtep-logical-switch') port_type = 'vtep' @@ -246,11 +255,6 @@ options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY] = ( ','.join(parents)) - port_security, new_macs = ( - self._get_allowed_addresses_from_port(port)) - addresses = [address] - addresses.extend(new_macs) - # Only adjust the OVN type if the port is not owned by Neutron # DHCP agents. if (port['device_owner'] == const.DEVICE_OWNER_DHCP and @@ -260,7 +264,7 @@ capabilities = utils.get_port_capabilities(port) vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL) - if (vnic_type == portbindings.VNIC_DIRECT and + if (vnic_type in ovn_const.EXTERNAL_PORT_TYPES and ovn_const.PORT_CAP_SWITCHDEV not in capabilities): if self.is_external_ports_supported(): port_type = ovn_const.LSP_TYPE_EXTERNAL @@ -268,9 +272,21 @@ LOG.warning('The version of OVN used does not support ' 'the "external ports" feature used for ' 'SR-IOV ports with OVN native DHCP') + addresses = [] + port_security, new_macs = ( + self._get_allowed_addresses_from_port(port)) + # TODO(egarciar): OVN supports MAC learning from v21.03. This + # if-else block is stated so as to keep compability with older OVN + # versions and should be removed in the future. + if self._sb_idl.is_table_present('FDB'): + if (port_security or port_type or dhcpv4_options or + dhcpv6_options): + addresses.append(address) + addresses.extend(new_macs) + else: + addresses = [address] + addresses.extend(new_macs) - # The "unknown" address should only be set for the normal LSP - # ports (the ones which type is empty) if not port_security and not port_type: # Port security is disabled for this port. # So this port can send traffic with any mac address. @@ -279,15 +295,20 @@ # So add it. addresses.append(ovn_const.UNKNOWN_ADDR) - dhcpv4_options = self._get_port_dhcp_options(port, const.IP_VERSION_4) - dhcpv6_options = self._get_port_dhcp_options(port, const.IP_VERSION_6) - # HA Chassis Group will bind the port to the highest # priority Chassis if port_type != ovn_const.LSP_TYPE_EXTERNAL: options.update({'requested-chassis': port.get(portbindings.HOST_ID, '')}) + # TODO(lucasagomes): Enable the mcast_flood_reports by default, + # according to core OVN developers it shouldn't cause any harm + # and will be ignored when mcast_snoop is False. We can revise + # this once https://bugzilla.redhat.com/show_bug.cgi?id=1933990 + # (see comment #3) is fixed in Core OVN. + if port_type not in ('vtep', 'localport', 'router'): + options.update({ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true'}) + device_owner = port.get('device_owner', '') sg_ids = ' '.join(utils.get_lsp_security_groups(port)) return OvnPortInfo(port_type, options, addresses, port_security, @@ -318,8 +339,6 @@ utils.get_revision_number( port, ovn_const.TYPE_PORTS))} lswitch_name = utils.ovn_name(port['network_id']) - sg_cache = {} - subnet_cache = {} # It's possible to have a network created on one controller and then a # port created on a different controller quickly enough that the second @@ -388,56 +407,21 @@ port_cmd = txn.add(self._nb_idl.create_lswitch_port( **kwargs)) - # Handle ACL's for this port. If we're not using Port Groups - # because either the schema doesn't support it or we didn't - # migrate old SGs from Address Sets to Port Groups, then we - # keep the old behavior. For those SGs this port belongs to - # that are modelled as a Port Group, we'll use it. sg_ids = utils.get_lsp_security_groups(port) - if self._nb_idl.is_port_groups_supported(): - # If this is not a trusted port or port security is enabled, - # add it to the default drop Port Group so that all traffic - # is dropped by default. - if not utils.is_lsp_trusted(port) or port_info.port_security: - self._add_port_to_drop_port_group(port_cmd, txn) - # For SGs modelled as OVN Port Groups, just add the port to - # its Port Group. - for sg in sg_ids: - txn.add(self._nb_idl.pg_add_ports( - utils.ovn_port_group_name(sg), port_cmd)) - else: - # SGs modelled as Address Sets: - acls_new = ovn_acl.add_acls(self._plugin, context, - port, sg_cache, subnet_cache, - self._nb_idl) - for acl in acls_new: - txn.add(self._nb_idl.add_acl(**acl)) - - if port.get('fixed_ips') and sg_ids: - addresses = ovn_acl.acl_port_ips(port) - # NOTE(rtheis): Fail port creation if the address set - # doesn't exist. This prevents ports from being created on - # any security groups out-of-sync between neutron and OVN. - for sg_id in sg_ids: - for ip_version in addresses: - if addresses[ip_version]: - txn.add(self._nb_idl.update_address_set( - name=utils.ovn_addrset_name(sg_id, - ip_version), - addrs_add=addresses[ip_version], - addrs_remove=None, - if_exists=False)) + # If this is not a trusted port or port security is enabled, + # add it to the default drop Port Group so that all traffic + # is dropped by default. + if not utils.is_lsp_trusted(port) or port_info.port_security: + self._add_port_to_drop_port_group(port_cmd, txn) + # Just add the port to its Port Group. + for sg in sg_ids: + txn.add(self._nb_idl.pg_add_ports( + utils.ovn_port_group_name(sg), port_cmd)) if self.is_dns_required_for_port(port): self.add_txns_to_sync_port_dns_records(txn, port) - # Add qos for port by qos table of logical flow instead of tc - qos_options = self._qos_driver.get_qos_options(port) - - if qos_options: - qos_rule_column = self._create_qos_rules(qos_options, - port, lswitch_name) - txn.add(self._nb_idl.qos_add(**qos_rule_column)) + self._qos_driver.create_port(txn, port, port_type=port_info.type) db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS) @@ -466,10 +450,9 @@ # TODO(lucasagomes): The ``port_object`` parameter was added to # keep things backward compatible. Remove it in the Rocky release. - def update_port(self, context, port, qos_options=None, port_object=None): + def update_port(self, context, port, port_object=None): if utils.is_lsp_ignored(port): return - # Does not need to add qos rule to port_info port_info = self._get_port_options(port) external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'], ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'], @@ -484,9 +467,6 @@ ovn_const.OVN_REV_NUM_EXT_ID_KEY: str( utils.get_revision_number( port, ovn_const.TYPE_PORTS))} - lswitch_name = utils.ovn_name(port['network_id']) - sg_cache = {} - subnet_cache = {} check_rev_cmd = self._nb_idl.check_revision_number( port['id'], port, ovn_const.TYPE_PORTS) @@ -512,6 +492,16 @@ else: dhcpv6_options = [port_info.dhcpv6_options['uuid']] + if self.is_metadata_port(port): + context = n_context.get_admin_context() + network = self._plugin.get_network(context, port['network_id']) + subnet_ids = set(_ip['subnet_id'] for _ip in port['fixed_ips']) + for subnet_id in subnet_ids: + subnet = self._plugin.get_subnet(context, subnet_id) + if not subnet['enable_dhcp']: + continue + self._update_subnet_dhcp_options(subnet, network, txn) + # NOTE(mjozefcz): Do not set addresses if the port is not # bound, has no device_owner and it is OVN LB VIP port. # For more details check related bug #1789686. @@ -540,6 +530,11 @@ context, txn, port, addr_pairs_diff.removed, unset=True) + # Keep key value pairs that were in the original external ids + # of the ovn port and we did not touch. + for k, v in ovn_port.external_ids.items(): + external_ids.setdefault(k, v) + # NOTE(lizk): Fail port updating if port doesn't exist. This # prevents any new inserted resources to be orphan, such as port # dhcp options or ACL rules for port, e.g. a port was created @@ -564,141 +559,25 @@ detached_sg_ids = old_sg_ids - new_sg_ids attached_sg_ids = new_sg_ids - old_sg_ids - if self._nb_idl.is_port_groups_supported(): - for sg in detached_sg_ids: - txn.add(self._nb_idl.pg_del_ports( - utils.ovn_port_group_name(sg), port['id'])) - for sg in attached_sg_ids: - txn.add(self._nb_idl.pg_add_ports( - utils.ovn_port_group_name(sg), port['id'])) - if (not utils.is_lsp_trusted(port) and - utils.is_port_security_enabled(port)): - self._add_port_to_drop_port_group(port['id'], txn) - # If the port doesn't belong to any security group and - # port_security is disabled, or it's a trusted port, then - # allow all traffic. - elif ((not new_sg_ids and - not utils.is_port_security_enabled(port)) or - utils.is_lsp_trusted(port)): - self._del_port_from_drop_port_group(port['id'], txn) - else: + for sg in detached_sg_ids: + txn.add(self._nb_idl.pg_del_ports( + utils.ovn_port_group_name(sg), port['id'])) + for sg in attached_sg_ids: + txn.add(self._nb_idl.pg_add_ports( + utils.ovn_port_group_name(sg), port['id'])) + if (not utils.is_lsp_trusted(port) and + utils.is_port_security_enabled(port)): + self._add_port_to_drop_port_group(port['id'], txn) + # If the port doesn't belong to any security group and + # port_security is disabled, or it's a trusted port, then + # allow all traffic. + elif ((not new_sg_ids and + not utils.is_port_security_enabled(port)) or + utils.is_lsp_trusted(port)): + self._del_port_from_drop_port_group(port['id'], txn) - old_fixed_ips = utils.remove_macs_from_lsp_addresses( - ovn_port.addresses) - new_fixed_ips = [x['ip_address'] for x in - port.get('fixed_ips', [])] - is_fixed_ips_updated = ( - sorted(old_fixed_ips) != sorted(new_fixed_ips)) - port_security_changed = ( - utils.is_port_security_enabled(port) != - bool(ovn_port.port_security)) - # Refresh ACLs for changed security groups or fixed IPs. - if (detached_sg_ids or attached_sg_ids or - is_fixed_ips_updated or port_security_changed): - # Note that update_acls will compare the port's ACLs to - # ensure only the necessary ACLs are added and deleted - # on the transaction. - acls_new = ovn_acl.add_acls(self._plugin, - context, - port, - sg_cache, - subnet_cache, - self._nb_idl) - txn.add(self._nb_idl.update_acls([port['network_id']], - [port], - {port['id']: acls_new}, - need_compare=True)) - - # Refresh address sets for changed security groups or fixed - # IPs. - if len(old_fixed_ips) != 0 or len(new_fixed_ips) != 0: - addresses = ovn_acl.acl_port_ips(port) - addresses_old = utils.sort_ips_by_version( - utils.get_ovn_port_addresses(ovn_port)) - # Add current addresses to attached security groups. - for sg_id in attached_sg_ids: - for ip_version in addresses: - if addresses[ip_version]: - txn.add(self._nb_idl.update_address_set( - name=utils.ovn_addrset_name(sg_id, - ip_version), - addrs_add=addresses[ip_version], - addrs_remove=None)) - # Remove old addresses from detached security groups. - for sg_id in detached_sg_ids: - for ip_version in addresses_old: - if addresses_old[ip_version]: - txn.add(self._nb_idl.update_address_set( - name=utils.ovn_addrset_name(sg_id, - ip_version), - addrs_add=None, - addrs_remove=addresses_old[ip_version])) - - if is_fixed_ips_updated or addr_pairs_diff.changed: - # We have refreshed address sets for attached and - # detached security groups, so now we only need to take - # care of unchanged security groups. - unchanged_sg_ids = new_sg_ids & old_sg_ids - for sg_id in unchanged_sg_ids: - for ip_version in addresses: - addr_add = ((set(addresses[ip_version]) - - set(addresses_old[ip_version])) or - None) - addr_remove = ( - (set(addresses_old[ip_version]) - - set(addresses[ip_version])) or None) - - if addr_add or addr_remove: - txn.add(self._nb_idl.update_address_set( - name=utils.ovn_addrset_name( - sg_id, ip_version), - addrs_add=addr_add, - addrs_remove=addr_remove)) - - # Update QoS policy rule, delete the old one, then add the new one - # If we create port with qos_policy, we also need execute - # update_port method, which qos_policy is in port dict, so we - # also need get policy from port dict if qos_options is None - qos_options_new = (qos_options if qos_options - else self._qos_driver.get_qos_options(port)) - # If port_object is None, we also need to get necessary params - # to delete the qos rule - qos_options_old = (self._qos_driver.get_qos_options(port_object) - if port_object else qos_options_new) - - ovn_net = self._nb_idl.get_lswitch(lswitch_name) - ovn_net_qos_policy = (ovn_net.external_ids - [ovn_const.OVN_QOS_POLICY_EXT_ID_KEY] - if ovn_const.OVN_QOS_POLICY_EXT_ID_KEY - in ovn_net.external_ids else None) - - if qos_options_new: - qos_rule_column_old = self._create_qos_rules(qos_options_old, - port, - lswitch_name, - if_delete=True) - # Delete old QoS rule first - txn.add(self._nb_idl.qos_del(**qos_rule_column_old)) - # Add new QoS rule - qos_rule_column_new = self._create_qos_rules(qos_options_new, - port, - lswitch_name) - txn.add(self._nb_idl.qos_add(**qos_rule_column_new)) - # If we want to delete port qos_rule by using - # param '--no-qos-policy' - elif qos_options_old: - - qos_rule_column_old = self._create_qos_rules(qos_options_old, - port, - lswitch_name, - if_delete=True) - # Delete old QoS rule - txn.add(self._nb_idl.qos_del(**qos_rule_column_old)) - - # If we want to delete network qos_rule by using - # param '--no-qos-policy' - elif not qos_options_old and ovn_net_qos_policy: - txn.add(self._nb_idl.qos_del(lswitch_name)) + self._qos_driver.update_port(txn, port, port_object, + port_type=port_info.type) if self.is_dns_required_for_port(port): self.add_txns_to_sync_port_dns_records( @@ -710,79 +589,23 @@ if check_rev_cmd.result == ovn_const.TXN_COMMITTED: db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS) - def _create_qos_rules(self, qos_options, port, lswitch_name, - if_delete=False): - qos_rule = {} - direction = 'from-lport' if qos_options['direction'] ==\ - 'egress' else 'to-lport' - qos_rule.update(switch=lswitch_name, direction=direction, - priority=2002) - - if direction == 'from-lport': - match = 'inport == ' + '"{}"'.format(port['id']) - qos_rule.update(match=match) - else: - match = 'outport == ' + '"{}"'.format(port['id']) - qos_rule.update(match=match) - # QoS of bandwidth_limit - if 'qos_max_rate' in qos_options: - burst = qos_options.get('qos_burst') - qos_rule.update(rate=qos_options['qos_max_rate'], - burst=burst, dscp=None) - # QoS of dscp - elif 'dscp_mark' in qos_options: - qos_rule.update(rate=None, burst=None, - dscp=qos_options['dscp_mark']) - # There is no need 'rate', 'burst' or 'dscp' for deleted method - if if_delete is True: - qos_rule.pop('rate') - qos_rule.pop('burst') - qos_rule.pop('dscp') - return qos_rule - def _delete_port(self, port_id, port_object=None): - ovn_port = self._nb_idl.lookup('Logical_Switch_Port', port_id) - network_id = ovn_port.external_ids.get( - ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY) + ovn_port = self._nb_idl.lookup( + 'Logical_Switch_Port', port_id, default=None) + if ovn_port is None: + return - # TODO(lucasagomes): For backward compatibility, if network_id - # is not in the OVNDB, look at the port_object - if not network_id and port_object: - network_id = port_object['network_id'] + ovn_network_name = ovn_port.external_ids.get( + ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY) + network_id = ovn_network_name.replace('neutron-', '') with self._nb_idl.transaction(check_error=True) as txn: txn.add(self._nb_idl.delete_lswitch_port( - port_id, network_id)) - - if not self._nb_idl.is_port_groups_supported(): - txn.add(self._nb_idl.delete_acl(network_id, port_id)) + port_id, ovn_network_name)) - addresses = utils.sort_ips_by_version( - utils.get_ovn_port_addresses(ovn_port)) - sec_groups = utils.get_ovn_port_security_groups( - ovn_port, skip_trusted_port=False) - for sg_id in sec_groups: - for ip_version, addr_list in addresses.items(): - if not addr_list: - continue - txn.add(self._nb_idl.update_address_set( - name=utils.ovn_addrset_name(sg_id, ip_version), - addrs_add=None, - addrs_remove=addr_list)) - # Delete qos rule of port - try: - if (not port_object or 'qos_policy_id' not in port_object or - port_object['qos_policy_id'] is None): - pass - else: - qos_options = self._qos_driver.get_qos_options(port_object) - qos_rule_column = self._create_qos_rules(qos_options, - port_object, - network_id, - if_delete=True) - txn.add(self._nb_idl.qos_del(**qos_rule_column)) - except KeyError: - pass + p_object = ({'id': port_id, 'network_id': network_id} + if not port_object else port_object) + self._qos_driver.delete_port(txn, p_object) if port_object and self.is_dns_required_for_port(port_object): self.add_txns_to_remove_port_dns_records(txn, port_object) @@ -790,7 +613,7 @@ # Check if the port being deleted is a virtual parent if (ovn_port.type != ovn_const.LSP_TYPE_VIRTUAL and self._is_virtual_port_supported()): - ls = self._nb_idl.ls_get(network_id).execute( + ls = self._nb_idl.ls_get(ovn_network_name).execute( check_error=True) cmd = self._nb_idl.unset_lswitch_port_to_virtual_type for lsp in ls.ports: @@ -818,6 +641,8 @@ admin_context = n_context.get_admin_context() fip_db = self._l3_plugin._get_floatingip( admin_context, floatingip['id']) + port_db = self._plugin.get_port( + admin_context, fip_db['floating_port_id']) gw_lrouter_name = utils.ovn_name(router_id) # TODO(chandrav): Since the floating ip port is not @@ -835,18 +660,16 @@ ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number( floatingip, ovn_const.TYPE_FLOATINGIPS)), ovn_const.OVN_FIP_PORT_EXT_ID_KEY: floatingip['port_id'], - ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: gw_lrouter_name} + ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: gw_lrouter_name, + ovn_const.OVN_FIP_EXT_MAC_KEY: port_db['mac_address']} columns = {'type': 'dnat_and_snat', 'logical_ip': floatingip['fixed_ip_address'], - 'external_ip': floatingip['floating_ip_address']} + 'external_ip': floatingip['floating_ip_address'], + 'logical_port': floatingip['port_id']} if ovn_conf.is_ovn_distributed_floating_ip(): - port = self._plugin.get_port( - admin_context, fip_db['floating_port_id']) - columns['logical_port'] = floatingip['port_id'] - ext_ids[ovn_const.OVN_FIP_EXT_MAC_KEY] = port['mac_address'] if self._nb_idl.lsp_get_up(floatingip['port_id']).execute(): - columns['external_mac'] = port['mac_address'] + columns['external_mac'] = port_db['mac_address'] # TODO(dalvarez): remove this check once the minimum OVS required # version contains the column (when OVS 2.8.2 is released). @@ -1199,12 +1022,14 @@ utils.ovn_lrouter_port_name(gw_port_id), gw_lrouter_name)) - def _get_nets_and_ipv6_ra_confs_for_router_port( - self, context, port_fixed_ips): + def _get_nets_and_ipv6_ra_confs_for_router_port(self, context, port): + port_fixed_ips = port['fixed_ips'] networks = set() ipv6_ra_configs = {} ipv6_ra_configs_supported = self._nb_idl.is_col_present( 'Logical_Router_Port', 'ipv6_ra_configs') + is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get( + 'device_owner') for fixed_ip in port_fixed_ips: subnet_id = fixed_ip['subnet_id'] @@ -1218,20 +1043,25 @@ ipv6_ra_configs['address_mode'] = ( utils.get_ovn_ipv6_address_mode( subnet['ipv6_address_mode'])) - ipv6_ra_configs['send_periodic'] = 'true' net = self._plugin.get_network(context, subnet['network_id']) + # If it's a gateway port and connected to a provider + # network set send_periodic to False, that way we do not + # leak the RAs generated for the tenant networks via the + # provider network + ipv6_ra_configs['send_periodic'] = 'true' + if is_gw_port and utils.is_provider_network(net): + ipv6_ra_configs['send_periodic'] = 'false' ipv6_ra_configs['mtu'] = str(net['mtu']) return list(networks), ipv6_ra_configs def _add_router_ext_gw(self, router, networks, txn): context = n_context.get_admin_context() - router_id = router['id'] # 1. Add the external gateway router port. gateways = self._get_gw_info(context, router) gw_port_id = router['gw_port_id'] port = self._plugin.get_port(context, gw_port_id) - self._create_lrouter_port(context, router_id, port, txn=txn) + self._create_lrouter_port(context, router, port, txn=txn) def _build_extids(gw_info): # TODO(lucasagomes): Remove this check after OVS 2.8.2 is tagged @@ -1246,7 +1076,7 @@ return columns # 2. Add default route with nexthop as gateway ip - lrouter_name = utils.ovn_name(router_id) + lrouter_name = utils.ovn_name(router['id']) for gw_info in gateways: columns = _build_extids(gw_info) txn.add(self._nb_idl.add_static_route( @@ -1340,7 +1170,9 @@ ovn_const.OVN_GW_PORT_EXT_ID_KEY: router.get('gw_port_id') or '', ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number( - router, ovn_const.TYPE_ROUTERS))} + router, ovn_const.TYPE_ROUTERS)), + ovn_const.OVN_ROUTER_AZ_HINTS_EXT_ID_KEY: + ','.join(utils.get_az_hints(router))} def create_router(self, context, router, add_external_gateway=True): """Create a logical router.""" @@ -1348,11 +1180,13 @@ enabled = router.get('admin_state_up') lrouter_name = utils.ovn_name(router['id']) added_gw_port = None + options = {'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'} with self._nb_idl.transaction(check_error=True) as txn: txn.add(self._nb_idl.create_lrouter(lrouter_name, external_ids=external_ids, enabled=enabled, - options={})) + options=options)) # TODO(lucasagomes): add_external_gateway is being only used # by the ovn_db_sync.py script, remove it after the database # synchronization work @@ -1417,11 +1251,10 @@ else: # Check if snat has been enabled/disabled and update new_snat_state = gateway_new.get('enable_snat', True) - if bool(ovn_snats) != new_snat_state: - if utils.is_snat_enabled(new_router) and networks: - self.update_nat_rules( - new_router, networks, - enable_snat=new_snat_state, txn=txn) + if bool(ovn_snats) != new_snat_state and networks: + self.update_nat_rules( + new_router, networks, + enable_snat=new_snat_state, txn=txn) update = {'external_ids': self._gen_router_ext_ids(new_router)} update['enabled'] = new_router.get('admin_state_up') or False @@ -1461,13 +1294,16 @@ db_rev.delete_revision(context, router_id, ovn_const.TYPE_ROUTERS) def get_candidates_for_scheduling(self, physnet, cms=None, - chassis_physnets=None): + chassis_physnets=None, + availability_zone_hints=None): """Return chassis for scheduling gateway router. Criteria for selecting chassis as candidates 1) chassis from cms with proper bridge mappings 2) if no chassis is available from 1) then, select chassis with proper bridge mappings + 3) Filter the available chassis accordingly to the routers + availability zone hints (if present) """ # TODO(lucasagomes): Simplify the logic here, the CMS option has # been introduced long ago and by now all gateway chassis should @@ -1484,11 +1320,21 @@ cms_bmaps.append(chassis) else: bmaps.append(chassis) - candidates = cms_bmaps or bmaps + candidates = cms_bmaps or bmaps or cms + + # Filter for availability zones + if availability_zone_hints: + LOG.debug('Filtering Chassis candidates by availability zone ' + 'hints: %s', ', '.join(availability_zone_hints)) + candidates = [ch for ch in candidates + for az in availability_zone_hints + if az in utils.get_chassis_availability_zones( + self._sb_idl.lookup('Chassis', ch, None))] + if not cms_bmaps: LOG.debug("No eligible chassis with external connectivity" " through ovn-cms-options for %s", physnet) - LOG.debug("Chassis candidates with external connectivity: %s", + LOG.debug("Chassis candidates for scheduling gateway router ports: %s", candidates) return candidates @@ -1522,7 +1368,8 @@ # logical router port is centralized in the chassis hosting the # distributed gateway port. # https://github.com/openvswitch/ovs/commit/85706c34d53d4810f54bec1de662392a3c06a996 - if network.get(pnet.NETWORK_TYPE) == const.TYPE_VLAN: + if (network.get(pnet.NETWORK_TYPE) == const.TYPE_VLAN and + not ovn_conf.is_ovn_distributed_floating_ip()): options['reside-on-redirect-chassis'] = 'true' is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get( @@ -1533,12 +1380,11 @@ return options - def _create_lrouter_port(self, context, router_id, port, txn=None): + def _create_lrouter_port(self, context, router, port, txn=None): """Create a logical router port.""" - lrouter = utils.ovn_name(router_id) + lrouter = utils.ovn_name(router['id']) networks, ipv6_ra_configs = ( - self._get_nets_and_ipv6_ra_confs_for_router_port( - context, port['fixed_ips'])) + self._get_nets_and_ipv6_ra_confs_for_router_port(context, port)) lrouter_port_name = utils.ovn_lrouter_port_name(port['id']) is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get( 'device_owner') @@ -1549,7 +1395,8 @@ port_net = self._plugin.get_network(n_context.get_admin_context(), port['network_id']) physnet = self._get_physnet(port_net) - candidates = self.get_candidates_for_scheduling(physnet) + candidates = self.get_candidates_for_scheduling( + physnet, availability_zone_hints=utils.get_az_hints(router)) selected_chassis = self._ovn_scheduler.select( self._nb_idl, self._sb_idl, lrouter_port_name, candidates=candidates) @@ -1576,6 +1423,7 @@ def create_router_port(self, context, router_id, router_interface): port = self._plugin.get_port(context, router_interface['port_id']) + router = self._l3_plugin.get_router(context, router_id) with self._nb_idl.transaction(check_error=True) as txn: multi_prefix = False if (len(router_interface.get('subnet_ids', [])) == 1 and @@ -1587,9 +1435,8 @@ self._update_lrouter_port(context, port, txn=txn) multi_prefix = True else: - self._create_lrouter_port(context, router_id, port, txn=txn) + self._create_lrouter_port(context, router, port, txn=txn) - router = self._l3_plugin.get_router(context, router_id) if router.get(l3.EXTERNAL_GW_INFO): cidr = None for fixed_ip in port['fixed_ips']: @@ -1611,8 +1458,7 @@ def _update_lrouter_port(self, context, port, if_exists=False, txn=None): """Update a logical router port.""" networks, ipv6_ra_configs = ( - self._get_nets_and_ipv6_ra_confs_for_router_port( - context, port['fixed_ips'])) + self._get_nets_and_ipv6_ra_confs_for_router_port(context, port)) lsp_address = ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER lrp_name = utils.ovn_lrouter_port_name(port['id']) @@ -1679,14 +1525,18 @@ router_id = router_id or ovn_port.external_ids.get( ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY) - if not router_id: + if port and not router_id: router_id = port.get('device_id') router = None if router_id: - router = self._l3_plugin.get_router(context, router_id) + try: + router = self._l3_plugin.get_router(context, router_id) + except l3_exc.RouterNotFound: + # If the router is gone, the router port is also gone + port_removed = True - if not router.get(l3.EXTERNAL_GW_INFO): + if not router or not router.get(l3.EXTERNAL_GW_INFO): if port_removed: self._delete_lrouter_port(context, port_id, router_id, txn=txn) @@ -1701,12 +1551,15 @@ cidr = None for sid in subnet_ids: - subnet = self._plugin.get_subnet(context, sid) + try: + subnet = self._plugin.get_subnet(context, sid) + except n_exc.SubnetNotFound: + continue if subnet['ip_version'] == 4: cidr = subnet['cidr'] break - if router and utils.is_snat_enabled(router) and cidr: + if utils.is_snat_enabled(router) and cidr: self.update_nat_rules( router, networks=[cidr], enable_snat=False, txn=txn) @@ -1734,15 +1587,44 @@ for network in networks] self._transaction(commands, txn=txn) - def _create_provnet_port(self, txn, network, physnet, tag): - txn.add(self._nb_idl.create_lswitch_port( - lport_name=utils.ovn_provnet_port_name(network['id']), - lswitch_name=utils.ovn_name(network['id']), + def create_provnet_port(self, network_id, segment, txn=None): + tag = segment.get(segment_def.SEGMENTATION_ID, []) + physnet = segment.get(segment_def.PHYSICAL_NETWORK) + options = {'network_name': physnet, + ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true', + ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'} + cmd = self._nb_idl.create_lswitch_port( + lport_name=utils.ovn_provnet_port_name(segment['id']), + lswitch_name=utils.ovn_name(network_id), addresses=[ovn_const.UNKNOWN_ADDR], external_ids={}, type=ovn_const.LSP_TYPE_LOCALNET, - tag=tag if tag else [], - options={'network_name': physnet})) + tag=tag, + options=options) + self._transaction([cmd], txn=txn) + + def delete_provnet_port(self, network_id, segment): + port_to_del = utils.ovn_provnet_port_name(segment['id']) + legacy_port_name = utils.ovn_provnet_port_name(network_id) + physnet = segment.get(segment_def.PHYSICAL_NETWORK) + lswitch = self._nb_idl.get_lswitch(utils.ovn_name(network_id)) + lports = [lp.name for lp in lswitch.ports] + + # Cover the situation where localnet ports + # were named after network_id and not segment_id. + # TODO(mjozefcz): Remove this in w-release. + if (port_to_del not in lports and + legacy_port_name in lports): + for lport in lswitch.ports: + if (legacy_port_name == lport.name and + lport.options['network_name'] == physnet): + port_to_del = legacy_port_name + break + + cmd = self._nb_idl.delete_lswitch_port( + lport_name=port_to_del, + lswitch_name=utils.ovn_name(network_id)) + self._transaction([cmd]) def _gen_network_parameters(self, network): params = {'external_ids': { @@ -1751,18 +1633,10 @@ ovn_const.OVN_REV_NUM_EXT_ID_KEY: str( utils.get_revision_number(network, ovn_const.TYPE_NETWORKS))}} - # NOTE(lucasagomes): There's a difference between the - # "qos_policy_id" key existing and it being None, the latter is a - # valid value. Since we can't save None in OVSDB, we are converting - # it to "null" as a placeholder. - if 'qos_policy_id' in network: - params['external_ids'][ovn_const.OVN_QOS_POLICY_EXT_ID_KEY] = ( - network['qos_policy_id'] or 'null') - # Enable IGMP snooping if igmp_snooping_enable is enabled in Neutron value = 'true' if ovn_conf.is_igmp_snooping_enabled() else 'false' params['other_config'] = {ovn_const.MCAST_SNOOP: value, - ovn_const.MCAST_FLOOD_UNREGISTERED: value} + ovn_const.MCAST_FLOOD_UNREGISTERED: 'false'} return params def create_network(self, context, network): @@ -1771,13 +1645,16 @@ # without having to track what UUID OVN assigned to it. lswitch_params = self._gen_network_parameters(network) lswitch_name = utils.ovn_name(network['id']) + # NOTE(mjozefcz): Remove this workaround when bug + # 1869877 will be fixed. + segments = segments_db.get_network_segments( + context, network['id']) with self._nb_idl.transaction(check_error=True) as txn: txn.add(self._nb_idl.ls_add(lswitch_name, **lswitch_params, may_exist=True)) - physnet = network.get(pnet.PHYSICAL_NETWORK) - if physnet: - self._create_provnet_port(txn, network, physnet, - network.get(pnet.SEGMENTATION_ID)) + for segment in segments: + if segment.get(segment_def.PHYSICAL_NETWORK): + self.create_provnet_port(network['id'], segment, txn=txn) db_rev.bump_revision(context, network, ovn_const.TYPE_NETWORKS) self.create_metadata_port(context, network) return network @@ -1794,21 +1671,6 @@ db_rev.delete_revision( context, network_id, ovn_const.TYPE_NETWORKS) - def _is_qos_update_required(self, network): - # Is qos service enabled - if 'qos_policy_id' not in network: - return False - - # Check if qos service wasn't enabled before - ovn_net = self._nb_idl.get_lswitch(utils.ovn_name(network['id'])) - if ovn_const.OVN_QOS_POLICY_EXT_ID_KEY not in ovn_net.external_ids: - return True - - # Check if the policy_id has changed - new_qos_id = network['qos_policy_id'] or 'null' - return new_qos_id != ovn_net.external_ids[ - ovn_const.OVN_QOS_POLICY_EXT_ID_KEY] - def set_gateway_mtu(self, context, prov_net, txn=None): ports = self._plugin.get_ports( context, filters=dict(network_id=[prov_net['id']], @@ -1824,10 +1686,8 @@ name=lrp_name, if_exists=True, options=options)) self._transaction(commands, txn=txn) - def update_network(self, context, network): + def update_network(self, context, network, original_network=None): lswitch_name = utils.ovn_name(network['id']) - # Check if QoS needs to be update, before updating OVNDB - qos_update_required = self._is_qos_update_required(network) check_rev_cmd = self._nb_idl.check_revision_number( lswitch_name, network, ovn_const.TYPE_NETWORKS) @@ -1880,9 +1740,9 @@ self.set_gateway_mtu(n_context.get_admin_context(), network, txn) + self._qos_driver.update_network(txn, network, original_network) + if check_rev_cmd.result == ovn_const.TXN_COMMITTED: - if qos_update_required: - self._qos_driver.update_network(network) db_rev.bump_revision(context, network, ovn_const.TYPE_NETWORKS) def _add_subnet_dhcp_options(self, subnet, network, @@ -2126,9 +1986,14 @@ def create_subnet(self, context, subnet, network): if subnet['enable_dhcp']: - if subnet['ip_version'] == 4: - self.update_metadata_port(context, network['id']) - self._add_subnet_dhcp_options(subnet, network) + mport_updated = False + if subnet['ip_version'] == const.IP_VERSION_4: + mport_updated = self.update_metadata_port( + context, network['id'], subnet=subnet) + if subnet['ip_version'] == const.IP_VERSION_6 or not mport_updated: + # NOTE(ralonsoh): if IPv4 but the metadata port has not been + # updated, the DHPC options register has not been created. + self._add_subnet_dhcp_options(subnet, network) db_rev.bump_revision(context, subnet, ovn_const.TYPE_SUBNETS) def _modify_subnet_dhcp_options(self, subnet, ovn_subnet, network, txn): @@ -2144,7 +2009,7 @@ subnet['id'])['subnet'] if subnet['enable_dhcp'] or ovn_subnet: - self.update_metadata_port(context, network['id']) + self.update_metadata_port(context, network['id'], subnet=subnet) check_rev_cmd = self._nb_idl.check_revision_number( subnet['id'], subnet, ovn_const.TYPE_SUBNETS) @@ -2165,47 +2030,19 @@ context, subnet_id, ovn_const.TYPE_SUBNETS) def create_security_group(self, context, security_group): - # If the OVN schema supports Port Groups, we'll model security groups - # as such. Otherwise, for backwards compatibility, we'll keep creating - # two Address Sets for each Neutron SG (one for IPv4 and one for - # IPv6). with self._nb_idl.transaction(check_error=True) as txn: ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: security_group['id']} - if self._nb_idl.is_port_groups_supported(): - name = utils.ovn_port_group_name(security_group['id']) - txn.add(self._nb_idl.pg_add( - name=name, acls=[], external_ids=ext_ids)) - # When a SG is created, it comes with some default rules, - # so we'll apply them to the Port Group. - ovn_acl.add_acls_for_sg_port_group(self._nb_idl, - security_group, txn) - else: - for ip_version in ('ip4', 'ip6'): - name = utils.ovn_addrset_name(security_group['id'], - ip_version) - txn.add(self._nb_idl.create_address_set( - name=name, external_ids=ext_ids)) + name = utils.ovn_port_group_name(security_group['id']) + txn.add(self._nb_idl.pg_add( + name=name, acls=[], external_ids=ext_ids)) + # When a SG is created, it comes with some default rules, + # so we'll apply them to the Port Group. + ovn_acl.add_acls_for_sg_port_group(self._nb_idl, + security_group, txn) db_rev.bump_revision( context, security_group, ovn_const.TYPE_SECURITY_GROUPS) - def create_default_drop_port_group(self, ports=None): - pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME - with self._nb_idl.transaction(check_error=True) as txn: - if not self._nb_idl.get_port_group(pg_name): - # If drop Port Group doesn't exist yet, create it. - txn.add(self._nb_idl.pg_add(pg_name, acls=[], may_exist=True)) - # Add ACLs to this Port Group so that all traffic is dropped. - acls = ovn_acl.add_acls_for_drop_port_group(pg_name) - for acl in acls: - txn.add(self._nb_idl.pg_acl_add(may_exist=True, **acl)) - - if ports: - ports_ids = [port['id'] for port in ports] - # Add the ports to the default Port Group - txn.add(self._nb_idl.pg_add_ports(pg_name, ports_ids)) - def _add_port_to_drop_port_group(self, port, txn): - self.create_default_drop_port_group() txn.add(self._nb_idl.pg_add_ports(ovn_const.OVN_DROP_PORT_GROUP_NAME, port)) @@ -2216,14 +2053,8 @@ def delete_security_group(self, context, security_group_id): with self._nb_idl.transaction(check_error=True) as txn: - if self._nb_idl.is_port_groups_supported(): - name = utils.ovn_port_group_name(security_group_id) - txn.add(self._nb_idl.pg_del(name=name)) - else: - for ip_version in ('ip4', 'ip6'): - name = utils.ovn_addrset_name(security_group_id, - ip_version) - txn.add(self._nb_idl.delete_address_set(name=name)) + name = utils.ovn_port_group_name(security_group_id) + txn.add(self._nb_idl.pg_del(name=name)) db_rev.delete_revision(context, security_group_id, ovn_const.TYPE_SECURITY_GROUPS) @@ -2243,6 +2074,15 @@ db_rev.delete_revision( context, rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES) + @staticmethod + def is_metadata_port(port): + # TODO(ralonsoh): This method is implemented in order to be backported + # to stable releases; this is why a "const.DEVICE_OWNER_DHCP" port + # could be a metadata port. + return (port['device_owner'] == 'network:distributed' or + (port['device_owner'] == const.DEVICE_OWNER_DHCP and + not utils.is_neutron_dhcp_agent_port(port))) + def _find_metadata_port(self, context, network_id): if not ovn_conf.is_ovn_metadata_enabled(): return @@ -2276,47 +2116,69 @@ # TODO(boden): rehome create_port into neutron-lib p_utils.create_port(self._plugin, context, port) - def update_metadata_port(self, context, network_id): + def update_metadata_port(self, context, network_id, subnet=None): """Update metadata port. This function will allocate an IP address for the metadata port of - the given network in all its IPv4 subnets. + the given network in all its IPv4 subnets or the given subnet. Returns + "True" if the metadata port has been updated and "False" if OVN + metadata is disabled or the metadata port does not exist. """ + def update_metadata_port_fixed_ips(metadata_port, add_subnet_ids, + del_subnet_ids): + wanted_fixed_ips = [ + {'subnet_id': fixed_ip['subnet_id'], + 'ip_address': fixed_ip['ip_address']} for fixed_ip in + metadata_port['fixed_ips'] if + fixed_ip['subnet_id'] not in del_subnet_ids] + wanted_fixed_ips.extend({'subnet_id': s_id} for s_id in + add_subnet_ids) + port = {'id': metadata_port['id'], + 'port': {'network_id': network_id, + 'fixed_ips': wanted_fixed_ips}} + self._plugin.update_port(n_context.get_admin_context(), + metadata_port['id'], port) + if not ovn_conf.is_ovn_metadata_enabled(): - return + return False # Retrieve the metadata port of this network metadata_port = self._find_metadata_port(context, network_id) if not metadata_port: LOG.error("Metadata port couldn't be found for network %s", network_id) - return + return False + + port_subnet_ids = set(ip['subnet_id'] for ip in + metadata_port['fixed_ips']) + + # If this method is called from "create_subnet" or "update_subnet", + # only the fixed IP address from this subnet should be updated in the + # metadata port. + if subnet and subnet['id']: + if subnet['enable_dhcp'] and subnet['id'] not in port_subnet_ids: + update_metadata_port_fixed_ips(metadata_port, + [subnet['id']], []) + elif not subnet['enable_dhcp'] and subnet['id'] in port_subnet_ids: + update_metadata_port_fixed_ips(metadata_port, + [], [subnet['id']]) + return True # Retrieve all subnets in this network subnets = self._plugin.get_subnets(context, filters=dict( - network_id=[network_id], ip_version=[4])) + network_id=[network_id], ip_version=[const.IP_VERSION_4], + enable_dhcp=[True])) subnet_ids = set(s['id'] for s in subnets) - port_subnet_ids = set(ip['subnet_id'] for ip in - metadata_port['fixed_ips']) # Find all subnets where metadata port doesn't have an IP in and # allocate one. if subnet_ids != port_subnet_ids: - wanted_fixed_ips = [] - for fixed_ip in metadata_port['fixed_ips']: - wanted_fixed_ips.append( - {'subnet_id': fixed_ip['subnet_id'], - 'ip_address': fixed_ip['ip_address']}) - wanted_fixed_ips.extend( - dict(subnet_id=s) - for s in subnet_ids - port_subnet_ids) + update_metadata_port_fixed_ips(metadata_port, + subnet_ids - port_subnet_ids, + port_subnet_ids - subnet_ids) - port = {'id': metadata_port['id'], - 'port': {'network_id': network_id, - 'fixed_ips': wanted_fixed_ips}} - self._plugin.update_port(n_context.get_admin_context(), - metadata_port['id'], port) + return True def get_parent_port(self, port_id): return self._nb_idl.get_parent_port(port_id) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,7 +16,7 @@ from eventlet import greenthread from neutron_lib.api.definitions import l3 -from neutron_lib.api.definitions import provider_net as pnet +from neutron_lib.api.definitions import segment as segment_def from neutron_lib import constants from neutron_lib import context from neutron_lib import exceptions as n_exc @@ -30,6 +30,7 @@ from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf +from neutron import manager from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.services.segments import db as segments_db @@ -73,6 +74,11 @@ self.mode = mode self.l3_plugin = directory.get_plugin(plugin_constants.L3) self._ovn_client = ovn_client.OVNClient(ovn_api, sb_ovn) + self.segments_plugin = directory.get_plugin('segments') + if not self.segments_plugin: + self.segments_plugin = ( + manager.NeutronManager.load_class_for_provider( + 'neutron.service_plugins', 'segments')()) def stop(self): if utils.is_ovn_l3(self.l3_plugin): @@ -88,7 +94,6 @@ ctx = context.get_admin_context() - self.sync_address_sets(ctx) self.sync_port_groups(ctx) self.sync_networks_ports_and_dhcp_opts(ctx) self.sync_port_dns_records(ctx) @@ -120,24 +125,6 @@ neutron_acls[port].remove(acl) nb_acls[port].remove(acl) - def compute_address_set_difference(self, neutron_sgs, nb_sgs): - neutron_sgs_name_set = set(neutron_sgs.keys()) - nb_sgs_name_set = set(nb_sgs.keys()) - sgnames_to_add = list(neutron_sgs_name_set - nb_sgs_name_set) - sgnames_to_delete = list(nb_sgs_name_set - neutron_sgs_name_set) - sgs_common = list(neutron_sgs_name_set & nb_sgs_name_set) - sgs_to_update = {} - for sg_name in sgs_common: - neutron_addr_set = set(neutron_sgs[sg_name]['addresses']) - nb_addr_set = set(nb_sgs[sg_name]['addresses']) - addrs_to_add = list(neutron_addr_set - nb_addr_set) - addrs_to_delete = list(nb_addr_set - neutron_addr_set) - if addrs_to_add or addrs_to_delete: - sgs_to_update[sg_name] = {'name': sg_name, - 'addrs_add': addrs_to_add, - 'addrs_remove': addrs_to_delete} - return sgnames_to_add, sgnames_to_delete, sgs_to_update - def get_acls(self, context): """create the list of ACLS in OVN. @@ -165,17 +152,12 @@ acl_list_dict[key] = list([acl]) return acl_list_dict - def get_address_sets(self): - return self.ovn_api.get_address_sets() - def sync_port_groups(self, ctx): """Sync Port Groups between neutron and NB. @param ctx: neutron_lib.context @type ctx: object of type neutron_lib.context.Context """ - if not self.ovn_api.is_port_groups_supported(): - return neutron_sgs = {} neutron_pgs = set() @@ -214,8 +196,8 @@ # already exists in OVN. The rest will be added during the # ports sync operation later. for n_port in db_ports: - if ((n_port['security_groups'] or - n_port['port_security_enabled']) and + if ((utils.is_security_groups_enabled(n_port) or + utils.is_port_security_enabled(n_port)) and n_port['id'] in ovn_ports): txn.add(self.ovn_api.pg_add_ports( pg, n_port['id'])) @@ -236,68 +218,6 @@ LOG.debug('Port-Group-SYNC: transaction finished @ %s', str(datetime.now())) - def sync_address_sets(self, ctx): - """Sync Address Sets between neutron and NB. - - @param ctx: neutron_lib.context - @type ctx: object of type neutron_lib.context.Context - @var db_ports: List of ports from neutron DB - """ - LOG.debug('Address-Set-SYNC: started @ %s', str(datetime.now())) - - sgnames_to_add = sgnames_to_delete = [] - sgs_to_update = {} - nb_sgs = self.get_address_sets() - - if self.ovn_api.is_port_groups_supported(): - # If Port Groups are supported, we just need to delete all Address - # Sets from NB database. - sgnames_to_delete = nb_sgs.keys() - else: - neutron_sgs = {} - with ctx.session.begin(subtransactions=True): - db_sgs = self.core_plugin.get_security_groups(ctx) - db_ports = self.core_plugin.get_ports(ctx) - - for sg in db_sgs: - for ip_version in ['ip4', 'ip6']: - name = utils.ovn_addrset_name(sg['id'], ip_version) - neutron_sgs[name] = { - 'name': name, 'addresses': [], - 'external_ids': { - ovn_const.OVN_SG_EXT_ID_KEY: sg['id']}} - - for port in db_ports: - sg_ids = utils.get_lsp_security_groups(port) - if port.get('fixed_ips') and sg_ids: - addresses = acl_utils.acl_port_ips(port) - for sg_id in sg_ids: - for ip_version in addresses: - name = utils.ovn_addrset_name(sg_id, ip_version) - neutron_sgs[name]['addresses'].extend( - addresses[ip_version]) - - sgnames_to_add, sgnames_to_delete, sgs_to_update = ( - self.compute_address_set_difference(neutron_sgs, nb_sgs)) - - LOG.debug('Address_Sets added %d, removed %d, updated %d', - len(sgnames_to_add), len(sgnames_to_delete), - len(sgs_to_update)) - - if self.mode == SYNC_MODE_REPAIR: - LOG.debug('Address-Set-SYNC: transaction started @ %s', - str(datetime.now())) - with self.ovn_api.transaction(check_error=True) as txn: - for sgname in sgnames_to_add: - sg = neutron_sgs[sgname] - txn.add(self.ovn_api.create_address_set(**sg)) - for sgname, sg in sgs_to_update.items(): - txn.add(self.ovn_api.update_address_set(**sg)) - for sgname in sgnames_to_delete: - txn.add(self.ovn_api.delete_address_set(name=sgname)) - LOG.debug('Address-Set-SYNC: transaction finished @ %s', - str(datetime.now())) - def _get_acls_from_port_groups(self): ovn_acls = [] port_groups = self.ovn_api.db_list_rows('Port_Group').execute() @@ -313,15 +233,20 @@ ovn_acls.append(acl_string) return ovn_acls - def _sync_acls_port_groups(self, ctx): - # If Port Groups are supported, the ACLs in the system will equal - # the number of SG rules plus the default drop rules as OVN would - # allow all traffic by default if those are not added. + def sync_acls(self, ctx): + """Sync ACLs between neutron and NB. + + @param ctx: neutron_lib.context + @type ctx: object of type neutron_lib.context.Context + @return: Nothing + """ + LOG.debug('ACL-SYNC: started @ %s', str(datetime.now())) + neutron_acls = [] for sgr in self.core_plugin.get_security_group_rules(ctx): pg_name = utils.ovn_port_group_name(sgr['security_group_id']) neutron_acls.append(acl_utils._add_sg_rule_acl_for_port_group( - pg_name, sgr, self.ovn_api)) + pg_name, sgr)) neutron_acls += acl_utils.add_acls_for_drop_port_group( ovn_const.OVN_DROP_PORT_GROUP_NAME) @@ -375,88 +300,6 @@ 'Switch %s', aclr[0]) txn.add(self.ovn_api.acl_del(aclr[0])) - def _sync_acls(self, ctx): - """Sync ACLs between neutron and NB when not using Port Groups. - - @param ctx: neutron_lib.context - @type ctx: object of type neutron_lib.context.Context - @var db_ports: List of ports from neutron DB - @var neutron_acls: neutron dictionary of port - vs list-of-acls - @var nb_acls: NB dictionary of port - vs list-of-acls - @var subnet_cache: cache for subnets - @return: Nothing - """ - db_ports = {} - for port in self.core_plugin.get_ports(ctx): - db_ports[port['id']] = port - - sg_cache = {} - subnet_cache = {} - neutron_acls = {} - for port_id, port in db_ports.items(): - if utils.get_lsp_security_groups(port): - acl_list = acl_utils.add_acls(self.core_plugin, - ctx, - port, - sg_cache, - subnet_cache, - self.ovn_api) - if port_id in neutron_acls: - neutron_acls[port_id].extend(acl_list) - else: - neutron_acls[port_id] = acl_list - - nb_acls = self.get_acls(ctx) - - self.remove_common_acls(neutron_acls, nb_acls) - - num_acls_to_add = len(list(itertools.chain(*neutron_acls.values()))) - num_acls_to_remove = len(list(itertools.chain(*nb_acls.values()))) - if 0 != num_acls_to_add or 0 != num_acls_to_remove: - LOG.warning('ACLs-to-be-added %(add)d ' - 'ACLs-to-be-removed %(remove)d', - {'add': num_acls_to_add, - 'remove': num_acls_to_remove}) - - if self.mode == SYNC_MODE_REPAIR: - with self.ovn_api.transaction(check_error=True) as txn: - for acla in list(itertools.chain(*neutron_acls.values())): - LOG.warning('ACL found in Neutron but not in ' - 'OVN DB for port %s', acla['lport']) - txn.add(self.ovn_api.add_acl(**acla)) - - with self.ovn_api.transaction(check_error=True) as txn: - for aclr in list(itertools.chain(*nb_acls.values())): - # Both lswitch and lport aren't needed within the ACL. - lswitchr = aclr.pop('lswitch').replace('neutron-', '') - lportr = aclr.pop('lport') - aclr_dict = {lportr: aclr} - LOG.warning('ACLs found in OVN DB but not in ' - 'Neutron for port %s', lportr) - txn.add(self.ovn_api.update_acls( - [lswitchr], - [lportr], - aclr_dict, - need_compare=False, - is_add_acl=False - )) - - def sync_acls(self, ctx): - """Sync ACLs between neutron and NB. - - @param ctx: neutron_lib.context - @type ctx: object of type neutron_lib.context.Context - @return: Nothing - """ - LOG.debug('ACL-SYNC: started @ %s', str(datetime.now())) - - if self.ovn_api.is_port_groups_supported(): - self._sync_acls_port_groups(ctx) - else: - self._sync_acls(ctx) - LOG.debug('ACL-SYNC: finished @ %s', str(datetime.now())) def _calculate_fips_differences(self, ovn_fips, db_fips): @@ -598,11 +441,13 @@ update_snats_list.append({'id': lrouter['name'], 'add': add_snats, 'del': del_snats}) - del db_routers[lrouter['name']] else: del_lrouters_list.append(lrouter) + lrouters_names = {lr['name'] for lr in lrouters} for r_id, router in db_routers.items(): + if r_id in lrouters_names: + continue LOG.warning("Router found in Neutron but not in " "OVN DB, router id=%s", router['id']) if self.mode == SYNC_MODE_REPAIR: @@ -641,8 +486,9 @@ try: LOG.warning("Creating the router port %s in OVN NB DB", rrport['id']) + router = db_routers[rrport['device_id']] self._ovn_client._create_lrouter_port( - ctx, rrport['device_id'], rrport) + ctx, router, rrport) except RuntimeError: LOG.warning("Create router port in OVN " "NB failed for router port %s", rrport['id']) @@ -899,14 +745,14 @@ return LOG.debug('OVN sync metadata ports started') for net in self.core_plugin.get_networks(ctx): - dhcp_ports = self.core_plugin.get_ports(ctx, filters=dict( - network_id=[net['id']], - device_owner=[constants.DEVICE_OWNER_DHCP])) - - for port in dhcp_ports: - # Do not touch the Neutron DHCP agents ports - if utils.is_neutron_dhcp_agent_port(port): - dhcp_ports.remove(port) + # Get only DHCP ports that don't belong to agent, it should return + # only OVN metadata ports + dhcp_ports = [ + p for p in self.core_plugin.get_ports( + ctx, filters=dict( + network_id=[net['id']], + device_owner=[constants.DEVICE_OWNER_DHCP])) + if not utils.is_neutron_dhcp_agent_port(p)] if not dhcp_ports: LOG.warning('Missing metadata port found in Neutron for ' @@ -970,6 +816,7 @@ del_lswitchs_list = [] del_lports_list = [] add_provnet_ports_list = [] + del_provnet_ports_list = [] for lswitch in lswitches: if lswitch['name'] in db_networks: for lport in lswitch['ports']: @@ -981,13 +828,29 @@ del_lports_list.append({'port': lport, 'lswitch': lswitch['name']}) db_network = db_networks[lswitch['name']] - physnet = db_network.get(pnet.PHYSICAL_NETWORK) - # Updating provider attributes is forbidden by neutron, thus - # we only need to consider missing provnet-ports in OVN DB. - if physnet and not lswitch['provnet_port']: - add_provnet_ports_list.append( - {'network': db_network, - 'lswitch': lswitch['name']}) + db_segments = self.segments_plugin.get_segments( + ctx, filters={'network_id': [db_network['id']]}) + segments_provnet_port_names = [] + for db_segment in db_segments: + physnet = db_segment.get(segment_def.PHYSICAL_NETWORK) + pname = utils.ovn_provnet_port_name(db_segment['id']) + segments_provnet_port_names.append(pname) + if physnet and pname not in lswitch['provnet_ports']: + add_provnet_ports_list.append( + {'network': db_network, + 'segment': db_segment, + 'lswitch': lswitch['name']}) + # Delete orhpaned provnet ports + for provnet_port in lswitch['provnet_ports']: + if provnet_port in segments_provnet_port_names: + continue + if provnet_port not in [ + utils.ovn_provnet_port_name(v['segment']) + for v in add_provnet_ports_list]: + del_provnet_ports_list.append( + {'network': db_network, + 'lport': provnet_port, + 'lswitch': lswitch['name']}) del db_networks[lswitch['name']] else: @@ -1043,15 +906,33 @@ for provnet_port_info in add_provnet_ports_list: network = provnet_port_info['network'] + segment = provnet_port_info['segment'] LOG.warning("Provider network found in Neutron but " "provider network port not found in OVN DB, " - "network_id=%s", provnet_port_info['lswitch']) + "network_id=%(net)s segment_id=%(seg)s", + {'net': network['id'], + 'seg': segment['id']}) if self.mode == SYNC_MODE_REPAIR: LOG.debug('Creating the provnet port %s in OVN NB DB', - utils.ovn_provnet_port_name(network['id'])) - self._ovn_client._create_provnet_port( - txn, network, network.get(pnet.PHYSICAL_NETWORK), - network.get(pnet.SEGMENTATION_ID)) + utils.ovn_provnet_port_name(segment['id'])) + self._ovn_client.create_provnet_port( + network['id'], segment, txn=txn) + + for provnet_port_info in del_provnet_ports_list: + network = provnet_port_info['network'] + lport = provnet_port_info['lport'] + lswitch = provnet_port_info['lswitch'] + LOG.warning("Provider network port found in OVN DB, " + "but not in neutron network_id=%(net)s " + "port_name=%(lport)s", + {'net': network, + 'seg': lport}) + if self.mode == SYNC_MODE_REPAIR: + LOG.debug('Deleting the port %s from OVN NB DB', + lport) + txn.add(self.ovn_api.delete_lswitch_port( + lport_name=lport, + lswitch_name=lswitch)) for lport_info in del_lports_list: LOG.warning("Port found in OVN but not in " @@ -1128,20 +1009,6 @@ # all the ACLs belonging to that Logical Switch. txn.add(self.ovn_api.acl_del(utils.ovn_name(net['id']))) - def _create_default_drop_port_group(self, db_ports): - with self.ovn_api.transaction(check_error=True) as txn: - pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME - if not self.ovn_api.get_port_group(pg_name): - # If drop Port Group doesn't exist yet, create it. - txn.add(self.ovn_api.pg_add(pg_name, acls=[])) - # Add ACLs to this Port Group so that all traffic is dropped. - acls = acl_utils.add_acls_for_drop_port_group(pg_name) - for acl in acls: - txn.add(self.ovn_api.pg_acl_add(**acl)) - ports_ids = [port['id'] for port in db_ports] - # Add the ports to the default Port Group - txn.add(self.ovn_api.pg_add_ports(pg_name, ports_ids)) - def _create_sg_port_groups_and_acls(self, ctx, db_ports): # Create a Port Group per Neutron Security Group with self.ovn_api.transaction(check_error=True) as txn: @@ -1161,17 +1028,14 @@ def migrate_to_port_groups(self, ctx): # This routine is responsible for migrating the current Security # Groups and SG Rules to the new Port Groups implementation. - # 1. Create the default drop Port Group and add all ports with port - # security enabled to it. - # 2. Create a Port Group for every existing Neutron Security Group and + # 1. Create a Port Group for every existing Neutron Security Group and # add all its Security Group Rules as ACLs to that Port Group. - # 3. Delete all existing Address Sets in NorthBound database which + # 2. Delete all existing Address Sets in NorthBound database which # correspond to a Neutron Security Group. - # 4. Delete all the ACLs in every Logical Switch (Neutron network). + # 3. Delete all the ACLs in every Logical Switch (Neutron network). - # If Port Groups are not supported or we've already migrated, return - if (not self.ovn_api.is_port_groups_supported() or - not self.ovn_api.get_address_sets()): + # If we've already migrated, return + if not self.ovn_api.get_address_sets(): return LOG.debug('Port Groups Migration task started') @@ -1184,7 +1048,6 @@ utils.is_lsp_trusted(port) and utils.is_port_security_enabled(port)] - self._create_default_drop_port_group(db_ports) self._create_sg_port_groups_and_acls(ctx, db_ports) self._delete_address_sets(ctx) self._delete_acls_from_lswitches(ctx) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,7 @@ # under the License. import abc +import contextlib import datetime from neutron_lib import context as neutron_context @@ -26,7 +27,6 @@ from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import event as row_event from ovsdbapp.backend.ovs_idl import idlutils -from ovsdbapp import event from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import exceptions @@ -34,6 +34,7 @@ from neutron.common.ovn import utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import ovn_hash_ring_db +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import backports CONF = cfg.CONF @@ -346,16 +347,34 @@ self.driver.delete_mac_binding_entries(row.external_ip) -class OvnDbNotifyHandler(event.RowEventHandler): +class NeutronPgDropPortGroupCreated(row_event.WaitEvent): + """WaitEvent for neutron_pg_drop Create event.""" + def __init__(self): + table = 'Port_Group' + events = (self.ROW_CREATE,) + conditions = (('name', '=', ovn_const.OVN_DROP_PORT_GROUP_NAME),) + super(NeutronPgDropPortGroupCreated, self).__init__( + events, table, conditions) + self.event_name = 'PortGroupCreated' + + +class OvnDbNotifyHandler(backports.RowEventHandler): def __init__(self, driver): super(OvnDbNotifyHandler, self).__init__() self.driver = driver -class BaseOvnIdl(connection.OvsdbIdl): +class Ml2OvnIdlBase(connection.OvsdbIdl): + def __init__(self, remote, schema, probe_interval=(), **kwargs): + if probe_interval == (): # None is a valid value to pass + probe_interval = ovn_conf.get_ovn_ovsdb_probe_interval() + super(Ml2OvnIdlBase, self).__init__( + remote, schema, probe_interval=probe_interval, **kwargs) + +class BaseOvnIdl(Ml2OvnIdlBase): def __init__(self, remote, schema): - self.notify_handler = event.RowEventHandler() + self.notify_handler = backports.RowEventHandler() super(BaseOvnIdl, self).__init__(remote, schema) @classmethod @@ -369,7 +388,7 @@ self.notify_handler.notify(event, row, updates) -class BaseOvnSbIdl(connection.OvsdbIdl): +class BaseOvnSbIdl(Ml2OvnIdlBase): @classmethod def from_server(cls, connection_string, schema_name): _check_and_set_ssl_files(schema_name) @@ -516,11 +535,15 @@ def from_server(cls, connection_string, schema_name, driver): _check_and_set_ssl_files(schema_name) helper = idlutils.get_schema_helper(connection_string, schema_name) + if 'Chassis_Private' in helper.schema_json['tables']: + helper.register_table('Chassis_Private') + if 'FDB' in helper.schema_json['tables']: + helper.register_table('FDB') helper.register_table('Chassis') helper.register_table('Encap') helper.register_table('Port_Binding') helper.register_table('Datapath_Binding') - helper.register_table('MAC_Binding') + helper.register_table('Connection') return cls(driver, connection_string, helper) def post_connect(self): @@ -538,6 +561,56 @@ PortBindingChassisUpdateEvent(self.driver)]) +class OvnInitPGNbIdl(OvnIdl): + """Very limited OVN NB IDL. + + This IDL is intended to be used only in initialization phase with short + living DB connections. + """ + + tables = ['Port_Group', 'Logical_Switch_Port', 'ACL'] + + def __init__(self, driver, remote, schema): + super(OvnInitPGNbIdl, self).__init__(driver, remote, schema) + # self.cond_change() doesn't work here because we are setting the + # condition *before an initial monitor request is made* so there is + # no previous session whose condition we wish to change + self.tables['Port_Group'].condition = [ + ['name', '==', ovn_const.OVN_DROP_PORT_GROUP_NAME]] + self.neutron_pg_drop_event = NeutronPgDropPortGroupCreated() + self.notify_handler.watch_event(self.neutron_pg_drop_event) + + @classmethod + def from_server( + cls, connection_string, schema_name, driver, pg_only=False): + _check_and_set_ssl_files(schema_name) + helper = idlutils.get_schema_helper(connection_string, schema_name) + if pg_only: + helper.register_table('Port_Group') + else: + for table in cls.tables: + helper.register_table(table) + + return cls(driver, connection_string, helper) + + +@contextlib.contextmanager +def short_living_ovsdb_api(api_class, idl): + """Context manager for short living connections to the database. + + :param api_class: Class implementing the database calls + (e.g. from the impl_idl module) + :param idl: An instance of IDL class (e.g. instance of OvnNbIdl) + """ + conn = connection.Connection( + idl, timeout=ovn_conf.get_ovn_ovsdb_timeout()) + api = api_class(conn) + try: + yield api + finally: + api.ovsdb_connection.stop() + + def _check_and_set_ssl_files(schema_name): if schema_name == 'OVN_Southbound': priv_key_file = ovn_conf.get_ovn_sb_private_key() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/plugin.py neutron-16.4.2/neutron/plugins/ml2/plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/plugins/ml2/plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/plugins/ml2/plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -1080,23 +1080,17 @@ need_network_update_notify = False with db_api.CONTEXT_WRITER.using(context): - original_network = self.get_network(context, id) + db_network = self._get_network(context, id) + original_network = self.get_network(context, id, net_db=db_network) self._update_provider_network_attributes( context, original_network, net_data) - updated_network = super(Ml2Plugin, self).update_network(context, - id, - network) + updated_network = super(Ml2Plugin, self).update_network( + context, id, network, db_network=db_network) self.extension_manager.process_update_network(context, net_data, updated_network) self._process_l3_update(context, updated_network, net_data) - # ToDO(QoS): This would change once EngineFacade moves out - db_network = self._get_network(context, id) - # Expire the db_network in current transaction, so that the join - # relationship can be updated. - context.session.expire(db_network) - if mtuw_apidef.MTU in net_data: db_network.mtu = self._get_network_mtu(db_network) # agents should now update all ports to reflect new MTU @@ -1138,10 +1132,9 @@ return updated_network @db_api.retry_if_session_inactive() - def get_network(self, context, id, fields=None): + def get_network(self, context, id, fields=None, net_db=None): with db_api.CONTEXT_READER.using(context): - net_db = self._get_network(context, id) - + net_db = net_db or self._get_network(context, id) net_data = self._make_network_dict(net_db, context=context) self.type_manager.extend_network_dict_provider(context, net_data) @@ -1192,7 +1185,12 @@ priority=0) def _network_delete_precommit_handler(self, rtype, event, trigger, context, network_id, **kwargs): - network = self.get_network(context, network_id) + if 'network' in kwargs: + network = kwargs['network'] + self.type_manager.extend_network_dict_provider(context, network) + else: + network = self.get_network(context, network_id) + mech_context = driver_context.NetworkContext(self, context, network) @@ -1245,8 +1243,11 @@ def _after_create_subnet(self, context, result, mech_context): # db base plugin post commit ops - self._create_subnet_postcommit(context, result) + self._create_subnet_postcommit(context, result, + network=mech_context.network.current) + # add network to subnet dict to save a DB call on dhcp notification + result['network'] = mech_context.network.current kwargs = {'context': context, 'subnet': result} registry.notify(resources.SUBNET, events.AFTER_CREATE, self, **kwargs) try: @@ -1300,11 +1301,11 @@ @registry.receives(resources.SUBNET, [events.PRECOMMIT_DELETE], priority=0) def _subnet_delete_precommit_handler(self, rtype, event, trigger, context, subnet_id, **kwargs): - subnet_obj = self._get_subnet_object(context, subnet_id) + subnet_obj = (kwargs.get('subnet_obj') or + self._get_subnet_object(context, subnet_id)) subnet = self._make_subnet_dict(subnet_obj, context=context) - network = self.get_network(context, subnet['network_id']) mech_context = driver_context.SubnetContext(self, context, - subnet, network) + subnet, network=None) # TODO(kevinbenton): move this mech context into something like # a 'delete context' so it's not polluting the real context object setattr(context, '_mech_context', mech_context) @@ -1365,9 +1366,6 @@ registry.notify(resources.PORT, events.BEFORE_CREATE, self, context=context, port=attrs) - # NOTE(kevinbenton): triggered outside of transaction since it - # emits 'AFTER' events if it creates. - self._ensure_default_security_group(context, attrs['tenant_id']) def _create_port_db(self, context, port): attrs = port[port_def.RESOURCE_NAME] @@ -1410,6 +1408,8 @@ return self._after_create_port(context, result, mech_context) def _after_create_port(self, context, result, mech_context): + # add network to port dict to save a DB call on dhcp notification + result['network'] = mech_context.network.current # notify any plugin that is interested in port create events kwargs = {'context': context, 'port': result} registry.notify(resources.PORT, events.AFTER_CREATE, self, **kwargs) @@ -1653,7 +1653,8 @@ need_port_update_notify |= mac_address_updated original_port = self._make_port_dict(port_db) updated_port = super(Ml2Plugin, self).update_port(context, id, - port) + port, + db_port=port_db) self.extension_manager.process_update_port(context, attrs, updated_port) self._portsec_ext_port_update_processing(updated_port, context, @@ -2261,12 +2262,14 @@ # TODO(boden); refactor into _handle_segment_change once all # event types use payloads return self._handle_segment_change( - rtype, event, trigger, payload.context, payload.latest_state) + rtype, event, trigger, payload.context, payload.latest_state, + for_net_delete=payload.metadata.get('for_net_delete')) @registry.receives(resources.SEGMENT, (events.PRECOMMIT_CREATE, events.PRECOMMIT_DELETE, events.AFTER_CREATE)) - def _handle_segment_change(self, rtype, event, trigger, context, segment): + def _handle_segment_change(self, rtype, event, trigger, context, segment, + for_net_delete=False): if (event == events.PRECOMMIT_CREATE and not isinstance(trigger, segments_plugin.Plugin)): # TODO(xiaohhui): Now, when create network, ml2 will reserve @@ -2287,6 +2290,9 @@ elif event == events.PRECOMMIT_DELETE: self.type_manager.release_network_segment(context, segment) + if for_net_delete: + return + # change in segments could affect resulting network mtu, so let's # recalculate it network_db = self._get_network(context, network_id) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/privileged/agent/linux/ip_lib.py neutron-16.4.2/neutron/privileged/agent/linux/ip_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/privileged/agent/linux/ip_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/privileged/agent/linux/ip_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -64,6 +64,8 @@ return rtnl.rt_scope.get(scope, scope) +# TODO(ralonsoh): move those exceptions out of priv_ip_lib to avoid other +# modules to import this one. class NetworkNamespaceNotFound(RuntimeError): message = _("Network namespace %(netns_name)s could not be found.") @@ -102,6 +104,21 @@ super(InterfaceOperationNotSupported, self).__init__(message) +class InvalidArgument(RuntimeError): + message = _("Invalid parameter/value used on interface %(device)s, " + "namespace %(namespace)s.") + + def __init__(self, message=None, device=None, namespace=None): + # NOTE(slaweq): 'message' can be passed as an optional argument + # because of how privsep daemon works. If exception is raised in + # function called by privsep daemon, it will then try to reraise it + # and will call it always with passing only message from originally + # raised exception. + message = message or self.message % {'device': device, + 'namespace': namespace} + super(InvalidArgument, self).__init__(message) + + class IpAddressAlreadyExists(RuntimeError): message = _("IP address %(ip)s already configured on %(device)s.") @@ -234,6 +251,8 @@ if e.code == errno.EOPNOTSUPP: raise InterfaceOperationNotSupported(device=device, namespace=namespace) + if e.code == errno.EINVAL: + raise InvalidArgument(device=device, namespace=namespace) def get_link_id(device, namespace, raise_exception=True): @@ -392,6 +411,11 @@ @privileged.default.entrypoint +def set_link_vf_feature(device, namespace, vf_config): + return _run_iproute_link("set", device, namespace=namespace, vf=vf_config) + + +@privileged.default.entrypoint def get_link_attributes(device, namespace): link = _run_iproute_link("get", device, namespace)[0] return { @@ -408,6 +432,24 @@ @privileged.default.entrypoint +def get_link_vfs(device, namespace): + link = _run_iproute_link('get', device, namespace=namespace, ext_mask=1)[0] + num_vfs = link.get_attr('IFLA_NUM_VF') + vfs = {} + if not num_vfs: + return vfs + + vfinfo_list = link.get_attr('IFLA_VFINFO_LIST') + for vinfo in vfinfo_list.get_attrs('IFLA_VF_INFO'): + mac = vinfo.get_attr('IFLA_VF_MAC') + link_state = vinfo.get_attr('IFLA_VF_LINK_STATE') + vfs[mac['vf']] = {'mac': mac['mac'], + 'link_state': link_state['link_state']} + + return vfs + + +@privileged.default.entrypoint def add_neigh_entry(ip_version, ip_address, mac_address, device, namespace, **kwargs): """Add a neighbour entry. @@ -489,11 +531,19 @@ :param name: The name of the namespace to create """ - try: - netns.create(name, libc=_get_cdll()) - except OSError as e: - if e.errno != errno.EEXIST: - raise + pid = os.fork() + if pid == 0: + try: + netns.create(name, libc=_get_cdll()) + except OSError as e: + if e.errno != errno.EEXIST: + os._exit(1) + except Exception: + os._exit(1) + os._exit(0) + else: + if os.waitpid(pid, 0)[1]: + raise RuntimeError(_('Error creating namespace %s') % name) @privileged.default.entrypoint diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/privileged/agent/linux/utils.py neutron-16.4.2/neutron/privileged/agent/linux/utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/privileged/agent/linux/utils.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/privileged/agent/linux/utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,55 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +from os import path +import re + +from oslo_concurrency import processutils +from oslo_utils import fileutils + +from neutron import privileged + + +NETSTAT_PIDS_REGEX = re.compile(r'.* (?P\d{2,6})/.*') + + +@privileged.default.entrypoint +def find_listen_pids_namespace(namespace): + return _find_listen_pids_namespace(namespace) + + +def _find_listen_pids_namespace(namespace): + """Retrieve a list of pids of listening processes within the given netns + + This method is implemented separately to allow unit testing. + """ + pids = set() + cmd = ['ip', 'netns', 'exec', namespace, 'netstat', '-nlp'] + output = processutils.execute(*cmd) + for line in output[0].splitlines(): + m = NETSTAT_PIDS_REGEX.match(line) + if m: + pids.add(m.group('pid')) + return list(pids) + + +@privileged.default.entrypoint +def delete_if_exists(_path, remove=os.unlink): + fileutils.delete_if_exists(_path, remove=remove) + + +@privileged.default.entrypoint +def path_exists(_path): + return path.exists(_path) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/scheduler/base_resource_filter.py neutron-16.4.2/neutron/scheduler/base_resource_filter.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/scheduler/base_resource_filter.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/scheduler/base_resource_filter.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,8 +16,11 @@ import abc from neutron_lib.db import api as db_api +from oslo_db import exception as db_exc import six +from neutron.common import utils as n_utils + @six.add_metaclass(abc.ABCMeta) class BaseResourceFilter(object): @@ -26,6 +29,7 @@ def filter_agents(self, plugin, context, resource): """Return the agents that can host the resource.""" + @n_utils.skip_exceptions(db_exc.DBError) def bind(self, context, agents, resource_id, force_scheduling=False): """Bind the resource to the agents.""" with db_api.CONTEXT_WRITER.using(context): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/scheduler/base_scheduler.py neutron-16.4.2/neutron/scheduler/base_scheduler.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/scheduler/base_scheduler.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/scheduler/base_scheduler.py 2021-11-12 13:56:42.000000000 +0000 @@ -85,3 +85,40 @@ chosen_agents = sorted(resource_hostable_agents, key=attrgetter('load'))[0:num_agents_needed] return chosen_agents + + +def get_vacant_binding_index(num_agents, bindings, lowest_binding_index, + force_scheduling=False): + """Return a vacant binding_index to use and whether or not it exists. + + This method can be used with DHCP and L3 agent schedulers. It will return + the lowest vacant index for one of those agents. + :param num_agents: (int) number of agents (DHCP, L3) already scheduled + :param bindings: (NetworkDhcpAgentBinding, RouterL3AgentBinding) agent + binding object, must have "binding_index" field. + :param lowest_binding_index: (int) lowest index number to be scheduled. + :param force_scheduling: (optional)(boolean) if enabled, the method will + always return an index, even if this number + exceeds the maximum configured number of agents. + """ + binding_indices = [b.binding_index for b in bindings] + all_indices = set(range(lowest_binding_index, num_agents + 1)) + open_slots = sorted(list(all_indices - set(binding_indices))) + + if open_slots: + return open_slots[0] + + if not force_scheduling: + return -1 + + # Last chance: if this is a manual scheduling, we're gonna allow + # creation of a binding_index even if it will exceed + # dhcp_agents_per_network. + if max(binding_indices) == len(binding_indices): + return max(binding_indices) + 1 + else: + # Find binding index set gaps and return first free one. + all_indices = set(range(lowest_binding_index, + max(binding_indices) + 1)) + open_slots = sorted(list(all_indices - set(binding_indices))) + return open_slots[0] diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/scheduler/dhcp_agent_scheduler.py neutron-16.4.2/neutron/scheduler/dhcp_agent_scheduler.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/scheduler/dhcp_agent_scheduler.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/scheduler/dhcp_agent_scheduler.py 2021-11-12 13:56:42.000000000 +0000 @@ -192,25 +192,11 @@ num_agents = agent_obj.Agent.count( context, agent_type=constants.AGENT_TYPE_DHCP) num_agents = min(num_agents, cfg.CONF.dhcp_agents_per_network) - bindings = network.NetworkDhcpAgentBinding.get_objects( context, network_id=network_id) - - binding_indices = [b.binding_index for b in bindings] - all_indices = set(range(ndab_model.LOWEST_BINDING_INDEX, - num_agents + 1)) - open_slots = sorted(list(all_indices - set(binding_indices))) - - if open_slots: - return open_slots[0] - - # Last chance: if this is a manual scheduling, we're gonna allow - # creation of a binding_index even if it will exceed - # max_l3_agents_per_router. - if force_scheduling: - return max(all_indices) + 1 - - return -1 + return base_scheduler.get_vacant_binding_index( + num_agents, bindings, ndab_model.LOWEST_BINDING_INDEX, + force_scheduling=force_scheduling) def bind(self, context, agents, network_id, force_scheduling=False): """Bind the network to the agents.""" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/externaldns/drivers/designate/driver.py neutron-16.4.2/neutron/services/externaldns/drivers/designate/driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/externaldns/drivers/designate/driver.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/externaldns/drivers/designate/driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,6 +23,7 @@ from neutron_lib import constants from neutron_lib.exceptions import dns as dns_exc from oslo_config import cfg +from oslo_log import log from neutron.conf.services import extdns_designate_driver from neutron.services.externaldns import driver @@ -37,6 +38,8 @@ CONF = cfg.CONF extdns_designate_driver.register_designate_opts() +LOG = log.getLogger(__name__) + def get_clients(context): global _SESSION @@ -62,6 +65,11 @@ return client, admin_client +def get_all_projects_client(context): + auth = token_endpoint.Token(CONF.designate.url, context.auth_token) + return d_client.Client(session=_SESSION, auth=auth, all_projects=True) + + class Designate(driver.ExternalDNSService): """Driver for Designate.""" @@ -118,9 +126,21 @@ in_addr_name, 'PTR', [recordset_name]) except d_exc.NotFound: - designate_admin.zones.create( - in_addr_zone_name, email=ptr_zone_email, - description=in_addr_zone_description) + # Note(jh): If multiple PTRs get created at the same time, + # the creation of the zone may fail with a conflict because + # it has already been created by a parallel job. So we + # ignore that error and try to create the recordset + # anyway. That call will still fail in the end if something + # is really broken. See bug 1891309. + try: + designate_admin.zones.create( + in_addr_zone_name, email=ptr_zone_email, + description=in_addr_zone_description) + except d_exc.Conflict: + LOG.debug('Conflict when trying to create PTR zone %s,' + ' assuming it exists.', + in_addr_zone_name) + pass designate_admin.recordsets.create(in_addr_zone_name, in_addr_name, 'PTR', [recordset_name]) @@ -147,18 +167,25 @@ CONF.designate.ipv6_ptr_zone_prefix_size) / 4) def delete_record_set(self, context, dns_domain, dns_name, records): - designate, designate_admin = get_clients(context) - ids_to_delete = self._get_ids_ips_to_delete( - dns_domain, '%s.%s' % (dns_name, dns_domain), records, designate) + client, admin_client = get_clients(context) + try: + ids_to_delete = self._get_ids_ips_to_delete( + dns_domain, '%s.%s' % (dns_name, dns_domain), records, client) + except dns_exc.DNSDomainNotFound: + # Try whether we have admin powers and can see all projects + client = get_all_projects_client(context) + ids_to_delete = self._get_ids_ips_to_delete( + dns_domain, '%s.%s' % (dns_name, dns_domain), records, client) + for _id in ids_to_delete: - designate.recordsets.delete(dns_domain, _id) + client.recordsets.delete(dns_domain, _id) if not CONF.designate.allow_reverse_dns_lookup: return for record in records: in_addr_name = netaddr.IPAddress(record).reverse_dns in_addr_zone_name = self._get_in_addr_zone_name(in_addr_name) - designate_admin.recordsets.delete(in_addr_zone_name, in_addr_name) + admin_client.recordsets.delete(in_addr_zone_name, in_addr_name) def _get_ids_ips_to_delete(self, dns_domain, name, records, designate_client): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/l3_router/service_providers/driver_controller.py neutron-16.4.2/neutron/services/l3_router/service_providers/driver_controller.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/l3_router/service_providers/driver_controller.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/l3_router/service_providers/driver_controller.py 2021-11-12 13:56:42.000000000 +0000 @@ -224,7 +224,7 @@ distributed = _is_distributed( router.get('distributed', lib_const.ATTR_NOT_SPECIFIED)) ha = _is_ha(router.get('ha', lib_const.ATTR_NOT_SPECIFIED)) - drivers = self.drivers.values() + drivers = list(self.drivers.values()) # make sure default is tried before the rest if defined if self.default_provider: drivers.insert(0, self.drivers[self.default_provider]) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/logapi/common/db_api.py neutron-16.4.2/neutron/services/logapi/common/db_api.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/logapi/common/db_api.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/logapi/common/db_api.py 2021-11-12 13:56:42.000000000 +0000 @@ -180,26 +180,32 @@ return [log for log in logs if is_bound(log)] -def get_logs_bound_sg(context, sg_id): +def get_logs_bound_sg(context, sg_id=None, project_id=None, port_id=None, + exclusive=False): """Return a list of log_resources bound to a security group""" - project_id = context.tenant_id - log_objs = log_object.Log.get_objects( - context, - project_id=project_id, - resource_type=constants.SECURITY_GROUP, - enabled=True) + kwargs = { + 'resource_type': constants.SECURITY_GROUP, + 'enabled': True} + if project_id: + kwargs['project_id'] = project_id + log_objs = log_object.Log.get_objects(context, **kwargs) log_resources = [] for log_obj in log_objs: - if log_obj.resource_id == sg_id: - log_resources.append(log_obj) - elif log_obj.target_id: - port = port_objects.Port.get_object( - context, id=log_obj.target_id) - if sg_id in port.security_group_ids: + if sg_id: + if log_obj.resource_id == sg_id: + log_resources.append(log_obj) + elif log_obj.target_id: + # NOTE: optimize this method just returning the SGs per port. + port = port_objects.Port.get_object( + context, id=log_obj.target_id) + if sg_id in port.security_group_ids: + log_resources.append(log_obj) + elif (not log_obj.resource_id and not log_obj.target_id and + not exclusive): log_resources.append(log_obj) - elif not log_obj.resource_id and not log_obj.target_id: + elif port_id and log_obj.target_id and log_obj.target_id == port_id: log_resources.append(log_obj) return log_resources diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/logapi/common/sg_callback.py neutron-16.4.2/neutron/services/logapi/common/sg_callback.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/logapi/common/sg_callback.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/logapi/common/sg_callback.py 2021-11-12 13:56:42.000000000 +0000 @@ -29,7 +29,8 @@ else: sg_id = kwargs.get('security_group_id') - log_resources = db_api.get_logs_bound_sg(context, sg_id) + log_resources = db_api.get_logs_bound_sg( + context, sg_id=sg_id, project_id=context.project_id) if log_resources: self.resource_push_api( log_const.RESOURCE_UPDATE, context, log_resources) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/logapi/logging_plugin.py neutron-16.4.2/neutron/services/logapi/logging_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/logapi/logging_plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/logapi/logging_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,9 @@ # under the License. from neutron_lib.api.definitions import logging +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources from neutron_lib.db import api as db_api from neutron_lib.services.logapi import constants as log_const @@ -21,11 +24,13 @@ from neutron.extensions import logging as log_ext from neutron.objects import base as base_obj from neutron.objects.logapi import logging_resource as log_object +from neutron.services.logapi.common import db_api as log_db_api from neutron.services.logapi.common import exceptions as log_exc from neutron.services.logapi.common import validators from neutron.services.logapi.drivers import manager as driver_mgr +@registry.has_registry_receivers class LoggingPlugin(log_ext.LoggingPluginBase): """Implementation of the Neutron logging api plugin.""" @@ -45,6 +50,24 @@ # supported_logging_types are be dynamically loaded from log_drivers return self.driver_manager.supported_logging_types + def _clean_logs(self, context, sg_id=None, port_id=None): + with db_api.CONTEXT_WRITER.using(context): + sg_logs = log_db_api.get_logs_bound_sg( + context, sg_id=sg_id, port_id=port_id, exclusive=True) + for log in sg_logs: + self.delete_log(context, log['id']) + + @registry.receives(resources.SECURITY_GROUP, [events.AFTER_DELETE]) + def _clean_logs_by_resource_id(self, resource, event, trigger, payload): + # log.resource_id == SG + self._clean_logs(payload.context.elevated(), sg_id=payload.resource_id) + + @registry.receives(resources.PORT, [events.AFTER_DELETE]) + def _clean_logs_by_target_id(self, resource, event, trigger, payload): + # log.target_id == port + self._clean_logs(payload.context.elevated(), + port_id=payload.resource_id) + @db_base_plugin_common.filter_fields @db_base_plugin_common.convert_result_to_dict def get_logs(self, context, filters=None, fields=None, sorts=None, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/ovn_l3/exceptions.py neutron-16.4.2/neutron/services/ovn_l3/exceptions.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/ovn_l3/exceptions.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/services/ovn_l3/exceptions.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,21 @@ +# Copyright 2020 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron._i18n import _ +from neutron_lib import exceptions as n_exc + + +class MechanismDriverNotFound(n_exc.NotFound): + message = _("None of the supported mechanism drivers found: " + "%(mechanism_drivers)s. Check your configuration.") diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/ovn_l3/plugin.py neutron-16.4.2/neutron/services/ovn_l3/plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/ovn_l3/plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/ovn_l3/plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,20 +12,18 @@ # under the License. # -from neutron.db import dns_db -from neutron.db import extraroute_db -from neutron.db import l3_gwmode_db -from neutron.db.models import l3 as l3_models -from neutron.quota import resource_registry +from neutron_lib.api.definitions import dns as dns_apidef from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net as pnet +from neutron_lib.api import extensions as api_extensions from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as n_const from neutron_lib import context as n_context from neutron_lib import exceptions as n_exc +from neutron_lib.exceptions import availability_zone as az_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_lib.services import base as service_base @@ -35,10 +33,17 @@ from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import extensions from neutron.common.ovn import utils +from neutron.db.availability_zone import router as router_az_db +from neutron.db import dns_db +from neutron.db import extraroute_db from neutron.db import l3_fip_port_details +from neutron.db import l3_gwmode_db +from neutron.db.models import l3 as l3_models from neutron.db import ovn_revision_numbers_db as db_rev from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client +from neutron.quota import resource_registry from neutron.scheduler import l3_ovn_scheduler +from neutron.services.ovn_l3 import exceptions as ovn_l3_exc LOG = log.getLogger(__name__) @@ -49,7 +54,8 @@ extraroute_db.ExtraRoute_dbonly_mixin, l3_gwmode_db.L3_NAT_db_mixin, dns_db.DNSDbMixin, - l3_fip_port_details.Fip_port_details_db_mixin): + l3_fip_port_details.Fip_port_details_db_mixin, + router_az_db.RouterAvailabilityZoneMixin): """Implementation of the OVN L3 Router Service Plugin. This class implements a L3 service plugin that provides @@ -59,7 +65,7 @@ # TODO(mjozefcz): Start consuming it from neutron-lib # once available. - supported_extension_aliases = ( + _supported_extension_aliases = ( extensions.ML2_SUPPORTED_API_EXTENSIONS_OVN_L3) @resource_registry.tracked_resources(router=l3_models.Router, @@ -81,6 +87,20 @@ self.create_floatingip_precommit, resources.FLOATING_IP, events.PRECOMMIT_CREATE) + @staticmethod + def disable_dns_extension_by_extension_drivers(aliases): + core_plugin = directory.get_plugin() + if not api_extensions.is_extension_supported( + core_plugin, dns_apidef.ALIAS): + aliases.remove(dns_apidef.ALIAS) + + @property + def supported_extension_aliases(self): + if not hasattr(self, '_aliases'): + self._aliases = self._supported_extension_aliases[:] + self.disable_dns_extension_by_extension_drivers(self._aliases) + return self._aliases + @property def _ovn_client(self): if self._ovn_client_inst is None: @@ -105,7 +125,17 @@ @property def _plugin_driver(self): if self._mech is None: - self._mech = self._plugin.mechanism_manager.mech_drivers['ovn'].obj + drivers = ('ovn', 'ovn-sync') + for driver in drivers: + try: + self._mech = self._plugin.mechanism_manager.mech_drivers[ + driver].obj + break + except KeyError: + pass + else: + raise ovn_l3_exc.MechanismDriverNotFound( + mechanism_drivers=drivers) return self._mech def get_plugin_type(self): @@ -183,10 +213,9 @@ return router_interface_info - def add_router_interface(self, context, router_id, interface_info, - may_exist=False): + def add_router_interface(self, context, router_id, interface_info=None): router_interface_info = self._add_neutron_router_interface( - context, router_id, interface_info, may_exist=may_exist) + context, router_id, interface_info) try: self._ovn_client.create_router_port( context, router_id, router_interface_info) @@ -314,6 +343,22 @@ if port['status'] != status: self._plugin.update_port_status(context, port['id'], status) + def _get_availability_zones_from_router_port(self, lrp_name): + """Return the availability zones hints for the router port. + + Return a list of availability zones hints associated with the + router that the router port belongs to. + """ + context = n_context.get_admin_context() + if not self._plugin_driver.list_availability_zones(context): + return [] + + lrp = self._ovn.get_lrouter_port(lrp_name) + router = self.get_router( + context, lrp.external_ids[ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY]) + az_hints = utils.get_az_hints(router) + return az_hints + def schedule_unhosted_gateways(self, event_from_chassis=None): # GW ports and its physnets. port_physnet_dict = self._get_gateway_port_physnet_mapping() @@ -353,9 +398,11 @@ nb_idl=self._ovn, gw_chassis=all_gw_chassis, physnet=physnet, chassis_physnets=chassis_with_physnets, existing_chassis=existing_chassis) + az_hints = self._get_availability_zones_from_router_port(g_name) candidates = self._ovn_client.get_candidates_for_scheduling( physnet, cms=all_gw_chassis, - chassis_physnets=chassis_with_physnets) + chassis_physnets=chassis_with_physnets, + availability_zone_hints=az_hints) chassis = self.scheduler.select( self._ovn, self._sb_ovn, g_name, candidates=candidates, existing_chassis=existing_chassis) @@ -425,3 +472,25 @@ # the OVN NB DB side l3plugin._ovn_client.update_router_port(kwargs['context'], current, if_exists=True) + + def get_router_availability_zones(self, router): + lr = self._ovn.get_lrouter(router['id']) + if not lr: + return [] + + return [az.strip() for az in lr.external_ids.get( + ovn_const.OVN_ROUTER_AZ_HINTS_EXT_ID_KEY, '').split(',') + if az.strip()] + + def validate_availability_zones(self, context, resource_type, + availability_zones): + """Verify that the availability zones exist.""" + if not availability_zones or resource_type != 'router': + return + + azs = {az['name'] for az in + self._plugin_driver.list_availability_zones(context).values()} + diff = set(availability_zones) - azs + if diff: + raise az_exc.AvailabilityZoneNotFound( + availability_zone=', '.join(diff)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/portforwarding/pf_plugin.py neutron-16.4.2/neutron/services/portforwarding/pf_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/portforwarding/pf_plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/portforwarding/pf_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -31,6 +31,7 @@ from neutron_lib.objects import exceptions as obj_exc from neutron_lib.plugins import constants from neutron_lib.plugins import directory +from oslo_db import exception as oslo_db_exc from oslo_log import log as logging from neutron._i18n import _ @@ -386,8 +387,9 @@ pf_obj.internal_port }) pf_obj.update_fields(port_forwarding, reset_changes=True) + self._check_port_forwarding_update(context, pf_obj) pf_obj.update() - except obj_exc.NeutronDbObjectDuplicateEntry: + except oslo_db_exc.DBDuplicateEntry: (__, conflict_params) = self._find_existing_port_forwarding( context, floatingip_id, pf_obj.to_dict()) message = _("A duplicate port forwarding entry with same " @@ -423,6 +425,48 @@ raise lib_exc.BadRequest(resource=apidef.RESOURCE_NAME, msg=message) + def _check_port_forwarding_update(self, context, pf_obj): + def _raise_port_forwarding_update_failed(conflict): + message = _("Another port forwarding entry with the same " + "attributes already exists, conflicting " + "values are %s") % conflict + raise lib_exc.BadRequest(resource=apidef.RESOURCE_NAME, + msg=message) + + db_port = self.core_plugin.get_port(context, pf_obj.internal_port_id) + for fixed_ip in db_port['fixed_ips']: + if str(pf_obj.internal_ip_address) == fixed_ip['ip_address']: + break + else: + # Reached end of internal_port iteration w/out finding fixed_ip + message = _("The internal IP does not correspond to an " + "address on the internal port, which has " + "fixed_ips %s") % db_port['fixed_ips'] + raise lib_exc.BadRequest(resource=apidef.RESOURCE_NAME, + msg=message) + objs = pf.PortForwarding.get_objects( + context, + floatingip_id=pf_obj.floatingip_id, + protocol=pf_obj.protocol) + for obj in objs: + if obj.id == pf_obj.id: + continue + # Ensure there are no conflicts on the outside + if (obj.floating_ip_address == pf_obj.floating_ip_address and + obj.external_port == pf_obj.external_port): + _raise_port_forwarding_update_failed( + {'floating_ip_address': str(obj.floating_ip_address), + 'external_port': obj.external_port}) + # Ensure there are no conflicts in the inside + # socket: internal_ip_address + internal_port + if (obj.internal_port_id == pf_obj.internal_port_id and + obj.internal_ip_address == pf_obj.internal_ip_address and + obj.internal_port == pf_obj.internal_port): + _raise_port_forwarding_update_failed( + {'internal_port_id': obj.internal_port_id, + 'internal_ip_address': str(obj.internal_ip_address), + 'internal_port': obj.internal_port}) + def _find_existing_port_forwarding(self, context, floatingip_id, port_forwarding, specify_params=None): # Because the session had been flushed by NeutronDbObjectDuplicateEntry diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/drivers/manager.py neutron-16.4.2/neutron/services/qos/drivers/manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/drivers/manager.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/qos/drivers/manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -154,6 +154,18 @@ return False + def validate_rule_for_network(self, context, rule, network_id): + for driver in self._drivers: + if driver.is_rule_supported(rule): + # https://review.opendev.org/c/openstack/neutron-lib/+/774083 + # is not present, in this release, in neutron-lib. + if hasattr(driver, 'validate_rule_for_network'): + return driver.validate_rule_for_network(context, rule, + network_id) + return True + + return False + @property def supported_rule_types(self): if not self._drivers: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/drivers/openvswitch/driver.py neutron-16.4.2/neutron/services/qos/drivers/openvswitch/driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/drivers/openvswitch/driver.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/qos/drivers/openvswitch/driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -60,11 +60,14 @@ requires_rpc_notifications=True) def validate_rule_for_port(self, context, rule, port): + return self.validate_rule_for_network(context, rule, port.network_id) + + def validate_rule_for_network(self, context, rule, network_id): # Minimum-bandwidth rule is only supported on networks whose # first segment is backed by a physnet. if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: net = network_object.Network.get_object( - context, id=port.network_id) + context, id=network_id) physnet = net.segments[0].physical_network if physnet is None: return False diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/drivers/ovn/driver.py neutron-16.4.2/neutron/services/qos/drivers/ovn/driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/drivers/ovn/driver.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/qos/drivers/ovn/driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -10,19 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron.objects.qos import policy as qos_policy -from neutron.objects.qos import rule as qos_rule from neutron_lib.api.definitions import portbindings from neutron_lib import constants -from neutron_lib import context as n_context from neutron_lib.db import constants as db_consts -from neutron_lib.plugins import directory from neutron_lib.services.qos import base from neutron_lib.services.qos import constants as qos_consts from oslo_config import cfg from oslo_log import log as logging -from neutron.common.ovn import utils LOG = logging.getLogger(__name__) @@ -45,130 +40,24 @@ VNIC_TYPES = [portbindings.VNIC_NORMAL] -class OVNQosNotificationDriver(base.DriverBase): +class OVNQosDriver(base.DriverBase): """OVN notification driver for QoS.""" - def __init__(self, name='OVNQosDriver', - vif_types=VIF_TYPES, - vnic_types=VNIC_TYPES, - supported_rules=SUPPORTED_RULES, - requires_rpc_notifications=False): - super(OVNQosNotificationDriver, self).__init__( - name, vif_types, vnic_types, supported_rules, - requires_rpc_notifications) - @classmethod def create(cls, plugin_driver): - cls._driver = plugin_driver - return cls() + obj = OVNQosDriver(name='OVNQosDriver', + vif_types=VIF_TYPES, + vnic_types=VNIC_TYPES, + supported_rules=SUPPORTED_RULES, + requires_rpc_notifications=False) + obj._driver = plugin_driver + return obj @property def is_loaded(self): return OVN_QOS in cfg.CONF.ml2.extension_drivers - def create_policy(self, context, policy): - # No need to update OVN on create - pass - def update_policy(self, context, policy): # Call into OVN client to update the policy + # TODO(ralonsoh): make the same call but to public methods/variables. self._driver._ovn_client._qos_driver.update_policy(context, policy) - - def delete_policy(self, context, policy): - # No need to update OVN on delete - pass - - -class OVNQosDriver(object): - """Qos driver for OVN""" - - def __init__(self, driver): - LOG.info("Starting OVNQosDriver") - super(OVNQosDriver, self).__init__() - self._driver = driver - self._plugin_property = None - - @property - def _plugin(self): - if self._plugin_property is None: - self._plugin_property = directory.get_plugin() - return self._plugin_property - - def _generate_port_options(self, context, policy_id): - if policy_id is None: - return {} - options = {} - # The policy might not have any rules - all_rules = qos_rule.get_rules(qos_policy.QosPolicy, - context, policy_id) - for rule in all_rules: - if isinstance(rule, qos_rule.QosBandwidthLimitRule): - options['qos_max_rate'] = rule.max_kbps - if rule.max_burst_kbps: - options['qos_burst'] = rule.max_burst_kbps - options['direction'] = rule.direction - if isinstance(rule, qos_rule.QosDscpMarkingRule): - options['dscp_mark'] = rule.dscp_mark - options['direction'] = constants.EGRESS_DIRECTION - return options - - def get_qos_options(self, port): - # Is qos service enabled - if 'qos_policy_id' not in port: - return {} - # Don't apply qos rules to network devices - if utils.is_network_device_port(port): - return {} - - # Determine if port or network policy should be used - context = n_context.get_admin_context() - port_policy_id = port.get('qos_policy_id') - network_policy_id = None - if not port_policy_id: - network_policy = qos_policy.QosPolicy.get_network_policy( - context, port['network_id']) - network_policy_id = network_policy.id if network_policy else None - - # Generate qos options for the selected policy - policy_id = port_policy_id or network_policy_id - return self._generate_port_options(context, policy_id) - - def _update_network_ports(self, context, network_id, options): - # Retrieve all ports for this network - ports = self._plugin.get_ports(context, - filters={'network_id': [network_id]}) - for port in ports: - # Don't apply qos rules if port has a policy - port_policy_id = port.get('qos_policy_id') - if port_policy_id: - continue - # Don't apply qos rules to network devices - if utils.is_network_device_port(port): - continue - # Call into OVN client to update port - self._driver.update_port(port, qos_options=options) - - def update_network(self, network): - # Is qos service enabled - if 'qos_policy_id' not in network: - return - - # Update the qos options on each network port - context = n_context.get_admin_context() - options = self._generate_port_options( - context, network['qos_policy_id']) - self._update_network_ports(context, network.get('id'), options) - - def update_policy(self, context, policy): - options = self._generate_port_options(context, policy.id) - - # Update each network bound to this policy - network_bindings = policy.get_bound_networks() - for network_id in network_bindings: - self._update_network_ports(context, network_id, options) - - # Update each port bound to this policy - port_bindings = policy.get_bound_ports() - for port_id in port_bindings: - port = self._plugin.get_port(context, port_id) - self._driver.update_port(port, qos_options=options) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/qos_plugin.py neutron-16.4.2/neutron/services/qos/qos_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/qos/qos_plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/qos/qos_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -44,10 +44,16 @@ from neutron.objects import ports as ports_object from neutron.objects.qos import policy as policy_object from neutron.objects.qos import qos_policy_validator as checker +from neutron.objects.qos import rule as rule_object from neutron.objects.qos import rule_type as rule_type_object from neutron.services.qos.drivers import manager +class QosRuleNotSupportedByNetwork(lib_exc.Conflict): + message = _("Rule %(rule_type)s is not supported " + "by network %(network_id)s") + + @resource_extend.has_resource_extenders class QoSPlugin(qos.QoSPluginBase): """Implementation of the Neutron QoS Service Plugin. @@ -85,6 +91,10 @@ self._validate_update_network_callback, callbacks_resources.NETWORK, callbacks_events.PRECOMMIT_UPDATE) + callbacks_registry.subscribe( + self._validate_create_network_callback, + callbacks_resources.NETWORK, + callbacks_events.PRECOMMIT_CREATE) @staticmethod @resource_extend.extends([port_def.COLLECTION_NAME]) @@ -102,9 +112,34 @@ port_res['resource_request'] = None if not qos_id: return port_res - qos_policy = policy_object.QosPolicy.get_object( - context.get_admin_context(), id=qos_id) + if port_res.get('bulk'): + port_res['resource_request'] = { + 'qos_id': qos_id, + 'network_id': port_db.network_id, + 'vnic_type': port_res[portbindings.VNIC_TYPE]} + return port_res + + min_bw_rules = rule_object.QosMinimumBandwidthRule.get_objects( + context.get_admin_context(), qos_policy_id=qos_id) + resources = QoSPlugin._get_resources(min_bw_rules) + if not resources: + return port_res + + segments = network_object.NetworkSegment.get_objects( + context.get_admin_context(), network_id=port_db.network_id) + traits = QoSPlugin._get_traits(port_res[portbindings.VNIC_TYPE], + segments) + if not traits: + return port_res + + port_res['resource_request'] = { + 'required': traits, + 'resources': resources} + return port_res + + @staticmethod + def _get_resources(min_bw_rules): resources = {} # NOTE(ralonsoh): we should move this translation dict to n-lib. rule_direction_class = { @@ -113,34 +148,71 @@ nl_constants.EGRESS_DIRECTION: pl_constants.CLASS_NET_BW_EGRESS_KBPS } - for rule in qos_policy.rules: - if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: - resources[rule_direction_class[rule.direction]] = rule.min_kbps - if not resources: - return port_res - - # NOTE(ralonsoh): we should not rely on the current execution order of - # the port extending functions. Although here we have - # port_res[VNIC_TYPE], we should retrieve this value from the port DB - # object instead. - vnic_trait = pl_utils.vnic_type_trait( - port_res[portbindings.VNIC_TYPE]) + for rule in min_bw_rules: + resources[rule_direction_class[rule.direction]] = rule.min_kbps + return resources + @staticmethod + def _get_traits(vnic_type, segments): # TODO(lajoskatona): Change to handle all segments when any traits # support will be available. See Placement spec: # https://review.opendev.org/565730 - first_segment = network_object.NetworkSegment.get_objects( - context.get_admin_context(), network_id=port_db.network_id)[0] - + first_segment = segments[0] if not first_segment or not first_segment.physical_network: - return port_res + return [] physnet_trait = pl_utils.physnet_trait( first_segment.physical_network) + # NOTE(ralonsoh): we should not rely on the current execution order of + # the port extending functions. Although here we have + # port_res[VNIC_TYPE], we should retrieve this value from the port DB + # object instead. + vnic_trait = pl_utils.vnic_type_trait(vnic_type) - port_res['resource_request'] = { - 'required': [physnet_trait, vnic_trait], - 'resources': resources} - return port_res + return [physnet_trait, vnic_trait] + + @staticmethod + # TODO(obondarev): use neutron_lib constant + @resource_extend.extends(['ports_bulk']) + def _extend_port_resource_request_bulk(ports_res, noop): + """Add resource request to a list of ports.""" + min_bw_rules = dict() + net_segments = dict() + + for port_res in ports_res: + if port_res.get('resource_request') is None: + continue + qos_id = port_res['resource_request'].pop('qos_id', None) + if not qos_id: + port_res['resource_request'] = None + continue + + net_id = port_res['resource_request'].pop('network_id') + vnic_type = port_res['resource_request'].pop('vnic_type') + + if qos_id not in min_bw_rules: + rules = rule_object.QosMinimumBandwidthRule.get_objects( + context.get_admin_context(), qos_policy_id=qos_id) + min_bw_rules[qos_id] = rules + + resources = QoSPlugin._get_resources(min_bw_rules[qos_id]) + if not resources: + continue + + if net_id not in net_segments: + segments = network_object.NetworkSegment.get_objects( + context.get_admin_context(), + network_id=net_id) + net_segments[net_id] = segments + + traits = QoSPlugin._get_traits(vnic_type, net_segments[net_id]) + if not traits: + continue + + port_res['resource_request'] = { + 'required': traits, + 'resources': resources} + + return ports_res def _get_ports_with_policy(self, context, policy): networks_ids = policy.get_bound_networks() @@ -189,6 +261,20 @@ self.validate_policy_for_port(context, policy, updated_port) + def _validate_create_network_callback(self, resource, event, trigger, + **kwargs): + context = kwargs['context'] + network_id = kwargs['network']['id'] + network = network_object.Network.get_object(context, id=network_id) + + policy_id = network.qos_policy_id + if policy_id is None: + return + + policy = policy_object.QosPolicy.get_object( + context.elevated(), id=policy_id) + self.validate_policy_for_network(context, policy, network_id) + def _validate_update_network_callback(self, resource, event, trigger, payload=None): context = payload.context @@ -203,6 +289,9 @@ policy = policy_object.QosPolicy.get_object( context.elevated(), id=policy_id) + self.validate_policy_for_network( + context, policy, network_id=updated_network['id']) + ports = ports_object.Port.get_objects( context, network_id=updated_network['id']) # Filter only this ports which don't have overwritten policy @@ -226,6 +315,13 @@ raise qos_exc.QosRuleNotSupported(rule_type=rule.rule_type, port_id=port['id']) + def validate_policy_for_network(self, context, policy, network_id): + for rule in policy.rules: + if not self.driver_manager.validate_rule_for_network( + context, rule, network_id): + raise QosRuleNotSupportedByNetwork( + rule_type=rule.rule_type, network_id=network_id) + def reject_min_bw_rule_updates(self, context, policy): ports = self._get_ports_with_policy(context, policy) for port in ports: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/segments/db.py neutron-16.4.2/neutron/services/segments/db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/segments/db.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/segments/db.py 2021-11-12 13:56:42.000000000 +0000 @@ -38,6 +38,7 @@ _USER_CONFIGURED_SEGMENT_PLUGIN = None +FOR_NET_DELETE = 'for_net_delete' def check_user_configured_segment_plugin(): @@ -189,7 +190,7 @@ self.delete_segment, payload=events.DBEventPayload( context, metadata={ - 'for_net_delete': for_net_delete}, + FOR_NET_DELETE: for_net_delete}, states=(segment_dict,), resource_id=uuid)) @@ -200,12 +201,15 @@ # Do some preliminary operations before deleting segment in db registry.notify(resources.SEGMENT, events.PRECOMMIT_DELETE, self.delete_segment, context=context, - segment=segment_dict) + segment=segment_dict, + for_net_delete=for_net_delete) registry.publish(resources.SEGMENT, events.AFTER_DELETE, self.delete_segment, payload=events.DBEventPayload( - context, states=(segment_dict,), + context, metadata={ + FOR_NET_DELETE: for_net_delete}, + states=(segment_dict,), resource_id=uuid)) @@ -331,7 +335,7 @@ def _delete_segments_for_network(resource, event, trigger, - context, network_id): + context, network_id, **kwargs): admin_ctx = context.elevated() global segments_plugin if not segments_plugin: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/segments/plugin.py neutron-16.4.2/neutron/services/segments/plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/segments/plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/segments/plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -45,7 +45,6 @@ from neutron._i18n import _ from neutron.common import ipv6_utils -from neutron.db import models_v2 from neutron.extensions import segment from neutron.notifiers import batch_notifier from neutron.objects import network as net_obj @@ -122,7 +121,7 @@ def _prevent_segment_delete_with_subnet_associated( self, resource, event, trigger, payload=None): """Raise exception if there are any subnets associated with segment.""" - if payload.metadata.get('for_net_delete'): + if payload.metadata.get(db.FOR_NET_DELETE): # don't check if this is a part of a network delete operation return segment_id = payload.resource_id @@ -146,28 +145,21 @@ if not is_auto_addr_subnet or subnet.segment_id is None: return - net_allocs = (context.session.query(models_v2.IPAllocation.port_id). - filter_by(subnet_id=subnet.id)) - port_ids_on_net = [ipalloc.port_id for ipalloc in net_allocs] - for port_id in port_ids_on_net: - try: - port = ports_obj.Port.get_object(context, id=port_id) - fixed_ips = [f for f in port['fixed_ips'] - if f['subnet_id'] != subnet.id] - if len(fixed_ips) != 0: - continue - - LOG.info("Found port %(port_id)s, with IP auto-allocation " - "only on subnet %(subnet)s which is associated with " - "segment %(segment_id)s, cannot delete", - {'port_id': port_id, - 'subnet': subnet.id, - 'segment_id': subnet.segment_id}) - raise n_exc.SubnetInUse(subnet_id=subnet.id) - except n_exc.PortNotFound: - # port is gone + ports = ports_obj.Port.get_ports_allocated_by_subnet_id(context, + subnet.id) + for port in ports: + fixed_ips = [f for f in port.fixed_ips if f.subnet_id != subnet.id] + if len(fixed_ips) != 0: continue + LOG.info("Found port %(port_id)s, with IP auto-allocation " + "only on subnet %(subnet)s which is associated with " + "segment %(segment_id)s, cannot delete", + {'port_id': port.id, + 'subnet': subnet.id, + 'segment_id': subnet.segment_id}) + raise n_exc.SubnetInUse(subnet_id=subnet.id) + class Event(object): @@ -351,6 +343,9 @@ @registry.receives(resources.SUBNET, [events.AFTER_DELETE]) def _notify_subnet_deleted(self, resource, event, trigger, context, subnet, **kwargs): + if kwargs.get(db.FOR_NET_DELETE): + return # skip segment RP update if it is going to be deleted + segment_id = subnet.get('segment_id') if not segment_id or subnet['ip_version'] != constants.IP_VERSION_4: return @@ -367,23 +362,32 @@ self._delete_nova_inventory, segment_id)) def _get_aggregate_id(self, segment_id): - aggregate_uuid = self.p_client.list_aggregates( - segment_id)['aggregates'][0] - aggregates = self.n_client.aggregates.list() - for aggregate in aggregates: + try: + aggregate_uuid = self.p_client.list_aggregates( + segment_id)['aggregates'][0] + except placement_exc.PlacementAggregateNotFound: + LOG.info('Segment %s resource provider aggregate not found', + segment_id) + return + + for aggregate in self.n_client.aggregates.list(): nc_aggregate_uuid = self._get_nova_aggregate_uuid(aggregate) if nc_aggregate_uuid == aggregate_uuid: return aggregate.id def _delete_nova_inventory(self, event): aggregate_id = self._get_aggregate_id(event.segment_id) - aggregate = self.n_client.aggregates.get_details( - aggregate_id) - for host in aggregate.hosts: - self.n_client.aggregates.remove_host(aggregate_id, - host) - self.n_client.aggregates.delete(aggregate_id) - self.p_client.delete_resource_provider(event.segment_id) + if aggregate_id: + aggregate = self.n_client.aggregates.get_details(aggregate_id) + for host in aggregate.hosts: + self.n_client.aggregates.remove_host(aggregate_id, host) + self.n_client.aggregates.delete(aggregate_id) + + try: + self.p_client.delete_resource_provider(event.segment_id) + except placement_exc.PlacementClientError as exc: + LOG.info('Segment %s resource provider not found; error: %s', + event.segment_id, str(exc)) @registry.receives(resources.SEGMENT_HOST_MAPPING, [events.AFTER_CREATE]) def _notify_host_addition_to_aggregate(self, resource, event, trigger, @@ -398,9 +402,8 @@ def _add_host_to_aggregate(self, event): for segment_id in event.segment_ids: - try: - aggregate_id = self._get_aggregate_id(segment_id) - except placement_exc.PlacementAggregateNotFound: + aggregate_id = self._get_aggregate_id(segment_id) + if not aggregate_id: LOG.info('When adding host %(host)s, aggregate not found ' 'for routed network segment %(segment_id)s', {'host': event.host, 'segment_id': segment_id}) @@ -480,6 +483,13 @@ ipv4_subnet_ids.append(ip['subnet_id']) return ipv4_subnet_ids + @registry.receives(resources.SEGMENT, [events.AFTER_DELETE]) + def _notify_segment_deleted( + self, resource, event, trigger, payload=None): + if payload: + self.batch_notifier.queue_event(Event( + self._delete_nova_inventory, payload.resource_id)) + @registry.has_registry_receivers class SegmentHostRoutes(object): @@ -648,8 +658,8 @@ # If there are other subnets on the network and subnet has segment_id # ensure host routes for all subnets are updated. - if (self._count_subnets(context, subnet['network_id']) > 1 and - subnet.get('segment_id')): + if (subnet.get('segment_id') and + self._count_subnets(context, subnet['network_id']) > 1): self._update_routed_network_host_routes(context, subnet['network_id']) @@ -658,6 +668,9 @@ subnet, **kwargs): # If this is a routed network, remove any routes to this subnet on # this networks remaining subnets. + if kwargs.get(db.FOR_NET_DELETE): + return # skip subnet update if the network is going to be deleted + if subnet.get('segment_id'): self._update_routed_network_host_routes( context, subnet['network_id'], deleted_cidr=subnet['cidr']) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/trunk/plugin.py neutron-16.4.2/neutron/services/trunk/plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/trunk/plugin.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/trunk/plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -79,17 +79,44 @@ 'port_id': x.port_id} for x in port_db.trunk_port.sub_ports } - core_plugin = directory.get_plugin() - ports = core_plugin.get_ports( - context.get_admin_context(), filters={'id': subports}) - for port in ports: - subports[port['id']]['mac_address'] = port['mac_address'] + if not port_res.get('bulk'): + core_plugin = directory.get_plugin() + ports = core_plugin.get_ports( + context.get_admin_context(), filters={'id': subports}) + for port in ports: + subports[port['id']]['mac_address'] = port['mac_address'] trunk_details = {'trunk_id': port_db.trunk_port.id, 'sub_ports': [x for x in subports.values()]} port_res['trunk_details'] = trunk_details return port_res + @staticmethod + # TODO(obondarev): use neutron_lib constant + @resource_extend.extends(['ports_bulk']) + def _extend_port_trunk_details_bulk(ports_res, noop): + """Add trunk subport details to a list of ports.""" + subport_ids = [] + trunk_ports = [] + for p in ports_res: + if 'trunk_details' in p and 'subports' in p['trunk_details']: + trunk_ports.append(p) + for subp in p['trunk_details']['subports']: + subport_ids.append(subp['port_id']) + if not subport_ids: + return ports_res + + core_plugin = directory.get_plugin() + subports = core_plugin.get_ports( + context.get_admin_context(), filters={'id': subport_ids}) + subport_macs = {p['id']: p['mac_address'] for p in subports} + + for tp in trunk_ports: + for subp in tp['trunk_details']['subports']: + subp['mac_address'] = subport_macs[subp['port_id']] + + return ports_res + def check_compatibility(self): """Verify the plugin can load correctly and fail otherwise.""" self.check_driver_compatibility() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/trunk/rpc/server.py neutron-16.4.2/neutron/services/trunk/rpc/server.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/services/trunk/rpc/server.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/services/trunk/rpc/server.py 2021-11-12 13:56:42.000000000 +0000 @@ -128,6 +128,12 @@ trunk_port_id = trunk.port_id trunk_port = self.core_plugin.get_port(context, trunk_port_id) trunk_host = trunk_port.get(portbindings.HOST_ID) + migrating_to_host = trunk_port.get( + portbindings.PROFILE, {}).get('migrating_to') + if migrating_to_host and trunk_host != migrating_to_host: + # Trunk is migrating now, so lets update host of the subports + # to the new host already + trunk_host = migrating_to_host # NOTE(status_police) Set the trunk in BUILD state before # processing subport bindings. The trunk will stay in BUILD diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/common/net_helpers.py neutron-16.4.2/neutron/tests/common/net_helpers.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/common/net_helpers.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/common/net_helpers.py 2021-11-12 13:56:42.000000000 +0000 @@ -539,10 +539,14 @@ if respawn: self.stop_processes() - self.client_process.writeline(testing_string) - message = self.server_process.read_stdout(READ_TIMEOUT).strip() - self.server_process.writeline(message) - message = self.client_process.read_stdout(READ_TIMEOUT).strip() + try: + self.client_process.writeline(testing_string) + message = self.server_process.read_stdout(READ_TIMEOUT).strip() + self.server_process.writeline(message) + message = self.client_process.read_stdout(READ_TIMEOUT).strip() + except ConnectionError as e: + LOG.debug("Error: %s occured during connectivity test.", e) + message = "" return message == testing_string @@ -634,8 +638,8 @@ def destroy(self): for port in self.ports: ip_wrapper = ip_lib.IPWrapper(port.namespace) - if (ip_wrapper.netns.exists(port.namespace) or - port.namespace is None): + if (port.namespace is None or + ip_wrapper.netns.exists(port.namespace)): try: ip_wrapper.del_veth(port.name) break diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/contrib/gate_hook.sh neutron-16.4.2/neutron/tests/contrib/gate_hook.sh --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/contrib/gate_hook.sh 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/contrib/gate_hook.sh 2021-11-12 13:56:42.000000000 +0000 @@ -6,11 +6,11 @@ FLAVOR=${2:-"all"} GATE_DEST=$BASE/new -NEUTRON_PATH=$GATE_DEST/neutron -GATE_HOOKS=$NEUTRON_PATH/neutron/tests/contrib/hooks +NEUTRON_DIR=$GATE_DEST/neutron +GATE_HOOKS=$NEUTRON_DIR/neutron/tests/contrib/hooks DEVSTACK_PATH=$GATE_DEST/devstack LOCAL_CONF=$DEVSTACK_PATH/late-local.conf -RALLY_EXTRA_DIR=$NEUTRON_PATH/rally-jobs/extra +RALLY_EXTRA_DIR=$NEUTRON_DIR/rally-jobs/extra DSCONF=/tmp/devstack-tools/bin/dsconf # Install devstack-tools used to produce local.conf; we can't rely on diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/agents/l3_agent.py neutron-16.4.2/neutron/tests/fullstack/agents/l3_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/agents/l3_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/agents/l3_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,6 +15,8 @@ import sys +from oslo_config import cfg # noqa + from neutron.common import eventlet_utils from neutron.tests.common.agents import l3_agent diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/resources/client.py neutron-16.4.2/neutron/tests/fullstack/resources/client.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/resources/client.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/resources/client.py 2021-11-12 13:56:42.000000000 +0000 @@ -81,7 +81,7 @@ def create_network(self, tenant_id, name=None, external=False, network_type=None, segmentation_id=None, - physical_network=None, mtu=None): + physical_network=None, mtu=None, qos_policy_id=None): resource_type = 'network' name = name or utils.get_rand_name(prefix=resource_type) @@ -96,6 +96,8 @@ spec['provider:physical_network'] = physical_network if mtu is not None: spec['mtu'] = mtu + if qos_policy_id is not None: + spec['qos_policy_id'] = qos_policy_id return self._create_resource(resource_type, spec) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/resources/config.py neutron-16.4.2/neutron/tests/fullstack/resources/config.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/resources/config.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/resources/config.py 2021-11-12 13:56:42.000000000 +0000 @@ -82,7 +82,7 @@ 'password': rabbitmq_environment.password, 'host': rabbitmq_environment.host, 'vhost': rabbitmq_environment.vhost}, - 'api_workers': '2', + 'api_workers': str(env_desc.api_workers), }, 'database': { 'connection': connection, @@ -187,9 +187,11 @@ }, }) - extension_drivers = ['port_security'] + extension_drivers = {'port_security'} if env_desc.qos: - extension_drivers.append(qos_ext.QOS_EXT_DRIVER_ALIAS) + extension_drivers.add(qos_ext.QOS_EXT_DRIVER_ALIAS) + if env_desc.ml2_extension_drivers: + extension_drivers.update(env_desc.ml2_extension_drivers) self.config['ml2']['extension_drivers'] = ','.join(extension_drivers) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/resources/environment.py neutron-16.4.2/neutron/tests/fullstack/resources/environment.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/resources/environment.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/resources/environment.py 2021-11-12 13:56:42.000000000 +0000 @@ -40,7 +40,8 @@ global_mtu=constants.DEFAULT_NETWORK_MTU, debug_iptables=False, log=False, report_bandwidths=False, has_placement=False, placement_port=None, - dhcp_scheduler_class=None,): + dhcp_scheduler_class=None, ml2_extension_drivers=None, + api_workers=1): self.network_type = network_type self.l2_pop = l2_pop self.qos = qos @@ -61,6 +62,8 @@ self.service_plugins += ',qos' if self.log: self.service_plugins += ',log' + self.ml2_extension_drivers = ml2_extension_drivers + self.api_workers = api_workers @property def tunneling_enabled(self): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/test_dhcp_agent.py neutron-16.4.2/neutron/tests/fullstack/test_dhcp_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/test_dhcp_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/test_dhcp_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -38,6 +38,7 @@ ] boot_vm_for_test = True dhcp_scheduler_class = None + api_workers = 1 def setUp(self): host_descriptions = [ @@ -52,6 +53,7 @@ arp_responder=False, agent_down_time=self.agent_down_time, dhcp_scheduler_class=self.dhcp_scheduler_class, + api_workers=self.api_workers, ), host_descriptions) @@ -205,6 +207,7 @@ agent_down_time = 30 number_of_hosts = 2 boot_vm_for_test = False + api_workers = 2 dhcp_scheduler_class = ('neutron.tests.fullstack.schedulers.dhcp.' 'AlwaysTheOtherAgentScheduler') diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/test_ports_api.py neutron-16.4.2/neutron/tests/fullstack/test_ports_api.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/test_ports_api.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/test_ports_api.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import constants +from oslo_utils import uuidutils + +from neutron.tests.fullstack import base +from neutron.tests.fullstack.resources import environment +from neutron.tests.unit import testlib_api + +load_tests = testlib_api.module_load_tests + + +class TestPortsApi(base.BaseFullStackTestCase): + + scenarios = [ + ('Sriov Agent', {'l2_agent_type': constants.AGENT_TYPE_NIC_SWITCH})] + + def setUp(self): + host_descriptions = [ + environment.HostDescription( + l2_agent_type=self.l2_agent_type)] + env = environment.Environment( + environment.EnvironmentDescription( + agent_down_time=10, + ml2_extension_drivers=['uplink_status_propagation']), + host_descriptions) + super(TestPortsApi, self).setUp(env) + + self.tenant_id = uuidutils.generate_uuid() + self.network = self.safe_client.create_network(self.tenant_id) + self.safe_client.create_subnet( + self.tenant_id, self.network['id'], '20.0.0.0/24') + + def test_create_port_with_propagate_uplink_status(self): + body = self.safe_client.create_port( + self.tenant_id, self.network['id'], propagate_uplink_status=False) + self.assertFalse(body['propagate_uplink_status']) + body = self.safe_client.client.list_ports(id=body['id'])['ports'][0] + self.assertFalse(body['propagate_uplink_status']) + body = self.safe_client.client.show_port(body['id'])['port'] + self.assertFalse(body['propagate_uplink_status']) + + def test_create_port_without_propagate_uplink_status(self): + body = self.safe_client.create_port(self.tenant_id, self.network['id']) + self.assertFalse(body['propagate_uplink_status']) + body = self.safe_client.client.list_ports(id=body['id'])['ports'][0] + self.assertFalse(body['propagate_uplink_status']) + body = self.safe_client.client.show_port(body['id'])['port'] + self.assertFalse(body['propagate_uplink_status']) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/test_qos.py neutron-16.4.2/neutron/tests/fullstack/test_qos.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/fullstack/test_qos.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/fullstack/test_qos.py 2021-11-12 13:56:42.000000000 +0000 @@ -707,6 +707,31 @@ queues = '\nList of OVS Queue registers:\n%s' % '\n'.join(queues) self.fail(queuenum + qoses + queues) + def test_min_bw_qos_create_network_vxlan_not_supported(self): + qos_policy = self._create_qos_policy() + qos_policy_id = qos_policy['id'] + self.safe_client.create_minimum_bandwidth_rule( + self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction) + network_args = {'network_type': 'vxlan', + 'qos_policy_id': qos_policy_id} + self.assertRaises( + exceptions.Conflict, + self.safe_client.create_network, + self.tenant_id, name='network-test', **network_args) + + def test_min_bw_qos_update_network_vxlan_not_supported(self): + network_args = {'network_type': 'vxlan'} + network = self.safe_client.create_network( + self.tenant_id, name='network-test', **network_args) + qos_policy = self._create_qos_policy() + qos_policy_id = qos_policy['id'] + self.safe_client.create_minimum_bandwidth_rule( + self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction) + self.assertRaises( + exceptions.Conflict, + self.client.update_network, network['id'], + body={'network': {'qos_policy_id': qos_policy_id}}) + def test_min_bw_qos_port_removed(self): """Test if min BW limit config is properly removed when port removed. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/common/test_ovs_lib.py neutron-16.4.2/neutron/tests/functional/agent/common/test_ovs_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/common/test_ovs_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/common/test_ovs_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,9 +16,12 @@ import functools import mock +from neutron_lib import constants as p_const from neutron_lib.services.qos import constants as qos_constants from oslo_utils import uuidutils +from ovsdbapp.backend.ovs_idl import event import six +import testtools from neutron.agent.common import ovs_lib from neutron.agent.linux import ip_lib @@ -37,6 +40,17 @@ six.u('min-rate'): six.u(str(MIN_RATE_DEFAULT))} +class WaitForPortCreateEvent(event.WaitEvent): + event_name = 'WaitForPortCreateEvent' + + def __init__(self, port_name): + table = 'Port' + events = (self.ROW_CREATE,) + conditions = (('name', '=', port_name),) + super(WaitForPortCreateEvent, self).__init__( + events, table, conditions, timeout=5) + + class BaseOVSTestCase(base.BaseSudoTestCase): def setUp(self): @@ -111,7 +125,11 @@ self.elements_to_clean['bridges'].append(self.br_name) def _create_port(self, port_name): - self.ovs.ovsdb.add_port(self.br_name, port_name).execute() + row_event = WaitForPortCreateEvent(port_name) + self.ovs.ovsdb.idl.notify_handler.watch_event(row_event) + self.ovs.ovsdb.add_port(self.br_name, port_name).execute( + check_error=True) + self.assertTrue(row_event.wait()) def _find_port_uuid(self, port_name): return self.ovs.ovsdb.db_get('Port', port_name, '_uuid').execute() @@ -291,7 +309,7 @@ self._check_value((qos_id, queues), self.ovs._find_qos) def test__set_port_qos(self): - port_name = 'test_port' + port_name = ('port-' + uuidutils.generate_uuid())[:8] self._create_bridge() self._create_port(port_name) self._check_value([], self._find_port_qos, port_name) @@ -333,7 +351,7 @@ self.assertNotIn(expected, flows) def test_update_minimum_bandwidth_queue(self): - port_name = 'test_output_port_1' + port_name = ('port-' + uuidutils.generate_uuid())[:8] self._create_bridge() self._create_port(port_name) queue_num = 1 @@ -357,7 +375,7 @@ self._check_value(expected, self._list_queues, queue_id) def test_update_minimum_bandwidth_queue_no_qos_no_queue(self): - port_name = 'test_output_port_2' + port_name = ('port-' + uuidutils.generate_uuid())[:8] self._create_bridge() self._create_port(port_name) queue_num = 1 @@ -453,3 +471,55 @@ self.assertEqual(8000, self.ovs.db_get_val('Controller', self.br_name, 'inactivity_probe')) + + @testtools.skip('bug/1938262') + def test_add_gre_tunnel_port(self): + ipv4_tunnel_port = "test-ipv4-port" + ipv6_tunnel_port = "test-ipv6-port" + self._create_bridge() + self.ovs.add_tunnel_port( + ipv4_tunnel_port, "10.0.0.1", "10.0.0.2", + tunnel_type=p_const.TYPE_GRE) + self.ovs.add_tunnel_port( + ipv6_tunnel_port, "2001:db8::1", "2001:db8:2", + tunnel_type=p_const.TYPE_GRE) + interfaces = self.ovs.get_ports_attributes( + "Interface", columns=["name", "type", "options"], + if_exists=True) + + ipv4_port_type = None + ipv6_port_type = None + ipv6_port_options = {} + for interface in interfaces: + if interface['name'] == ipv4_tunnel_port: + ipv4_port_type = interface['type'] + elif interface['name'] == ipv6_tunnel_port: + ipv6_port_type = interface['type'] + ipv6_port_options = interface['options'] + self.assertEqual(p_const.TYPE_GRE, ipv4_port_type) + self.assertEqual(ovs_lib.TYPE_GRE_IP6, ipv6_port_type) + self.assertEqual('legacy_l2', ipv6_port_options.get('packet_type')) + + def test_set_igmp_snooping_flood(self): + port_name = 'test_output_port_2' + self._create_bridge() + self._create_port(port_name) + self.ovs.set_igmp_snooping_flood(port_name, True) + ports_other_config = self.ovs.db_get_val('Port', port_name, + 'other_config') + self.assertEqual( + 'true', + ports_other_config.get('mcast-snooping-flood', '').lower()) + self.assertEqual( + 'true', + ports_other_config.get('mcast-snooping-flood-reports', '').lower()) + + self.ovs.set_igmp_snooping_flood(port_name, False) + ports_other_config = self.ovs.db_get_val('Port', port_name, + 'other_config') + self.assertEqual( + 'false', + ports_other_config.get('mcast-snooping-flood', '').lower()) + self.assertEqual( + 'false', + ports_other_config.get('mcast-snooping-flood-reports', '').lower()) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/framework.py neutron-16.4.2/neutron/tests/functional/agent/l3/framework.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/framework.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/l3/framework.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,7 +15,7 @@ import copy import functools -import textwrap +import os import mock import netaddr @@ -27,6 +27,7 @@ from neutron.agent.common import ovs_lib from neutron.agent.l3 import agent as neutron_l3_agent +from neutron.agent.l3 import dvr_local_router from neutron.agent.l3 import namespaces from neutron.agent.l3 import router_info as l3_router_info from neutron.agent import l3_agent as l3_agent_main @@ -37,6 +38,7 @@ from neutron.agent.metadata import driver as metadata_driver from neutron.common import utils as common_utils from neutron.conf.agent import common as agent_config +from neutron.conf.agent.l3 import config as l3_config from neutron.conf import common as common_config from neutron.tests.common import l3_test_common from neutron.tests.common import net_helpers @@ -47,6 +49,39 @@ OVS_INTERFACE_DRIVER = 'neutron.agent.linux.interface.OVSInterfaceDriver' +KEEPALIVED_CONFIG = """\ +global_defs { + notification_email_from %(email_from)s + router_id %(router_id)s +} +vrrp_instance VR_1 { + state BACKUP + interface %(ha_device_name)s + virtual_router_id 1 + priority 50 + garp_master_delay 60 + nopreempt + advert_int 2 + track_interface { + %(ha_device_name)s + } + virtual_ipaddress { + 169.254.0.1/24 dev %(ha_device_name)s + } + virtual_ipaddress_excluded { + %(floating_ip_cidr)s dev %(ex_device_name)s no_track + %(external_device_cidr)s dev %(ex_device_name)s no_track + %(internal_device_cidr)s dev %(internal_device_name)s no_track + %(ex_port_ipv6)s dev %(ex_device_name)s scope link no_track + %(int_port_ipv6)s dev %(internal_device_name)s scope link no_track + } + virtual_routes { + 0.0.0.0/0 via %(default_gateway_ip)s dev %(ex_device_name)s no_track + 8.8.8.0/24 via 19.4.4.4 no_track + %(extra_subnet_cidr)s dev %(ex_device_name)s scope link no_track + } +}""" + def get_ovs_bridge(br_name): return ovs_lib.OVSBridge(br_name) @@ -61,6 +96,7 @@ self.mock_plugin_api = mock.patch( 'neutron.agent.l3.agent.L3PluginApi').start().return_value mock.patch('neutron.agent.rpc.PluginReportStateAPI').start() + l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF) self.conf = self._configure_agent('agent1') self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1', self.conf) @@ -370,7 +406,9 @@ ovsbr.clear_db_attribute('Port', device_name, 'tag') with mock.patch(OVS_INTERFACE_DRIVER + '.plug_new', autospec=True) as ( - ovs_plug): + ovs_plug), \ + mock.patch.object(dvr_local_router.DvrLocalRouter, + '_load_used_fip_information'): ovs_plug.side_effect = new_ovs_plug agent._process_added_router(router) @@ -401,12 +439,15 @@ def _namespace_exists(self, namespace): return ip_lib.network_namespace_exists(namespace) - def _metadata_proxy_exists(self, conf, router): - pm = external_process.ProcessManager( + def _metadata_proxy(self, conf, router): + return external_process.ProcessManager( conf, router.router_id, router.ns_name, service=metadata_driver.HAPROXY_SERVICE) + + def _metadata_proxy_exists(self, conf, router): + pm = self._metadata_proxy(conf, router) return pm.active def device_exists_with_ips_and_mac(self, expected_device, name_getter, @@ -442,38 +483,7 @@ router.get_floating_ips()[0]['floating_ip_address']) default_gateway_ip = external_port['subnets'][0].get('gateway_ip') extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr') - return textwrap.dedent("""\ - global_defs { - notification_email_from %(email_from)s - router_id %(router_id)s - } - vrrp_instance VR_1 { - state BACKUP - interface %(ha_device_name)s - virtual_router_id 1 - priority 50 - garp_master_delay 60 - nopreempt - advert_int 2 - track_interface { - %(ha_device_name)s - } - virtual_ipaddress { - 169.254.0.1/24 dev %(ha_device_name)s - } - virtual_ipaddress_excluded { - %(floating_ip_cidr)s dev %(ex_device_name)s - %(external_device_cidr)s dev %(ex_device_name)s - %(internal_device_cidr)s dev %(internal_device_name)s - %(ex_port_ipv6)s dev %(ex_device_name)s scope link - %(int_port_ipv6)s dev %(internal_device_name)s scope link - } - virtual_routes { - 0.0.0.0/0 via %(default_gateway_ip)s dev %(ex_device_name)s - 8.8.8.0/24 via 19.4.4.4 - %(extra_subnet_cidr)s dev %(ex_device_name)s scope link - } - }""") % { + return KEEPALIVED_CONFIG % { 'email_from': keepalived.KEEPALIVED_EMAIL_FROM, 'router_id': keepalived.KEEPALIVED_ROUTER_ID, 'ha_device_name': ha_device_name, @@ -498,23 +508,36 @@ # then the devices and iptable rules have also been deleted, # so there's no need to check that explicitly. self.assertFalse(self._namespace_exists(router.ns_name)) - common_utils.wait_until_true( - lambda: not self._metadata_proxy_exists(self.agent.conf, router), - timeout=10) + try: + common_utils.wait_until_true( + lambda: not self._metadata_proxy_exists(self.agent.conf, + router), + timeout=10) + except common_utils.WaitTimeout: + pm = self._metadata_proxy(self.agent.conf, router) + pid_file = pm.get_pid_file_name() + if os.path.exists(pid_file): + msg = 'PID file %s still exists and it should not.' % pid_file + else: + msg = 'PID file %s is not present.' % pid_file + self.fail(msg) - def _assert_snat_chains(self, router): - self.assertFalse(router.iptables_manager.is_chain_empty( - 'nat', 'snat')) - self.assertFalse(router.iptables_manager.is_chain_empty( - 'nat', 'POSTROUTING')) + def _assert_snat_chains(self, router, enable_gw=True): + check = self.assertFalse if enable_gw else self.assertTrue + check(router.iptables_manager.is_chain_empty('nat', 'snat')) + check(router.iptables_manager.is_chain_empty('nat', 'POSTROUTING')) - def _assert_floating_ip_chains(self, router, snat_bound_fip=False): + def _assert_floating_ip_chains(self, router, snat_bound_fip=False, + enable_gw=True): if snat_bound_fip: - self.assertFalse(router.snat_iptables_manager.is_chain_empty( - 'nat', 'float-snat')) + if enable_gw: + self.assertFalse(router.snat_iptables_manager.is_chain_empty( + 'nat', 'float-snat')) + else: + self.assertIsNone(router.snat_iptables_manager) - self.assertFalse(router.iptables_manager.is_chain_empty( - 'nat', 'float-snat')) + check = self.assertFalse if enable_gw else self.assertTrue + check(router.iptables_manager.is_chain_empty('nat', 'float-snat')) def _assert_iptables_rules_converged(self, router): # if your code is failing on this line, it means you are not generating @@ -541,7 +564,7 @@ self.assertTrue(self.device_exists_with_ips_and_mac( device, router.get_internal_device_name, router.ns_name)) - def _assert_extra_routes(self, router, namespace=None): + def _assert_extra_routes(self, router, namespace=None, enable_gw=True): if namespace is None: namespace = router.ns_name routes = ip_lib.get_routing_table(4, namespace=namespace) @@ -549,10 +572,11 @@ 'destination': route['destination']} for route in routes] for extra_route in router.router['routes']: - self.assertIn(extra_route, routes) + check = self.assertIn if enable_gw else self.assertNotIn + check(extra_route, routes) def _assert_onlink_subnet_routes( - self, router, ip_versions, namespace=None): + self, router, ip_versions, namespace=None, enable_gw=True): ns_name = namespace or router.ns_name routes = [] for ip_version in ip_versions: @@ -560,7 +584,13 @@ namespace=ns_name) routes.extend(_routes) routes = set(route['destination'] for route in routes) - extra_subnets = router.get_ex_gw_port()['extra_subnets'] + ex_gw_port = router.get_ex_gw_port() + if not ex_gw_port: + if not enable_gw: + return + self.fail('GW port is enabled but not present in the router') + + extra_subnets = ex_gw_port['extra_subnets'] for extra_subnet in (route['cidr'] for route in extra_subnets): self.assertIn(extra_subnet, routes) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_dvr_router.py neutron-16.4.2/neutron/tests/functional/agent/l3/test_dvr_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_dvr_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/l3/test_dvr_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -216,6 +216,15 @@ self._dvr_router_lifecycle(enable_ha=True, enable_snat=True, snat_bound_fip=True) + def test_dvr_lifecycle_no_ha_with_snat_with_fips_with_cent_fips_no_gw( + self): + self._dvr_router_lifecycle(enable_ha=False, enable_snat=True, + snat_bound_fip=True, enable_gw=False) + + def test_dvr_lifecycle_ha_with_snat_with_fips_with_cent_fips_no_gw(self): + self._dvr_router_lifecycle(enable_ha=True, enable_snat=True, + snat_bound_fip=True, enable_gw=False) + def _check_routes(self, expected_routes, actual_routes): actual_routes = [{key: route[key] for key in expected_routes[0].keys()} for route in actual_routes] @@ -542,7 +551,8 @@ custom_mtu=2000, ip_version=lib_constants.IP_VERSION_4, dual_stack=False, - snat_bound_fip=False): + snat_bound_fip=False, + enable_gw=True): '''Test dvr router lifecycle :param enable_ha: sets the ha value for the router. @@ -558,12 +568,13 @@ # We get the router info particular to a dvr router router_info = self.generate_dvr_router_info( enable_ha, enable_snat, extra_routes=True, - snat_bound_fip=snat_bound_fip) + snat_bound_fip=snat_bound_fip, enable_gw=enable_gw) for key in ('_interfaces', '_snat_router_interfaces', '_floatingip_agent_interfaces'): - for port in router_info[key]: + for port in router_info.get(key, []): port['mtu'] = custom_mtu - router_info['gw_port']['mtu'] = custom_mtu + if router_info['gw_port']: + router_info['gw_port']['mtu'] = custom_mtu if enable_ha: router_info['_ha_interface']['mtu'] = custom_mtu @@ -581,7 +592,10 @@ # manage the router (create it, create namespaces, # attach interfaces, etc...) router = self.manage_router(self.agent, router_info) - if enable_ha: + if enable_ha and not enable_gw: + port = router.get_ex_gw_port() + self.assertEqual({}, port) + elif enable_ha and enable_gw: port = router.get_ex_gw_port() interface_name = router.get_external_device_name(port['id']) self._assert_no_ip_addresses_on_interface(router.ha_namespace, @@ -608,13 +622,15 @@ utils.wait_until_true( lambda: self._metadata_proxy_exists(self.agent.conf, router)) self._assert_internal_devices(router) - self._assert_dvr_external_device(router) - self._assert_dvr_gateway(router) - self._assert_dvr_floating_ips(router, snat_bound_fip=snat_bound_fip) - self._assert_snat_chains(router) - self._assert_floating_ip_chains(router, snat_bound_fip=snat_bound_fip) + self._assert_dvr_external_device(router, enable_gw) + self._assert_dvr_gateway(router, enable_gw) + self._assert_dvr_floating_ips(router, snat_bound_fip=snat_bound_fip, + enable_gw=enable_gw) + self._assert_snat_chains(router, enable_gw=enable_gw) + self._assert_floating_ip_chains(router, snat_bound_fip=snat_bound_fip, + enable_gw=enable_gw) self._assert_metadata_chains(router) - self._assert_rfp_fpr_mtu(router, custom_mtu) + self._assert_rfp_fpr_mtu(router, custom_mtu, enable_gw=enable_gw) if enable_snat: if (ip_version == lib_constants.IP_VERSION_6 or dual_stack): ip_versions = [lib_constants.IP_VERSION_4, @@ -624,8 +640,9 @@ snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( router.router_id) self._assert_onlink_subnet_routes( - router, ip_versions, snat_ns_name) - self._assert_extra_routes(router, namespace=snat_ns_name) + router, ip_versions, snat_ns_name, enable_gw=enable_gw) + self._assert_extra_routes(router, namespace=snat_ns_name, + enable_gw=enable_gw) # During normal operation, a router-gateway-clear followed by # a router delete results in two notifications to the agent. This @@ -634,7 +651,8 @@ # that the L3 agent is robust enough to handle that case and delete # the router correctly. self._delete_router(self.agent, router.router_id) - self._assert_fip_namespace_deleted(ext_gateway_port) + self._assert_fip_namespace_deleted(ext_gateway_port, + enable_gw=enable_gw) self._assert_router_does_not_exist(router) self._assert_snat_namespace_does_not_exist(router) @@ -667,8 +685,13 @@ } return fip_agent_gw_port_info - def _assert_dvr_external_device(self, router): + def _assert_dvr_external_device(self, router, enable_gw): external_port = router.get_ex_gw_port() + if not external_port: + if not enable_gw: + return + self.fail('GW port is enabled but not present in the router') + snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( router.router_id) @@ -695,12 +718,12 @@ else: self.fail("Agent not configured for dvr or dvr_snat") - def _assert_dvr_gateway(self, router): + def _assert_dvr_gateway(self, router, enable_gw): gateway_expected_in_snat_namespace = ( self.agent.conf.agent_mode == 'dvr_snat' ) if gateway_expected_in_snat_namespace: - self._assert_dvr_snat_gateway(router) + self._assert_dvr_snat_gateway(router, enable_gw=enable_gw) self._assert_removal_of_already_deleted_gateway_device(router) snat_namespace_should_not_exist = ( @@ -709,10 +732,15 @@ if snat_namespace_should_not_exist: self._assert_snat_namespace_does_not_exist(router) - def _assert_dvr_snat_gateway(self, router): + def _assert_dvr_snat_gateway(self, router, enable_gw=True): namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( router.router_id) external_port = router.get_ex_gw_port() + if not external_port: + if not enable_gw: + return + self.fail('GW port is enabled but not present in the router') + external_device_name = router.get_external_device_name( external_port['id']) external_device = ip_lib.IPDevice(external_device_name, @@ -737,7 +765,8 @@ router.router_id) self.assertFalse(self._namespace_exists(namespace)) - def _assert_dvr_floating_ips(self, router, snat_bound_fip=False): + def _assert_dvr_floating_ips(self, router, snat_bound_fip=False, + enable_gw=True): # in the fip namespace: # Check that the fg- (floatingip_agent_gateway) # is created with the ip address of the external gateway port @@ -745,6 +774,9 @@ self.assertTrue(floating_ips) # We need to fetch the floatingip agent gateway port info # from the router_info + if not enable_gw: + return + floating_agent_gw_port = ( router.router[lib_constants.FLOATINGIP_AGENT_INTF_KEY]) self.assertTrue(floating_agent_gw_port) @@ -782,6 +814,54 @@ self._assert_iptables_rules_exist( iptables_mgr, 'nat', expected_rules) + def test_dvr_router_fip_associations_exist_when_router_reenabled(self): + """Test to validate the fip associations when router is re-enabled. + + This test validates the fip associations when the router is disabled + and enabled back again. This test is specifically for the host where + snat namespace is not created or gateway port is binded on other host. + """ + self.agent.conf.agent_mode = 'dvr_snat' + router_info = self.generate_dvr_router_info(enable_snat=True) + # Ensure agent does not create snat namespace by changing gw_port_host + router_info['gw_port_host'] = 'agent2' + router_info_copy = copy.deepcopy(router_info) + router1 = self.manage_router(self.agent, router_info) + + fip_ns_name = router1.fip_ns.name + self.assertTrue(self._namespace_exists(router1.fip_ns.name)) + + # Simulate disable router + self.agent._safe_router_removed(router1.router['id']) + self.assertFalse(self._namespace_exists(router1.ns_name)) + self.assertTrue(self._namespace_exists(fip_ns_name)) + + # Simulated enable router + router_updated = self.manage_router(self.agent, router_info_copy) + self._assert_dvr_floating_ips(router_updated) + + def test_dvr_router_fip_associations_exist_when_snat_removed(self): + """Test to validate the fip associations when snat is removed. + + This test validates the fip associations when the snat is removed from + the agent. The fip associations should exist when the snat is moved to + another l3 agent. + """ + self.agent.conf.agent_mode = 'dvr_snat' + router_info = self.generate_dvr_router_info(enable_snat=True) + router_info_copy = copy.deepcopy(router_info) + router1 = self.manage_router(self.agent, router_info) + + # Remove gateway port host and the binding host_id to simulate + # removal of snat from l3 agent + router_info_copy['gw_port_host'] = '' + router_info_copy['gw_port']['binding:host_id'] = '' + router_info_copy['gw_port']['binding:vif_type'] = 'unbound' + router_info_copy['gw_port']['binding:vif_details'] = {} + self.agent._process_updated_router(router_info_copy) + router_updated = self.agent.router_info[router1.router['id']] + self._assert_dvr_floating_ips(router_updated) + def test_dvr_router_with_ha_for_fip_disassociation(self): """Test to validate the fip rules are deleted in dvr_snat_ha router. @@ -958,23 +1038,43 @@ # cache is properly populated. self.agent.conf.agent_mode = 'dvr_snat' router_info = self.generate_dvr_router_info(enable_snat=True) - expected_neighbor = '35.4.1.10' + expected_neighbors = ['35.4.1.10', '10.0.0.10', '10.200.0.3'] + allowed_address_net = netaddr.IPNetwork('10.100.0.0/30') port_data = { - 'fixed_ips': [{'ip_address': expected_neighbor}], + 'fixed_ips': [{'ip_address': expected_neighbors[0]}], 'mac_address': 'fa:3e:aa:bb:cc:dd', - 'device_owner': DEVICE_OWNER_COMPUTE + 'device_owner': DEVICE_OWNER_COMPUTE, + 'allowed_address_pairs': [ + {'ip_address': expected_neighbors[1], + 'mac_address': 'fa:3e:aa:bb:cc:dd'}, + {'ip_address': '10.200.0.3/32', + 'mac_address': 'fa:3e:aa:bb:cc:dd'}, + {'ip_address': str(allowed_address_net), + 'mac_address': 'fa:3e:aa:bb:cc:dd'}] } self.agent.plugin_rpc.get_ports_by_subnet.return_value = [port_data] router1 = self.manage_router(self.agent, router_info) internal_device = router1.get_internal_device_name( router_info['_interfaces'][0]['id']) - neighbor = ip_lib.dump_neigh_entries(4, internal_device, - router1.ns_name, - dst=expected_neighbor) - self.assertNotEqual([], neighbor) - self.assertEqual(expected_neighbor, neighbor[0]['dst']) + for expected_neighbor in expected_neighbors: + neighbor = ip_lib.dump_neigh_entries( + lib_constants.IP_VERSION_4, internal_device, + router1.ns_name, + dst=expected_neighbor) + self.assertNotEqual([], neighbor) + self.assertEqual(expected_neighbor, neighbor[0]['dst']) + for not_expected_neighbor in allowed_address_net: + neighbor = ip_lib.dump_neigh_entries( + lib_constants.IP_VERSION_4, internal_device, + router1.ns_name, + dst=str(not_expected_neighbor)) + self.assertEqual([], neighbor) + + def _assert_rfp_fpr_mtu(self, router, expected_mtu=1500, enable_gw=True): + if not enable_gw: + self.assertIsNone(router.fip_ns) + return - def _assert_rfp_fpr_mtu(self, router, expected_mtu=1500): dev_mtu = self.get_device_mtu( router.router_id, router.fip_ns.get_rtr_ext_device_name, router.ns_name) @@ -1539,8 +1639,12 @@ master, backup = self._get_master_and_slave_routers( router1, router2, check_external_device=False) + utils.wait_until_true(lambda: master.ha_state == 'master') + utils.wait_until_true(lambda: backup.ha_state == 'backup') + self._assert_ip_addresses_in_dvr_ha_snat_namespace_with_fip(master) self._assert_no_ip_addresses_in_dvr_ha_snat_namespace_with_fip(backup) + self.fail_ha_router(master) utils.wait_until_true(lambda: backup.ha_state == 'master') @@ -1565,6 +1669,9 @@ master, backup = self._get_master_and_slave_routers( router1, router2, check_external_device=False) + utils.wait_until_true(lambda: master.ha_state == 'master') + utils.wait_until_true(lambda: backup.ha_state == 'backup') + self._assert_ip_addresses_in_dvr_ha_snat_namespace(master) self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(backup) @@ -1970,7 +2077,12 @@ self.assertIsNone(fg_device.route.get_gateway()) def _assert_fip_namespace_deleted(self, ext_gateway_port, - assert_ovs_interface=True): + assert_ovs_interface=True, + enable_gw=True): + if not enable_gw: + self.assertEqual({}, ext_gateway_port) + return + ext_net_id = ext_gateway_port['network_id'] fip_ns = self.agent.get_fip_ns(ext_net_id) fip_ns.unsubscribe = mock.Mock() @@ -2134,3 +2246,72 @@ in fip_agent_gw_port['extra_subnets']) routes_cidr = set(route['cidr'] for route in routes) self.assertEqual(extra_subnet_cidr, routes_cidr) + + def _test_router_interface_mtu_update(self, ha): + original_mtu = 1450 + router_info = self.generate_dvr_router_info( + enable_ha=ha, enable_snat=True) + router_info['_interfaces'][0]['mtu'] = original_mtu + router_info['gw_port']['mtu'] = original_mtu + router_info[lib_constants.SNAT_ROUTER_INTF_KEY][0]['mtu'] = ( + original_mtu) + + router = self.manage_router(self.agent, router_info) + if ha: + utils.wait_until_true(lambda: router.ha_state == 'master') + # Keepalived notifies of a state transition when it starts, + # not when it ends. Thus, we have to wait until keepalived finishes + # configuring everything. We verify this by waiting until the last + # device has an IP address. + device = router.router[lib_constants.INTERFACE_KEY][-1] + device_exists = functools.partial( + self.device_exists_with_ips_and_mac, + device, + router.get_internal_device_name, + router.ns_name) + utils.wait_until_true(device_exists) + + interface_name = router.get_internal_device_name( + router_info['_interfaces'][0]['id']) + gw_interface_name = router.get_external_device_name( + router_info['gw_port']['id']) + snat_internal_port = router_info[lib_constants.SNAT_ROUTER_INTF_KEY] + snat_interface_name = router._get_snat_int_device_name( + snat_internal_port[0]['id']) + snat_namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router_info['id']) + + self.assertEqual( + original_mtu, + ip_lib.IPDevice(interface_name, router.ns_name).link.mtu) + self.assertEqual( + original_mtu, + ip_lib.IPDevice(gw_interface_name, snat_namespace).link.mtu) + self.assertEqual( + original_mtu, + ip_lib.IPDevice(snat_interface_name, snat_namespace).link.mtu) + + updated_mtu = original_mtu + 1 + router_info_copy = copy.deepcopy(router_info) + router_info_copy['_interfaces'][0]['mtu'] = updated_mtu + router_info_copy['gw_port']['mtu'] = updated_mtu + router_info_copy[lib_constants.SNAT_ROUTER_INTF_KEY][0]['mtu'] = ( + updated_mtu) + + self.agent._process_updated_router(router_info_copy) + + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(interface_name, router.ns_name).link.mtu) + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(gw_interface_name, snat_namespace).link.mtu) + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(snat_interface_name, snat_namespace).link.mtu) + + def test_dvr_router_interface_mtu_update(self): + self._test_router_interface_mtu_update(ha=False) + + def test_dvr_ha_router_interface_mtu_update(self): + self._test_router_interface_mtu_update(ha=True) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_ha_router.py neutron-16.4.2/neutron/tests/functional/agent/l3/test_ha_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_ha_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/l3/test_ha_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -295,6 +295,7 @@ def test_delete_external_gateway_on_standby_router(self): router_info = self.generate_router_info(enable_ha=True) router = self.manage_router(self.agent, router_info) + common_utils.wait_until_true(lambda: router.ha_state == 'master') self.fail_ha_router(router) common_utils.wait_until_true(lambda: router.ha_state == 'backup') @@ -383,6 +384,40 @@ common_utils.wait_until_true(lambda: router.ha_state == 'master') self._wait_until_ipv6_forwarding_has_state(router.ns_name, 'all', 1) + def test_router_interface_mtu_update(self): + original_mtu = 1450 + router_info = self.generate_router_info(False) + router_info['_interfaces'][0]['mtu'] = original_mtu + router_info['gw_port']['mtu'] = original_mtu + + router = self.manage_router(self.agent, router_info) + + interface_name = router.get_internal_device_name( + router_info['_interfaces'][0]['id']) + gw_interface_name = router.get_external_device_name( + router_info['gw_port']['id']) + + self.assertEqual( + original_mtu, + ip_lib.IPDevice(interface_name, router.ns_name).link.mtu) + self.assertEqual( + original_mtu, + ip_lib.IPDevice(gw_interface_name, router.ns_name).link.mtu) + + updated_mtu = original_mtu + 1 + router_info_copy = copy.deepcopy(router_info) + router_info_copy['_interfaces'][0]['mtu'] = updated_mtu + router_info_copy['gw_port']['mtu'] = updated_mtu + + self.agent._process_updated_router(router_info_copy) + + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(interface_name, router.ns_name).link.mtu) + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(gw_interface_name, router.ns_name).link.mtu) + class L3HATestFailover(framework.L3AgentTestFramework): @@ -434,6 +469,11 @@ self._assert_ipv6_accept_ra(slave_router, False) self._assert_ipv6_forwarding(slave_router, False, False) + common_utils.wait_until_true( + lambda: master_router.ha_state == 'master') + common_utils.wait_until_true( + lambda: slave_router.ha_state == 'backup') + self.fail_ha_router(router1) # NOTE: passing slave_router as first argument, because we expect diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_keepalived_state_change.py neutron-16.4.2/neutron/tests/functional/agent/l3/test_keepalived_state_change.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_keepalived_state_change.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/l3/test_keepalived_state_change.py 2021-11-12 13:56:42.000000000 +0000 @@ -54,9 +54,10 @@ self._generate_cmd_opts() self.ext_process = external_process.ProcessManager( - None, '%s.monitor' % self.pid_file, None, service='test_ip_mon', - pids_path=self.conf_dir, default_cmd_callback=self._callback, - run_as_root=True) + conf=None, uuid=self.router_id, namespace=self.router.namespace, + service='test_ip_mon', pids_path=self.conf_dir, + default_cmd_callback=self._callback, run_as_root=True, + pid_file=self.pid_file) server = linux_utils.UnixDomainWSGIServer( 'neutron-keepalived-state-change', num_threads=1) @@ -188,3 +189,22 @@ self._run_monitor() msg = 'Initial status of router %s is %s' % (self.router_id, 'master') self._search_in_file(self.log_file, msg) + + def test_handle_initial_state_backup_error_reading_initial_status(self): + # By passing this wrong IP address, the thread "_thread_initial_state" + # will fail generating an exception (caught inside the called method). + # The main thread will timeout waiting for an initial state and + # "backup" will be set. + self.router.port.addr.add(self.cidr) + self._generate_cmd_opts(cidr='failed_IP_address') + self.ext_process = external_process.ProcessManager( + conf=None, uuid=self.router_id, namespace=self.router.namespace, + service='test_ip_mon', pids_path=self.conf_dir, + default_cmd_callback=self._callback, run_as_root=True, + pid_file=self.pid_file) + self._run_monitor() + msg = ('Timeout reading the initial status of router %s' % + self.router_id) + self._search_in_file(self.log_file, msg) + msg = 'Initial status of router %s is %s' % (self.router_id, 'backup') + self._search_in_file(self.log_file, msg) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_legacy_router.py neutron-16.4.2/neutron/tests/functional/agent/l3/test_legacy_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/l3/test_legacy_router.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/l3/test_legacy_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -226,7 +226,7 @@ # make sure all events are processed while not self.agent._queue._queue.empty(): - self.agent._process_router_update() + self.agent._process_update() for r in routers_to_keep: self.assertIn(r['id'], self.agent.router_info) @@ -436,3 +436,37 @@ # networks that are in the same address scope as external network net_helpers.assert_ping(machine_diff_scope.namespace, machine_same_scope.ip) + + def test_router_interface_mtu_update(self): + original_mtu = 1450 + router_info = self.generate_router_info(False) + router_info['_interfaces'][0]['mtu'] = original_mtu + router_info['gw_port']['mtu'] = original_mtu + + router = self.manage_router(self.agent, router_info) + + interface_name = router.get_internal_device_name( + router_info['_interfaces'][0]['id']) + gw_interface_name = router.get_external_device_name( + router_info['gw_port']['id']) + + self.assertEqual( + original_mtu, + ip_lib.IPDevice(interface_name, router.ns_name).link.mtu) + self.assertEqual( + original_mtu, + ip_lib.IPDevice(gw_interface_name, router.ns_name).link.mtu) + + updated_mtu = original_mtu + 1 + router_info_copy = copy.deepcopy(router_info) + router_info_copy['_interfaces'][0]['mtu'] = updated_mtu + router_info_copy['gw_port']['mtu'] = updated_mtu + + self.agent._process_updated_router(router_info_copy) + + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(interface_name, router.ns_name).link.mtu) + self.assertEqual( + updated_mtu, + ip_lib.IPDevice(gw_interface_name, router.ns_name).link.mtu) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/bin/ipt_binname.py neutron-16.4.2/neutron/tests/functional/agent/linux/bin/ipt_binname.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/bin/ipt_binname.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/linux/bin/ipt_binname.py 2021-11-12 13:56:42.000000000 +0000 @@ -20,7 +20,7 @@ import sys -import eventlet +from oslo_config import cfg # noqa def print_binary_name(): @@ -33,6 +33,9 @@ if __name__ == "__main__": + + import eventlet + if 'spawn' in sys.argv: eventlet.spawn(print_binary_name).wait() else: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_interface.py neutron-16.4.2/neutron/tests/functional/agent/linux/test_interface.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_interface.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/linux/test_interface.py 2021-11-12 13:56:42.000000000 +0000 @@ -69,6 +69,22 @@ self.interface.set_mtu, device_name=device_name, namespace=namespace)) + def test_ipv6_lla_create_and_get(self): + lla_address = "fe80::f816:3eff:fe66:73bf/64" + global_address = "2001::1/64" + device_name = utils.get_rand_name() + namespace = self.useFixture(net_helpers.NamespaceFixture()) + namespace.ip_wrapper.add_dummy(device_name) + self.interface.add_ipv6_addr( + device_name, lla_address, namespace.name, 'link') + self.interface.add_ipv6_addr( + device_name, global_address, namespace.name, 'global') + existing_addresses = [ + a['cidr'] for a in self.interface.get_ipv6_llas( + device_name, namespace.name)] + self.assertIn(lla_address, existing_addresses) + self.assertNotIn(global_address, existing_addresses) + class OVSInterfaceDriverTestCase(linux_base.BaseOVSLinuxTestCase, InterfaceDriverTestCaseMixin): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_ip_lib.py neutron-16.4.2/neutron/tests/functional/agent/linux/test_ip_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_ip_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/linux/test_ip_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. import collections +import copy import itertools import signal @@ -659,10 +660,16 @@ ip_lib.delete_network_namespace(self.namespace) def test_network_namespace_exists_ns_exists(self): - self.assertTrue(ip_lib.network_namespace_exists(self.namespace)) + for use_helper_for_ns_read in (True, False): + cfg.CONF.set_override('use_helper_for_ns_read', + use_helper_for_ns_read, 'AGENT') + self.assertTrue(ip_lib.network_namespace_exists(self.namespace)) def test_network_namespace_exists_ns_doesnt_exists(self): - self.assertFalse(ip_lib.network_namespace_exists('another_ns')) + for use_helper_for_ns_read in (True, False): + cfg.CONF.set_override('use_helper_for_ns_read', + use_helper_for_ns_read, 'AGENT') + self.assertFalse(ip_lib.network_namespace_exists('another_ns')) def test_network_namespace_exists_ns_exists_try_is_ready(self): self.assertTrue(ip_lib.network_namespace_exists(self.namespace, @@ -982,3 +989,114 @@ self.device.route.flush(ip_version, table=table) routes = self.device.route.list_routes(ip_version, table=table) self.assertEqual([], routes) + + +class IpAddrCommandTestCase(functional_base.BaseSudoTestCase): + + def setUp(self): + super(IpAddrCommandTestCase, self).setUp() + self.namespace = self.useFixture(net_helpers.NamespaceFixture()).name + ip_lib.IPWrapper(self.namespace).add_dummy('test_device') + self.device = ip_lib.IPDevice('test_device', namespace=self.namespace) + self.device.link.set_up() + + def test_list_with_scope(self): + scope_ip = [ + ('global', '192.168.100.1/24'), + ('global', '2001:db8::1/64'), + ('link', '192.168.101.1/24'), + ('link', 'fe80::1:1/64'), + ('site', 'fec0:0:0:f101::1/64'), + ('host', '192.168.102.1/24')] + for scope, _ip in scope_ip: + self.device.addr.add(_ip, scope=scope) + + devices = self.device.addr.list() + devices_cidr = {device['cidr'] for device in devices} + for scope in scope_ip: + self.assertIn(scope[1], devices_cidr) + + for scope, _ip in scope_ip: + devices_filtered = self.device.addr.list(scope=scope) + devices_cidr = {device['cidr'] for device in devices_filtered} + self.assertIn(_ip, devices_cidr) + + +class GetDevicesWithIpTestCase(functional_base.BaseSudoTestCase): + + def setUp(self): + super().setUp() + self.namespace = self.useFixture(net_helpers.NamespaceFixture()).name + self.devices = [] + self.num_devices = 5 + self.num_devices_with_ip = 3 + for idx in range(self.num_devices): + dev_name = 'test_device_%s' % idx + ip_lib.IPWrapper(self.namespace).add_dummy(dev_name) + device = ip_lib.IPDevice(dev_name, namespace=self.namespace) + device.link.set_up() + self.devices.append(device) + + self.cidrs = [netaddr.IPNetwork('10.10.0.0/24'), + netaddr.IPNetwork('10.20.0.0/24'), + netaddr.IPNetwork('2001:db8:1234:1111::/64'), + netaddr.IPNetwork('2001:db8:1234:2222::/64')] + for idx in range(self.num_devices_with_ip): + for cidr in self.cidrs: + self.devices[idx].addr.add(str(cidr.ip + idx) + '/' + + str(cidr.netmask.netmask_bits())) + + @staticmethod + def _remove_loopback_interface(ip_addresses): + return [ipa for ipa in ip_addresses if + ipa['name'] != ip_lib.LOOPBACK_DEVNAME] + + @staticmethod + def _remove_ipv6_scope_link(ip_addresses): + # Remove all IPv6 addresses with scope link (fe80::...). + return [ipa for ipa in ip_addresses if not ( + ipa['scope'] == 'link' and utils.get_ip_version(ipa['cidr']))] + + @staticmethod + def _pop_ip_address(ip_addresses, cidr): + for idx, ip_address in enumerate(copy.deepcopy(ip_addresses)): + if cidr == ip_address['cidr']: + ip_addresses.pop(idx) + return + + def test_get_devices_with_ip(self): + ip_addresses = ip_lib.get_devices_with_ip(self.namespace) + ip_addresses = self._remove_loopback_interface(ip_addresses) + ip_addresses = self._remove_ipv6_scope_link(ip_addresses) + self.assertEqual(self.num_devices_with_ip * len(self.cidrs), + len(ip_addresses)) + for idx in range(self.num_devices_with_ip): + for cidr in self.cidrs: + cidr = (str(cidr.ip + idx) + '/' + + str(cidr.netmask.netmask_bits())) + self._pop_ip_address(ip_addresses, cidr) + + self.assertEqual(0, len(ip_addresses)) + + def test_get_devices_with_ip_name(self): + for idx in range(self.num_devices_with_ip): + dev_name = 'test_device_%s' % idx + ip_addresses = ip_lib.get_devices_with_ip(self.namespace, + name=dev_name) + ip_addresses = self._remove_loopback_interface(ip_addresses) + ip_addresses = self._remove_ipv6_scope_link(ip_addresses) + + for cidr in self.cidrs: + cidr = (str(cidr.ip + idx) + '/' + + str(cidr.netmask.netmask_bits())) + self._pop_ip_address(ip_addresses, cidr) + + self.assertEqual(0, len(ip_addresses)) + + for idx in range(self.num_devices_with_ip, self.num_devices): + dev_name = 'test_device_%s' % idx + ip_addresses = ip_lib.get_devices_with_ip(self.namespace, + name=dev_name) + ip_addresses = self._remove_loopback_interface(ip_addresses) + ip_addresses = self._remove_ipv6_scope_link(ip_addresses) + self.assertEqual(0, len(ip_addresses)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_keepalived.py neutron-16.4.2/neutron/tests/functional/agent/linux/test_keepalived.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_keepalived.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/linux/test_keepalived.py 2021-11-12 13:56:42.000000000 +0000 @@ -22,6 +22,7 @@ from neutron.agent.linux import ip_lib from neutron.agent.linux import keepalived from neutron.common import utils as common_utils +from neutron.conf.agent.l3 import config as l3_config from neutron.tests.common import net_helpers from neutron.tests.functional.agent.linux import helpers from neutron.tests.functional import base @@ -33,6 +34,7 @@ def setUp(self): super(KeepalivedManagerTestCase, self).setUp() + l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF) cfg.CONF.set_override('check_child_processes_interval', 1, 'AGENT') self.expected_config = self._get_config() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py neutron-16.4.2/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,6 +21,7 @@ - sudo testing is enabled (see neutron.tests.functional.base for details) """ +import time from oslo_config import cfg @@ -129,6 +130,9 @@ lambda: self._expected_devices_events(removed_devices, 'removed')) # restart self.monitor.stop(block=True) + # NOTE(slaweq): lets give async process few more seconds to receive + # "error" from the old ovsdb monitor process and then start new one + time.sleep(5) self.monitor.start(block=True, timeout=60) try: utils.wait_until_true( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_tc_lib.py neutron-16.4.2/neutron/tests/functional/agent/linux/test_tc_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/linux/test_tc_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/linux/test_tc_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -166,17 +166,17 @@ max_kbps=2000, burst_kb=1000, min_kbps=3, namespace=self.ns[0]) mock_log.warning.assert_called_once_with( - *warning_args(3 * 128, tc_lib._calc_min_rate(1000 * 128))) + *warning_args(3 * 125, tc_lib._calc_min_rate(1000 * 125))) - # rate < min_rate: min_rate = 466 with burst = 0.8 ceil = 256000 + # rate < min_rate: min_rate = 455 with burst = 0.8 ceil = 256000 mock_log.reset_mock() tc_lib.add_tc_policy_class(self.device[0], '1:', '1:10', max_kbps=2000, burst_kb=None, min_kbps=5, namespace=self.ns[0]) min_rate = tc_lib._calc_min_rate(qos_consts.DEFAULT_BURST_RATE * - 2000 * 128) + 2000 * 125) mock_log.warning.assert_called_once_with( - *warning_args(5 * 128, min_rate)) + *warning_args(5 * 125, min_rate)) class TcFiltersTestCase(functional_base.BaseSudoTestCase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py neutron-16.4.2/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import re + import mock from oslo_config import fixture as fixture_config from oslo_utils import uuidutils @@ -20,6 +22,7 @@ from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.tests.functional.schema.ovn_southbound import event as test_event +from neutron.agent.linux import iptables_manager from neutron.agent.ovn.metadata import agent from neutron.agent.ovn.metadata import ovsdb from neutron.agent.ovn.metadata import server as metadata_server @@ -27,17 +30,18 @@ from neutron.common import utils as n_utils from neutron.conf.agent.metadata import config as meta_config from neutron.conf.agent.ovn.metadata import config as meta_config_ovn +from neutron.tests.common import net_helpers from neutron.tests.functional import base class MetadataAgentHealthEvent(event.WaitEvent): event_name = 'MetadataAgentHealthEvent' - def __init__(self, chassis, sb_cfg, timeout=5): + def __init__(self, chassis, sb_cfg, table, timeout=5): self.chassis = chassis self.sb_cfg = sb_cfg super(MetadataAgentHealthEvent, self).__init__( - (self.ROW_UPDATE,), 'Chassis', (('name', '=', self.chassis),), + (self.ROW_UPDATE,), table, (('name', '=', self.chassis),), timeout=timeout) def matches(self, event, row, old=None): @@ -55,13 +59,24 @@ super(TestMetadataAgent, self).setUp() self.handler = self.sb_api.idl.notify_handler # We only have OVN NB and OVN SB running for functional tests - mock.patch.object(ovsdb, 'MetadataAgentOvsIdl').start() + self.mock_ovsdb_idl = mock.Mock() + mock_metadata_instance = mock.Mock() + mock_metadata_instance.start.return_value = self.mock_ovsdb_idl + mock_metadata = mock.patch.object( + ovsdb, 'MetadataAgentOvsIdl').start() + mock_metadata.return_value = mock_metadata_instance self._mock_get_ovn_br = mock.patch.object( agent.MetadataAgent, '_get_ovn_bridge', return_value=self.OVN_BRIDGE).start() self.agent = self._start_metadata_agent() + @property + def agent_chassis_table(self): + if self.agent.has_chassis_private: + return 'Chassis_Private' + return 'Chassis' + def _start_metadata_agent(self): conf = self.useFixture(fixture_config.Config()).conf conf.register_opts(meta_config.SHARED_OPTS) @@ -93,7 +108,8 @@ def test_metadata_agent_healthcheck(self): chassis_row = self.sb_api.db_find( - 'Chassis', ('name', '=', self.chassis_name)).execute( + self.agent_chassis_table, + ('name', '=', self.chassis_name)).execute( check_error=True)[0] # Assert that, prior to creating a resource the metadata agent @@ -107,7 +123,8 @@ # this event, Metadata agent will update the external_ids on its # Chassis row to signal that it's healthy. - row_event = MetadataAgentHealthEvent(self.chassis_name, 1) + row_event = MetadataAgentHealthEvent(self.chassis_name, 1, + self.agent_chassis_table) self.handler.watch_event(row_event) self.new_list_request('agents').get_response(self.api) @@ -215,22 +232,23 @@ "unbind.")) def test_agent_registration_at_chassis_create_event(self): - chassis = self.sb_api.lookup('Chassis', self.chassis_name) - self.assertIn(ovn_const.OVN_AGENT_METADATA_ID_KEY, - chassis.external_ids) + def check_for_metadata(): + chassis = self.sb_api.lookup( + self.agent_chassis_table, self.chassis_name) + return ovn_const.OVN_AGENT_METADATA_ID_KEY in chassis.external_ids + + exc = Exception('Chassis not created, %s is not in chassis ' + 'external_ids' % ovn_const.OVN_AGENT_METADATA_ID_KEY) + n_utils.wait_until_true(check_for_metadata, timeout=5, exception=exc) # Delete Chassis and assert + chassis = self.sb_api.lookup('Chassis', self.chassis_name) self.del_fake_chassis(chassis.name) self.assertRaises(idlutils.RowNotFound, self.sb_api.lookup, 'Chassis', self.chassis_name) # Re-add the Chassis self.add_fake_chassis(self.FAKE_CHASSIS_HOST, name=self.chassis_name) - - def check_for_metadata(): - chassis = self.sb_api.lookup('Chassis', self.chassis_name) - return ovn_const.OVN_AGENT_METADATA_ID_KEY in chassis.external_ids - exc = Exception('Agent metadata failed to re-register itself ' 'after the Chassis %s was re-created' % self.chassis_name) @@ -251,23 +269,41 @@ self.assertEqual(other_chassis, other_name) event = MetadataAgentHealthEvent(chassis=other_name, sb_cfg=-1, + table=self.agent_chassis_table, timeout=0) # Use the agent's sb_idl to watch for the event since it has condition self.agent.sb_idl.idl.notify_handler.watch_event(event) # Use the test sb_api to set other_chassis values since shouldn't exist # on agent's sb_idl self.sb_api.db_set( - 'Chassis', other_chassis, + self.agent_chassis_table, other_chassis, ('external_ids', {'test': 'value'})).execute(check_error=True) - event2 = MetadataAgentHealthEvent(chassis=self.chassis_name, sb_cfg=-1) + event2 = MetadataAgentHealthEvent(chassis=self.chassis_name, sb_cfg=-1, + table=self.agent_chassis_table) self.agent.sb_idl.idl.notify_handler.watch_event(event2) # Use the test's sb_api again to send a command so we can see if it # completes and short-circuit the need to wait for a timeout to pass # the test. If we get the result to this, we would have gotten the # previous result as well. self.sb_api.db_set( - 'Chassis', self.chassis_name, + self.agent_chassis_table, self.chassis_name, ('external_ids', {'test': 'value'})).execute(check_error=True) self.assertTrue(event2.wait()) self.assertFalse(event.wait()) + + def test__ensure_datapath_checksum_if_dpdk(self): + self.mock_ovsdb_idl.db_get.return_value.execute.return_value = ( + ovn_const.CHASSIS_DATAPATH_NETDEV) + regex = re.compile(r'-A POSTROUTING -p tcp -m tcp ' + r'-j CHECKSUM --checksum-fill') + namespace = self.useFixture(net_helpers.NamespaceFixture()).name + self.agent._ensure_datapath_checksum(namespace) + iptables_mgr = iptables_manager.IptablesManager( + use_ipv6=True, nat=False, namespace=namespace, external_lock=False) + for rule in iptables_mgr.get_rules_for_table('mangle'): + if regex.match(rule): + return + else: + self.fail('Rule not found in "mangle" table, in namespace %s' % + namespace) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/test_firewall.py neutron-16.4.2/neutron/tests/functional/agent/test_firewall.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/test_firewall.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/test_firewall.py 2021-11-12 13:56:42.000000000 +0000 @@ -585,7 +585,8 @@ [remote_sg_id], self.net_id) - vm_sg_members = {'IPv4': [self.tester.peer_ip_address]} + vm_sg_members = {'IPv4': [ + (self.tester.peer_ip_address, self.tester.peer_mac_address)]} peer_sg_rules = [{'ethertype': 'IPv4', 'direction': 'egress', 'protocol': 'icmp'}] self.firewall.update_security_group_rules(remote_sg_id, peer_sg_rules) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/test_ovs_flows.py neutron-16.4.2/neutron/tests/functional/agent/test_ovs_flows.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/test_ovs_flows.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/test_ovs_flows.py 2021-11-12 13:56:42.000000000 +0000 @@ -344,7 +344,8 @@ 'dst_mac': '12:34:56:78:cc:dd', 'dst_port': 123} self.br_int.install_dvr_to_src_mac(network_type='vlan', **kwargs) - self.br_int.add_dvr_mac_vlan(mac=other_dvr_mac, port=other_dvr_port) + self.br_int.add_dvr_mac_physical(mac=other_dvr_mac, + port=other_dvr_port) trace = self._run_trace(self.br.br_name, "in_port=%d," % other_dvr_port + diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/test_ovs_lib.py neutron-16.4.2/neutron/tests/functional/agent/test_ovs_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/agent/test_ovs_lib.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/agent/test_ovs_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -20,6 +20,7 @@ from neutron_lib import constants as const from oslo_config import cfg from ovsdbapp.backend.ovs_idl import idlutils +import testtools from neutron.agent.common import ovs_lib from neutron.agent.linux import ip_lib @@ -196,8 +197,9 @@ 'Bridge', ('name', '=', self.br.br_name), columns=['other_config'] ).execute()[0]['other_config'] self.assertEqual( - str(state), - br_other_config['mcast-snooping-disable-flood-unregistered']) + 'false', + br_other_config.get( + 'mcast-snooping-disable-flood-unregistered', '').lower()) def test_set_igmp_snooping_enabled(self): self._test_set_igmp_snooping_state(True) @@ -212,11 +214,12 @@ self.br.br_name, 'datapath_id', dpid) self.assertIn(dpid, self.br.get_datapath_id()) - def _test_add_tunnel_port(self, attrs): + def _test_add_tunnel_port(self, attrs, + expected_tunnel_type=const.TYPE_GRE): port_name = utils.get_rand_device_name(net_helpers.PORT_PREFIX) self.br.add_tunnel_port(port_name, attrs['remote_ip'], attrs['local_ip']) - self.assertEqual('gre', + self.assertEqual(expected_tunnel_type, self.ovs.db_get_val('Interface', port_name, 'type')) options = self.ovs.db_get_val('Interface', port_name, 'options') for attr, val in attrs.items(): @@ -229,12 +232,15 @@ } self._test_add_tunnel_port(attrs) + @testtools.skip('bug/1938262') def test_add_tunnel_port_ipv6(self): attrs = { 'remote_ip': '2001:db8:200::1', 'local_ip': '2001:db8:100::1', + 'packet_type': 'legacy_l2', } - self._test_add_tunnel_port(attrs) + self._test_add_tunnel_port( + attrs, expected_tunnel_type=ovs_lib.TYPE_GRE_IP6) def test_add_tunnel_port_custom_port(self): port_name = utils.get_rand_device_name(net_helpers.PORT_PREFIX) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/base.py neutron-16.4.2/neutron/tests/functional/base.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/base.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/base.py 2021-11-12 13:56:42.000000000 +0000 @@ -42,11 +42,13 @@ from neutron.db import models # noqa from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker from neutron.plugins.ml2.drivers import type_geneve # noqa +from neutron import service # noqa from neutron.tests import base from neutron.tests.common import base as common_base from neutron.tests.common import helpers from neutron.tests.functional.resources import process from neutron.tests.unit.plugins.ml2 import test_plugin +import neutron.wsgi LOG = log.getLogger(__name__) @@ -172,6 +174,7 @@ ovn_conf.cfg.CONF.set_override('dns_servers', ['10.10.10.10'], group='ovn') + ovn_conf.cfg.CONF.set_override('api_workers', 1) self.addCleanup(exts.PluginAwareExtensionManager.clear_instance) super(TestOVNFunctionalBase, self).setUp() @@ -182,6 +185,7 @@ mm = directory.get_plugin().mechanism_manager self.mech_driver = mm.mech_drivers['ovn'].obj self.l3_plugin = directory.get_plugin(constants.L3) + self.segments_plugin = directory.get_plugin('segments') self.ovsdb_server_mgr = None self.ovn_northd_mgr = None self.maintenance_worker = maintenance_worker @@ -219,6 +223,7 @@ def get_additional_service_plugins(self): p = super(TestOVNFunctionalBase, self).get_additional_service_plugins() p.update({'revision_plugin_name': 'revisions'}) + p.update({'segments': 'neutron.services.segments.plugin.Plugin'}) return p @property @@ -275,9 +280,13 @@ if self.maintenance_worker: trigger_cls.trigger.__self__.__class__ = worker.MaintenanceWorker cfg.CONF.set_override('neutron_sync_mode', 'off', 'ovn') + else: + trigger_cls.trigger.__self__.__class__ = neutron.wsgi.WorkerService self.addCleanup(self._collect_processes_logs) self.addCleanup(self.stop) + self.mech_driver.pre_fork_initialize( + mock.ANY, mock.ANY, trigger_cls.trigger) # mech_driver.post_fork_initialize creates the IDL connections self.mech_driver.post_fork_initialize( @@ -340,11 +349,18 @@ # fake chassis but from the SB db point of view, 'ip' column can be # any string so we could add entries with ip='172.24.4.1000'. self._counter += 1 - self.sb_api.chassis_add( + chassis = self.sb_api.chassis_add( name, ['geneve'], '172.24.4.%d' % self._counter, external_ids=external_ids, hostname=host).execute(check_error=True) + if self.sb_api.is_table_present('Chassis_Private'): + self.sb_api.db_create( + 'Chassis_Private', name=name, external_ids=external_ids, + chassis=chassis.uuid).execute(check_error=True) return name def del_fake_chassis(self, chassis, if_exists=True): self.sb_api.chassis_del( chassis, if_exists=if_exists).execute(check_error=True) + if self.sb_api.is_table_present('Chassis_Private'): + self.sb_api.db_destroy( + 'Chassis_Private', chassis).execute(check_error=True) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/db/test_migrations.py neutron-16.4.2/neutron/tests/functional/db/test_migrations.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/db/test_migrations.py 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/db/test_migrations.py 2021-11-12 13:56:42.000000000 +0000 @@ -325,25 +325,9 @@ self.alembic_config, 'upgrade', '%s@head' % migration.CONTRACT_BRANCH) - def _test_has_offline_migrations(self, revision, expected): - engine = self.get_engine() - cfg.CONF.set_override('connection', engine.url, group='database') - migration.do_alembic_command(self.alembic_config, 'upgrade', revision) - self.assertEqual(expected, - migration.has_offline_migrations(self.alembic_config, - 'unused')) - - def test_has_offline_migrations_pending_contract_scripts(self): - self._test_has_offline_migrations('kilo', True) - - def test_has_offline_migrations_all_heads_upgraded(self): - self._test_has_offline_migrations('heads', False) - # NOTE(ihrachys): if this test fails for you, it probably means that you # attempt to add an unsafe contract migration script, that is in # contradiction to blueprint online-upgrades - # TODO(ihrachys): revisit later in Pike+ where some contract scripts may be - # safe again def test_forbid_offline_migrations_starting_newton(self): engine = self.get_engine() cfg.CONF.set_override('connection', engine.url, group='database') @@ -398,16 +382,6 @@ super(TestModelsMigrationsMysql, self).test_branches() @test_base.skip_if_timeout("bug 1687027") - def test_has_offline_migrations_pending_contract_scripts(self): - super(TestModelsMigrationsMysql, - self).test_has_offline_migrations_pending_contract_scripts() - - @test_base.skip_if_timeout("bug 1687027") - def test_has_offline_migrations_all_heads_upgraded(self): - super(TestModelsMigrationsMysql, - self).test_has_offline_migrations_all_heads_upgraded() - - @test_base.skip_if_timeout("bug 1687027") def test_models_sync(self): super(TestModelsMigrationsMysql, self).test_models_sync() @@ -593,18 +567,18 @@ # Destination, current yield rev.revision, rev.down_revision - def _migrate_up(self, config, engine, dest, curr, with_data=False): - if with_data: - data = None - pre_upgrade = getattr( - self, "_pre_upgrade_%s" % dest, None) - if pre_upgrade: - data = pre_upgrade(engine) - migration.do_alembic_command(config, 'upgrade', dest) - if with_data: - check = getattr(self, "_check_%s" % dest, None) - if check and data: - check(engine, data) + def _migrate_up(self, config, engine, dest, curr): + data = None + check = getattr(self, "_check_%s" % dest, None) + pre_upgrade = getattr(self, "_pre_upgrade_%s" % dest, None) + if pre_upgrade: + if curr: + migration.do_alembic_command(config, 'upgrade', curr) + data = pre_upgrade(engine) + + if check and data: + migration.do_alembic_command(config, 'upgrade', dest) + check(engine, data) def test_walk_versions(self): """Test migrations ability to upgrade and downgrade. @@ -614,7 +588,10 @@ config = self._get_alembic_config(engine.url) revisions = self._revisions() for dest, curr in revisions: - self._migrate_up(config, engine, dest, curr, with_data=True) + self._migrate_up(config, engine, dest, curr) + + if dest: + migration.do_alembic_command(config, 'upgrade', dest) class TestWalkMigrationsMysql(testlib_api.MySQLTestCaseMixin, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_base.py neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_base.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_base.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_base.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,90 @@ +# Copyright 2021 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import itertools + +from neutron_lib import context + +from neutron.tests.unit import testlib_api + + +class _SegmentAllocation(testlib_api.SqlTestCase): + + PHYSNETS = ('phys1', 'phys2') + NUM_SEGIDS = 10 + segment_allocation_class = None + + def setUp(self): + if not self.segment_allocation_class: + self.skipTest('No allocation class defined') + super().setUp() + self.context = context.Context(user_id='usier_id', + tenant_id='tenant_id') + self.segid_field = ( + self.segment_allocation_class.get_segmentation_id().name) + self.is_vlan = ('physical_network' in + self.segment_allocation_class.db_model.primary_keys()) + pk_columns = self.segment_allocation_class.db_model.__table__.\ + primary_key.columns + self.primary_keys = {col.name for col in pk_columns} + self.segments = None + + def _create_segments(self, num_segids, physnets, allocated=False): + + if self.is_vlan: + self.segments = list(itertools.product(physnets, + range(1, num_segids + 1))) + kwargs_list = [ + {'physical_network': physnet, + self.segid_field: segid, + 'allocated': allocated} for physnet, segid in self.segments] + else: + self.segments = list(range(1, num_segids + 1)) + kwargs_list = [{self.segid_field: segid, + 'allocated': allocated} for segid in self.segments] + + for kwargs in kwargs_list: + self.segment_allocation_class(self.context, **kwargs).create() + + self.assertTrue( + len(kwargs_list), + len(self.segment_allocation_class.get_objects(self.context))) + + def test_get_random_unallocated_segment_and_allocate(self): + m_get = self.segment_allocation_class.get_random_unallocated_segment + m_alloc = self.segment_allocation_class.allocate + self._create_segments(self.NUM_SEGIDS, self.PHYSNETS) + for _ in range(len(self.segments)): + unalloc = m_get(self.context) + segment = dict((k, unalloc[k]) for k in self.primary_keys) + m_alloc(self.context, **segment) + if self.is_vlan: + self.segments.remove((unalloc['physical_network'], + unalloc.segmentation_id)) + else: + self.segments.remove(unalloc.segmentation_id) + + self.assertEqual(0, len(self.segments)) + self.assertIsNone(m_get(self.context)) + + +class _SegmentAllocationMySQL(_SegmentAllocation, + testlib_api.MySQLTestCaseMixin): + pass + + +class _SegmentAllocationPostgreSQL(_SegmentAllocation, + testlib_api.PostgreSQLTestCaseMixin): + pass diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_geneveallocation.py neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_geneveallocation.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_geneveallocation.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_geneveallocation.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,26 @@ +# Copyright 2021 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.objects.plugins.ml2 import geneveallocation +from neutron.tests.functional.objects.plugins.ml2 import test_base + + +class TestGeneveSegmentAllocationMySQL(test_base._SegmentAllocationMySQL): + segment_allocation_class = geneveallocation.GeneveAllocation + + +class TestGeneveSegmentAllocationPostgreSQL( + test_base._SegmentAllocationPostgreSQL): + segment_allocation_class = geneveallocation.GeneveAllocation diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_greallocation.py neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_greallocation.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_greallocation.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_greallocation.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,26 @@ +# Copyright 2021 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.objects.plugins.ml2 import greallocation +from neutron.tests.functional.objects.plugins.ml2 import test_base + + +class TestGreSegmentAllocationMySQL(test_base._SegmentAllocationMySQL): + segment_allocation_class = greallocation.GreAllocation + + +class TestGreSegmentAllocationPostgreSQL( + test_base._SegmentAllocationPostgreSQL): + segment_allocation_class = greallocation.GreAllocation diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_vlanallocation.py neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_vlanallocation.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_vlanallocation.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_vlanallocation.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,26 @@ +# Copyright 2021 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.objects.plugins.ml2 import vlanallocation +from neutron.tests.functional.objects.plugins.ml2 import test_base + + +class TestVlanSegmentAllocationMySQL(test_base._SegmentAllocationMySQL): + segment_allocation_class = vlanallocation.VlanAllocation + + +class TestVlanSegmentAllocationPostgreSQL( + test_base._SegmentAllocationPostgreSQL): + segment_allocation_class = vlanallocation.VlanAllocation diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_vxlanallocation.py neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_vxlanallocation.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/plugins/ml2/test_vxlanallocation.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/objects/plugins/ml2/test_vxlanallocation.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,26 @@ +# Copyright 2021 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.objects.plugins.ml2 import vxlanallocation +from neutron.tests.functional.objects.plugins.ml2 import test_base + + +class TestVxlanSegmentAllocationMySQL(test_base._SegmentAllocationMySQL): + segment_allocation_class = vxlanallocation.VxlanAllocation + + +class TestVxlanSegmentAllocationPostgreSQL( + test_base._SegmentAllocationPostgreSQL): + segment_allocation_class = vxlanallocation.VxlanAllocation diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/test_quota.py neutron-16.4.2/neutron/tests/functional/objects/test_quota.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/objects/test_quota.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/objects/test_quota.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,71 @@ +# Copyright 2021 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime + +from neutron_lib import context +from oslo_utils import uuidutils + +from neutron.objects import quota +from neutron.tests.unit import testlib_api + + +class _ReservationSql(testlib_api.SqlTestCase): + + def setUp(self): + super().setUp() + self.context = context.Context(user_id=None, tenant_id=None, + is_admin=True, overwrite=False) + + def _create_test_reservation(self, exp): + res_id = uuidutils.generate_uuid() + project_id = uuidutils.generate_uuid() + reservation = quota.Reservation( + self.context, id=res_id, expiration=exp, project_id=project_id) + reservation.create() + return reservation + + def _get_reservation(self, _id): + return quota.Reservation.get_object(self.context, id=_id) + + def _create_resource_delta(self, resource, reservation_id, amount): + resource_delta = quota.ResourceDelta( + self.context, resource=resource, reservation_id=reservation_id, + amount=amount) + resource_delta.create() + return resource_delta + + def test_get_total_reservations_map(self): + resources = ['port'] + a_long_time_ago = datetime.datetime(1978, 9, 4) + res = self._create_test_reservation(a_long_time_ago) + res_delta = self._create_resource_delta('port', res.id, 100) + res = self._get_reservation(res.id) + self.assertEqual(1, len(res.resource_deltas)) + self.assertEqual(res_delta, res.resource_deltas[0]) + res_map = quota.Reservation.get_total_reservations_map( + self.context, datetime.datetime.utcnow(), res.project_id, + resources, True) + self.assertEqual({'port': 100}, res_map) + self.assertIsInstance(res_map['port'], int) + + +class TestReservationMySQL(_ReservationSql, testlib_api.MySQLTestCaseMixin): + pass + + +class TestReservationPostgreSQL(_ReservationSql, + testlib_api.PostgreSQLTestCaseMixin): + pass diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,113 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from neutron_lib import constants +from neutron_lib.services.qos import constants as qos_constants + +from neutron.common.ovn import utils as ovn_utils +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ + import qos as qos_extension +from neutron.tests.functional import base + + +QOS_RULE_BW_1 = {'max_kbps': 200, 'max_burst_kbps': 100} +QOS_RULE_BW_2 = {'max_kbps': 300} +QOS_RULE_DSCP_1 = {'dscp_mark': 16} +QOS_RULE_DSCP_2 = {'dscp_mark': 20} + +QOS_RULES_1 = { + constants.EGRESS_DIRECTION: { + qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1, + qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1}, + constants.INGRESS_DIRECTION: { + qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2} +} + +QOS_RULES_2 = { + constants.EGRESS_DIRECTION: { + qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2, + qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_2} +} + +QOS_RULES_3 = { + constants.INGRESS_DIRECTION: { + qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1, + qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1} +} + + +class _OVNClient(object): + + def __init__(self, nd_idl): + self._nb_idl = nd_idl + + +class TestOVNClientQosExtension(base.TestOVNFunctionalBase): + + def setUp(self, maintenance_worker=False): + super(TestOVNClientQosExtension, self).setUp( + maintenance_worker=maintenance_worker) + self._add_logical_switch() + _ovn_client = _OVNClient(self.nb_api) + self.qos_driver = qos_extension.OVNClientQosExtension(_ovn_client) + + def _add_logical_switch(self): + self.network_1 = 'network_1' + with self.nb_api.transaction(check_error=True) as txn: + txn.add(self.nb_api.ls_add(ovn_utils.ovn_name(self.network_1))) + + def _check_rules(self, rules, port_id, network_id): + egress_ovn_rule = self.qos_driver._ovn_qos_rule( + constants.EGRESS_DIRECTION, rules.get(constants.EGRESS_DIRECTION), + port_id, network_id) + ingress_ovn_rule = self.qos_driver._ovn_qos_rule( + constants.INGRESS_DIRECTION, + rules.get(constants.INGRESS_DIRECTION), port_id, network_id) + + with self.nb_api.transaction(check_error=True): + ls = self.qos_driver._driver._nb_idl.lookup( + 'Logical_Switch', ovn_utils.ovn_name(self.network_1)) + self.assertEqual(len(rules), len(ls.qos_rules)) + for rule in ls.qos_rules: + ref_rule = (egress_ovn_rule if rule.direction == 'from-lport' + else ingress_ovn_rule) + action = {} + if 'dscp' in ref_rule: + action = {'dscp': ref_rule['dscp']} + bandwidth = {} + if 'rate' in ref_rule: + bandwidth['rate'] = ref_rule['rate'] + if ref_rule.get('burst'): + bandwidth['burst'] = ref_rule['burst'] + self.assertIn(port_id, rule.match) + self.assertEqual(action, rule.action) + self.assertEqual(bandwidth, rule.bandwidth) + + def test__update_port_qos_rules(self): + port = 'port1' + + def update_and_check(qos_rules): + with self.nb_api.transaction(check_error=True) as txn, \ + mock.patch.object(self.qos_driver, + '_qos_rules') as mock_rules: + mock_rules.return_value = qos_rules + self.qos_driver._update_port_qos_rules( + txn, port, self.network_1, 'qos1', None) + self._check_rules(qos_rules, port, self.network_1) + + update_and_check(QOS_RULES_1) + update_and_check(QOS_RULES_2) + update_and_check(QOS_RULES_3) + update_and_check({}) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ import uuid +from ovsdbapp.backend.ovs_idl import connection from ovsdbapp import event as ovsdb_event from ovsdbapp.tests.functional import base from ovsdbapp.tests import utils @@ -147,3 +148,23 @@ self.assertEqual( (chassis.name, str(binding.datapath.uuid)), self.api.get_logical_port_chassis_and_datapath(port.name)) + + +class TestIgnoreConnectionTimeout(base.FunctionalTestCase, + n_base.BaseLoggingTestCase): + schemas = ['OVN_Southbound', 'OVN_Northbound'] + + def setUp(self): + super(TestIgnoreConnectionTimeout, self).setUp() + self.api = impl.OvsdbSbOvnIdl(self.connection['OVN_Southbound']) + self.nbapi = impl.OvsdbNbOvnIdl(self.connection['OVN_Northbound']) + self.handler = ovsdb_event.RowEventHandler() + self.api.idl.notify = self.handler.notify + + @classmethod + def create_connection(cls, schema): + idl = connection.OvsdbIdl.from_server(cls.schema_map[schema], schema) + return connection.Connection(idl, 0) + + def test_setUp_will_fail_if_this_is_broken(self): + pass diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py 2021-11-12 13:56:42.000000000 +0000 @@ -169,15 +169,9 @@ return self.deserialize(self.fmt, res)['security_group'] def _find_security_group_row_by_id(self, sg_id): - if self.nb_api.is_port_groups_supported(): - for row in self.nb_api._tables['Port_Group'].rows.values(): - if row.name == utils.ovn_port_group_name(sg_id): - return row - else: - for row in self.nb_api._tables['Address_Set'].rows.values(): - if (row.external_ids.get( - ovn_const.OVN_SG_EXT_ID_KEY) == sg_id): - return row + for row in self.nb_api._tables['Port_Group'].rows.values(): + if row.name == utils.ovn_port_group_name(sg_id): + return row def _create_security_group_rule(self, sg_id): data = {'security_group_rule': {'security_group_id': sg_id, @@ -825,4 +819,4 @@ self.assertEqual('true', ls['other_config'][ovn_const.MCAST_SNOOP]) self.assertEqual( - 'true', ls['other_config'][ovn_const.MCAST_FLOOD_UNREGISTERED]) + 'false', ls['other_config'][ovn_const.MCAST_FLOOD_UNREGISTERED]) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,8 @@ # under the License. +import copy + import mock import netaddr @@ -822,104 +824,10 @@ return port_acls def _verify_port_acls(self, port_id, expected_acls): - if self.nb_api.is_port_groups_supported(): - port_acls = self._get_port_related_acls(port_id) - else: - port_acls = self._get_port_related_acls_port_group_not_supported( - port_id) + port_acls = self._get_port_related_acls(port_id) self.assertItemsEqual(expected_acls, port_acls) - @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' - 'impl_idl_ovn.OvsdbNbOvnIdl.is_port_groups_supported', - lambda *args: False) - def test_port_security_port_group_not_supported(self): - n1 = self._make_network(self.fmt, 'n1', True) - res = self._create_subnet(self.fmt, n1['network']['id'], '10.0.0.0/24') - subnet = self.deserialize(self.fmt, res)['subnet'] - p = self._make_port(self.fmt, n1['network']['id'], - fixed_ips=[{'subnet_id': subnet['id']}]) - port_id = p['port']['id'] - sg_id = p['port']['security_groups'][0].replace('-', '_') - expected_acls_with_sg_ps_enabled = [ - {'match': 'inport == "' + str(port_id) + '" && ip', - 'action': 'drop', - 'priority': 1001, - 'direction': 'from-lport'}, - {'match': 'outport == "' + str(port_id) + '" && ip', - 'action': 'drop', - 'priority': 1001, - 'direction': 'to-lport'}, - {'match': 'inport == "' + str(port_id) + '" && ip4 && ip4.dst == ' - '{255.255.255.255, 10.0.0.0/24} && udp && udp.src == 68 ' - '&& udp.dst == 67', - 'action': 'allow', - 'priority': 1002, - 'direction': 'from-lport'}, - {'match': 'inport == "' + str(port_id) + '" && ip6', - 'action': 'allow-related', - 'priority': 1002, - 'direction': 'from-lport'}, - {'match': 'inport == "' + str(port_id) + '" && ip4', - 'action': 'allow-related', - 'priority': 1002, - 'direction': 'from-lport'}, - {'match': 'outport == "' + str(port_id) + '" && ip4 && ' - 'ip4.src == $as_ip4_' + str(sg_id), - 'action': 'allow-related', - 'priority': 1002, - 'direction': 'to-lport'}, - {'match': 'outport == "' + str(port_id) + '" && ip6 && ' - 'ip6.src == $as_ip6_' + str(sg_id), - 'action': 'allow-related', - 'priority': 1002, - 'direction': 'to-lport'}, - ] - self._verify_port_acls(port_id, expected_acls_with_sg_ps_enabled) - - # clear the security groups. - data = {'port': {'security_groups': []}} - port_req = self.new_update_request('ports', data, p['port']['id']) - port_req.get_response(self.api) - - # No security groups and port security enabled - > ACLs should be - # added to drop the packets. - expected_acls_with_no_sg_ps_enabled = [ - {'match': 'inport == "' + str(port_id) + '" && ip', - 'action': 'drop', - 'priority': 1001, - 'direction': 'from-lport'}, - {'match': 'outport == "' + str(port_id) + '" && ip', - 'action': 'drop', - 'priority': 1001, - 'direction': 'to-lport'}, - ] - self._verify_port_acls(port_id, expected_acls_with_no_sg_ps_enabled) - - # Disable port security - data = {'port': {'port_security_enabled': False}} - port_req = self.new_update_request('ports', data, p['port']['id']) - port_req.get_response(self.api) - # No security groups and port security disabled - > No ACLs should be - # added (allowing all the traffic). - self._verify_port_acls(port_id, []) - - # Enable port security again with no security groups - > ACLs should - # be added back to drop the packets. - data = {'port': {'port_security_enabled': True}} - port_req = self.new_update_request('ports', data, p['port']['id']) - port_req.get_response(self.api) - self._verify_port_acls(port_id, expected_acls_with_no_sg_ps_enabled) - - # Set security groups back - data = {'port': {'security_groups': p['port']['security_groups']}} - port_req = self.new_update_request('ports', data, p['port']['id']) - port_req.get_response(self.api) - self._verify_port_acls(port_id, expected_acls_with_sg_ps_enabled) - def test_port_security_port_group(self): - if not self.nb_api.is_port_groups_supported(): - self.skipTest('Port groups is not supported') - n1 = self._make_network(self.fmt, 'n1', True) res = self._create_subnet(self.fmt, n1['network']['id'], '10.0.0.0/24') subnet = self.deserialize(self.fmt, res)['subnet'] @@ -1143,6 +1051,69 @@ self._validate_dns_records([]) +class TestPortExternalIds(base.TestOVNFunctionalBase): + + def _get_lsp_external_id(self, port_id): + ovn_port = self.nb_api.lookup('Logical_Switch_Port', port_id) + return copy.deepcopy(ovn_port.external_ids) + + def _set_lsp_external_id(self, port_id, **pairs): + external_ids = self._get_lsp_external_id(port_id) + for key, val in pairs.items(): + external_ids[key] = val + self.nb_api.set_lswitch_port(lport_name=port_id, + external_ids=external_ids).execute() + + def _create_lsp(self): + n1 = self._make_network(self.fmt, 'n1', True) + res = self._create_subnet(self.fmt, n1['network']['id'], '10.0.0.0/24') + subnet = self.deserialize(self.fmt, res)['subnet'] + p = self._make_port(self.fmt, n1['network']['id'], + fixed_ips=[{'subnet_id': subnet['id']}]) + port_id = p['port']['id'] + return port_id, self._get_lsp_external_id(port_id) + + def test_port_update_has_ext_ids(self): + port_id, ext_ids = self._create_lsp() + self.assertIsNotNone(ext_ids) + + def test_port_update_add_ext_id(self): + port_id, ext_ids = self._create_lsp() + ext_ids['another'] = 'value' + self._set_lsp_external_id(port_id, another='value') + self.assertEqual(ext_ids, self._get_lsp_external_id(port_id)) + + def test_port_update_change_ext_id_value(self): + port_id, ext_ids = self._create_lsp() + ext_ids['another'] = 'value' + self._set_lsp_external_id(port_id, another='value') + self.assertEqual(ext_ids, self._get_lsp_external_id(port_id)) + ext_ids['another'] = 'value2' + self._set_lsp_external_id(port_id, another='value2') + self.assertEqual(ext_ids, self._get_lsp_external_id(port_id)) + + def test_port_update_with_foreign_ext_ids(self): + port_id, ext_ids = self._create_lsp() + new_ext_ids = {ovn_const.OVN_PORT_FIP_EXT_ID_KEY: '1.11.11.1', + 'foreign_key2': 'value1234'} + self._set_lsp_external_id(port_id, **new_ext_ids) + ext_ids.update(new_ext_ids) + self.assertEqual(ext_ids, self._get_lsp_external_id(port_id)) + # invoke port update and make sure the the values we added to the + # external_ids remain undisturbed. + data = {'port': {'extra_dhcp_opts': [{'ip_version': 4, + 'opt_name': 'ip-forward-enable', + 'opt_value': '0'}]}} + port_req = self.new_update_request('ports', data, port_id) + port_req.get_response(self.api) + actual_ext_ids = self._get_lsp_external_id(port_id) + # update port should have not removed keys it does not use from the + # external ids of the lsp. + self.assertEqual('1.11.11.1', + actual_ext_ids.get(ovn_const.OVN_PORT_FIP_EXT_ID_KEY)) + self.assertEqual('value1234', actual_ext_ids.get('foreign_key2')) + + class TestNBDbResourcesOverTcp(TestNBDbResources): def get_ovsdb_server_protocol(self): return 'tcp' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,8 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -import mock - from neutron.common.ovn import acl as acl_utils from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils @@ -29,7 +27,6 @@ from neutron_lib.api.definitions import port_security as ps from neutron_lib import constants from neutron_lib import context -from neutron_lib.plugins import directory from oslo_utils import uuidutils from ovsdbapp.backend.ovs_idl import idlutils @@ -60,9 +57,6 @@ self.delete_lrouter_routes = [] self.delete_lrouter_nats = [] self.delete_acls = [] - self.create_address_sets = [] - self.delete_address_sets = [] - self.update_address_sets = [] self.create_port_groups = [] self.delete_port_groups = [] self.expected_dhcp_options_rows = [] @@ -379,9 +373,13 @@ uuidutils.generate_uuid(), 'neutron-' + n1['network']['id'])) self.delete_lswitches.append('neutron-' + n2['network']['id']) - self.delete_lswitch_ports.append( - (utils.ovn_provnet_port_name(e1['network']['id']), - utils.ovn_name(e1['network']['id']))) + for seg in self.segments_plugin.get_segments( + self.context, + filters={'network_id': [e1['network']['id']]}): + if seg.get('physical_network'): + self.delete_lswitch_ports.append( + (utils.ovn_provnet_port_name(seg['id']), + utils.ovn_name(e1['network']['id']))) r1 = self.l3_plugin.create_router( self.context, @@ -579,17 +577,6 @@ 'neutron-' + r1['id'])) self.delete_lrouters.append('neutron-' + r2['id']) - address_set_name = n1_prtr['port']['security_groups'][0] - self.create_address_sets.extend([('fake_sg', 'ip4'), - ('fake_sg', 'ip6')]) - self.delete_address_sets.append((address_set_name, 'ip6')) - address_adds = ['10.0.0.101', '10.0.0.102'] - address_dels = [] - for address in n1_prtr['port']['fixed_ips']: - address_dels.append(address['ip_address']) - self.update_address_sets.append((address_set_name, 'ip4', - address_adds, address_dels)) - self.create_port_groups.extend([{'name': 'pg1', 'acls': []}, {'name': 'pg2', 'acls': []}]) self.delete_port_groups.append( @@ -758,26 +745,10 @@ txn.add(self.nb_api.delete_acl(lswitch_name, lport_name, True)) - for name, ip_version in self.create_address_sets: - ovn_name = utils.ovn_addrset_name(name, ip_version) - external_ids = {ovn_const.OVN_SG_EXT_ID_KEY: name} - txn.add(self.nb_api.create_address_set( - ovn_name, True, external_ids=external_ids)) - - for name, ip_version in self.delete_address_sets: - ovn_name = utils.ovn_addrset_name(name, ip_version) - txn.add(self.nb_api.delete_address_set(ovn_name, True)) - - for name, ip_version, ip_adds, ip_dels in self.update_address_sets: - ovn_name = utils.ovn_addrset_name(name, ip_version) - txn.add(self.nb_api.update_address_set(ovn_name, - ip_adds, ip_dels, True)) - - if self.nb_api.is_port_groups_supported(): - for pg in self.create_port_groups: - txn.add(self.nb_api.pg_add(**pg)) - for pg in self.delete_port_groups: - txn.add(self.nb_api.pg_del(pg)) + for pg in self.create_port_groups: + txn.add(self.nb_api.pg_add(**pg)) + for pg in self.delete_port_groups: + txn.add(self.nb_api.pg_del(pg)) for lport_name in self.reset_lport_dhcpv4_options: txn.add(self.nb_api.set_lswitch_port(lport_name, True, @@ -848,9 +819,14 @@ def _validate_networks(self, should_match=True): db_networks = self._list('networks') db_net_ids = [net['id'] for net in db_networks['networks']] - db_provnet_ports = [utils.ovn_provnet_port_name(net['id']) - for net in db_networks['networks'] - if net.get('provider:physical_network')] + db_provnet_ports = [] + for net in db_networks['networks']: + for seg in self.segments_plugin.get_segments( + self.context, + filters={'network_id': [net['id']]}): + if seg.get('physical_network'): + db_provnet_ports.append( + utils.ovn_provnet_port_name(seg['id'])) # Get the list of lswitch ids stored in the OVN plugin IDL _plugin_nb_ovn = self.mech_driver._nb_ovn @@ -1079,54 +1055,39 @@ def _validate_acls(self, should_match=True): # Get the neutron DB ACLs. db_acls = [] - sg_cache = {} - subnet_cache = {} _plugin_nb_ovn = self.mech_driver._nb_ovn - if not _plugin_nb_ovn.is_port_groups_supported(): - for db_port in self._list('ports')['ports']: - acls = acl_utils.add_acls(self.plugin, - context.get_admin_context(), - db_port, - sg_cache, - subnet_cache, - self.mech_driver._nb_ovn) - for acl in acls: - db_acls.append(acl_utils.filter_acl_dict(acl)) - else: - # ACLs due to SGs and default drop port group - for sg in self._list('security-groups')['security_groups']: - for sgr in sg['security_group_rules']: - acl = acl_utils._add_sg_rule_acl_for_port_group( - utils.ovn_port_group_name(sg['id']), sgr, - self.mech_driver._nb_ovn) - db_acls.append(TestOvnNbSync._build_acl_for_pgs(**acl)) - for acl in acl_utils.add_acls_for_drop_port_group( - ovn_const.OVN_DROP_PORT_GROUP_NAME): + # ACLs due to SGs and default drop port group + for sg in self._list('security-groups')['security_groups']: + for sgr in sg['security_group_rules']: + acl = acl_utils._add_sg_rule_acl_for_port_group( + utils.ovn_port_group_name(sg['id']), sgr) db_acls.append(TestOvnNbSync._build_acl_for_pgs(**acl)) + for acl in acl_utils.add_acls_for_drop_port_group( + ovn_const.OVN_DROP_PORT_GROUP_NAME): + db_acls.append(TestOvnNbSync._build_acl_for_pgs(**acl)) + # Get the list of ACLs stored in the OVN plugin IDL. plugin_acls = [] for row in _plugin_nb_ovn._tables['Logical_Switch'].rows.values(): for acl in getattr(row, 'acls', []): plugin_acls.append(self._build_acl_to_compare(acl)) - if self.nb_api.is_port_groups_supported(): - for row in _plugin_nb_ovn._tables['Port_Group'].rows.values(): - for acl in getattr(row, 'acls', []): - plugin_acls.append( - self._build_acl_to_compare( - acl, extra_fields=['external_ids'])) + for row in _plugin_nb_ovn._tables['Port_Group'].rows.values(): + for acl in getattr(row, 'acls', []): + plugin_acls.append( + self._build_acl_to_compare( + acl, extra_fields=['external_ids'])) # Get the list of ACLs stored in the OVN monitor IDL. monitor_acls = [] for row in self.nb_api.tables['Logical_Switch'].rows.values(): for acl in getattr(row, 'acls', []): monitor_acls.append(self._build_acl_to_compare(acl)) - if _plugin_nb_ovn.is_port_groups_supported(): - for row in self.nb_api.tables['Port_Group'].rows.values(): - for acl in getattr(row, 'acls', []): - monitor_acls.append(self._build_acl_to_compare(acl)) + for row in self.nb_api.tables['Port_Group'].rows.values(): + for acl in getattr(row, 'acls', []): + monitor_acls.append(self._build_acl_to_compare(acl)) if should_match: self.assertItemsEqual(db_acls, plugin_acls) @@ -1205,18 +1166,18 @@ AssertionError, self.assertItemsEqual, db_router_ids, monitor_lrouter_ids) - def _get_networks_for_router_port(port_fixed_ips): + def _get_networks_for_router_port(port): _ovn_client = self.l3_plugin._ovn_client networks, _ = ( _ovn_client._get_nets_and_ipv6_ra_confs_for_router_port( - self.ctx, port_fixed_ips)) + self.ctx, port)) return networks - def _get_ipv6_ra_configs_for_router_port(port_fixed_ips): + def _get_ipv6_ra_configs_for_router_port(port): _ovn_client = self.l3_plugin._ovn_client networks, ipv6_ra_configs = ( _ovn_client._get_nets_and_ipv6_ra_confs_for_router_port( - self.ctx, port_fixed_ips)) + self.ctx, port)) return ipv6_ra_configs for router_id in db_router_ids: @@ -1225,10 +1186,10 @@ r_port_ids = [p['id'] for p in r_ports['ports']] r_port_networks = { p['id']: - _get_networks_for_router_port(p['fixed_ips']) + _get_networks_for_router_port(p) for p in r_ports['ports']} r_port_ipv6_ra_configs = { - p['id']: _get_ipv6_ra_configs_for_router_port(p['fixed_ips']) + p['id']: _get_ipv6_ra_configs_for_router_port(p) for p in r_ports['ports']} r_routes = db_routes[router_id] r_nats = db_nats[router_id] @@ -1348,57 +1309,8 @@ AssertionError, self.assertItemsEqual, r_nats, monitor_nats) - def _validate_address_sets(self, should_match=True): - _plugin_nb_ovn = self.mech_driver._nb_ovn - if _plugin_nb_ovn.is_port_groups_supported(): - # If Port Groups are supported, no Address Sets are expected. - # This validation is still useful as we expect existing ones to - # be deleted after the sync. - db_sgs = [] - else: - db_ports = self._list('ports')['ports'] - sgs = self._list('security-groups')['security_groups'] - db_sgs = {} - for sg in sgs: - for ip_version in ['ip4', 'ip6']: - name = utils.ovn_addrset_name(sg['id'], ip_version) - db_sgs[name] = [] - - for port in db_ports: - sg_ids = utils.get_lsp_security_groups(port) - addresses = acl_utils.acl_port_ips(port) - for sg_id in sg_ids: - for ip_version in addresses: - name = utils.ovn_addrset_name(sg_id, ip_version) - db_sgs[name].extend(addresses[ip_version]) - - nb_address_sets = _plugin_nb_ovn.get_address_sets() - nb_sgs = {} - for nb_sgid, nb_values in nb_address_sets.items(): - nb_sgs[nb_sgid] = nb_values['addresses'] - - mn_sgs = {} - for row in self.nb_api.tables['Address_Set'].rows.values(): - mn_sgs[getattr(row, 'name')] = getattr(row, 'addresses') - - if should_match: - self.assertItemsEqual(nb_sgs, db_sgs) - self.assertItemsEqual(mn_sgs, db_sgs) - else: - # This condition is to cover the case when we use Port Groups - # and we completely deleted the NB DB. At this point, the expected - # number of Address Sets is 0 and the observed number in NB is - # also 0 so we can't have the asserts below as both will be empty. - if _plugin_nb_ovn.is_port_groups_supported() and nb_sgs: - self.assertRaises(AssertionError, self.assertItemsEqual, - nb_sgs, db_sgs) - self.assertRaises(AssertionError, self.assertItemsEqual, - mn_sgs, db_sgs) - def _validate_port_groups(self, should_match=True): _plugin_nb_ovn = self.mech_driver._nb_ovn - if not _plugin_nb_ovn.is_port_groups_supported(): - return db_pgs = [] for sg in self._list('security-groups')['security_groups']: @@ -1471,7 +1383,6 @@ self._validate_dhcp_opts(should_match=should_match) self._validate_acls(should_match=should_match) self._validate_routers_and_router_ports(should_match=should_match) - self._validate_address_sets(should_match=should_match) self._validate_port_groups(should_match=should_match) self._validate_dns_records(should_match=should_match) @@ -1523,17 +1434,11 @@ def setUp(self): super(TestOvnSbSync, self).setUp(maintenance_worker=True) - self.segments_plugin = directory.get_plugin('segments') self.sb_synchronizer = ovn_db_sync.OvnSbSynchronizer( self.plugin, self.mech_driver._sb_ovn, self.mech_driver) self.addCleanup(self.sb_synchronizer.stop) self.ctx = context.get_admin_context() - def get_additional_service_plugins(self): - p = super(TestOvnSbSync, self).get_additional_service_plugins() - p.update({'segments': 'neutron.services.segments.plugin.Plugin'}) - return p - def _sync_resources(self): self.sb_synchronizer.sync_hostname_and_physical_networks(self.ctx) @@ -1626,9 +1531,3 @@ class TestOvnSbSyncOverSsl(TestOvnSbSync): def get_ovsdb_server_protocol(self): return 'ssl' - - -@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.impl_idl_ovn.' - 'OvsdbNbOvnIdl.is_port_groups_supported', lambda *args: False) -class TestOvnNbSyncNoPgs(TestOvnNbSync): - pass diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,28 +13,35 @@ # under the License. import mock + +import fixtures as og_fixtures +from oslo_concurrency import processutils +from oslo_config import cfg +from oslo_serialization import jsonutils from oslo_utils import uuidutils from neutron.common.ovn import constants as ovn_const from neutron.common import utils as n_utils +from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import ovn_hash_ring_db as db_hash_ring from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor from neutron.tests.functional import base from neutron.tests.functional.resources.ovsdb import fixtures +from neutron.tests.functional.resources import process from neutron_lib.api.definitions import portbindings from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from ovsdbapp.backend.ovs_idl import event -class WaitForMACBindingDeleteEvent(event.WaitEvent): - event_name = 'WaitForMACBindingDeleteEvent' +class WaitForDataPathBindingCreateEvent(event.WaitEvent): + event_name = 'WaitForDataPathBindingCreateEvent' - def __init__(self, entry): - table = 'MAC_Binding' - events = (self.ROW_DELETE,) - conditions = (('_uuid', '=', entry),) - super(WaitForMACBindingDeleteEvent, self).__init__( + def __init__(self, net_name): + table = 'Datapath_Binding' + events = (self.ROW_CREATE,) + conditions = (('external_ids', '=', {'name2': net_name}),) + super(WaitForDataPathBindingCreateEvent, self).__init__( events, table, conditions, timeout=15) @@ -111,6 +118,40 @@ 'port_id': port['id']}}) return r1_f2 + def _check_mac_binding_exists(self, macb_id): + cmd = ['ovsdb-client', 'transact', + self.mech_driver.sb_ovn.connection_string] + + if self._ovsdb_protocol == 'ssl': + cmd += ['-p', self.ovsdb_server_mgr.private_key, '-c', + self.ovsdb_server_mgr.certificate, '-C', + self.ovsdb_server_mgr.ca_cert] + + cmd += ['["OVN_Southbound", {"op": "select", "table": "MAC_Binding", ' + '"where": [["_uuid", "==", ["uuid", "%s"]]]}]' % macb_id] + + out, _ = processutils.execute(*cmd, + log_errors=False) + return str(macb_id) in out + + def _add_mac_binding_row(self, ip, datapath): + cmd = ['ovsdb-client', 'transact', + self.mech_driver.sb_ovn.connection_string] + + if self._ovsdb_protocol == 'ssl': + cmd += ['-p', self.ovsdb_server_mgr.private_key, '-c', + self.ovsdb_server_mgr.certificate, '-C', + self.ovsdb_server_mgr.ca_cert] + + cmd += ['["OVN_Southbound", {"op": "insert", "table": "MAC_Binding", ' + '"row": {"ip": "%s", "datapath": ' + '["uuid", "%s"]}}]' % (ip, datapath)] + + out, _ = processutils.execute(*cmd, + log_errors=False) + out_parsed = jsonutils.loads(out) + return out_parsed[0]['uuid'][1] + def test_floatingip_mac_bindings(self): """Check that MAC_Binding entries are cleared on FIP add/removal @@ -124,31 +165,34 @@ * Delete the FIP. * Check that the MAC_Binding entry gets deleted. """ - self._make_network(self.fmt, 'network1', True) + net_name = 'network1' + row_event = WaitForDataPathBindingCreateEvent(net_name) + self.mech_driver._sb_ovn.idl.notify_handler.watch_event(row_event) + self._make_network(self.fmt, net_name, True) + self.assertTrue(row_event.wait()) dp = self.sb_api.db_find( 'Datapath_Binding', - ('external_ids', '=', {'name2': 'network1'})).execute() - macb_id = self.sb_api.db_create('MAC_Binding', datapath=dp[0]['_uuid'], - ip='100.0.0.21').execute() + ('external_ids', '=', {'name2': net_name})).execute() + macb_id = self._add_mac_binding_row( + ip='100.0.0.21', datapath=dp[0]['_uuid']) port = self.create_port() # Ensure that the MAC_Binding entry gets deleted after creating a FIP - row_event = WaitForMACBindingDeleteEvent(macb_id) - self.mech_driver._sb_ovn.idl.notify_handler.watch_event(row_event) fip = self._create_fip(port, '100.0.0.21') - self.assertTrue(row_event.wait()) + n_utils.wait_until_true( + lambda: not self._check_mac_binding_exists(macb_id), + timeout=15, sleep=1) # Now that the FIP is created, add a new MAC_Binding entry with the # same IP address - - macb_id = self.sb_api.db_create('MAC_Binding', datapath=dp[0]['_uuid'], - ip='100.0.0.21').execute() + macb_id = self._add_mac_binding_row( + ip='100.0.0.21', datapath=dp[0]['_uuid']) # Ensure that the MAC_Binding entry gets deleted after deleting the FIP - row_event = WaitForMACBindingDeleteEvent(macb_id) - self.mech_driver._sb_ovn.idl.notify_handler.watch_event(row_event) self.l3_plugin.delete_floatingip(self.context, fip['id']) - self.assertTrue(row_event.wait()) + n_utils.wait_until_true( + lambda: not self._check_mac_binding_exists(macb_id), + timeout=15, sleep=1) def _test_port_binding_and_status(self, port_id, action, status): # This function binds or unbinds port to chassis and @@ -181,12 +225,14 @@ self._test_port_binding_and_status(port['id'], 'unbind', 'DOWN') def test_distributed_lock(self): + api_workers = 11 + cfg.CONF.set_override('api_workers', api_workers) row_event = DistributedLockTestEvent() self.mech_driver._nb_ovn.idl.notify_handler.watch_event(row_event) worker_list = [self.mech_driver._nb_ovn, ] # Create 10 fake workers - for _ in range(10): + for _ in range(api_workers - len(worker_list)): node_uuid = uuidutils.generate_uuid() db_hash_ring.add_node( self.context, ovn_const.HASH_RING_ML2_GROUP, node_uuid) @@ -231,3 +277,34 @@ class TestNBDbMonitorOverSsl(TestNBDbMonitor): def get_ovsdb_server_protocol(self): return 'ssl' + + +class TestOvnIdlProbeInterval(base.TestOVNFunctionalBase): + def setUp(self): + # skip parent setUp, we don't need it, but we do need grandparent + # pylint: disable=bad-super-call + super(base.TestOVNFunctionalBase, self).setUp() + mm = directory.get_plugin().mechanism_manager + self.mech_driver = mm.mech_drivers['ovn'].obj + self.temp_dir = self.useFixture(og_fixtures.TempDir()).path + install_share_path = self._get_install_share_path() + self.mgr = self.useFixture( + process.OvsdbServer(self.temp_dir, install_share_path, + ovn_nb_db=True, ovn_sb_db=True, + protocol='tcp')) + connection = self.mgr.get_ovsdb_connection_path + self.connections = {'OVN_Northbound': connection(), + 'OVN_Southbound': connection(db_type='sb')} + + def test_ovsdb_probe_interval(self): + klasses = { + ovsdb_monitor.BaseOvnIdl: ('OVN_Northbound', {}), + ovsdb_monitor.OvnNbIdl: ('OVN_Northbound', + {'driver': self.mech_driver}), + ovsdb_monitor.OvnSbIdl: ('OVN_Southbound', + {'driver': self.mech_driver})} + idls = [kls.from_server(self.connections[schema], schema, **kwargs) + for kls, (schema, kwargs) in klasses.items()] + interval = ovn_conf.get_ovn_ovsdb_probe_interval() + for idl in idls: + self.assertEqual(interval, idl._session.reconnect.probe_interval) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,16 +13,26 @@ # under the License. import functools +import re import mock +import netaddr from neutron_lib.api.definitions import portbindings +from neutron_lib import constants from oslo_config import cfg from oslo_utils import uuidutils +from ovsdbapp.backend.ovs_idl import event +from ovsdbapp.tests.functional import base as ovs_base +from neutron.agent.linux import utils as linux_utils from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils from neutron.common import utils as n_utils +from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import ovn_revision_numbers_db as db_rev +from neutron.plugins.ml2.drivers.ovn.mech_driver import mech_driver +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.tests import base as tests_base from neutron.tests.functional import base @@ -438,6 +448,21 @@ self.assertNotIn(ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY, ovn_vport.options) + def test_virtual_port_not_set_similiar_address(self): + # Create one port + self._create_port(fixed_ip='10.0.0.110') + # Create second port with similar IP, so that + # string matching will return True + second_port = self._create_port(fixed_ip='10.0.0.11') + + # Assert the virtual port has not been set. + ovn_vport = self._find_port_row(second_port['id']) + self.assertEqual("", ovn_vport.type) + self.assertNotIn(ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY, + ovn_vport.options) + self.assertNotIn(ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY, + ovn_vport.options) + class TestExternalPorts(base.TestOVNFunctionalBase): @@ -460,11 +485,11 @@ rows = cmd.execute(check_error=True) return rows[0] if rows else None - def test_external_port_create(self): + def _test_external_port_create(self, vnic_type): port_data = { 'port': {'network_id': self.n1['network']['id'], 'tenant_id': self._tenant_id, - portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT}} + portbindings.VNIC_TYPE: vnic_type}} port_req = self.new_create_request('ports', port_data, self.fmt) port_res = port_req.get_response(self.api) @@ -476,7 +501,16 @@ self.assertEqual(str(self.default_ch_grp.uuid), str(ovn_port.ha_chassis_group[0].uuid)) - def test_external_port_update(self): + def test_external_port_create_vnic_direct(self): + self._test_external_port_create(portbindings.VNIC_DIRECT) + + def test_external_port_create_vnic_direct_physical(self): + self._test_external_port_create(portbindings.VNIC_DIRECT_PHYSICAL) + + def test_external_port_create_vnic_macvtap(self): + self._test_external_port_create(portbindings.VNIC_MACVTAP) + + def _test_external_port_update(self, vnic_type): port_data = { 'port': {'network_id': self.n1['network']['id'], 'tenant_id': self._tenant_id}} @@ -490,7 +524,7 @@ self.assertEqual([], ovn_port.ha_chassis_group) port_upt_data = { - 'port': {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT}} + 'port': {portbindings.VNIC_TYPE: vnic_type}} port_req = self.new_update_request( 'ports', port_upt_data, port['id'], self.fmt) port_res = port_req.get_response(self.api) @@ -502,11 +536,20 @@ self.assertEqual(str(self.default_ch_grp.uuid), str(ovn_port.ha_chassis_group[0].uuid)) - def test_external_port_create_switchdev(self): + def test_external_port_update_vnic_direct(self): + self._test_external_port_update(portbindings.VNIC_DIRECT) + + def test_external_port_update_vnic_direct_physical(self): + self._test_external_port_update(portbindings.VNIC_DIRECT_PHYSICAL) + + def test_external_port_update_vnic_macvtap(self): + self._test_external_port_update(portbindings.VNIC_MACVTAP) + + def _test_external_port_create_switchdev(self, vnic_type): port_data = { 'port': {'network_id': self.n1['network']['id'], 'tenant_id': self._tenant_id, - portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + portbindings.VNIC_TYPE: vnic_type, ovn_const.OVN_PORT_BINDING_PROFILE: { 'capabilities': [ovn_const.PORT_CAP_SWITCHDEV]}}} @@ -521,13 +564,23 @@ # Assert the poer hasn't been added to any HA Chassis Group either self.assertEqual(0, len(ovn_port.ha_chassis_group)) - def test_external_port_update_switchdev(self): + def test_external_port_create_switchdev_vnic_direct(self): + self._test_external_port_create_switchdev(portbindings.VNIC_DIRECT) + + def test_external_port_create_switchdev_vnic_direct_physical(self): + self._test_external_port_create_switchdev( + portbindings.VNIC_DIRECT_PHYSICAL) + + def test_external_port_create_switchdev_vnic_macvtap(self): + self._test_external_port_create_switchdev(portbindings.VNIC_MACVTAP) + + def _test_external_port_update_switchdev(self, vnic_type): port_data = { 'port': {'network_id': self.n1['network']['id'], 'tenant_id': self._tenant_id, - portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT}} + portbindings.VNIC_TYPE: vnic_type}} - # Create a VNIC_DIRECT type port without the "switchdev" + # Create a VNIC_DIRECT[_PHYSICAL] type port without the "switchdev" # capability and assert that it's an "external" port port_req = self.new_create_request('ports', port_data, self.fmt) port_res = port_req.get_response(self.api) @@ -556,3 +609,299 @@ self.assertEqual("", ovn_port.type) # Assert the poer hasn't been added to any HA Chassis Group either self.assertEqual(0, len(ovn_port.ha_chassis_group)) + + def test_external_port_update_switchdev_vnic_direct(self): + self._test_external_port_update_switchdev(portbindings.VNIC_DIRECT) + + def test_external_port_update_switchdev_vnic_direct_physical(self): + self._test_external_port_update_switchdev( + portbindings.VNIC_DIRECT_PHYSICAL) + + def test_external_port_update_switchdev_vnic_macvtap(self): + self._test_external_port_update_switchdev(portbindings.VNIC_MACVTAP) + + +class TestProvnetPorts(base.TestOVNFunctionalBase): + + def setUp(self): + super(TestProvnetPorts, self).setUp() + self._ovn_client = self.mech_driver._ovn_client + + def _find_port_row_by_name(self, name): + cmd = self.nb_api.db_find_rows( + 'Logical_Switch_Port', ('name', '=', name)) + rows = cmd.execute(check_error=True) + return rows[0] if rows else None + + def create_segment(self, network_id, physical_network, segmentation_id): + segment_data = {'network_id': network_id, + 'physical_network': physical_network, + 'segmentation_id': segmentation_id, + 'network_type': 'vlan', + 'name': constants.ATTR_NOT_SPECIFIED, + 'description': constants.ATTR_NOT_SPECIFIED} + return self.segments_plugin.create_segment( + self.context, segment={'segment': segment_data}) + + def delete_segment(self, segment_id): + return self.segments_plugin.delete_segment( + self.context, segment_id) + + def get_segments(self, network_id): + return self.segments_plugin.get_segments( + self.context, filters={'network_id': [network_id]}) + + def test_network_segments_localnet_ports(self): + n1 = self._make_network( + self.fmt, 'n1', True, + arg_list=('provider:network_type', + 'provider:segmentation_id', + 'provider:physical_network'), + **{'provider:network_type': 'vlan', + 'provider:segmentation_id': 100, + 'provider:physical_network': 'physnet1'})['network'] + ovn_port = self._find_port_row_by_name( + utils.ovn_provnet_port_name(n1['id'])) + # Assert that localnet port name is not based + # on network name. + self.assertIsNone(ovn_port) + seg_db = self.get_segments(n1['id']) + ovn_localnetport = self._find_port_row_by_name( + utils.ovn_provnet_port_name(seg_db[0]['id'])) + self.assertEqual(ovn_localnetport.tag, [100]) + self.assertEqual(ovn_localnetport.options['network_name'], 'physnet1') + seg_2 = self.create_segment(n1['id'], 'physnet2', '222') + ovn_localnetport = self._find_port_row_by_name( + utils.ovn_provnet_port_name(seg_2['id'])) + self.assertEqual(ovn_localnetport.options['network_name'], 'physnet2') + self.assertEqual(ovn_localnetport.tag, [222]) + + # Delete segments and ensure that localnet + # ports are deleted. + self.delete_segment(seg_db[0]['id']) + ovn_localnetport = self._find_port_row_by_name( + utils.ovn_provnet_port_name(seg_db[0]['id'])) + self.assertIsNone(ovn_localnetport) + + # Make sure that other localnet port is not touched. + ovn_localnetport = self._find_port_row_by_name( + utils.ovn_provnet_port_name(seg_2['id'])) + self.assertIsNotNone(ovn_localnetport) + + # Delete second segment and validate that the + # second localnet port has been deleted. + self.delete_segment(seg_2['id']) + ovn_localnetport = self._find_port_row_by_name( + utils.ovn_provnet_port_name(seg_2['id'])) + self.assertIsNone(ovn_localnetport) + + +class TestMetadataPorts(base.TestOVNFunctionalBase): + + def setUp(self, *args, **kwargs): + super().setUp(*args, **kwargs) + self._ovn_client = self.mech_driver._ovn_client + self.meta_regex = re.compile( + r'169.254.169.254/32,(\d+\.\d+\.\d+\.\d+)') + + def _create_network_ovn(self, metadata_enabled=True): + self.mock_is_ovn_metadata_enabled = mock.patch.object( + ovn_conf, 'is_ovn_metadata_enabled').start() + self.mock_is_ovn_metadata_enabled.return_value = metadata_enabled + self.n1 = self._make_network(self.fmt, 'n1', True) + self.n1_id = self.n1['network']['id'] + + def _create_subnet_ovn(self, cidr, enable_dhcp=True): + _cidr = netaddr.IPNetwork(cidr) + res = self._create_subnet(self.fmt, self.n1_id, cidr, + enable_dhcp=enable_dhcp, + ip_version=_cidr.version) + return self.deserialize(self.fmt, res)['subnet'] + + def _list_ports_ovn(self, net_id=None): + res = self._list_ports(self.fmt, net_id=net_id) + return self.deserialize(self.fmt, res)['ports'] + + def _check_metadata_port(self, net_id, fixed_ip): + for port in self._list_ports_ovn(net_id=net_id): + if ovn_client.OVNClient.is_metadata_port(port): + self.assertEqual(net_id, port['network_id']) + if fixed_ip: + self.assertIn(fixed_ip, port['fixed_ips']) + else: + self.assertEqual([], port['fixed_ips']) + return port['id'] + + self.fail('Metadata port is not present in network %s or data is not ' + 'correct' % self.n1_id) + + def _check_subnet_dhcp_options(self, subnet_id, cidr): + # This method checks the DHCP options CIDR and returns, if exits, the + # metadata port IP address, included in the classless static routes. + dhcp_opts = self._ovn_client._nb_idl.get_subnet_dhcp_options(subnet_id) + self.assertEqual(cidr, dhcp_opts['subnet']['cidr']) + routes = dhcp_opts['subnet']['options'].get('classless_static_route') + if not routes: + return + + match = self.meta_regex.search(routes) + if match: + return match.group(1) + + def test_subnet_ipv4(self): + self._create_network_ovn(metadata_enabled=True) + subnet = self._create_subnet_ovn('10.0.0.0/24') + metatada_ip = self._check_subnet_dhcp_options(subnet['id'], + '10.0.0.0/24') + fixed_ip = {'subnet_id': subnet['id'], 'ip_address': metatada_ip} + port_id = self._check_metadata_port(self.n1_id, fixed_ip) + + # Update metatada port IP address to 10.0.0.5 + data = {'port': {'fixed_ips': [{'subnet_id': subnet['id'], + 'ip_address': '10.0.0.5'}]}} + req = self.new_update_request('ports', data, port_id) + req.get_response(self.api) + metatada_ip = self._check_subnet_dhcp_options(subnet['id'], + '10.0.0.0/24') + self.assertEqual('10.0.0.5', metatada_ip) + fixed_ip = {'subnet_id': subnet['id'], 'ip_address': metatada_ip} + self._check_metadata_port(self.n1_id, fixed_ip) + + def test_subnet_ipv4_no_metadata(self): + self._create_network_ovn(metadata_enabled=False) + subnet = self._create_subnet_ovn('10.0.0.0/24') + self.assertIsNone(self._check_subnet_dhcp_options(subnet['id'], + '10.0.0.0/24')) + self.assertEqual([], self._list_ports_ovn(self.n1_id)) + + def test_subnet_ipv6(self): + self._create_network_ovn(metadata_enabled=True) + subnet = self._create_subnet_ovn('2001:db8::/64') + self.assertIsNone(self._check_subnet_dhcp_options(subnet['id'], + '2001:db8::/64')) + self._check_metadata_port(self.n1_id, []) + + +class TestAgentApi(base.TestOVNFunctionalBase): + + def setUp(self): + super().setUp() + self.host = 'test-host' + self.controller_agent = self.add_fake_chassis(self.host) + self.plugin = self.mech_driver._plugin + agent = {'agent_type': 'test', 'binary': '/bin/test', + 'host': self.host, 'topic': 'test_topic'} + _, status = self.plugin.create_or_update_agent(self.context, agent) + self.test_agent = status['id'] + mock.patch.object(self.mech_driver, 'ping_all_chassis', + return_value=False).start() + + def test_agent_show_non_ovn(self): + self.assertTrue(self.plugin.get_agent(self.context, self.test_agent)) + + def test_agent_show_ovn_controller(self): + self.assertTrue(self.plugin.get_agent(self.context, + self.controller_agent)) + + +class TestCreateDefaultDropPortGroup(ovs_base.FunctionalTestCase, + base.BaseLoggingTestCase): + schemas = ['OVN_Southbound', 'OVN_Northbound'] + PG_NAME = ovn_const.OVN_DROP_PORT_GROUP_NAME + + def setUp(self): + super(TestCreateDefaultDropPortGroup, self).setUp() + self.api = impl_idl_ovn.OvsdbNbOvnIdl( + self.connection['OVN_Northbound']) + self.addCleanup(self.api.pg_del(self.PG_NAME, if_exists=True).execute, + check_error=True) + + def test_port_group_exists(self): + """Test new port group is not added or modified. + + If Port Group was not existent, acls would be added. + """ + self.api.pg_add( + self.PG_NAME, acls=[], may_exist=True).execute(check_error=True) + mech_driver.create_default_drop_port_group(self.api) + port_group = self.api.get_port_group(self.PG_NAME) + self.assertFalse(port_group.acls) + + def _test_pg_with_ports(self, expected_ports=None): + expected_ports = expected_ports or [] + mech_driver.create_default_drop_port_group(self.api) + port_group = self.api.get_port_group(self.PG_NAME) + self.assertItemsEqual( + expected_ports, [port.name for port in port_group.ports]) + + def test_with_ports_available(self): + expected_ports = ['port1', 'port2'] + testing_pg = 'testing' + testing_ls = 'testing' + with self.api.transaction(check_error=True) as txn: + txn.add(self.api.pg_add( + testing_pg, + external_ids={ovn_const.OVN_SG_EXT_ID_KEY: 'foo'})) + txn.add(self.api.ls_add(testing_ls)) + port_uuids = [txn.add(self.api.lsp_add(testing_ls, port)) + for port in expected_ports] + txn.add(self.api.pg_add_ports(testing_pg, port_uuids)) + + self.addCleanup(self.api.pg_del(testing_pg, if_exists=True).execute, + check_error=True) + + self._test_pg_with_ports(expected_ports) + + def test_without_ports(self): + self._test_pg_with_ports(expected_ports=[]) + + +class ConnectionInactivityProbeSetEvent(event.WaitEvent): + """Wait for a Connection (NB/SB) to have the inactivity probe set""" + + ONETIME = False + + def __init__(self, target, inactivity_probe): + table = 'Connection' + events = (self.ROW_UPDATE,) + super().__init__(events, table, None) + self.event_name = "ConnectionEvent" + self.target = target + self.inactivity_probe = inactivity_probe + + def match_fn(self, event, row, old): + return row.target in self.target + + def run(self, event, row, old): + if (row.inactivity_probe and + row.inactivity_probe[0] == self.inactivity_probe): + self.event.set() + + +class TestSetInactivityProbe(base.TestOVNFunctionalBase): + + def setUp(self): + super().setUp() + self.dbs = [(ovn_conf.get_ovn_nb_connection(), 'ptcp:1000:1.2.3.4'), + (ovn_conf.get_ovn_sb_connection(), 'ptcp:1001:1.2.3.4')] + linux_utils.execute( + ['ovn-nbctl', '--db=%s' % self.dbs[0][0], + 'set-connection', self.dbs[0][1]], run_as_root=True) + linux_utils.execute( + ['ovn-sbctl', '--db=%s' % self.dbs[1][0], + 'set-connection', self.dbs[1][1]], run_as_root=True) + + def test_1(self): + mock.patch.object(ovn_conf, 'get_ovn_ovsdb_probe_interval', + return_value='2500').start() + nb_connection = ConnectionInactivityProbeSetEvent(self.dbs[0][1], 2500) + sb_connection = ConnectionInactivityProbeSetEvent(self.dbs[1][1], 2500) + self.nb_api.idl.notify_handler.watch_event(nb_connection) + self.sb_api.idl.notify_handler.watch_event(sb_connection) + with mock.patch.object(utils, 'connection_config_to_target_string') \ + as mock_target: + mock_target.side_effect = [self.dbs[0][1], self.dbs[1][1]] + self.mech_driver._set_inactivity_probe() + + self.assertTrue(nb_connection.wait()) + self.assertTrue(sb_connection.wait()) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py neutron-16.4.2/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -279,8 +279,8 @@ namespace=self.namespace) # NOTE(ralonsoh): - # - rate: 320000 bytes/sec (pyroute2 units) = 2500 kbits/sec (OS units) - # - burst: 192000 bytes/sec = 1500 kbits/sec + # - rate: 320000 bytes/sec (pyroute2 units) = 2560 kbits/sec (OS units) + # - burst: 192000 bytes/sec = 1536 kbits/sec priv_tc_lib.add_tc_filter_policy( self.device, 'ffff:', 49, 320000, 192000, 1200, 'drop', namespace=self.namespace) @@ -288,6 +288,6 @@ filters = tc_lib.list_tc_filters( self.device, 'ffff:', namespace=self.namespace) self.assertEqual(1, len(filters)) - self.assertEqual(2500, filters[0]['rate_kbps']) - self.assertEqual(1500, filters[0]['burst_kb']) + self.assertEqual(2560, filters[0]['rate_kbps']) + self.assertEqual(1536, filters[0]['burst_kb']) self.assertEqual(1200, filters[0]['mtu']) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/privileged/agent/linux/test_utils.py neutron-16.4.2/neutron/tests/functional/privileged/agent/linux/test_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/privileged/agent/linux/test_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/privileged/agent/linux/test_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,39 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.agent.linux import ip_lib +from neutron.privileged.agent.linux import utils as priv_utils +from neutron.tests.common import net_helpers +from neutron.tests.functional import base as functional_base + + +class FindListenPidsNamespaceTestCase(functional_base.BaseSudoTestCase): + + def test_find_listen_pids_namespace(self): + ns = self.useFixture(net_helpers.NamespaceFixture()).name + ip_wrapper = ip_lib.IPWrapper(namespace=ns) + ip_wrapper.add_dummy('device') + device = ip_lib.IPDevice('device', namespace=ns) + device.addr.add('10.20.30.40/24') + device.link.set_up() + + self.assertEqual(tuple(), priv_utils.find_listen_pids_namespace(ns)) + + netcat = net_helpers.NetcatTester(ns, ns, '10.20.30.40', 12345, 'udp') + proc = netcat.server_process + self.assertEqual((str(proc.child_pid), ), + priv_utils.find_listen_pids_namespace(ns)) + + netcat.stop_processes() + self.assertEqual(tuple(), priv_utils.find_listen_pids_namespace(ns)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/requirements.txt neutron-16.4.2/neutron/tests/functional/requirements.txt --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/requirements.txt 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/requirements.txt 2021-11-12 13:56:42.000000000 +0000 @@ -5,5 +5,4 @@ # process, which may cause wedges in the gate later. psycopg2 -psutil>=1.1.1,<3.2.2 PyMySQL>=0.6.2 # MIT License diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/sanity/test_sanity.py neutron-16.4.2/neutron/tests/functional/sanity/test_sanity.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/sanity/test_sanity.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/sanity/test_sanity.py 2021-11-12 13:56:42.000000000 +0000 @@ -77,12 +77,6 @@ def test_icmpv6_header_match_runs(self): checks.icmpv6_header_match_supported() - def test_vf_management_runs(self): - checks.vf_management_supported() - - def test_vf_extended_management_runs(self): - checks.vf_extended_management_supported() - def test_namespace_root_read_detection_runs(self): checks.netns_read_requires_helper() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/services/l3_router/test_l3_dvr_router_plugin.py neutron-16.4.2/neutron/tests/functional/services/l3_router/test_l3_dvr_router_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/services/l3_router/test_l3_dvr_router_plugin.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/services/l3_router/test_l3_dvr_router_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -1024,6 +1024,145 @@ floatingips = router_sync_info[0][constants.FLOATINGIP_KEY] self.assertTrue(floatingips[0][constants.DVR_SNAT_BOUND]) + def test_allowed_addr_pairs_delayed_fip_and_update_arp_entry(self): + HOST1 = 'host1' + helpers.register_l3_agent( + host=HOST1, agent_mode=constants.L3_AGENT_MODE_DVR) + HOST2 = 'host2' + helpers.register_l3_agent( + host=HOST2, agent_mode=constants.L3_AGENT_MODE_DVR) + router = self._create_router(ha=False) + private_net1 = self._make_network(self.fmt, 'net1', True) + test_allocation_pools = [{'start': '10.1.0.2', + 'end': '10.1.0.20'}] + fixed_vrrp_ip = [{'ip_address': '10.1.0.201'}] + kwargs = {'arg_list': (extnet_apidef.EXTERNAL,), + extnet_apidef.EXTERNAL: True} + ext_net = self._make_network(self.fmt, '', True, **kwargs) + self._make_subnet( + self.fmt, ext_net, '10.20.0.1', '10.20.0.0/24', + ip_version=constants.IP_VERSION_4, enable_dhcp=True) + self.l3_plugin.schedule_router(self.context, + router['id'], + candidates=[self.l3_agent]) + + # Set gateway to router + self.l3_plugin._update_router_gw_info( + self.context, router['id'], + {'network_id': ext_net['network']['id']}) + private_subnet1 = self._make_subnet( + self.fmt, + private_net1, + '10.1.0.1', + cidr='10.1.0.0/24', + ip_version=constants.IP_VERSION_4, + allocation_pools=test_allocation_pools, + enable_dhcp=True) + vrrp_port = self._make_port( + self.fmt, + private_net1['network']['id'], + fixed_ips=fixed_vrrp_ip) + allowed_address_pairs = [ + {'ip_address': '10.1.0.201', + 'mac_address': vrrp_port['port']['mac_address']}] + with self.port( + subnet=private_subnet1, + device_owner=DEVICE_OWNER_COMPUTE) as int_port,\ + self.port(subnet=private_subnet1, + device_owner=DEVICE_OWNER_COMPUTE) as int_port2: + self.l3_plugin.add_router_interface( + self.context, router['id'], + {'subnet_id': private_subnet1['subnet']['id']}) + router_handle = ( + self.l3_plugin.list_active_sync_routers_on_active_l3_agent( + self.context, self.l3_agent['host'], [router['id']])) + self.assertEqual(self.l3_agent['host'], + router_handle[0]['gw_port_host']) + with mock.patch.object(self.l3_plugin, + '_l3_rpc_notifier') as l3_notifier: + vm_port = self.core_plugin.update_port( + self.context, int_port['port']['id'], + {'port': {portbindings.HOST_ID: HOST1}}) + vm_port_mac = vm_port['mac_address'] + vm_port_fixed_ips = vm_port['fixed_ips'] + vm_port_subnet_id = vm_port_fixed_ips[0]['subnet_id'] + vm_arp_table = { + 'ip_address': vm_port_fixed_ips[0]['ip_address'], + 'mac_address': vm_port_mac, + 'subnet_id': vm_port_subnet_id} + vm_port2 = self.core_plugin.update_port( + self.context, int_port2['port']['id'], + {'port': {portbindings.HOST_ID: HOST2}}) + # Now update the VM port with the allowed_address_pair + self.core_plugin.update_port( + self.context, vm_port['id'], + {'port': { + 'allowed_address_pairs': allowed_address_pairs}}) + self.core_plugin.update_port( + self.context, vm_port2['id'], + {'port': { + 'allowed_address_pairs': allowed_address_pairs}}) + self.assertEqual( + 2, l3_notifier.routers_updated_on_host.call_count) + updated_vm_port1 = self.core_plugin.get_port( + self.context, vm_port['id']) + updated_vm_port2 = self.core_plugin.get_port( + self.context, vm_port2['id']) + expected_allowed_address_pairs = updated_vm_port1.get( + 'allowed_address_pairs') + self.assertEqual(expected_allowed_address_pairs, + allowed_address_pairs) + expected_allowed_address_pairs_2 = updated_vm_port2.get( + 'allowed_address_pairs') + self.assertEqual(expected_allowed_address_pairs_2, + allowed_address_pairs) + # Now the VRRP port is attached to the VM port. At this + # point, the VRRP port should not have inherited the + # port host bindings from the parent VM port. + cur_vrrp_port_db = self.core_plugin.get_port( + self.context, vrrp_port['port']['id']) + self.assertNotEqual( + cur_vrrp_port_db[portbindings.HOST_ID], HOST1) + self.assertNotEqual( + cur_vrrp_port_db[portbindings.HOST_ID], HOST2) + # Next we can try to associate the floatingip to the + # VRRP port that is already attached to the VM port + floating_ip = {'floating_network_id': ext_net['network']['id'], + 'router_id': router['id'], + 'port_id': vrrp_port['port']['id'], + 'tenant_id': vrrp_port['port']['tenant_id']} + floating_ip = self.l3_plugin.create_floatingip( + self.context, {'floatingip': floating_ip}) + + post_update_vrrp_port_db = self.core_plugin.get_port( + self.context, vrrp_port['port']['id']) + vrrp_port_fixed_ips = post_update_vrrp_port_db['fixed_ips'] + vrrp_port_subnet_id = vrrp_port_fixed_ips[0]['subnet_id'] + vrrp_arp_table1 = { + 'ip_address': vrrp_port_fixed_ips[0]['ip_address'], + 'mac_address': vm_port_mac, + 'subnet_id': vrrp_port_subnet_id} + + expected_calls = [ + mock.call(self.context, + router['id'], vm_arp_table), + mock.call(self.context, + router['id'], vrrp_arp_table1)] + l3_notifier.add_arp_entry.assert_has_calls( + expected_calls) + expected_routers_updated_calls = [ + mock.call(self.context, mock.ANY, HOST1), + mock.call(self.context, mock.ANY, HOST2), + mock.call(self.context, mock.ANY, 'host0')] + l3_notifier.routers_updated_on_host.assert_has_calls( + expected_routers_updated_calls, any_order=True) + self.assertFalse(l3_notifier.routers_updated.called) + router_info = ( + self.l3_plugin.list_active_sync_routers_on_active_l3_agent( + self.context, self.l3_agent['host'], [router['id']])) + floatingips = router_info[0][constants.FLOATINGIP_KEY] + self.assertTrue(floatingips[0][constants.DVR_SNAT_BOUND]) + def test_dvr_gateway_host_binding_is_set(self): router = self._create_router(ha=False) private_net1 = self._make_network(self.fmt, 'net1', True) @@ -1058,6 +1197,110 @@ self.assertEqual(self.l3_agent['host'], router_handle[0]['gw_port_host']) + def test_allowed_address_pairs_update_arp_entry(self): + HOST1 = 'host1' + helpers.register_l3_agent( + host=HOST1, agent_mode=constants.L3_AGENT_MODE_DVR) + router = self._create_router(ha=False) + private_net1 = self._make_network(self.fmt, 'net1', True) + test_allocation_pools = [{'start': '10.1.0.2', + 'end': '10.1.0.20'}] + fixed_vrrp_ip = [{'ip_address': '10.1.0.201'}] + kwargs = {'arg_list': (extnet_apidef.EXTERNAL,), + extnet_apidef.EXTERNAL: True} + ext_net = self._make_network(self.fmt, '', True, **kwargs) + self._make_subnet( + self.fmt, ext_net, '10.20.0.1', '10.20.0.0/24', + ip_version=constants.IP_VERSION_4, enable_dhcp=True) + self.l3_plugin.schedule_router(self.context, + router['id'], + candidates=[self.l3_agent]) + # Set gateway to router + self.l3_plugin._update_router_gw_info( + self.context, router['id'], + {'network_id': ext_net['network']['id']}) + private_subnet1 = self._make_subnet( + self.fmt, + private_net1, + '10.1.0.1', + cidr='10.1.0.0/24', + ip_version=constants.IP_VERSION_4, + allocation_pools=test_allocation_pools, + enable_dhcp=True) + vrrp_port = self._make_port( + self.fmt, + private_net1['network']['id'], + fixed_ips=fixed_vrrp_ip) + allowed_address_pairs = [ + {'ip_address': '10.1.0.201', + 'mac_address': vrrp_port['port']['mac_address']}] + with self.port( + subnet=private_subnet1, + device_owner=DEVICE_OWNER_COMPUTE) as int_port: + self.l3_plugin.add_router_interface( + self.context, router['id'], + {'subnet_id': private_subnet1['subnet']['id']}) + router_handle = ( + self.l3_plugin.list_active_sync_routers_on_active_l3_agent( + self.context, self.l3_agent['host'], [router['id']])) + self.assertEqual(self.l3_agent['host'], + router_handle[0]['gw_port_host']) + with mock.patch.object(self.l3_plugin, + '_l3_rpc_notifier') as l3_notifier: + vm_port = self.core_plugin.update_port( + self.context, int_port['port']['id'], + {'port': {portbindings.HOST_ID: HOST1}}) + vm_port_mac = vm_port['mac_address'] + vm_port_fixed_ips = vm_port['fixed_ips'] + vm_port_subnet_id = vm_port_fixed_ips[0]['subnet_id'] + vm_arp_table = { + 'ip_address': vm_port_fixed_ips[0]['ip_address'], + 'mac_address': vm_port_mac, + 'subnet_id': vm_port_subnet_id} + self.assertEqual(1, l3_notifier.add_arp_entry.call_count) + floating_ip = {'floating_network_id': ext_net['network']['id'], + 'router_id': router['id'], + 'port_id': vrrp_port['port']['id'], + 'tenant_id': vrrp_port['port']['tenant_id']} + floating_ip = self.l3_plugin.create_floatingip( + self.context, {'floatingip': floating_ip}) + vrrp_port_db = self.core_plugin.get_port( + self.context, vrrp_port['port']['id']) + self.assertNotEqual(vrrp_port_db[portbindings.HOST_ID], HOST1) + # Now update the VM port with the allowed_address_pair + self.core_plugin.update_port( + self.context, vm_port['id'], + {'port': { + 'allowed_address_pairs': allowed_address_pairs}}) + updated_vm_port = self.core_plugin.get_port( + self.context, vm_port['id']) + expected_allowed_address_pairs = updated_vm_port.get( + 'allowed_address_pairs') + self.assertEqual(expected_allowed_address_pairs, + allowed_address_pairs) + cur_vrrp_port_db = self.core_plugin.get_port( + self.context, vrrp_port['port']['id']) + vrrp_port_fixed_ips = cur_vrrp_port_db['fixed_ips'] + vrrp_port_subnet_id = vrrp_port_fixed_ips[0]['subnet_id'] + vrrp_arp_table1 = { + 'ip_address': vrrp_port_fixed_ips[0]['ip_address'], + 'mac_address': vm_port_mac, + 'subnet_id': vrrp_port_subnet_id} + + expected_calls = [ + mock.call(self.context, + router['id'], vm_arp_table), + mock.call(self.context, + router['id'], vrrp_arp_table1)] + l3_notifier.add_arp_entry.assert_has_calls( + expected_calls) + expected_routers_updated_calls = [ + mock.call(self.context, mock.ANY, HOST1), + mock.call(self.context, mock.ANY, 'host0')] + l3_notifier.routers_updated_on_host.assert_has_calls( + expected_routers_updated_calls) + self.assertFalse(l3_notifier.routers_updated.called) + def test_update_vm_port_host_router_update(self): # register l3 agents in dvr mode in addition to existing dvr_snat agent HOST1 = 'host1' @@ -1618,6 +1861,56 @@ self.context, self.l3_agent, [router1['id'], router3['id']]) self.assertEqual({router1['id'], router3['id']}, set(ids)) + def test__get_router_ids_for_agent_related_router(self): + router1 = self._create_router() + router2 = self._create_router() + router3 = self._create_router() + arg_list = (portbindings.HOST_ID,) + dvr_l3_agent = helpers.register_l3_agent( + host="host1", agent_mode=constants.L3_AGENT_MODE_DVR) + host = dvr_l3_agent['host'] + with self.subnet() as wan_subnet,\ + self.subnet(cidr='20.0.0.0/24') as subnet1,\ + self.subnet(cidr='30.0.0.0/24') as subnet2,\ + self.subnet(cidr='40.0.0.0/24') as subnet3,\ + self.port(subnet=wan_subnet) as wan_port1,\ + self.port(subnet=wan_subnet) as wan_port2,\ + self.port(subnet=subnet1, + device_owner=constants.DEVICE_OWNER_DHCP, + arg_list=arg_list, + **{portbindings.HOST_ID: host}): + + self.l3_plugin.add_router_interface( + self.context, router1['id'], + {'subnet_id': subnet1['subnet']['id']}) + self.l3_plugin.add_router_interface( + self.context, router2['id'], + {'subnet_id': subnet2['subnet']['id']}) + # Router3 is here just to be sure that it will not be returned as + # is not related to the router1 and router2 in any way + self.l3_plugin.add_router_interface( + self.context, router3['id'], + {'subnet_id': subnet3['subnet']['id']}) + + self.l3_plugin.add_router_interface( + self.context, router1['id'], + {'port_id': wan_port1['port']['id']}) + self.l3_plugin.add_router_interface( + self.context, router2['id'], + {'port_id': wan_port2['port']['id']}) + + ids = self.l3_plugin._get_router_ids_for_agent( + self.context, dvr_l3_agent, []) + self.assertEqual({router1['id'], router2['id']}, set(ids)) + + ids = self.l3_plugin._get_router_ids_for_agent( + self.context, dvr_l3_agent, [router2['id']]) + self.assertEqual({router1['id'], router2['id']}, set(ids)) + + ids = self.l3_plugin._get_router_ids_for_agent( + self.context, dvr_l3_agent, [router1['id']]) + self.assertEqual({router1['id'], router2['id']}, set(ids)) + def test_remove_router_interface(self): HOST1 = 'host1' helpers.register_l3_agent( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/test_server.py neutron-16.4.2/neutron/tests/functional/test_server.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/functional/test_server.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/functional/test_server.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import multiprocessing import os import signal import socket @@ -23,6 +24,7 @@ import mock from neutron_lib import worker as neutron_worker from oslo_config import cfg +from oslo_log import log import psutil from neutron.common import utils @@ -32,12 +34,14 @@ from neutron import wsgi +LOG = log.getLogger(__name__) + CONF = cfg.CONF # Those messages will be written to temporary file each time # start/reset methods are called. -FAKE_START_MSG = b"start" -FAKE_RESET_MSG = b"reset" +FAKE_START_MSG = 'start' +FAKE_RESET_MSG = 'reset' TARGET_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin' @@ -47,7 +51,7 @@ super(TestNeutronServer, self).setUp() self.service_pid = None self.workers = None - self.temp_file = self.get_temp_file_path("test_server.tmp") + self._mp_queue = multiprocessing.Queue() self.health_checker = self._check_active self.pipein, self.pipeout = os.pipe() self.addCleanup(self._destroy_workers) @@ -128,12 +132,10 @@ return True def _fake_start(self): - with open(self.temp_file, 'ab') as f: - f.write(FAKE_START_MSG) + self._mp_queue.put(FAKE_START_MSG) def _fake_reset(self): - with open(self.temp_file, 'ab') as f: - f.write(FAKE_RESET_MSG) + self._mp_queue.put(FAKE_RESET_MSG) def _test_restart_service_on_sighup(self, service, workers=1): """Test that a service correctly (re)starts on receiving SIGHUP. @@ -155,31 +157,27 @@ # Wait for temp file to be created and its size reaching the expected # value expected_size = len(expected_msg) - condition = lambda: (os.path.isfile(self.temp_file) and - os.stat(self.temp_file).st_size == - expected_size) + ret_msg = '' + + def is_ret_buffer_ok(): + nonlocal ret_msg + LOG.debug('Checking returned buffer size') + while not self._mp_queue.empty(): + ret_msg += self._mp_queue.get() + LOG.debug('Size of buffer is %s. Expected size: %s', + len(ret_msg), expected_size) + return len(ret_msg) == expected_size try: - utils.wait_until_true(condition, timeout=5, sleep=1) - except utils.TimerTimeout: - if not os.path.isfile(self.temp_file): - raise RuntimeError( - "Timed out waiting for file %(filename)s to be created" % - {'filename': self.temp_file}) - else: - raise RuntimeError( - "Expected size for file %(filename)s: %(size)s, current " - "size: %(current_size)s" % - {'filename': self.temp_file, - 'size': expected_size, - 'current_size': os.stat(self.temp_file).st_size}) + utils.wait_until_true(is_ret_buffer_ok, timeout=5, sleep=1) + except utils.WaitTimeout: + raise RuntimeError('Expected buffer size: %s, current size: %s' % + (len(ret_msg), expected_size)) # Verify that start has been called twice for each worker (one for # initial start, and the second one on SIGHUP after children were # terminated). - with open(self.temp_file, 'rb') as f: - res = f.readline() - self.assertEqual(expected_msg, res) + self.assertEqual(expected_msg, ret_msg) class TestWsgiServer(TestNeutronServer): @@ -228,7 +226,7 @@ # Memorize a port that was chosen for the service self.port = server.port - os.write(self.pipeout, bytes(self.port)) + os.write(self.pipeout, bytes(str(self.port), 'utf-8')) server.wait() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/common/test_ovs_lib.py neutron-16.4.2/neutron/tests/unit/agent/common/test_ovs_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/common/test_ovs_lib.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/common/test_ovs_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -207,6 +207,29 @@ return self.execute.assert_called_once_with(cmd, run_as_root=True, **kwargs) + def test_add_protocols_all_already_set(self): + self.br = ovs_lib.OVSBridge(self.BR_NAME) + with mock.patch.object(self.br, 'db_get_val') as db_get_val, \ + mock.patch.object(self.br.ovsdb, 'db_add') as db_add: + db_get_val.return_value = [p_const.OPENFLOW10, + p_const.OPENFLOW13] + self.br.add_protocols(p_const.OPENFLOW10, p_const.OPENFLOW13) + db_get_val.assert_called_once_with( + 'Bridge', self.BR_NAME, 'protocols') + db_add.assert_not_called() + + def test_add_protocols_some_already_set(self): + self.br = ovs_lib.OVSBridge(self.BR_NAME) + with mock.patch.object(self.br, 'db_get_val') as db_get_val, \ + mock.patch.object(self.br.ovsdb, 'db_add') as db_add: + db_get_val.return_value = [p_const.OPENFLOW10] + self.br.add_protocols(p_const.OPENFLOW10, p_const.OPENFLOW13) + db_get_val.assert_called_once_with( + 'Bridge', self.BR_NAME, 'protocols') + db_add.assert_has_calls([ + mock.call('Bridge', self.BR_NAME, + 'protocols', p_const.OPENFLOW13)]) + def test_add_flow_timeout_set(self): flow_dict = collections.OrderedDict([ ('cookie', 1234), @@ -471,7 +494,8 @@ ) as port_exists_mock: self.br.delete_egress_bw_limit_for_port("test_port") port_exists_mock.assert_called_once_with("test_port") - set_egress_mock.assert_called_once_with("test_port", 0, 0) + set_egress_mock.assert_called_once_with("test_port", 0, 0, + check_error=False) def test_delete_egress_bw_limit_for_port_port_not_exists(self): with mock.patch.object( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/common/test_utils.py neutron-16.4.2/neutron/tests/unit/agent/common/test_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/common/test_utils.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/common/test_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -79,24 +79,122 @@ utils.load_interface_driver(self.conf) +class TestGetHypervisorHostname(base.BaseTestCase): + + @mock.patch('socket.getaddrinfo') + @mock.patch('socket.gethostname') + def test_get_hypervisor_hostname_gethostname_fqdn(self, hostname_mock, + addrinfo_mock): + hostname_mock.return_value = 'host.domain' + self.assertEqual( + 'host.domain', + utils.get_hypervisor_hostname()) + addrinfo_mock.assert_not_called() + + @mock.patch('socket.getaddrinfo') + @mock.patch('socket.gethostname') + def test_get_hypervisor_hostname_gethostname_localhost(self, hostname_mock, + addrinfo_mock): + hostname_mock.return_value = 'localhost' + self.assertEqual( + 'localhost', + utils.get_hypervisor_hostname()) + addrinfo_mock.assert_not_called() + + @mock.patch('socket.getaddrinfo') + @mock.patch('socket.gethostname') + def test_get_hypervisor_hostname_getaddrinfo(self, hostname_mock, + addrinfo_mock): + hostname_mock.return_value = 'host' + addrinfo_mock.return_value = [(None, None, None, 'host.domain', None)] + self.assertEqual( + 'host.domain', + utils.get_hypervisor_hostname()) + addrinfo_mock.assert_called_once_with( + host='host', port=None, family=socket.AF_UNSPEC, + flags=socket.AI_CANONNAME) + + @mock.patch('socket.getaddrinfo') + @mock.patch('socket.gethostname') + def test_get_hypervisor_hostname_getaddrinfo_no_canonname(self, + hostname_mock, + addrinfo_mock): + hostname_mock.return_value = 'host' + addrinfo_mock.return_value = [(None, None, None, '', None)] + self.assertEqual( + 'host', + utils.get_hypervisor_hostname()) + addrinfo_mock.assert_called_once_with( + host='host', port=None, family=socket.AF_UNSPEC, + flags=socket.AI_CANONNAME) + + @mock.patch('socket.getaddrinfo') + @mock.patch('socket.gethostname') + def test_get_hypervisor_hostname_getaddrinfo_localhost(self, hostname_mock, + addrinfo_mock): + hostname_mock.return_value = 'host' + addrinfo_mock.return_value = [(None, None, None, + 'localhost', None)] + self.assertEqual( + 'host', + utils.get_hypervisor_hostname()) + addrinfo_mock.assert_called_once_with( + host='host', port=None, family=socket.AF_UNSPEC, + flags=socket.AI_CANONNAME) + + @mock.patch('socket.getaddrinfo') + @mock.patch('socket.gethostname') + def test_get_hypervisor_hostname_getaddrinfo_fail(self, hostname_mock, + addrinfo_mock): + hostname_mock.return_value = 'host' + addrinfo_mock.side_effect = OSError + self.assertEqual( + 'host', + utils.get_hypervisor_hostname()) + addrinfo_mock.assert_called_once_with( + host='host', port=None, family=socket.AF_UNSPEC, + flags=socket.AI_CANONNAME) + + # TODO(bence romsics): rehome this to neutron_lib class TestDefaultRpHypervisors(base.BaseTestCase): - def test_defaults(self): - this_host = socket.gethostname() + @mock.patch.object(utils, 'get_hypervisor_hostname', + return_value='thishost') + def test_defaults(self, hostname_mock): + + self.assertEqual( + {'eth0': 'thishost', 'eth1': 'thishost'}, + utils.default_rp_hypervisors( + hypervisors={}, + device_mappings={'physnet0': ['eth0', 'eth1']}, + default_hypervisor=None, + ) + ) + + self.assertEqual( + {'eth0': 'thathost', 'eth1': 'thishost'}, + utils.default_rp_hypervisors( + hypervisors={'eth0': 'thathost'}, + device_mappings={'physnet0': ['eth0', 'eth1']}, + default_hypervisor=None, + ) + ) self.assertEqual( - {'eth0': this_host, 'eth1': this_host}, + {'eth0': 'defaulthost', 'eth1': 'defaulthost'}, utils.default_rp_hypervisors( hypervisors={}, device_mappings={'physnet0': ['eth0', 'eth1']}, + default_hypervisor='defaulthost', ) ) self.assertEqual( - {'eth0': 'thathost', 'eth1': this_host}, + {'eth0': 'thathost', 'eth1': 'defaulthost'}, utils.default_rp_hypervisors( hypervisors={'eth0': 'thathost'}, device_mappings={'physnet0': ['eth0', 'eth1']}, + default_hypervisor='defaulthost', ) ) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/dhcp/test_agent.py neutron-16.4.2/neutron/tests/unit/agent/dhcp/test_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/dhcp/test_agent.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/dhcp/test_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,6 +15,8 @@ import collections import copy +import datetime +import signal import sys import uuid @@ -33,6 +35,7 @@ from neutron.agent import dhcp_agent as entry from neutron.agent.linux import dhcp from neutron.agent.linux import interface +from neutron.agent.linux import utils as linux_utils from neutron.agent.metadata import driver as metadata_driver from neutron.common import config as common_config from neutron.common import utils @@ -146,7 +149,7 @@ fixed_ips=[fake_meta_fixed_ip]) fake_meta_dvr_port = dhcp.DictModel(fake_meta_port) -fake_meta_dvr_port.device_owner = const.DEVICE_OWNER_DVR_INTERFACE +fake_meta_dvr_port['device_owner'] = const.DEVICE_OWNER_DVR_INTERFACE fake_dist_port = dhcp.DictModel(id='12345678-1234-aaaa-1234567890ab', mac_address='aa:bb:cc:dd:ee:ff', @@ -199,7 +202,7 @@ ports=[fake_meta_port]) fake_meta_dvr_network = dhcp.NetModel(fake_meta_network) -fake_meta_dvr_network.ports = [fake_meta_dvr_port] +fake_meta_dvr_network['ports'] = [fake_meta_dvr_port] fake_dist_network = dhcp.NetModel(id=FAKE_NETWORK_UUID, tenant_id=FAKE_TENANT_ID, @@ -758,6 +761,12 @@ self.mock_resize_p = mock.patch('neutron.agent.dhcp.agent.' 'DhcpAgent._resize_process_pool') self.mock_resize = self.mock_resize_p.start() + self.mock_wait_until_address_ready_p = mock.patch( + 'neutron.agent.linux.ip_lib.' + 'IpAddrCommand.wait_until_address_ready') + self.mock_wait_until_address_ready_p.start() + mock.patch.object(linux_utils, 'delete_if_exists').start() + self.addCleanup(self.mock_wait_until_address_ready_p.stop) def _process_manager_constructor_call(self, ns=FAKE_NETWORK_DHCP_NS): return mock.call(conf=cfg.CONF, @@ -784,7 +793,7 @@ else: self.external_process.assert_has_calls([ self._process_manager_constructor_call(), - mock.call().disable()]) + mock.call().disable(sig=str(int(signal.SIGTERM)))]) def test_enable_dhcp_helper_enable_metadata_isolated_network(self): self._enable_dhcp_helper(isolated_network, @@ -903,7 +912,7 @@ self.call_driver.assert_called_once_with('disable', fake_network) self.external_process.assert_has_calls([ self._process_manager_constructor_call(), - mock.call().disable()]) + mock.call().disable(sig=str(int(signal.SIGTERM)))]) def test_disable_dhcp_helper_known_network_isolated_metadata(self): self._disable_dhcp_helper_known_network(isolated_metadata=True) @@ -932,7 +941,7 @@ [mock.call.get_network_by_id(fake_network.id)]) self.external_process.assert_has_calls([ self._process_manager_constructor_call(), - mock.call().disable() + mock.call().disable(sig=str(int(signal.SIGTERM))) ]) def test_disable_dhcp_helper_driver_failure_isolated_metadata(self): @@ -1646,6 +1655,12 @@ nc = dhcp_agent.NetworkCache() nc.add_to_deleted_ports(fake_port1.id) utils.wait_until_true(lambda: nc._deleted_ports == set(), timeout=7) + self.assertEqual([], self.nc._deleted_ports_ts) + + # check the second iteration is ok too + nc.add_to_deleted_ports(fake_port2.id) + utils.wait_until_true(lambda: nc._deleted_ports == set(), timeout=7) + self.assertEqual([], self.nc._deleted_ports_ts) class FakePort1(object): @@ -2305,3 +2320,63 @@ self.assertEqual(2, device.route.get_gateway.call_count) self.assertFalse(device.route.delete_gateway.called) device.route.add_gateway.assert_has_calls(expected) + + +class TestDHCPResourceUpdate(base.BaseTestCase): + + date1 = datetime.datetime(year=2021, month=2, day=1, hour=9, minute=1, + second=2) + date2 = datetime.datetime(year=2021, month=2, day=1, hour=9, minute=1, + second=1) # older than date1 + + def test__lt__no_port_event(self): + # Lower numerical priority always gets precedence. DHCPResourceUpdate + # (and ResourceUpdate) objects with more precedence will return as + # "lower" in a "__lt__" method comparison. + update1 = dhcp_agent.DHCPResourceUpdate('id1', 5, obj_type='network') + update2 = dhcp_agent.DHCPResourceUpdate('id2', 6, obj_type='network') + self.assertLess(update1, update2) + + def test__lt__no_port_event_timestamp(self): + update1 = dhcp_agent.DHCPResourceUpdate( + 'id1', 5, timestamp=self.date1, obj_type='network') + update2 = dhcp_agent.DHCPResourceUpdate( + 'id2', 6, timestamp=self.date2, obj_type='network') + self.assertLess(update1, update2) + + def test__lt__port_no_fixed_ips(self): + update1 = dhcp_agent.DHCPResourceUpdate( + 'id1', 5, timestamp=self.date1, resource={}, obj_type='port') + update2 = dhcp_agent.DHCPResourceUpdate( + 'id2', 6, timestamp=self.date2, resource={}, obj_type='port') + self.assertLess(update1, update2) + + def test__lt__port_fixed_ips_not_matching(self): + resource1 = {'fixed_ips': [ + {'subnet_id': 'subnet1', 'ip_address': '10.0.0.1'}]} + resource2 = {'fixed_ips': [ + {'subnet_id': 'subnet1', 'ip_address': '10.0.0.2'}, + {'subnet_id': 'subnet2', 'ip_address': '10.0.1.1'}]} + update1 = dhcp_agent.DHCPResourceUpdate( + 'id1', 5, timestamp=self.date1, resource=resource1, + obj_type='port') + update2 = dhcp_agent.DHCPResourceUpdate( + 'id2', 6, timestamp=self.date2, resource=resource2, + obj_type='port') + self.assertLess(update1, update2) + + def test__lt__port_fixed_ips_matching(self): + resource1 = {'fixed_ips': [ + {'subnet_id': 'subnet1', 'ip_address': '10.0.0.1'}]} + resource2 = {'fixed_ips': [ + {'subnet_id': 'subnet1', 'ip_address': '10.0.0.1'}, + {'subnet_id': 'subnet2', 'ip_address': '10.0.0.2'}]} + update1 = dhcp_agent.DHCPResourceUpdate( + 'id1', 5, timestamp=self.date1, resource=resource1, + obj_type='port') + update2 = dhcp_agent.DHCPResourceUpdate( + 'id2', 6, timestamp=self.date2, resource=resource2, + obj_type='port') + # In this case, both "port" events have matching IPs. "__lt__" method + # uses the timestamp: date2 < date1 + self.assertLess(update2, update1) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py neutron-16.4.2/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. import mock +import netaddr from neutron_lib import constants as lib_const from neutron_lib import context from oslo_utils import uuidutils @@ -274,12 +275,44 @@ self.portforwarding1.external_port, protocol=self.portforwarding1.protocol) mock_device.delete_addr_and_conntrack_state.assert_called_once_with( - str(self.portforwarding1.floating_ip_address)) + str(netaddr.IPNetwork(self.portforwarding1.floating_ip_address))) fip_status = { self.portforwarding1.floatingip_id: lib_const.FLOATINGIP_STATUS_DOWN} mock_send_fip_status.assert_called_once_with(mock.ANY, fip_status) + @mock.patch.object(pf.PortForwardingAgentExtension, + '_sending_port_forwarding_fip_status') + @mock.patch.object(iptables_manager.IptablesTable, 'add_rule') + @mock.patch.object(iptables_manager.IptablesTable, 'add_chain') + @mock.patch.object(l3router.RouterInfo, 'add_floating_ip') + def test_add_delete_router(self, mock_add_fip, + mock_add_chain, mock_add_rule, + mock_send_fip_status): + # simulate the router add and already there is a port forwarding + # resource association. + mock_add_fip.return_value = lib_const.FLOATINGIP_STATUS_ACTIVE + self.fip_pf_ext.add_router(self.context, self.router) + self._assert_called_iptables_process( + mock_add_chain, mock_add_rule, mock_add_fip, mock_send_fip_status, + target_obj=self.portforwarding1) + + router_fip_ids = self.fip_pf_ext.mapping.router_fip_mapping.get( + self.router['id']) + self.assertIsNotNone(router_fip_ids) + for fip_id in router_fip_ids: + pf_ids = self.fip_pf_ext.mapping.fip_port_forwarding.get(fip_id) + self.assertIsNotNone(pf_ids) + for pf_id in pf_ids: + pf = self.fip_pf_ext.mapping.managed_port_forwardings.get( + pf_id) + self.assertIsNotNone(pf) + + self.fip_pf_ext.delete_router(self.context, self.router) + + self.assertIsNone( + self.fip_pf_ext.mapping.router_fip_mapping.get(self.router['id'])) + def test_check_if_need_process_no_snat_ns(self): ex_gw_port = {'id': _uuid()} router_id = _uuid() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/test_agent.py neutron-16.4.2/neutron/tests/unit/agent/l3/test_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/test_agent.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/l3/test_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -55,12 +55,14 @@ from neutron.agent.linux import iptables_manager from neutron.agent.linux import pd from neutron.agent.linux import ra +from neutron.agent.linux import utils as linux_utils from neutron.agent.metadata import driver as metadata_driver from neutron.agent import rpc as agent_rpc from neutron.conf.agent import common as agent_config from neutron.conf.agent.l3 import config as l3_config from neutron.conf.agent.l3 import ha as ha_conf from neutron.conf import common as base_config +from neutron.privileged.agent.linux import utils as priv_utils from neutron.tests import base from neutron.tests.common import l3_test_common from neutron.tests.unit.agent.linux.test_utils import FakeUser @@ -101,6 +103,8 @@ self.list_network_namespaces_p = mock.patch( 'neutron.agent.linux.ip_lib.list_network_namespaces') self.list_network_namespaces = self.list_network_namespaces_p.start() + self.path_exists_p = mock.patch.object(priv_utils, 'path_exists') + self.path_exists = self.path_exists_p.start() self.ensure_dir = mock.patch( 'oslo_utils.fileutils.ensure_tree').start() @@ -215,6 +219,9 @@ def setUp(self): super(TestBasicRouterOperations, self).setUp() self.useFixture(IptablesFixture()) + self._mock_load_fip = mock.patch.object( + dvr_local_router.DvrLocalRouter, '_load_used_fip_information') + self.mock_load_fip = self._mock_load_fip.start() def test_request_id_changes(self): a = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -1265,6 +1272,7 @@ router[lib_constants.INTERFACE_KEY][0]) ri.router['distributed'] = True ri.router['_snat_router_interfaces'] = [{ + 'mac_address': 'fa:16:3e:80:8d:80', 'fixed_ips': [{'subnet_id': subnet_id, 'ip_address': '1.2.3.4'}]}] ri.router['gw_port_host'] = None @@ -2259,11 +2267,11 @@ agent._create_router = mock.Mock(return_value=ri) agent._fetch_external_net_id = mock.Mock( return_value=router['external_gateway_info']['network_id']) - agent._process_router_update() + agent._process_update() log_exception.assert_has_calls(calls) ri.initialize.side_effect = None - agent._process_router_update() + agent._process_update() self.assertTrue(ri.delete.called) self.assertEqual(2, ri.initialize.call_count) self.assertEqual(2, agent._create_router.call_count) @@ -2570,6 +2578,17 @@ self.assertFalse(agent._queue.add.called) def test_network_update(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + agent.router_info = { + _uuid(): mock.Mock(), + _uuid(): mock.Mock()} + network_id = _uuid() + agent._queue = mock.Mock() + network = {'id': network_id} + agent.network_update(None, network=network) + self.assertEqual(2, agent._queue.add.call_count) + + def test__process_network_update(self): router = l3_test_common.prepare_router_data(num_internal_ports=2) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent._process_added_router(router) @@ -2578,10 +2597,27 @@ internal_ports = ri.router.get(lib_constants.INTERFACE_KEY, []) network_id = internal_ports[0]['network_id'] agent._queue = mock.Mock() - network = {'id': network_id} - agent.network_update(None, network=network) + agent._process_network_update(ri.router_id, network_id) self.assertEqual(1, agent._queue.add.call_count) + def test__process_network_update_no_router_info_found(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + network_id = _uuid() + agent._queue = mock.Mock() + agent._process_network_update(_uuid(), network_id) + agent._queue.add.assert_not_called() + + def test__process_network_update_not_connected_to_router(self): + router = l3_test_common.prepare_router_data(num_internal_ports=2) + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + agent._process_added_router(router) + ri = l3router.RouterInfo(agent, router['id'], + router, **self.ri_kwargs) + network_id = _uuid() + agent._queue = mock.Mock() + agent._process_network_update(ri.router_id, network_id) + agent._queue.add.assert_not_called() + def test_create_router_namespace(self): self.mock_ip.ensure_namespace.return_value = self.mock_ip agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -2710,7 +2746,7 @@ update.resource = None agent._queue.each_update_to_next_resource.side_effect = [ [(None, update)]] - agent._process_router_update() + agent._process_update() self.assertFalse(agent.fullsync) self.assertEqual(ext_net_call, agent._process_router_if_compatible.called) @@ -2737,48 +2773,72 @@ resource=router, timestamp=timeutils.utcnow()) agent._queue.add(update) - agent._process_router_update() + agent._process_update() # The update contained the router object, get_routers won't be called self.assertFalse(agent.plugin_rpc.get_routers.called) # The update failed, assert that get_routers was called - agent._process_router_update() + agent._process_update() self.assertTrue(agent.plugin_rpc.get_routers.called) def test_process_routers_update_rpc_timeout_on_get_ext_net(self): self._test_process_routers_update_rpc_timeout(ext_net_call=True, ext_net_call_failed=True) - @mock.patch.object(pd, 'remove_router') - def _test_process_routers_update_router_deleted(self, remove_router, + def test_process_routers_update_router_update(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + agent._queue = mock.Mock() + update = mock.Mock() + update.resource = None + update.action = l3_agent.ADD_UPDATE_ROUTER + router_info = mock.MagicMock() + agent.router_info[update.id] = router_info + router_processor = mock.Mock() + agent._queue.each_update_to_next_resource.side_effect = [ + [(router_processor, update)]] + agent._resync_router = mock.Mock() + agent._safe_router_removed = mock.Mock() + agent.plugin_rpc = mock.MagicMock() + agent.plugin_rpc.get_routers.side_effect = ( + Exception("Failed to get router info")) + # start test + agent._process_update() + router_info.delete.assert_not_called() + self.assertFalse(router_info.delete.called) + self.assertTrue(agent.router_info) + self.assertTrue(agent._resync_router.called) + self.assertFalse(agent._safe_router_removed.called) + + def _test_process_routers_update_router_deleted(self, error=False): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent._queue = mock.Mock() update = mock.Mock() update.resource = None - update.action = 1 # ROUTER_DELETED + update.action = l3_agent.DELETE_ROUTER router_info = mock.MagicMock() agent.router_info[update.id] = router_info router_processor = mock.Mock() agent._queue.each_update_to_next_resource.side_effect = [ [(router_processor, update)]] agent._resync_router = mock.Mock() + agent._safe_router_removed = mock.Mock() if error: - agent._safe_router_removed = mock.Mock() agent._safe_router_removed.return_value = False - agent._process_router_update() + agent._process_update() if error: self.assertFalse(router_processor.fetched_and_processed.called) agent._resync_router.assert_called_with(update) - self.assertFalse(remove_router.called) + self.assertTrue(agent._safe_router_removed.called) else: - router_info.delete.assert_called_once_with() - self.assertFalse(agent.router_info) + router_info.delete.assert_not_called() + self.assertFalse(router_info.delete.called) + self.assertTrue(agent.router_info) self.assertFalse(agent._resync_router.called) router_processor.fetched_and_processed.assert_called_once_with( update.timestamp) - self.assertTrue(remove_router.called) + self.assertTrue(agent._safe_router_removed.called) def test_process_routers_update_router_deleted_success(self): self._test_process_routers_update_router_deleted() @@ -2826,6 +2886,16 @@ router.distributed = True router.ha = True router_info = mock.MagicMock() + + def mock_get(name): + if name == 'ha': + return router.ha + if name == 'distributed': + return router.distributed + return mock.Mock() + + router_info.router.get.side_effect = mock_get + agent.router_info[router.id] = router_info updated_router = {'id': '1234', 'distributed': True, @@ -2858,6 +2928,16 @@ router._ha_interface = True router.ha = True router_info = mock.MagicMock() + + def mock_get(name): + if name == 'ha': + return router.ha + if name == 'distributed': + return router.distributed + return mock.Mock() + + router_info.router.get.side_effect = mock_get + agent.router_info[router.id] = router_info updated_router = {'id': '1234', 'distributed': True, 'ha': True, @@ -2922,6 +3002,46 @@ agent._process_router_if_compatible(router) self.assertIn(router['id'], agent.router_info) + def test_process_router_if_compatible_type_match(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + + router = {'id': _uuid(), + 'routes': [], + 'admin_state_up': True, + 'ha': False, 'distributed': False, + 'external_gateway_info': {'network_id': 'aaa'}} + + ri = mock.Mock(router=router) + agent.router_info[router['id']] = ri + with mock.patch.object(agent, "_create_router") as create_router_mock: + agent._process_router_if_compatible(router) + create_router_mock.assert_not_called() + self.assertIn(router['id'], agent.router_info) + self.assertFalse(agent.router_info[router['id']].router['ha']) + self.assertFalse(agent.router_info[router['id']].router['distributed']) + + def test_process_router_if_compatible_type_changed(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + + router = {'id': _uuid(), + 'routes': [], + 'admin_state_up': True, + 'revision_number': 1, + 'ha': True, 'distributed': False, + 'external_gateway_info': {'network_id': 'aaa'}} + + ri = mock.Mock(router=router) + agent.router_info[router['id']] = ri + new_router = copy.deepcopy(router) + new_router['ha'] = False + with mock.patch.object(agent, "_create_router") as create_router_mock: + agent._process_router_if_compatible(new_router) + create_router_mock.assert_called_once_with( + new_router['id'], new_router) + self.assertIn(router['id'], agent.router_info) + self.assertFalse(agent.router_info[router['id']].router['ha']) + self.assertFalse(agent.router_info[router['id']].router['distributed']) + def test_nonexistent_interface_driver(self): self.conf.set_override('interface_driver', None) self.assertRaises(SystemExit, l3_agent.L3NATAgent, @@ -2973,9 +3093,10 @@ dvr_snat_ns.SNAT_NS_PREFIX + 'foo'] other_namespaces = ['unknown'] - self._cleanup_namespace_test(stale_namespaces, - [], - other_namespaces) + with mock.patch.object(linux_utils, 'delete_if_exists'): + self._cleanup_namespace_test(stale_namespaces, + [], + other_namespaces) def test_cleanup_namespace_with_registered_router_ids(self): stale_namespaces = [namespaces.NS_PREFIX + 'cccc', @@ -2985,9 +3106,10 @@ {'id': 'aaaa', 'distributed': False}] other_namespaces = ['qdhcp-aabbcc', 'unknown'] - self._cleanup_namespace_test(stale_namespaces, - router_list, - other_namespaces) + with mock.patch.object(linux_utils, 'delete_if_exists'): + self._cleanup_namespace_test(stale_namespaces, + router_list, + other_namespaces) def test_create_dvr_gateway(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -3814,6 +3936,53 @@ self._pd_assert_dibbler_calls(expected_calls, self.external_process.mock_calls[-len(expected_calls):]) + @mock.patch.object(pd.PrefixDelegation, 'update_subnet') + @mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True) + @mock.patch.object(dibbler.os, 'getpid', return_value=1234) + @mock.patch.object(pd.PrefixDelegation, '_is_lla_active', + return_value=True) + @mock.patch.object(dibbler.os, 'chmod') + @mock.patch.object(dibbler.shutil, 'rmtree') + @mock.patch.object(pd.PrefixDelegation, '_get_sync_data') + def test_pd_lla_already_exists(self, mock1, mock2, mock3, mock4, + mock_getpid, mock_get_prefix, + mock_pd_update_subnet): + '''Test HA in the active router + The intent is to test the PD code with HA. To avoid unnecessary + complexities, use the regular router. + ''' + # Initial setup + agent, router, ri = self._pd_setup_agent_router(enable_ha=True) + + agent.pd.intf_driver = mock.MagicMock() + agent.pd.intf_driver.add_ipv6_addr.side_effect = ( + ip_lib.IpAddressAlreadyExists()) + + # Create one pd-enabled subnet and add router interface + l3_test_common.router_append_pd_enabled_subnet(router) + self._pd_add_gw_interface(agent, ri) + ri.process() + + # No client should be started since it's standby router + agent.pd.process_prefix_update() + self.assertFalse(self.external_process.called) + self.assertFalse(mock_get_prefix.called) + + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) + + # Turn the router to be active + agent.pd.process_ha_state(router['id'], True) + + # Get prefixes + self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix) + + # Update the router with the new prefix + ri.router = update_router + ri.process() + + self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet) + @mock.patch.object(dibbler.os, 'chmod') def test_pd_generate_dibbler_conf(self, mock_chmod): pddib = dibbler.PDDibbler("router_id", "subnet-id", "ifname") diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/test_dvr_local_router.py neutron-16.4.2/neutron/tests/unit/agent/l3/test_dvr_local_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/test_dvr_local_router.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/l3/test_dvr_local_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + import mock from neutron_lib.api.definitions import portbindings from neutron_lib import constants as lib_constants @@ -22,6 +24,7 @@ from neutron.agent.l3 import agent as l3_agent from neutron.agent.l3 import dvr_edge_ha_router as dvr_edge_ha_rtr from neutron.agent.l3 import dvr_edge_router as dvr_edge_rtr +from neutron.agent.l3 import dvr_fip_ns from neutron.agent.l3 import dvr_local_router as dvr_router from neutron.agent.l3 import link_local_allocator as lla from neutron.agent.l3 import router_info @@ -38,6 +41,9 @@ _uuid = uuidutils.generate_uuid FIP_PRI = 32768 HOSTNAME = 'myhost' +FIP_RULE_PRIO_LIST = [['fip_1', 'fixed_ip_1', 'prio_1'], + ['fip_2', 'fixed_ip_2', 'prio_2'], + ['fip_3', 'fixed_ip_3', 'prio_3']] class TestDvrRouterOperations(base.BaseTestCase): @@ -116,6 +122,10 @@ 'oslo_service.loopingcall.FixedIntervalLoopingCall') self.looping_call_p.start() + self.mock_load_fip_p = mock.patch.object(dvr_router.DvrLocalRouter, + '_load_used_fip_information') + self.mock_load_fip = self.mock_load_fip_p.start() + subnet_id_1 = _uuid() subnet_id_2 = _uuid() self.snat_ports = [{'subnets': [{'cidr': '152.2.0.0/16', @@ -222,6 +232,41 @@ self.assertTrue( ri.fip_ns.create_rtr_2_fip_link.called) + @mock.patch.object(ip_lib, 'add_ip_rule') + def test__load_used_fip_information(self, mock_add_ip_rule): + # This test will simulate how "DvrLocalRouter" reloads the FIP + # information from both the FipNamespace._rule_priorities state file + # and the namespace "ip rule" list. + router = self._create_router() + self.mock_load_fip_p.stop() + fip_ns = router.agent.get_fip_ns('net_id') + + # To simulate a partially populated FipNamespace._rule_priorities + # state file, we load all FIPs but last. + fip_rule_prio_list = copy.deepcopy(FIP_RULE_PRIO_LIST) + for idx, (fip, _, _) in enumerate(FIP_RULE_PRIO_LIST[:-1]): + prio = fip_ns.allocate_rule_priority(fip) + fip_rule_prio_list[idx][2] = prio + + fips = [{'floating_ip_address': fip, 'fixed_ip_address': fixed_ip} for + fip, fixed_ip, _ in fip_rule_prio_list] + with mock.patch.object(dvr_fip_ns.FipNamespace, + 'allocate_rule_priority', + return_value=fip_rule_prio_list[-1][2]), \ + mock.patch.object(router, '_cleanup_unused_fip_ip_rules'), \ + mock.patch.object(router, 'get_floating_ips', + return_value=fips): + router._load_used_fip_information() + + mock_add_ip_rule.assert_called_once_with( + router.ns_name, fip_rule_prio_list[2][1], + table=dvr_fip_ns.FIP_RT_TBL, priority=fip_rule_prio_list[-1][2]) + self.assertEqual(3, len(router.floating_ips_dict)) + ret = [[fip, fixed_ip, prio] for fip, (fixed_ip, prio) in + router.floating_ips_dict.items()] + self.assertEqual(sorted(ret, key=lambda ret: ret[0]), + fip_rule_prio_list) + def test_get_floating_ips_dvr(self): router = mock.MagicMock() router.get.return_value = [{'host': HOSTNAME}, @@ -498,11 +543,19 @@ ri = dvr_router.DvrLocalRouter(HOSTNAME, **self.ri_kwargs) ports = ri.router.get(lib_constants.INTERFACE_KEY, []) subnet_id = l3_test_common.get_subnet_id(ports[0]) + ri.router['_snat_router_interfaces'] = [{ + 'mac_address': 'fa:16:3e:80:8d:80', + 'fixed_ips': [{'subnet_id': subnet_id, + 'ip_address': '1.2.3.10'}]}] + test_ports = [{'mac_address': '00:11:22:33:44:55', 'device_owner': lib_constants.DEVICE_OWNER_DHCP, 'fixed_ips': [{'ip_address': '1.2.3.4', 'prefixlen': 24, - 'subnet_id': subnet_id}]}, + 'subnet_id': subnet_id}], + 'allowed_address_pairs': [ + {'ip_address': '10.20.30.40', + 'mac_address': '00:11:22:33:44:55'}]}, {'mac_address': '11:22:33:44:55:66', 'device_owner': lib_constants.DEVICE_OWNER_LOADBALANCER, 'fixed_ips': [{'ip_address': '1.2.3.5', @@ -524,8 +577,10 @@ '_process_arp_cache_for_internal_port') as parp: ri._set_subnet_arp_info(subnet_id) self.assertEqual(1, parp.call_count) - self.mock_ip_dev.neigh.add.assert_called_once_with( - '1.2.3.4', '00:11:22:33:44:55') + self.mock_ip_dev.neigh.add.assert_has_calls([ + mock.call('1.2.3.4', '00:11:22:33:44:55'), + mock.call('10.20.30.40', '00:11:22:33:44:55'), + mock.call('1.2.3.10', 'fa:16:3e:80:8d:80')]) # Test negative case router['distributed'] = False @@ -831,7 +886,7 @@ fip = {'id': _uuid()} fip_cidr = '11.22.33.44/24' - ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri.is_router_master = mock.Mock(return_value=False) ri._add_vip = mock.Mock() interface_name = ri.get_snat_external_device_interface_name( @@ -842,7 +897,7 @@ router[lib_constants.HA_INTERFACE_KEY]['status'] = 'DOWN' self._set_ri_kwargs(agent, router['id'], router) - ri_1 = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri_1 = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri_1.is_router_master = mock.Mock(return_value=True) ri_1._add_vip = mock.Mock() interface_name = ri_1.get_snat_external_device_interface_name( @@ -853,7 +908,7 @@ router[lib_constants.HA_INTERFACE_KEY]['status'] = 'ACTIVE' self._set_ri_kwargs(agent, router['id'], router) - ri_2 = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri_2 = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri_2.is_router_master = mock.Mock(return_value=True) ri_2._add_vip = mock.Mock() interface_name = ri_2.get_snat_external_device_interface_name( @@ -875,14 +930,14 @@ self._set_ri_kwargs(agent, router['id'], router) fip_cidr = '11.22.33.44/24' - ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri.is_router_master = mock.Mock(return_value=False) ri._remove_vip = mock.Mock() ri.remove_centralized_floatingip(fip_cidr) ri._remove_vip.assert_called_once_with(fip_cidr) super_remove_centralized_floatingip.assert_not_called() - ri1 = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri1 = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri1.is_router_master = mock.Mock(return_value=True) ri1._remove_vip = mock.Mock() ri1.remove_centralized_floatingip(fip_cidr) @@ -898,10 +953,9 @@ router[lib_constants.HA_INTERFACE_KEY]['status'] = 'ACTIVE' self.mock_driver.unplug.reset_mock() self._set_ri_kwargs(agent, router['id'], router) - ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri._ha_state_path = self.get_temp_file_path('router_ha_state') ri._create_snat_namespace = mock.Mock() - ri.update_initial_state = mock.Mock() ri._plug_external_gateway = mock.Mock() ri.initialize(mock.Mock()) ri._create_dvr_gateway(mock.Mock(), mock.Mock()) @@ -917,14 +971,13 @@ self.mock_driver.unplug.reset_mock() self._set_ri_kwargs(agent, router['id'], router) - ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, [], **self.ri_kwargs) + ri = dvr_edge_ha_rtr.DvrEdgeHaRouter(HOSTNAME, **self.ri_kwargs) ri._ha_state_path = self.get_temp_file_path('router_ha_state') with open(ri._ha_state_path, "w") as f: f.write("master") ri._create_snat_namespace = mock.Mock() - ri.update_initial_state = mock.Mock() ri._plug_external_gateway = mock.Mock() with mock.patch("neutron.agent.linux.keepalived." "KeepalivedManager.check_processes", diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/test_ha_router.py neutron-16.4.2/neutron/tests/unit/agent/l3/test_ha_router.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/l3/test_ha_router.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/l3/test_ha_router.py 2021-11-12 13:56:42.000000000 +0000 @@ -40,8 +40,7 @@ router = mock.MagicMock() self.agent_conf = mock.Mock() self.router_id = _uuid() - return ha_router.HaRouter(mock.sentinel.enqueue_state, - mock.sentinel.agent, + return ha_router.HaRouter(mock.sentinel.agent, self.router_id, router, self.agent_conf, @@ -84,6 +83,10 @@ ri._add_default_gw_virtual_route(ex_gw_port, 'qg-abc') self.assertEqual(0, len(mock_instance.virtual_routes.gateway_routes)) + subnets[1]['gateway_ip'] = '30.0.1.1' + ri._add_default_gw_virtual_route(ex_gw_port, 'qg-abc') + self.assertEqual(2, len(mock_instance.virtual_routes.gateway_routes)) + @mock.patch.object(router_info.RouterInfo, 'remove_floating_ip') def test_remove_floating_ip(self, super_remove_floating_ip): ri = self._create_router(mock.MagicMock()) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py neutron-16.4.2/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,6 +23,7 @@ from neutron.agent.common import ovs_lib from neutron.agent.common import utils +from neutron.agent import firewall as agent_firewall from neutron.agent.linux.openvswitch_firewall import constants as ovsfw_consts from neutron.agent.linux.openvswitch_firewall import exceptions from neutron.agent.linux.openvswitch_firewall import firewall as ovsfw @@ -37,11 +38,18 @@ TESTING_SEGMENT = 1000 -def create_ofport(port_dict, network_type=None, physical_network=None): +def create_ofport(port_dict, network_type=None, + physical_network=None, segment_id=TESTING_SEGMENT): + allowed_pairs_v4 = ovsfw.OFPort._get_allowed_pairs( + port_dict, version=constants.IPv4) + allowed_pairs_v6 = ovsfw.OFPort._get_allowed_pairs( + port_dict, version=constants.IPv6) ovs_port = mock.Mock(vif_mac='00:00:00:00:00:00', ofport=1, - port_name="port-name") + port_name="port-name", + allowed_pairs_v4=allowed_pairs_v4, + allowed_pairs_v6=allowed_pairs_v6) return ovsfw.OFPort(port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG, - segment_id=TESTING_SEGMENT, + segment_id=segment_id, network_type=network_type, physical_network=physical_network) @@ -281,23 +289,50 @@ constants.IPv6) def test_delete_sg(self): - test_data = [('sg1', 'sg1'), ('sg1', 'sg2')] + test_data = [ + # conj_id: 8 + ('sg1', 'sg1', constants.INGRESS_DIRECTION, constants.IPv6, 0), + # conj_id: 10 + ('sg1', 'sg1', constants.INGRESS_DIRECTION, constants.IPv6, 1), + # conj_id: 12 + ('sg1', 'sg1', constants.INGRESS_DIRECTION, constants.IPv6, 2), + # conj_id: 16 + ('sg2', 'sg1', constants.EGRESS_DIRECTION, constants.IPv6, 0), + # conj_id: 24 + ('sg1', 'sg3', constants.INGRESS_DIRECTION, constants.IPv6, 0), + # conj_id: 36 (and 32 without priority offset, stored in id_map) + ('sg3', 'sg4', constants.INGRESS_DIRECTION, constants.IPv4, 2), + # conj_id: 40 + ('sg5', 'sg4', constants.EGRESS_DIRECTION, constants.IPv4, 0), + ] ids = [] - for sg_id, remote_sg_id in test_data: - ids.append(self.conj_id_map.get_conj_id( - sg_id, remote_sg_id, - constants.INGRESS_DIRECTION, constants.IPv6)) + conj_id_segment = set([]) # see ConjIPFlowManager.get_conj_id + # This is similar to ConjIPFlowManager.add method + for sg_id, rsg_id, direction, ip_version, prio_offset in test_data: + conj_id_tuple = (sg_id, rsg_id, direction, ip_version) + conj_id = self.conj_id_map.get_conj_id(*conj_id_tuple) + conj_id_segment.add(conj_id) + conj_id_plus_prio = conj_id + prio_offset * 2 + self.conj_id_map.id_map_group[conj_id_tuple].add(conj_id_plus_prio) + ids.append(conj_id_plus_prio) result = self.conj_id_map.delete_sg('sg1') - self.assertIn(('sg1', ids[0]), result) - self.assertIn(('sg2', ids[1]), result) - self.assertFalse(self.conj_id_map.id_map) - - reallocated = self.conj_id_map.get_conj_id( - 'sg-foo', 'sg-foo', constants.INGRESS_DIRECTION, - constants.IPv6) - self.assertIn(reallocated, ids) + self.assertEqual( + {('sg3', 24), ('sg1', 12), ('sg1', 16), ('sg1', 8), ('sg1', 10)}, + result) + result = self.conj_id_map.delete_sg('sg3') + self.assertEqual({('sg4', 32), ('sg4', 36)}, result) + result = self.conj_id_map.delete_sg('sg4') + self.assertEqual({('sg4', 40)}, result) + self.assertEqual({}, self.conj_id_map.id_map) + self.assertEqual({}, self.conj_id_map.id_map_group) + + reallocated = set([]) + for sg_id, rsg_id, direction, ip_version, _ in test_data: + conj_id_tuple = (sg_id, rsg_id, direction, ip_version) + reallocated.add(self.conj_id_map.get_conj_id(*conj_id_tuple)) + self.assertEqual(reallocated, conj_id_segment) class TestConjIPFlowManager(base.BaseTestCase): @@ -323,9 +358,10 @@ def test_update_flows_for_vlan_no_ports_but_members(self): remote_group = self.driver.sg_port_map.get_sg.return_value remote_group.ports = set() - remote_group.members = {constants.IPv4: ['10.22.3.4']} + remote_group.members = {constants.IPv4: [ + ('10.22.3.4', 'fa:16:3e:aa:bb:cc'), ]} remote_group.get_ethertype_filtered_addresses.return_value = [ - '10.22.3.4'] + ('10.22.3.4', 'fa:16:3e:aa:bb:cc'), ] with mock.patch.object(self.manager.conj_id_map, 'get_conj_id') as get_conj_id_mock: get_conj_id_mock.return_value = self.conj_id @@ -338,7 +374,7 @@ def test_update_flows_for_vlan(self): remote_group = self.driver.sg_port_map.get_sg.return_value remote_group.get_ethertype_filtered_addresses.return_value = [ - '10.22.3.4'] + ('10.22.3.4', 'fa:16:3e:aa:bb:cc'), ] with mock.patch.object(self.manager.conj_id_map, 'get_conj_id') as get_conj_id_mock: get_conj_id_mock.return_value = self.conj_id @@ -361,7 +397,7 @@ dl_type=2048, nw_src='10.22.3.4/32', priority=73, reg_net=self.vlan_tag, table=82)]) - def test_sg_removed(self): + def _sg_removed(self, sg_name): with mock.patch.object(self.manager.conj_id_map, 'get_conj_id') as get_id_mock, \ mock.patch.object(self.manager.conj_id_map, @@ -374,11 +410,26 @@ constants.INGRESS_DIRECTION, constants.IPv4)] = { '10.22.3.4': [self.conj_id]} - self.manager.sg_removed('sg') + self.manager.sg_removed(sg_name) + + def test_sg_removed(self): + self._sg_removed('sg') + self.driver._add_flow.assert_not_called() + self.driver.delete_flows_for_flow_state.assert_called_once_with( + {'10.22.3.4': [self.conj_id]}, {}, + constants.INGRESS_DIRECTION, constants.IPv4, self.vlan_tag) + self.driver.delete_flow_for_ip.assert_not_called() + + def test_remote_sg_removed(self): + self._sg_removed('remote_id') self.driver._add_flow.assert_not_called() - self.driver.delete_flows_for_ip_addresses.assert_called_once_with( - {'10.22.3.4'}, constants.INGRESS_DIRECTION, constants.IPv4, - self.vlan_tag) + self.driver.delete_flows_for_flow_state.assert_called_once_with( + {'10.22.3.4': [self.conj_id]}, {}, + constants.INGRESS_DIRECTION, constants.IPv4, self.vlan_tag) + # "conj_id_to_remove" is populated with the remote_sg conj_id assigned, + # "_update_flows_for_vlan_subr" will call "delete_flow_for_ip". + self.driver.delete_flow_for_ip.assert_called_once_with( + '10.22.3.4', 'ingress', 'IPv4', 100, {self.conj_id}) class FakeOVSPort(object): @@ -395,12 +446,14 @@ ovs_lib, 'OVSBridge', autospec=True).start() securitygroups_rpc.register_securitygroups_opts() self.firewall = ovsfw.OVSFirewallDriver(mock_bridge) + self.delete_invalid_conntrack_entries_mock = mock.patch.object( + self.firewall.ipconntrack, + "delete_conntrack_state_by_remote_ips").start() self.mock_bridge = self.firewall.int_br self.mock_bridge.reset_mock() self.fake_ovs_port = FakeOVSPort('port', 1, '00:00:00:00:00:00') self.mock_bridge.br.get_vif_port_by_id.return_value = \ self.fake_ovs_port - cfg.CONF.set_override('explicitly_egress_direct', True, 'AGENT') def _prepare_security_group(self): security_group_rules = [ @@ -420,6 +473,16 @@ 'direction': constants.EGRESS_DIRECTION}] self.firewall.update_security_group_rules(2, security_group_rules) + def _assert_invalid_conntrack_entries_deleted(self, port_dict): + port_dict['of_port'] = mock.Mock(vlan_tag=10) + self.delete_invalid_conntrack_entries_mock.assert_has_calls([ + mock.call( + [port_dict], constants.IPv4, set(), + mark=ovsfw_consts.CT_MARK_INVALID), + mock.call( + [port_dict], constants.IPv6, set(), + mark=ovsfw_consts.CT_MARK_INVALID)]) + @property def port_ofport(self): return self.mock_bridge.br.get_vif_port_by_id.return_value.ofport @@ -577,6 +640,7 @@ calls = self.mock_bridge.br.add_flow.call_args_list for call in exp_ingress_classifier, exp_egress_classifier, filter_rule: self.assertIn(call, calls) + self._assert_invalid_conntrack_entries_deleted(port_dict) def test_prepare_port_filter_port_security_disabled(self): port_dict = {'device': 'port-id', @@ -587,19 +651,25 @@ self.firewall, 'initialize_port_flows') as m_init_flows: self.firewall.prepare_port_filter(port_dict) self.assertFalse(m_init_flows.called) + self.delete_invalid_conntrack_entries_mock.assert_not_called() - def test_initialize_port_flows_vlan_dvr_conntrack_direct(self): + def _test_initialize_port_flows_dvr_conntrack_direct(self, network_type): port_dict = { 'device': 'port-id', 'security_groups': [1]} + segment_id = None + if network_type == constants.TYPE_VLAN: + segment_id = TESTING_SEGMENT of_port = create_ofport(port_dict, - network_type=constants.TYPE_VXLAN) + network_type=network_type, + segment_id=segment_id) self.firewall.sg_port_map.ports[of_port.id] = of_port port = self.firewall.get_or_create_ofport(port_dict) fake_patch_port = 999 self.mock_bridge.br.get_port_ofport.return_value = fake_patch_port + expected_calls = [] self.firewall.initialize_port_flows(port) call_args1 = { @@ -614,22 +684,39 @@ port.vlan_tag, ovsfw_consts.REG_NET, ovs_consts.BASE_EGRESS_TABLE)} - egress_flow_call = mock.call(**call_args1) + expected_calls.append(mock.call(**call_args1)) - call_args2 = { - 'table': ovs_consts.TRANSIENT_TABLE, - 'priority': 90, - 'dl_dst': port.mac, - 'dl_vlan': '0x%x' % port.segment_id, - 'actions': 'set_field:{:d}->reg{:d},' - 'set_field:{:d}->reg{:d},' - 'strip_vlan,resubmit(,{:d})'.format( - port.ofport, - ovsfw_consts.REG_PORT, - port.vlan_tag, - ovsfw_consts.REG_NET, - ovs_consts.BASE_INGRESS_TABLE)} - ingress_flow_call1 = mock.call(**call_args2) + if network_type == constants.TYPE_VLAN: + call_args2 = { + 'table': ovs_consts.TRANSIENT_TABLE, + 'priority': 90, + 'dl_dst': port.mac, + 'dl_vlan': '0x%x' % port.segment_id, + 'actions': 'set_field:{:d}->reg{:d},' + 'set_field:{:d}->reg{:d},' + 'strip_vlan,resubmit(,{:d})'.format( + port.ofport, + ovsfw_consts.REG_PORT, + port.vlan_tag, + ovsfw_consts.REG_NET, + ovs_consts.BASE_INGRESS_TABLE)} + expected_calls.append(mock.call(**call_args2)) + + if network_type == constants.TYPE_FLAT: + call_args2 = { + 'table': ovs_consts.TRANSIENT_TABLE, + 'priority': 90, + 'dl_dst': port.mac, + 'vlan_tci': ovs_consts.FLAT_VLAN_TCI, + 'actions': 'set_field:{:d}->reg{:d},' + 'set_field:{:d}->reg{:d},' + 'resubmit(,{:d})'.format( + port.ofport, + ovsfw_consts.REG_PORT, + port.vlan_tag, + ovsfw_consts.REG_NET, + ovs_consts.BASE_INGRESS_TABLE)} + expected_calls.append(mock.call(**call_args2)) call_args3 = { 'table': ovs_consts.TRANSIENT_TABLE, @@ -644,9 +731,20 @@ port.vlan_tag, ovsfw_consts.REG_NET, ovs_consts.BASE_INGRESS_TABLE)} - ingress_flow_call2 = mock.call(**call_args3) - self.mock_bridge.br.add_flow.assert_has_calls( - [egress_flow_call, ingress_flow_call1, ingress_flow_call2]) + expected_calls.append(mock.call(**call_args3)) + self.mock_bridge.br.add_flow.assert_has_calls(expected_calls) + + def test_initialize_port_flows_dvr_conntrack_direct_vxlan(self): + self._test_initialize_port_flows_dvr_conntrack_direct( + network_type='vxlan') + + def test_initialize_port_flows_dvr_conntrack_direct_vlan(self): + self._test_initialize_port_flows_dvr_conntrack_direct( + network_type='vlan') + + def test_initialize_port_flows_dvr_conntrack_direct_flat(self): + self._test_initialize_port_flows_dvr_conntrack_direct( + network_type='flat') def test_initialize_port_flows_vlan_dvr_conntrack_direct_vlan(self): port_dict = { @@ -725,6 +823,7 @@ self.assertFalse(self.mock_bridge.br.delete_flows.called) self.firewall.prepare_port_filter(port_dict) self.assertTrue(self.mock_bridge.br.delete_flows.called) + self._assert_invalid_conntrack_entries_deleted(port_dict) def test_update_port_filter(self): port_dict = {'device': 'port-id', @@ -756,6 +855,7 @@ table=ovs_consts.RULES_EGRESS_TABLE)] self.mock_bridge.br.add_flow.assert_has_calls( filter_rules, any_order=True) + self._assert_invalid_conntrack_entries_deleted(port_dict) def test_update_port_filter_create_new_port_if_not_present(self): port_dict = {'device': 'port-id', @@ -775,15 +875,18 @@ self.assertFalse(self.mock_bridge.br.delete_flows.called) self.assertTrue(initialize_port_flows_mock.called) self.assertTrue(add_flows_from_rules_mock.called) + self._assert_invalid_conntrack_entries_deleted(port_dict) def test_update_port_filter_port_security_disabled(self): port_dict = {'device': 'port-id', 'security_groups': [1]} self._prepare_security_group() self.firewall.prepare_port_filter(port_dict) + self.delete_invalid_conntrack_entries_mock.reset_mock() port_dict['port_security_enabled'] = False self.firewall.update_port_filter(port_dict) self.assertTrue(self.mock_bridge.br.delete_flows.called) + self.delete_invalid_conntrack_entries_mock.assert_not_called() def test_update_port_filter_applies_added_flows(self): """Check flows are applied right after _set_flows is called.""" @@ -804,6 +907,7 @@ self.mock_bridge.br.get_vif_port_by_id.return_value = None self.firewall.update_port_filter(port_dict) self.assertTrue(self.mock_bridge.br.delete_flows.called) + self._assert_invalid_conntrack_entries_deleted(port_dict) def test_remove_port_filter(self): port_dict = {'device': 'port-id', @@ -896,6 +1000,51 @@ with testtools.ExpectedException(exceptions.OVSFWPortNotHandled): self.firewall._remove_egress_no_port_security('foo') + def test__initialize_egress_ipv6_icmp(self): + port_dict = { + 'device': 'port-id', + 'security_groups': [1], + 'fixed_ips': ["10.0.0.1"], + 'allowed_address_pairs': [ + {'mac_address': 'aa:bb:cc:dd:ee:ff', + 'ip_address': '192.168.1.1'}, + {'mac_address': 'aa:bb:cc:dd:ee:ff', + 'ip_address': '2003::1'} + ]} + of_port = create_ofport(port_dict) + self.mock_bridge.br.db_get_val.return_value = {'tag': TESTING_VLAN_TAG} + self.firewall._initialize_egress_ipv6_icmp( + of_port, set([('aa:bb:cc:dd:ee:ff', '2003::1')])) + expected_calls = [] + for icmp_type in agent_firewall.ICMPV6_ALLOWED_EGRESS_TYPES: + expected_calls.append( + mock.call( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=TESTING_VLAN_TAG, + reg5=TESTING_VLAN_TAG, + dl_type='0x86dd', + nw_proto=constants.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + dl_src='aa:bb:cc:dd:ee:ff', + ipv6_src='2003::1', + actions='resubmit(,%d)' % ( + ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE))) + for icmp_type in agent_firewall.ICMPV6_RESTRICTED_EGRESS_TYPES: + expected_calls.append( + mock.call( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=TESTING_VLAN_TAG, + reg5=TESTING_VLAN_TAG, + dl_type='0x86dd', + nw_proto=constants.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + nd_target='2003::1', + actions='resubmit(,%d)' % ( + ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE))) + self.mock_bridge.br.add_flow.assert_has_calls(expected_calls) + def test_process_trusted_ports_caches_port_id(self): vif_port = ovs_lib.VifPort('name', 1, 'id', 'mac', mock.ANY) with mock.patch.object(self.firewall.int_br.br, 'get_vifs_by_ids', @@ -924,6 +1073,53 @@ """Check that exception is not propagated outside.""" self.firewall.remove_trusted_ports(['port_id']) + def _test_delete_flows_for_flow_state(self, addr_to_conj, + explicitly_egress_direct=True): + direction = 'one_direction' + ethertype = 'ethertype' + vlan_tag = 'taaag' + with mock.patch.object(self.firewall, 'delete_flow_for_ip') as \ + mock_delete_flow_for_ip: + flow_state = {'addr1': {8, 16, 24}, 'addr2': {32, 40}} + cfg.CONF.set_override('explicitly_egress_direct', + explicitly_egress_direct, 'AGENT') + self.firewall.delete_flows_for_flow_state( + flow_state, addr_to_conj, direction, ethertype, vlan_tag) + calls = [] + for removed_ip in flow_state.keys() - addr_to_conj.keys(): + calls.append(mock.call(removed_ip, direction, ethertype, vlan_tag, + flow_state[removed_ip])) + if explicitly_egress_direct: + calls.append(mock.call(removed_ip, direction, ethertype, + vlan_tag, [0])) + mock_delete_flow_for_ip.assert_has_calls(calls) + + def test_delete_flows_for_flow_state_no_removed_ips_exp_egress(self): + addr_to_conj = {'addr1': {8, 16, 24}, 'addr2': {32, 40}} + self._test_delete_flows_for_flow_state(addr_to_conj) + + def test_delete_flows_for_flow_state_no_removed_ips_no_exp_egress(self): + addr_to_conj = {'addr1': {8, 16, 24}, 'addr2': {32, 40}} + self._test_delete_flows_for_flow_state(addr_to_conj, False) + + def test_delete_flows_for_flow_state_removed_ips_exp_egress(self): + addr_to_conj = {'addr2': {32, 40}} + self._test_delete_flows_for_flow_state(addr_to_conj) + + def test_delete_flows_for_flow_state_removed_ips_no_exp_egress(self): + addr_to_conj = {'addr1': {8, 16, 24}} + self._test_delete_flows_for_flow_state(addr_to_conj, False) + + def test_delete_flow_for_ip_using_cookie_any(self): + with mock.patch.object(self.firewall, '_delete_flows') as \ + mock_delete_flows: + self.firewall.delete_flow_for_ip(('10.1.2.3', None), + constants.INGRESS_DIRECTION, + constants.IPv4, 100, [0]) + _, kwargs = mock_delete_flows.call_args + self.assertIn('cookie', kwargs) + self.assertIs(ovs_lib.COOKIE_ANY, kwargs['cookie']) + class TestCookieContext(base.BaseTestCase): def setUp(self): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_rules.py neutron-16.4.2/neutron/tests/unit/agent/linux/openvswitch_firewall/test_rules.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_rules.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/openvswitch_firewall/test_rules.py 2021-11-12 13:56:42.000000000 +0000 @@ -349,7 +349,8 @@ conj_ids = [12, 20] flows = rules.create_flows_for_ip_address( - '192.168.0.1', constants.EGRESS_DIRECTION, constants.IPv4, + ('192.168.0.1', 'fa:16:3e:aa:bb:cc'), + constants.EGRESS_DIRECTION, constants.IPv4, 0x123, conj_ids) self.assertEqual(2, len(flows)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_dhcp.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_dhcp.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_dhcp.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_dhcp.py 2021-11-12 13:56:42.000000000 +0000 @@ -30,6 +30,7 @@ from neutron.agent.linux import dhcp from neutron.agent.linux import ip_lib +from neutron.cmd import runtime_checks as checks from neutron.conf.agent import common as config from neutron.conf.agent import dhcp as dhcp_config from neutron.conf import common as base_config @@ -228,6 +229,9 @@ self.extra_dhcp_opts = [ DhcpOpt(opt_name='dns-server', opt_value='ffea:3ba5:a17a:4ba3::100', + ip_version=constants.IP_VERSION_6), + DhcpOpt(opt_name='malicious-option\nwith-new-line', + opt_value='aaa\nbbb.ccc\n', ip_version=constants.IP_VERSION_6)] @@ -306,6 +310,19 @@ for ip in self.fixed_ips] +class FakeRouterHAPort(object): + def __init__(self): + self.id = 'hahahaha-haha-haha-haha-hahahahahaha' + self.admin_state_up = True + self.device_owner = constants.DEVICE_OWNER_ROUTER_HA_INTF + self.mac_address = '00:00:0f:aa:aa:aa' + self.device_id = 'fake_router_ha_port' + self.dns_assignment = [] + self.extra_dhcp_opts = [] + self.fixed_ips = [FakeIPAllocation( + '169.254.169.20', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] + + class FakeRouterPortNoDHCP(object): def __init__(self, dev_owner=constants.DEVICE_OWNER_ROUTER_INTF, ip_address='192.168.0.1', domain='openstacklocal'): @@ -692,6 +709,7 @@ self.namespace = 'qdhcp-ns' self.ports = [FakePort1(domain=domain), FakeV6Port(domain=domain), FakeDualPort(domain=domain), + FakeRouterHAPort(), FakeRouterPort(domain=domain)] @@ -1212,6 +1230,7 @@ mocks['interface_name'].__get__ = mock.Mock(return_value='tap0') lp = LocalChild(self.conf, network) lp.disable(retain_port=True) + self.rmtree.assert_not_called() self._assert_disabled(lp) def test_disable(self): @@ -1223,6 +1242,7 @@ with mock.patch('neutron.agent.linux.ip_lib.' 'delete_network_namespace') as delete_ns: lp.disable() + self.rmtree.assert_called_once() self._assert_disabled(lp) @@ -1285,6 +1305,21 @@ def mock_get_conf_file_name(kind): return '/dhcp/%s/%s' % (network.id, kind) + # Empty string passed to --conf-file in dnsmasq is invalid + # we must force '' to '/dev/null' because the dhcp agent + # does the same. Therefore we allow empty string to + # be passed to neutron but not to dnsmasq. + def check_conf_file_empty(cmd_list): + for i in cmd_list: + conf_file = '' + value = '' + if i.startswith('--conf-file='): + conf_file = i + value = i[12:].strip() + if not value: + idx = cmd_list.index(conf_file) + cmd_list[idx] = '--conf-file=/dev/null' + # if you need to change this path here, think twice, # that means pid files will move around, breaking upgrades # or backwards-compatibility @@ -1346,7 +1381,9 @@ expected.append('--dhcp-option-force=option:T1,%ds' % dhcp_t1) if dhcp_t2: expected.append('--dhcp-option-force=option:T2,%ds' % dhcp_t2) + expected.extend(extra_options) + check_conf_file_empty(expected) self.execute.return_value = ('', '') @@ -1395,9 +1432,23 @@ self.conf.set_override('dnsmasq_config_file', '/foo') self._test_spawn(['--conf-file=/foo', '--domain=openstacklocal']) - def test_spawn_no_dns_domain(self): + @mock.patch.object(checks, 'dnsmasq_host_tag_support', autospec=True) + def test_spawn_no_dns_domain(self, mock_tag_support): + mock_tag_support.return_value = False (exp_host_name, exp_host_data, - exp_addn_name, exp_addn_data) = self._test_no_dns_domain_alloc_data + exp_addn_name, exp_addn_data) = self._test_no_dns_domain_alloc_data() + self.conf.set_override('dns_domain', '') + network = FakeDualNetwork(domain=self.conf.dns_domain) + self._test_spawn(['--conf-file='], network=network) + self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), + mock.call(exp_addn_name, exp_addn_data)]) + + @mock.patch.object(checks, 'dnsmasq_host_tag_support', autospec=True) + def test_spawn_no_dns_domain_tag_support(self, mock_tag_support): + mock_tag_support.return_value = True + (exp_host_name, exp_host_data, exp_addn_name, + exp_addn_data) = self._test_no_dns_domain_alloc_data( + tag=dhcp.HOST_DHCPV6_TAG) self.conf.set_override('dns_domain', '') network = FakeDualNetwork(domain=self.conf.dns_domain) self._test_spawn(['--conf-file='], network=network) @@ -1514,6 +1565,68 @@ timestamp = 0 self._test_output_init_lease_file(timestamp) + @mock.patch('time.time') + @mock.patch('os.path.isfile', return_value=True) + def test_output_init_lease_file_existing(self, isfile, tmock): + + duid = 'duid 00:01:00:01:27:da:58:97:fa:16:3e:6c:ad:c1' + ipv4_leases = ( + '1623162161 00:00:80:aa:bb:cc 192.168.0.2 host-192-168-0-2 *\n' + '1623147425 00:00:0f:aa:bb:cc 192.168.0.3 host-192-168-0-3 ' + 'ff:b5:5e:67:ff:00:02:00:00:ab:11:43:e5:86:52:f3:d7:2c:97\n' + '1623138717 00:00:0f:rr:rr:rr 192.168.0.1 host-192-168-0-1 ' + 'ff:b5:5e:67:ff:00:02:00:00:ab:11:f6:f2:aa:cb:94:c1:b4:86' + ) + ipv6_lease_v6_port = ( + '1623083263 755752236 fdca:3ba5:a17a:4ba3::2 ' + 'host-fdca-3ba5-a17a-4ba3--2 ' + '00:01:00:01:28:50:e8:31:5a:42:2d:0b:dd:2c' + ) + additional_ipv6_leases = ( + '1623143299 3042863103 2001:db8::45 host-2001-db8--45 ' + '00:02:00:00:ab:11:fa:c9:0e:0f:3d:90:73:f0\n' + '1623134168 3042863103 2001:db8::12 host-2001-db8--12 ' + '00:02:00:00:ab:11:f6:f2:aa:cb:94:c1:b4:86' + ) + existing_leases = '\n'.join((ipv4_leases, duid, ipv6_lease_v6_port, + additional_ipv6_leases)) + + # lease duration should be added to current time + timestamp = 1000000 + 500 + # The expected lease file contains: + # * The DHCPv6 servers DUID + # * A lease for all IPv4 addresses + # * A lease for the IPv6 addresses present in the existing lease file + # (IPv6 of FakeV6Port) + # * No lease for the IPv6 addresses NOT present in the existing lease + # file (IPv6 of FakeDualPort) + # * No lease for the IPv6 addresses present in the existing lease file + # which are no longer assigned to any port + expected = ( + '%s\n' + '%s 00:00:80:aa:bb:cc 192.168.0.2 * *\n' + '%s\n' + '%s 00:00:0f:aa:bb:cc 192.168.0.3 * *\n' + '%s 00:00:0f:rr:rr:rr 192.168.0.1 * *\n' + ) % (duid, timestamp, ipv6_lease_v6_port, timestamp, timestamp) + + self.conf.set_override('dhcp_lease_duration', 500) + tmock.return_value = 1000000 + + with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn: + conf_fn.return_value = '/foo/leases' + dm = self._get_dnsmasq(FakeDualNetwork()) + + # Patch __iter__ into mock for Python < 3.8 compatibility + open_mock = mock.mock_open(read_data=existing_leases) + open_mock.return_value.__iter__ = lambda s: iter(s.readline, '') + + with mock.patch('builtins.open', open_mock): + dm._output_init_lease_file() + + # Assert the lease file contains the existing ipv6_leases + self.safe.assert_called_once_with('/foo/leases', expected) + def _test_output_opts_file(self, expected, network, ipm_retval=None): with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn: conf_fn.return_value = '/foo/opts' @@ -2028,19 +2141,18 @@ self.conf.force_metadata = True self._test_output_opts_file(expected, FakeV6Network()) - @property - def _test_no_dns_domain_alloc_data(self): + def _test_no_dns_domain_alloc_data(self, tag=''): exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host' exp_host_data = ('00:00:80:aa:bb:cc,host-192-168-0-2,' '192.168.0.2\n' - '00:00:f3:aa:bb:cc,host-fdca-3ba5-a17a-4ba3--2,' + '00:00:f3:aa:bb:cc,{tag}host-fdca-3ba5-a17a-4ba3--2,' '[fdca:3ba5:a17a:4ba3::2]\n' '00:00:0f:aa:bb:cc,host-192-168-0-3,' '192.168.0.3\n' - '00:00:0f:aa:bb:cc,host-fdca-3ba5-a17a-4ba3--3,' + '00:00:0f:aa:bb:cc,{tag}host-fdca-3ba5-a17a-4ba3--3,' '[fdca:3ba5:a17a:4ba3::3]\n' '00:00:0f:rr:rr:rr,host-192-168-0-1,' - '192.168.0.1\n').lstrip() + '192.168.0.1\n').format(tag=tag).lstrip() exp_addn_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/addn_hosts' exp_addn_data = ( '192.168.0.2\t' @@ -2060,19 +2172,18 @@ return (exp_host_name, exp_host_data, exp_addn_name, exp_addn_data) - @property - def _test_reload_allocation_data(self): + def _test_reload_allocation_data(self, tag=''): exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host' exp_host_data = ('00:00:80:aa:bb:cc,host-192-168-0-2.openstacklocal.,' '192.168.0.2\n' - '00:00:f3:aa:bb:cc,host-fdca-3ba5-a17a-4ba3--2.' + '00:00:f3:aa:bb:cc,{tag}host-fdca-3ba5-a17a-4ba3--2.' 'openstacklocal.,[fdca:3ba5:a17a:4ba3::2]\n' '00:00:0f:aa:bb:cc,host-192-168-0-3.openstacklocal.,' '192.168.0.3\n' - '00:00:0f:aa:bb:cc,host-fdca-3ba5-a17a-4ba3--3.' + '00:00:0f:aa:bb:cc,{tag}host-fdca-3ba5-a17a-4ba3--3.' 'openstacklocal.,[fdca:3ba5:a17a:4ba3::3]\n' '00:00:0f:rr:rr:rr,host-192-168-0-1.openstacklocal.,' - '192.168.0.1\n').lstrip() + '192.168.0.1\n').format(tag=tag).lstrip() exp_addn_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/addn_hosts' exp_addn_data = ( '192.168.0.2\t' @@ -2120,10 +2231,12 @@ dm.reload_allocations() self.assertFalse(test_pm.register.called) - def test_reload_allocations(self): + @mock.patch.object(checks, 'dnsmasq_host_tag_support', autospec=True) + def test_reload_allocations(self, mock_tag_support): + mock_tag_support.return_value = False (exp_host_name, exp_host_data, exp_addn_name, exp_addn_data, - exp_opt_name, exp_opt_data,) = self._test_reload_allocation_data + exp_opt_name, exp_opt_data,) = self._test_reload_allocation_data() net = FakeDualNetwork() hpath = '/dhcp/%s/host' % net.id @@ -2143,6 +2256,22 @@ mock.call(exp_opt_name, exp_opt_data), ]) + mock_tag_support.return_value = True + (exp_host_name, exp_host_data, + exp_addn_name, exp_addn_data, + exp_opt_name, exp_opt_data,) = self._test_reload_allocation_data( + tag=dhcp.HOST_DHCPV6_TAG) + test_pm.reset_mock() + dm = self._get_dnsmasq(net, test_pm) + dm.reload_allocations() + self.assertTrue(test_pm.register.called) + + self.safe.assert_has_calls([ + mock.call(exp_host_name, exp_host_data), + mock.call(exp_addn_name, exp_addn_data), + mock.call(exp_opt_name, exp_opt_data), + ]) + def test_release_unused_leases(self): dnsmasq = self._get_dnsmasq(FakeDualNetwork()) @@ -2541,51 +2670,57 @@ def test_release_unused_leases_one_lease_mult_times_removed(self): self._test_release_unused_leases_one_lease_mult_times(True) - def test_read_hosts_file_leases(self): + def test__parse_ip_addresses(self): + ip_list = ['192.168.0.1', '[fdca:3ba5:a17a::1]', 'no_ip_address'] + self.assertEqual(['192.168.0.1', 'fdca:3ba5:a17a::1'], + dhcp.Dnsmasq._parse_ip_addresses(ip_list)) + + def _test_read_hosts_file_leases(self, lines, expected_result): filename = '/path/to/file' - lines = ["00:00:80:aa:bb:cc,inst-name,192.168.0.1", - "00:00:80:aa:bb:cc,inst-name,[fdca:3ba5:a17a::1]"] mock_open = self.useFixture( lib_fixtures.OpenFixture(filename, '\n'.join(lines))).mock_open dnsmasq = self._get_dnsmasq(FakeDualNetwork()) leases = dnsmasq._read_hosts_file_leases(filename) - - self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc", None), - ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", - None)]), leases) + self.assertEqual(expected_result, leases) mock_open.assert_called_once_with(filename) + def test_read_hosts_file_leases(self): + lines = ["00:00:80:aa:bb:cc,inst-name,192.168.0.1", + "00:00:80:aa:bb:cc,inst-name,[fdca:3ba5:a17a::1]"] + result = {("192.168.0.1", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", None)} + self._test_read_hosts_file_leases(lines, result) + def test_read_hosts_file_leases_with_client_id(self): - filename = '/path/to/file' lines = ["00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1", "00:00:80:aa:bb:cc,id:client2,inst-name," "[fdca:3ba5:a17a::1]"] - mock_open = self.useFixture( - lib_fixtures.OpenFixture(filename, '\n'.join(lines))).mock_open - dnsmasq = self._get_dnsmasq(FakeDualNetwork()) - leases = dnsmasq._read_hosts_file_leases(filename) - - self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), - ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", - 'client2')]), leases) - mock_open.assert_called_once_with(filename) + result = {("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", 'client2')} + self._test_read_hosts_file_leases(lines, result) def test_read_hosts_file_leases_with_stateless_IPv6_tag(self): - filename = self.get_temp_file_path('leases') - with open(filename, "w") as leasesfile: - lines = [ - "00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1\n", - "00:00:80:aa:bb:cc,set:ccccccccc-cccc-cccc-cccc-cccccccc\n", - "00:00:80:aa:bb:cc,id:client2,inst-name,[fdca:3ba5:a17a::1]\n"] - for line in lines: - leasesfile.write(line) - - dnsmasq = self._get_dnsmasq(FakeDualNetwork()) - leases = dnsmasq._read_hosts_file_leases(filename) + lines = [ + "00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1", + "00:00:80:aa:bb:cc,set:ccccccccc-cccc-cccc-cccc-cccccccc", + "00:00:80:aa:bb:cc,id:client2,inst-name,[fdca:3ba5:a17a::1]"] + result = {("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", 'client2')} + self._test_read_hosts_file_leases(lines, result) - self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), - ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", - 'client2')]), leases) + def test_read_hosts_file_leases_with_IPv6_tag_and_multiple_ips(self): + lines = [ + "00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1", + "00:00:80:aa:bb:cc,set:ccccccccc-cccc-cccc-cccc-cccccccc", + "00:00:80:aa:bb:cc,tag:dhcpv6,inst-name,[fdca:3ba5:a17a::1]," + "[fdca:3ba5:a17a::2],[fdca:3ba5:a17a::3],[fdca:3ba5:a17a::4]," + "set:port-fe2baee9-aba9-4b67-be03-be4aeee40cca"] + result = {("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::2", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::3", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::4", "00:00:80:aa:bb:cc", None)} + self._test_read_hosts_file_leases(lines, result) def _test_read_leases_file_leases(self, ip_version, add_bad_line=False): filename = '/path/to/file' @@ -2776,7 +2911,10 @@ self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data)]) - def test_host_and_opts_file_on_stateless_dhcpv6_network(self): + @mock.patch.object(checks, 'dnsmasq_host_tag_support', autospec=True) + def test_host_and_opts_file_on_stateless_dhcpv6_network( + self, mock_tag_support): + mock_tag_support.return_value = False exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host' exp_host_data = ( '00:16:3e:c2:77:1d,' @@ -2785,14 +2923,29 @@ exp_opt_data = ('tag:subnet-eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee,' 'option6:domain-search,openstacklocal\n' 'tag:port-hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh,' - 'option6:dns-server,ffea:3ba5:a17a:4ba3::100').lstrip() + 'option6:dns-server,ffea:3ba5:a17a:4ba3::100\n' + 'tag:port-hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh,' + 'option6:malicious-option,aaa').lstrip() + dm = self._get_dnsmasq(FakeV6NetworkStatelessDHCP()) + dm._output_hosts_file() + dm._output_opts_file() + self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), + mock.call(exp_opt_name, exp_opt_data)]) + + mock_tag_support.return_value = True + exp_host_data = ( + '00:16:3e:c2:77:1d,tag:dhcpv6,' + 'set:port-hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh\n').lstrip() dm = self._get_dnsmasq(FakeV6NetworkStatelessDHCP()) dm._output_hosts_file() dm._output_opts_file() self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), mock.call(exp_opt_name, exp_opt_data)]) - def test_host_and_opts_file_on_stateful_dhcpv6_same_subnet_fixedips(self): + @mock.patch.object(checks, 'dnsmasq_host_tag_support', autospec=True) + def test_host_and_opts_file_on_stateful_dhcpv6_same_subnet_fixedips( + self, mock_tag_support): + mock_tag_support.return_value = False self.conf.set_override('dnsmasq_enable_addr6_list', True) exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host' exp_host_data = ( @@ -2809,6 +2962,17 @@ self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), mock.call(exp_opt_name, exp_opt_data)]) + mock_tag_support.return_value = True + exp_host_data = ( + '00:00:f3:aa:bb:cc,tag:dhcpv6,' + 'host-fdca-3ba5-a17a-4ba3--2.openstacklocal.,' + '[fdca:3ba5:a17a:4ba3::2],[fdca:3ba5:a17a:4ba3::4]\n'.lstrip()) + dm = self._get_dnsmasq(FakeV6NetworkStatefulDHCPSameSubnetFixedIps()) + dm._output_hosts_file() + dm._output_opts_file() + self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), + mock.call(exp_opt_name, exp_opt_data)]) + def test_host_and_opts_file_on_stateless_dhcpv6_network_no_dns(self): exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host' exp_opt_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/opts' @@ -2835,8 +2999,10 @@ dm._output_hosts_file() self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data)]) + @mock.patch.object(checks, 'dnsmasq_host_tag_support', autospec=True) def test_host_and_opts_file_on_net_with_V6_stateless_and_V4_subnets( - self): + self, mock_tag_support): + mock_tag_support.return_value = False exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host' exp_host_data = ( '00:16:3e:c2:77:1d,set:port-hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh\n' @@ -2864,6 +3030,20 @@ dm = self._get_dnsmasq(FakeNetworkWithV6SatelessAndV4DHCPSubnets()) dm._output_hosts_file() dm._output_opts_file() + self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), + mock.call(exp_opt_name, exp_opt_data)]) + + mock_tag_support.return_value = True + exp_host_data = ( + '00:16:3e:c2:77:1d,tag:dhcpv6,' + 'set:port-hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh\n' + '00:16:3e:c2:77:1d,host-192-168-0-3.openstacklocal.,' + '192.168.0.3,set:port-hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh\n' + '00:00:0f:rr:rr:rr,' + 'host-192-168-0-1.openstacklocal.,192.168.0.1\n').lstrip() + dm = self._get_dnsmasq(FakeNetworkWithV6SatelessAndV4DHCPSubnets()) + dm._output_hosts_file() + dm._output_opts_file() self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), mock.call(exp_opt_name, exp_opt_data)]) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_interface.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_interface.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_interface.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_interface.py 2021-11-12 13:56:42.000000000 +0000 @@ -19,11 +19,14 @@ from pyroute2.netlink import exceptions as pyroute2_exc from neutron.agent.common import ovs_lib +from neutron.agent.linux import ethtool from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.common import utils from neutron.conf.agent import common as config from neutron.conf.plugins.ml2.drivers import ovs_conf +from neutron.plugins.ml2.drivers.openvswitch.agent.common \ + import constants as ovs_const from neutron.tests import base @@ -57,12 +60,25 @@ network_id = network.id +class FakeLegacyInterfaceDriver(interface.LinuxInterfaceDriver): + + def plug_new(self, network_id, port_id, device_name, mac_address, + bridge=None, namespace=None, prefix=None, mtu=None): + """This is legacy method which don't accepts link_up argument.""" + pass + + def unplug(self, device_name, bridge=None, namespace=None, prefix=None): + pass + + class TestBase(base.BaseTestCase): def setUp(self): super(TestBase, self).setUp() self.conf = config.setup_conf() ovs_conf.register_ovs_opts(self.conf) config.register_interface_opts(self.conf) + self.eth_tool_p = mock.patch.object(ethtool, 'Ethtool') + self.eth_tool = self.eth_tool_p.start() self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice') self.ip_dev = self.ip_dev_p.start() self.ip_p = mock.patch.object(ip_lib, 'IPWrapper') @@ -507,7 +523,12 @@ def setUp(self): super(TestOVSInterfaceDriverWithVeth, self).setUp() + ovs_conf.register_ovs_agent_opts(self.conf) self.conf.set_override('ovs_use_veth', True) + self.conf.set_override( + 'datapath_type', + ovs_const.OVS_DATAPATH_NETDEV, + group='OVS') def test_get_device_name(self): br = interface.OVSInterfaceDriver(self.conf) @@ -538,6 +559,7 @@ mock.patch.object( interface, '_get_veth', return_value=(root_dev, ns_dev)).start() + ns_dev.name = devname expected = [mock.call(), mock.call().add_veth('tap0', devname, @@ -568,6 +590,9 @@ self.ip.assert_has_calls(expected) root_dev.assert_has_calls([mock.call.link.set_up()]) ns_dev.assert_has_calls([mock.call.link.set_up()]) + self.eth_tool.assert_has_calls([mock.call.offload( + devname, rx=False, + tx=False, namespace=namespace)]) def test_plug_new(self): # The purpose of test_plug_new in parent class(TestOVSInterfaceDriver) @@ -660,3 +685,20 @@ self.ip_dev.assert_has_calls([mock.call('tap0', namespace=None), mock.call().link.delete()]) + + +class TestLegacyDriver(TestBase): + + def test_plug(self): + self.device_exists.return_value = False + with mock.patch('neutron.agent.linux.interface.LOG.warning') as log: + driver = FakeLegacyInterfaceDriver(self.conf) + try: + driver.plug( + '01234567-1234-1234-99', 'port-1234', 'tap0', + 'aa:bb:cc:dd:ee:ff') + except TypeError: + self.fail("LinuxInterfaceDriver class can not call properly " + "plug_new method from the legacy drivers that " + "do not accept 'link_up' parameter.") + log.assert_called_once() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ip_conntrack.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_ip_conntrack.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ip_conntrack.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_ip_conntrack.py 2021-11-12 13:56:42.000000000 +0000 @@ -39,3 +39,26 @@ dev_info_list = [dev_info for _ in range(10)] self.mgr._delete_conntrack_state(dev_info_list, rule) self.assertEqual(1, len(self.execute.mock_calls)) + + +class OvsIPConntrackTestCase(IPConntrackTestCase): + + def setUp(self): + super(IPConntrackTestCase, self).setUp() + self.execute = mock.Mock() + self.mgr = ip_conntrack.OvsIpConntrackManager(self.execute) + + def test_delete_conntrack_state_dedupes(self): + rule = {'ethertype': 'IPv4', 'direction': 'ingress'} + dev_info = { + 'device': 'tapdevice', + 'fixed_ips': ['1.2.3.4'], + 'of_port': mock.Mock(of_port=10)} + dev_info_list = [dev_info for _ in range(10)] + self.mgr._delete_conntrack_state(dev_info_list, rule) + self.assertEqual(1, len(self.execute.mock_calls)) + + def test_get_device_zone(self): + of_port = mock.Mock(vlan_tag=10) + port = {'id': 'port-id', 'of_port': of_port} + self.assertEqual(10, self.mgr.get_device_zone(port)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ip_lib.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_ip_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ip_lib.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_ip_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -24,7 +24,6 @@ from oslo_utils import netutils from oslo_utils import uuidutils import pyroute2 -from pyroute2.netlink.rtnl import ifaddrmsg from pyroute2.netlink.rtnl import ifinfmsg from pyroute2.netlink.rtnl import ndmsg from pyroute2 import NetlinkError @@ -791,7 +790,7 @@ self.addr_cmd.list(scope='link') mock_get_dev_ip.assert_called_once_with('test_ns', name=self.addr_cmd.name, - scope=253) + scope='link') @mock.patch.object(ip_lib, 'get_devices_with_ip') def test_list_to(self, mock_get_dev_ip): @@ -865,31 +864,6 @@ self.netns_cmd.delete('ns') remove.assert_called_once_with('ns') - @mock.patch.object(pyroute2.netns, 'listnetns') - @mock.patch.object(priv_lib, 'list_netns') - def test_namespace_exists_use_helper(self, priv_listnetns, listnetns): - self.config(group='AGENT', use_helper_for_ns_read=True) - priv_listnetns.return_value = NETNS_SAMPLE - # need another instance to avoid mocking - netns_cmd = ip_lib.IpNetnsCommand(ip_lib.SubProcessBase()) - self.assertTrue( - netns_cmd.exists('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb')) - self.assertEqual(1, priv_listnetns.call_count) - self.assertFalse(listnetns.called) - - @mock.patch.object(pyroute2.netns, 'listnetns') - @mock.patch.object(priv_lib, 'list_netns') - def test_namespace_does_not_exist_no_helper(self, priv_listnetns, - listnetns): - self.config(group='AGENT', use_helper_for_ns_read=False) - listnetns.return_value = NETNS_SAMPLE - # need another instance to avoid mocking - netns_cmd = ip_lib.IpNetnsCommand(ip_lib.SubProcessBase()) - self.assertFalse( - netns_cmd.exists('bbbbbbbb-1111-2222-3333-bbbbbbbbbbbb')) - self.assertEqual(1, listnetns.call_count) - self.assertFalse(priv_listnetns.called) - def test_execute(self): self.parent.namespace = 'ns' with mock.patch('neutron.agent.common.utils.execute') as execute: @@ -1603,39 +1577,6 @@ self.assertEqual(reference, retval) -class ParseLinkDeviceTestCase(base.BaseTestCase): - - def setUp(self): - super(ParseLinkDeviceTestCase, self).setUp() - self._mock_get_ip_addresses = mock.patch.object(priv_lib, - 'get_ip_addresses') - self.mock_get_ip_addresses = self._mock_get_ip_addresses.start() - self.addCleanup(self._stop_mock) - - def _stop_mock(self): - self._mock_get_ip_addresses.stop() - - def test_parse_link_devices(self): - device = ({'index': 1, 'attrs': [['IFLA_IFNAME', 'int_name']]}) - self.mock_get_ip_addresses.return_value = [ - {'prefixlen': 24, 'scope': 200, 'event': 'RTM_NEWADDR', 'attrs': [ - ['IFA_ADDRESS', '192.168.10.20'], - ['IFA_FLAGS', ifaddrmsg.IFA_F_PERMANENT]]}, - {'prefixlen': 64, 'scope': 200, 'event': 'RTM_DELADDR', 'attrs': [ - ['IFA_ADDRESS', '2001:db8::1'], - ['IFA_FLAGS', ifaddrmsg.IFA_F_PERMANENT]]}] - - retval = ip_lib._parse_link_device('namespace', device) - expected = [{'scope': 'site', 'cidr': '192.168.10.20/24', - 'dynamic': False, 'dadfailed': False, 'name': 'int_name', - 'broadcast': None, 'tentative': False, 'event': 'added'}, - {'scope': 'site', 'cidr': '2001:db8::1/64', - 'dynamic': False, 'dadfailed': False, 'name': 'int_name', - 'broadcast': None, 'tentative': False, - 'event': 'removed'}] - self.assertEqual(expected, retval) - - class GetDevicesInfoTestCase(base.BaseTestCase): DEVICE_LO = { diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ip_link_support.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_ip_link_support.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ip_link_support.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_ip_link_support.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,182 +0,0 @@ -# Copyright 2014 Mellanox Technologies, Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import mock - -from neutron.agent.linux import ip_link_support as ip_link -from neutron.tests import base - - -class TestIpLinkSupport(base.BaseTestCase): - IP_LINK_HELP = """Usage: ip link add [link DEV] [ name ] NAME - [ txqueuelen PACKETS ] - [ address LLADDR ] - [ broadcast LLADDR ] - [ mtu MTU ] [index IDX ] - [ numtxqueues QUEUE_COUNT ] - [ numrxqueues QUEUE_COUNT ] - type TYPE [ ARGS ] - ip link delete DEV type TYPE [ ARGS ] - - ip link set { dev DEVICE | group DEVGROUP } [ { up | down } ] - [ arp { on | off } ] - [ dynamic { on | off } ] - [ multicast { on | off } ] - [ allmulticast { on | off } ] - [ promisc { on | off } ] - [ trailers { on | off } ] - [ txqueuelen PACKETS ] - [ name NEWNAME ] - [ address LLADDR ] - [ broadcast LLADDR ] - [ mtu MTU ] - [ netns PID ] - [ netns NAME ] - [ alias NAME ] - [ vf NUM [ mac LLADDR ] - [ vlan VLANID [ qos VLAN-QOS ] ] - [ rate TXRATE ] ] - [ spoofchk { on | off} ] ] - [ state { auto | enable | disable} ] ] - [ master DEVICE ] - [ nomaster ] - ip link show [ DEVICE | group GROUP ] [up] - -TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | macvtap | - can | bridge | bond | ipoib | ip6tnl | ipip | sit | - vxlan | gre | gretap | ip6gre | ip6gretap | vti } - """ - - IP_LINK_HELP_NO_STATE = """Usage: ip link add link DEV [ name ] NAME - [ txqueuelen PACKETS ] - [ address LLADDR ] - [ broadcast LLADDR ] - [ mtu MTU ] - type TYPE [ ARGS ] - ip link delete DEV type TYPE [ ARGS ] - - ip link set DEVICE [ { up | down } ] - [ arp { on | off } ] - [ dynamic { on | off } ] - [ multicast { on | off } ] - [ allmulticast { on | off } ] - [ promisc { on | off } ] - [ trailers { on | off } ] - [ txqueuelen PACKETS ] - [ name NEWNAME ] - [ address LLADDR ] - [ broadcast LLADDR ] - [ mtu MTU ] - [ netns PID ] - [ alias NAME ] - [ vf NUM [ mac LLADDR ] - [ vlan VLANID [ qos VLAN-QOS ] ] - [ rate TXRATE ] ] - ip link show [ DEVICE ] - -TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | can } - """ - - IP_LINK_HELP_NO_SPOOFCHK = IP_LINK_HELP_NO_STATE - - IP_LINK_HELP_NO_VF = """Usage: ip link set DEVICE { up | down | - arp { on | off } | - dynamic { on | off } | - multicast { on | off } | - allmulticast { on | off } | - promisc { on | off } | - trailers { on | off } | - txqueuelen PACKETS | - name NEWNAME | - address LLADDR | broadcast LLADDR | - mtu MTU } - ip link show [ DEVICE ] - - """ - - def _test_capability(self, capability, subcapability=None, - expected=True, stdout="", stderr=""): - with mock.patch("neutron.agent.linux.utils.execute") as mock_exec: - mock_exec.return_value = (stdout, stderr) - vf_section = ip_link.IpLinkSupport.get_vf_mgmt_section() - capable = ip_link.IpLinkSupport.vf_mgmt_capability_supported( - vf_section, capability, subcapability) - self.assertEqual(expected, capable) - mock_exec.assert_called_once_with(['ip', 'link', 'help'], - check_exit_code=False, - return_stderr=True, - log_fail_as_error=False) - - def test_vf_mgmt(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - stderr=self.IP_LINK_HELP) - - def test_execute_with_stdout(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - stdout=self.IP_LINK_HELP) - - def test_vf_mgmt_no_state(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - expected=False, - stderr=self.IP_LINK_HELP_NO_STATE) - - def test_vf_mgmt_no_spoofchk(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_SPOOFCHK, - expected=False, - stderr=self.IP_LINK_HELP_NO_SPOOFCHK) - - def test_vf_mgmt_no_vf(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - expected=False, - stderr=self.IP_LINK_HELP_NO_VF) - - def test_vf_mgmt_unknown_capability(self): - self._test_capability( - "state1", - expected=False, - stderr=self.IP_LINK_HELP) - - def test_vf_mgmt_sub_capability(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_VLAN, - ip_link.IpLinkConstants.IP_LINK_SUB_CAPABILITY_QOS, - stderr=self.IP_LINK_HELP) - - def test_vf_mgmt_sub_capability_mismatch(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - ip_link.IpLinkConstants.IP_LINK_SUB_CAPABILITY_QOS, - expected=False, - stderr=self.IP_LINK_HELP) - - def test_vf_mgmt_sub_capability_invalid(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_VLAN, - "qos1", - expected=False, - stderr=self.IP_LINK_HELP) - - def test_vf_mgmt_error(self): - with mock.patch("neutron.agent.linux.utils.execute") as mock_exec: - mock_exec.side_effect = Exception() - self.assertRaises( - ip_link.UnsupportedIpLinkCommand, - ip_link.IpLinkSupport.get_vf_mgmt_section) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ipset_manager.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_ipset_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_ipset_manager.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_ipset_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -20,8 +20,12 @@ ETHERTYPE = 'IPv4' TEST_SET_NAME = ipset_manager.IpsetManager.get_name(TEST_SET_ID, ETHERTYPE) TEST_SET_NAME_NEW = TEST_SET_NAME + ipset_manager.SWAP_SUFFIX -FAKE_IPS = ['10.0.0.1', '10.0.0.2', '10.0.0.3', '10.0.0.4', - '10.0.0.5', '10.0.0.6'] +FAKE_IPS = [('10.0.0.1', 'fa:16:3e:aa:bb:c1'), + ('10.0.0.2', 'fa:16:3e:aa:bb:c2'), + ('10.0.0.3', 'fa:16:3e:aa:bb:c3'), + ('10.0.0.4', 'fa:16:3e:aa:bb:c4'), + ('10.0.0.5', 'fa:16:3e:aa:bb:c5'), + ('10.0.0.6', 'fa:16:3e:aa:bb:c6')] class BaseIpsetManagerTest(base.BaseTestCase): @@ -149,13 +153,15 @@ self.verify_mock_calls() def test_set_members_adding_all_zero_ipv4(self): - self.expect_set(['0.0.0.0/0']) - self.ipset.set_members(TEST_SET_ID, ETHERTYPE, ['0.0.0.0/0']) + self.expect_set([('0.0.0.0/0', 'fa:16:3e:aa:bb:c1'), ]) + self.ipset.set_members(TEST_SET_ID, ETHERTYPE, + [('0.0.0.0/0', 'fa:16:3e:aa:bb:c1'), ]) self.verify_mock_calls() def test_set_members_adding_all_zero_ipv6(self): - self.expect_set(['::/0']) - self.ipset.set_members(TEST_SET_ID, ETHERTYPE, ['::/0']) + self.expect_set([('::/0', 'fa:16:3e:aa:bb:c1'), ]) + self.ipset.set_members(TEST_SET_ID, ETHERTYPE, + [('::/0', 'fa:16:3e:aa:bb:c1'), ]) self.verify_mock_calls() def test_destroy(self): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_iptables_firewall.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_iptables_firewall.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_iptables_firewall.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_iptables_firewall.py 2021-11-12 13:56:42.000000000 +0000 @@ -118,6 +118,15 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase): + def test__get_port_device_name(self): + self.assertEqual( + "name", + self.firewall._get_port_device_name({'device': 'name'})) + self.assertEqual( + "name", + self.firewall._get_port_device_name( + {'device': '%s_name' % constants.TAP_DEVICE_PREFIX})) + def test_prepare_port_filter_with_no_sg(self): port = self._fake_port() self.firewall.prepare_port_filter(port) @@ -1527,12 +1536,16 @@ if ethertype == "IPv4": ethertype = "ipv4" - members_add = {'IPv4': ['10.0.0.2', '10.0.0.3']} - members_after_delete = {'IPv4': ['10.0.0.3']} + members_add = {'IPv4': [('10.0.0.2', 'fa:16:3e:aa:bb:c1'), + ('10.0.0.3', 'fa:16:3e:aa:bb:c2')]} + members_after_delete = { + 'IPv4': [('10.0.0.3', 'fa:16:3e:aa:bb:c2'), ]} else: ethertype = "ipv6" - members_add = {'IPv6': ['fe80::2', 'fe80::3']} - members_after_delete = {'IPv6': ['fe80::3']} + members_add = {'IPv6': [('fe80::2', 'fa:16:3e:aa:bb:c3'), + ('fe80::3', 'fa:16:3e:aa:bb:c4')]} + members_after_delete = { + 'IPv6': [('fe80::3', 'fa:16:3e:aa:bb:c4'), ]} with mock.patch.dict(self.firewall.ipconntrack._device_zone_map, {port['network_id']: ct_zone}): @@ -2073,6 +2086,22 @@ mock.call.add_rule('sg-chain', '-j ACCEPT')] self.v4filter_inst.assert_has_calls(calls) + def test__get_sg_members(self): + sg_info = {_uuid(): {constants.IPv4: [['ip1', None]], + constants.IPv6: []}, + _uuid(): {constants.IPv4: [['ip2', None], ['ip3', None]], + constants.IPv6: [['ip4', None]]}, + } + sg_ids = list(sg_info.keys()) + self.assertEqual({'ip1'}, self.firewall._get_sg_members( + sg_info, sg_ids[0], constants.IPv4)) + self.assertEqual(set([]), self.firewall._get_sg_members( + sg_info, sg_ids[0], constants.IPv6)) + self.assertEqual({'ip2', 'ip3'}, self.firewall._get_sg_members( + sg_info, sg_ids[1], constants.IPv4)) + self.assertEqual({'ip4'}, self.firewall._get_sg_members( + sg_info, sg_ids[1], constants.IPv6)) + class IptablesFirewallEnhancedIpsetTestCase(BaseIptablesFirewallTestCase): def setUp(self): @@ -2304,10 +2333,12 @@ self.firewall.ipset.assert_has_calls(calls, True) def test_sg_rule_expansion_with_remote_ips(self): - other_ips = ['10.0.0.2', '10.0.0.3', '10.0.0.4'] + other_ips = [('10.0.0.2', 'fa:16:3e:aa:bb:c1'), + ('10.0.0.3', 'fa:16:3e:aa:bb:c2'), + ('10.0.0.4', 'fa:16:3e:aa:bb:c3')] self.firewall.sg_members = {'fake_sgid': { - 'IPv4': [FAKE_IP['IPv4']] + other_ips, - 'IPv6': [FAKE_IP['IPv6']]}} + 'IPv4': [(FAKE_IP['IPv4'], 'fa:16:3e:aa:bb:c4'), ] + other_ips, + 'IPv6': [(FAKE_IP['IPv6'], 'fa:16:3e:aa:bb:c5'), ]}} port = self._fake_port() rule = self._fake_sg_rule_for_ethertype(_IPv4, FAKE_SGID) @@ -2316,7 +2347,7 @@ self.assertEqual(list(rules), [dict(list(rule.items()) + [('source_ip_prefix', '%s/32' % ip)]) - for ip in other_ips]) + for ip, _mac in other_ips]) def test_build_ipv4v6_mac_ip_list(self): mac_oth = 'ffff-ff0f-ffff' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_keepalived.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_keepalived.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_keepalived.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_keepalived.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,13 +14,18 @@ # import os +import signal import textwrap import mock from neutron_lib import constants as n_consts +from oslo_config import cfg +from oslo_utils import uuidutils import testtools +from neutron.agent.linux import external_process from neutron.agent.linux import keepalived +from neutron.conf.agent.l3 import config as l3_config from neutron.tests import base # Keepalived user guide: @@ -37,7 +42,14 @@ VRRP_INTERVAL = 5 -class KeepalivedGetFreeRangeTestCase(base.BaseTestCase): +class KeepalivedBaseTestCase(base.BaseTestCase): + + def setUp(self): + super(KeepalivedBaseTestCase, self).setUp() + l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF) + + +class KeepalivedGetFreeRangeTestCase(KeepalivedBaseTestCase): def test_get_free_range(self): free_range = keepalived.get_free_range( parent_range='169.254.0.0/16', @@ -83,16 +95,16 @@ instance1.track_interfaces.append("eth0") vip_address1 = keepalived.KeepalivedVipAddress('192.168.1.0/24', - 'eth1') + 'eth1', track=False) vip_address2 = keepalived.KeepalivedVipAddress('192.168.2.0/24', - 'eth2') + 'eth2', track=False) vip_address3 = keepalived.KeepalivedVipAddress('192.168.3.0/24', - 'eth2') + 'eth2', track=False) vip_address_ex = keepalived.KeepalivedVipAddress('192.168.55.0/24', - 'eth10') + 'eth10', track=False) instance1.vips.append(vip_address1) instance1.vips.append(vip_address2) @@ -110,7 +122,7 @@ instance2.track_interfaces.append("eth4") vip_address1 = keepalived.KeepalivedVipAddress('192.168.3.0/24', - 'eth6') + 'eth6', track=False) instance2.vips.append(vip_address1) instance2.vips.append(vip_address2) @@ -122,7 +134,7 @@ return config -class KeepalivedConfTestCase(base.BaseTestCase, +class KeepalivedConfTestCase(KeepalivedBaseTestCase, KeepalivedConfBaseMixin): expected = KEEPALIVED_GLOBAL_CONFIG + textwrap.dedent(""" @@ -144,13 +156,13 @@ 169.254.0.1/24 dev eth0 } virtual_ipaddress_excluded { - 192.168.1.0/24 dev eth1 - 192.168.2.0/24 dev eth2 - 192.168.3.0/24 dev eth2 - 192.168.55.0/24 dev eth10 + 192.168.1.0/24 dev eth1 no_track + 192.168.2.0/24 dev eth2 no_track + 192.168.3.0/24 dev eth2 no_track + 192.168.55.0/24 dev eth10 no_track } virtual_routes { - 0.0.0.0/0 via 192.168.1.1 dev eth1 + 0.0.0.0/0 via 192.168.1.1 dev eth1 no_track } } vrrp_instance VR_2 { @@ -167,9 +179,9 @@ 169.254.0.2/24 dev eth4 } virtual_ipaddress_excluded { - 192.168.2.0/24 dev eth2 - 192.168.3.0/24 dev eth6 - 192.168.55.0/24 dev eth10 + 192.168.2.0/24 dev eth2 no_track + 192.168.3.0/24 dev eth6 no_track + 192.168.55.0/24 dev eth10 no_track } }""") @@ -191,7 +203,62 @@ self.assertEqual(['192.168.2.0/24', '192.168.3.0/24'], current_vips) -class KeepalivedStateExceptionTestCase(base.BaseTestCase): +class KeepalivedConfWithoutNoTrackTestCase(KeepalivedConfTestCase): + + expected = KEEPALIVED_GLOBAL_CONFIG + textwrap.dedent(""" + vrrp_instance VR_1 { + state MASTER + interface eth0 + virtual_router_id 1 + priority 50 + garp_master_delay 60 + advert_int 5 + authentication { + auth_type AH + auth_pass pass123 + } + track_interface { + eth0 + } + virtual_ipaddress { + 169.254.0.1/24 dev eth0 + } + virtual_ipaddress_excluded { + 192.168.1.0/24 dev eth1 + 192.168.2.0/24 dev eth2 + 192.168.3.0/24 dev eth2 + 192.168.55.0/24 dev eth10 + } + virtual_routes { + 0.0.0.0/0 via 192.168.1.1 dev eth1 + } + } + vrrp_instance VR_2 { + state MASTER + interface eth4 + virtual_router_id 2 + priority 50 + garp_master_delay 60 + mcast_src_ip 224.0.0.1 + track_interface { + eth4 + } + virtual_ipaddress { + 169.254.0.2/24 dev eth4 + } + virtual_ipaddress_excluded { + 192.168.2.0/24 dev eth2 + 192.168.3.0/24 dev eth6 + 192.168.55.0/24 dev eth10 + } + }""") + + def setUp(self): + super(KeepalivedConfWithoutNoTrackTestCase, self).setUp() + cfg.CONF.set_override('keepalived_use_no_track', False) + + +class KeepalivedStateExceptionTestCase(KeepalivedBaseTestCase): def test_state_exception(self): invalid_vrrp_state = 'a seal walks' self.assertRaises(keepalived.InvalidInstanceStateException, @@ -207,7 +274,7 @@ invalid_auth_type, 'some_password') -class KeepalivedInstanceRoutesTestCase(base.BaseTestCase): +class KeepalivedInstanceRoutesTestCase(KeepalivedBaseTestCase): @classmethod def _get_instance_routes(cls): routes = keepalived.KeepalivedInstanceRoutes() @@ -239,24 +306,36 @@ def test_build_config(self): expected = """ virtual_routes { + 0.0.0.0/0 via 1.0.0.254 dev eth0 no_track + ::/0 via fe80::3e97:eff:fe26:3bfa/64 dev eth1 no_track + 10.0.0.0/8 via 1.0.0.1 no_track + 20.0.0.0/8 via 2.0.0.2 no_track + 30.0.0.0/8 dev eth0 scope link no_track + }""" + routes = self._get_instance_routes() + self.assertEqual(expected, '\n'.join(routes.build_config())) + + def test_build_config_without_no_track_option(self): + expected = """ virtual_routes { 0.0.0.0/0 via 1.0.0.254 dev eth0 ::/0 via fe80::3e97:eff:fe26:3bfa/64 dev eth1 10.0.0.0/8 via 1.0.0.1 20.0.0.0/8 via 2.0.0.2 30.0.0.0/8 dev eth0 scope link }""" + cfg.CONF.set_override('keepalived_use_no_track', False) routes = self._get_instance_routes() self.assertEqual(expected, '\n'.join(routes.build_config())) -class KeepalivedInstanceTestCase(base.BaseTestCase, +class KeepalivedInstanceTestCase(KeepalivedBaseTestCase, KeepalivedConfBaseMixin): def test_get_primary_vip(self): instance = keepalived.KeepalivedInstance('MASTER', 'ha0', 42, ['169.254.192.0/18']) self.assertEqual('169.254.0.42/24', instance.get_primary_vip()) - def test_remove_addresses_by_interface(self): + def _test_remove_addresses_by_interface(self, no_track_value): config = self._get_config() instance = config.get_instance(1) instance.remove_vips_vroutes_by_interface('eth2') @@ -281,10 +360,10 @@ 169.254.0.1/24 dev eth0 } virtual_ipaddress_excluded { - 192.168.1.0/24 dev eth1 + 192.168.1.0/24 dev eth1%(no_track)s } virtual_routes { - 0.0.0.0/0 via 192.168.1.1 dev eth1 + 0.0.0.0/0 via 192.168.1.1 dev eth1%(no_track)s } } vrrp_instance VR_2 { @@ -301,14 +380,21 @@ 169.254.0.2/24 dev eth4 } virtual_ipaddress_excluded { - 192.168.2.0/24 dev eth2 - 192.168.3.0/24 dev eth6 - 192.168.55.0/24 dev eth10 + 192.168.2.0/24 dev eth2%(no_track)s + 192.168.3.0/24 dev eth6%(no_track)s + 192.168.55.0/24 dev eth10%(no_track)s } - }""") + }""" % {'no_track': no_track_value}) self.assertEqual(expected, config.get_config_str()) + def test_remove_addresses_by_interface(self): + self._test_remove_addresses_by_interface(" no_track") + + def test_remove_addresses_by_interface_without_no_track(self): + cfg.CONF.set_override('keepalived_use_no_track', False) + self._test_remove_addresses_by_interface("") + def test_build_config_no_vips(self): expected = textwrap.dedent("""\ vrrp_instance VR_1 { @@ -351,7 +437,7 @@ self.assertEqual(expected, '\n'.join(instance.build_config())) -class KeepalivedVipAddressTestCase(base.BaseTestCase): +class KeepalivedVipAddressTestCase(KeepalivedBaseTestCase): def test_vip_with_scope(self): vip = keepalived.KeepalivedVipAddress('fe80::3e97:eff:fe26:3bfa/64', 'eth1', @@ -367,19 +453,32 @@ self.assertEqual(1, len(instance.vips)) -class KeepalivedVirtualRouteTestCase(base.BaseTestCase): +class KeepalivedVirtualRouteTestCase(KeepalivedBaseTestCase): def test_virtual_route_with_dev(self): route = keepalived.KeepalivedVirtualRoute(n_consts.IPv4_ANY, '1.2.3.4', 'eth0') + self.assertEqual('0.0.0.0/0 via 1.2.3.4 dev eth0 no_track', + route.build_config()) + + def test_virtual_route_with_dev_without_no_track(self): + cfg.CONF.set_override('keepalived_use_no_track', False) + route = keepalived.KeepalivedVirtualRoute(n_consts.IPv4_ANY, '1.2.3.4', + 'eth0') self.assertEqual('0.0.0.0/0 via 1.2.3.4 dev eth0', route.build_config()) def test_virtual_route_without_dev(self): route = keepalived.KeepalivedVirtualRoute('50.0.0.0/8', '1.2.3.4') + self.assertEqual('50.0.0.0/8 via 1.2.3.4 no_track', + route.build_config()) + + def test_virtual_route_without_dev_without_no_track(self): + cfg.CONF.set_override('keepalived_use_no_track', False) + route = keepalived.KeepalivedVirtualRoute('50.0.0.0/8', '1.2.3.4') self.assertEqual('50.0.0.0/8 via 1.2.3.4', route.build_config()) -class KeepalivedTrackScriptTestCase(base.BaseTestCase): +class KeepalivedTrackScriptTestCase(KeepalivedBaseTestCase): def test_build_config_preamble(self): exp_conf = [ @@ -447,3 +546,40 @@ with mock.patch.object(keepalived, 'file_utils') as patched_utils: ts.write_check_script() patched_utils.replace_file.assert_not_called() + + +class KeepalivedManagerTestCase(base.BaseTestCase): + + def setUp(self): + super().setUp() + self.mock_config = mock.Mock() + self.mock_config.AGENT.check_child_processes_interval = False + self.process_monitor = external_process.ProcessMonitor( + self.mock_config, mock.ANY) + self.uuid = uuidutils.generate_uuid() + self.process_monitor.register( + self.uuid, keepalived.KEEPALIVED_SERVICE_NAME, mock.ANY) + self.keepalived_manager = keepalived.KeepalivedManager( + self.uuid, self.mock_config, self.process_monitor, mock.ANY) + self.mock_get_process = mock.patch.object(self.keepalived_manager, + 'get_process') + + def test_destroy(self): + mock_get_process = self.mock_get_process.start() + process = mock.Mock() + mock_get_process.return_value = process + process.active = False + self.keepalived_manager.disable() + process.disable.assert_called_once_with( + sig=str(int(signal.SIGTERM))) + + def test_destroy_force(self): + mock_get_process = self.mock_get_process.start() + with mock.patch.object(keepalived, 'SIGTERM_TIMEOUT', 0): + process = mock.Mock() + mock_get_process.return_value = process + process.active = True + self.keepalived_manager.disable() + process.disable.assert_has_calls([ + mock.call(sig=str(int(signal.SIGTERM))), + mock.call(sig=str(int(signal.SIGKILL)))]) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_pd.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_pd.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_pd.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_pd.py 2021-11-12 13:56:42.000000000 +0000 @@ -46,7 +46,9 @@ pd_router = l3_agent.pd.routers.get(router.router_id) self.assertEqual(ns_name, pd_router.get('ns_name')) - def test_add_update_dvr_edge_router(self): + @mock.patch.object(dvr_edge_router.DvrEdgeRouter, + '_load_used_fip_information') + def test_add_update_dvr_edge_router(self, *args): l3_agent = mock.Mock() l3_agent.pd.routers = {} router_id = '1' @@ -59,7 +61,9 @@ ns_name = ri.snat_namespace.name self._test_add_update_pd(l3_agent, ri, ns_name) - def test_add_update_dvr_local_router(self): + @mock.patch.object(dvr_local_router.DvrLocalRouter, + '_load_used_fip_information') + def test_add_update_dvr_local_router(self, *args): l3_agent = mock.Mock() l3_agent.pd.routers = {} router_id = '1' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_tc_lib.py neutron-16.4.2/neutron/tests/unit/agent/linux/test_tc_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/linux/test_tc_lib.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/linux/test_tc_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -270,9 +270,9 @@ tc_lib.add_tc_qdisc('device', 'tbf', parent='root', max_kbps=10000, burst_kb=1500, latency_ms=70, kernel_hz=250, namespace=self.namespace) - burst = tc_lib._get_tbf_burst_value(10000, 1500, 70) * 1024 / 8 + burst = tc_lib._get_tbf_burst_value(10000, 1500, 70) * 1000 / 8 self.mock_add_tc_qdisc.assert_called_once_with( - 'device', parent=rtnl.TC_H_ROOT, kind='tbf', rate=10000 * 128, + 'device', parent=rtnl.TC_H_ROOT, kind='tbf', rate=10000 * 125, burst=burst, latency=70000, namespace=self.namespace) def test_add_tc_qdisc_tbf_missing_arguments(self): @@ -317,8 +317,8 @@ self.assertEqual('root', qdiscs[0]['parent']) self.assertEqual('5:1', qdiscs[0]['handle']) self.assertEqual('tbf', qdiscs[0]['qdisc_type']) - self.assertEqual(2500, qdiscs[0]['max_kbps']) - self.assertEqual(1500, qdiscs[0]['burst_kb']) + self.assertEqual(2560, qdiscs[0]['max_kbps']) + self.assertEqual(1536, qdiscs[0]['burst_kb']) self.assertEqual(50, qdiscs[0]['latency_ms']) def test__get_tbf_burst_value_when_burst_bigger_then_minimal(self): @@ -345,8 +345,8 @@ 'device', 'root', '1:10', min_kbps=1000, max_kbps=2000, burst_kb=1600, namespace=self.namespace) self.mock_add_tc_policy_class.assert_called_once_with( - 'device', rtnl.TC_H_ROOT, '1:10', 'htb', rate=1000 * 128, - ceil=2000 * 128, burst=1600 * 128, namespace=self.namespace) + 'device', rtnl.TC_H_ROOT, '1:10', 'htb', rate=1000 * 125, + ceil=2000 * 125, burst=1600 * 125, namespace=self.namespace) @mock.patch('pyroute2.netlink.rtnl.tcmsg.common.tick_in_usec', 15.625) def test_list_tc_policy_classes(self): @@ -366,9 +366,9 @@ 'parent': 'root', 'classid': '1:1', 'qdisc_type': 'htb', - 'min_kbps': 1500, - 'max_kbps': 2000, - 'burst_kb': 1200} + 'min_kbps': 1536, + 'max_kbps': 2048, + 'burst_kb': 1228} self.assertEqual(reference, _class) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/metadata/test_driver.py neutron-16.4.2/neutron/tests/unit/agent/metadata/test_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/metadata/test_driver.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/metadata/test_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. import os +import signal import mock from neutron_lib import constants @@ -24,6 +25,7 @@ from neutron.agent.l3 import agent as l3_agent from neutron.agent.l3 import router_info from neutron.agent.linux import iptables_manager +from neutron.agent.linux import utils as linux_utils from neutron.agent.metadata import driver as metadata_driver from neutron.conf.agent import common as agent_config from neutron.conf.agent.l3 import config as l3_config @@ -35,6 +37,12 @@ _uuid = uuidutils.generate_uuid +class FakeL3NATAgent(object): + + def __init__(self): + self.conf = cfg.CONF + + class TestMetadataDriverRules(base.BaseTestCase): def test_metadata_nat_rules(self): @@ -72,6 +80,11 @@ mock.patch('neutron.agent.l3.agent.L3PluginApi').start() mock.patch('neutron.agent.l3.ha.AgentMixin' '._init_ha_conf_path').start() + self.delete_if_exists = mock.patch.object(linux_utils, + 'delete_if_exists') + self.mock_get_process = mock.patch.object( + metadata_driver.MetadataDriver, + '_get_metadata_proxy_process_manager') l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF) ha_conf.register_l3_agent_ha_opts() @@ -196,3 +209,33 @@ mock.ANY, mock.ANY) self.assertRaises(metadata_driver.InvalidUserOrGroupException, config.create_config_file) + + def test_destroy_monitored_metadata_proxy(self): + delete_if_exists = self.delete_if_exists.start() + mproxy_process = mock.Mock( + active=False, get_pid_file_name=mock.Mock(return_value='pid_file')) + mock_get_process = self.mock_get_process.start() + mock_get_process.return_value = mproxy_process + driver = metadata_driver.MetadataDriver(FakeL3NATAgent()) + driver.destroy_monitored_metadata_proxy(mock.Mock(), 'uuid', 'conf', + 'ns_name') + mproxy_process.disable.assert_called_once_with( + sig=str(int(signal.SIGTERM))) + delete_if_exists.assert_has_calls([ + mock.call('pid_file', run_as_root=True)]) + + def test_destroy_monitored_metadata_proxy_force(self): + delete_if_exists = self.delete_if_exists.start() + mproxy_process = mock.Mock( + active=True, get_pid_file_name=mock.Mock(return_value='pid_file')) + mock_get_process = self.mock_get_process.start() + mock_get_process.return_value = mproxy_process + driver = metadata_driver.MetadataDriver(FakeL3NATAgent()) + with mock.patch.object(metadata_driver, 'SIGTERM_TIMEOUT', 0): + driver.destroy_monitored_metadata_proxy(mock.Mock(), 'uuid', + 'conf', 'ns_name') + mproxy_process.disable.assert_has_calls([ + mock.call(sig=str(int(signal.SIGTERM))), + mock.call(sig=str(int(signal.SIGKILL)))]) + delete_if_exists.assert_has_calls([ + mock.call('pid_file', run_as_root=True)]) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/ovn/metadata/test_agent.py neutron-16.4.2/neutron/tests/unit/agent/ovn/metadata/test_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/ovn/metadata/test_agent.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/ovn/metadata/test_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -32,7 +32,7 @@ OvnPortInfo = collections.namedtuple( 'OvnPortInfo', ['datapath', 'type', 'mac', 'external_ids', 'logical_port']) -DatapathInfo = collections.namedtuple('DatapathInfo', 'uuid') +DatapathInfo = collections.namedtuple('DatapathInfo', ['uuid', 'external_ids']) def makePort(datapath=None, type='', mac=None, external_ids=None, @@ -118,11 +118,14 @@ ports = [] for i in range(0, 3): - ports.append(makePort(datapath=DatapathInfo(uuid=str(i)))) - ports.append(makePort(datapath=DatapathInfo(uuid='1'))) - ports.append(makePort(datapath=DatapathInfo(uuid='3'), - type='external')) - ports.append(makePort(datapath=DatapathInfo(uuid='5'), type='unknown')) + ports.append(makePort(datapath=DatapathInfo(uuid=str(i), + external_ids={'name': 'neutron-%d' % i}))) + ports.append(makePort(datapath=DatapathInfo(uuid='1', + external_ids={'name': 'neutron-1'}))) + ports.append(makePort(datapath=DatapathInfo(uuid='3', + external_ids={'name': 'neutron-3'}), type='external')) + ports.append(makePort(datapath=DatapathInfo(uuid='5', + external_ids={'name': 'neutron-5'}), type='unknown')) with mock.patch.object(self.agent, 'provision_datapath', return_value=None) as pdp,\ @@ -130,40 +133,42 @@ return_value=ports): self.agent.ensure_all_networks_provisioned() - expected_calls = [mock.call(str(i)) for i in range(0, 4)] + expected_calls = [mock.call(str(i), str(i)) for i in range(0, 4)] self.assertEqual(sorted(expected_calls), sorted(pdp.call_args_list)) def test_update_datapath_provision(self): ports = [] for i in range(0, 3): - ports.append(makePort(datapath=DatapathInfo(uuid=str(i)))) - ports.append(makePort(datapath=DatapathInfo(uuid='3'), - type='external')) + ports.append(makePort(datapath=DatapathInfo(uuid=str(i), + external_ids={'name': 'neutron-%d' % i}))) + ports.append(makePort(datapath=DatapathInfo(uuid='3', + external_ids={'name': 'neutron-3'}), type='external')) with mock.patch.object(self.agent, 'provision_datapath', return_value=None) as pdp,\ mock.patch.object(self.agent, 'teardown_datapath') as tdp,\ mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis', return_value=ports): - self.agent.update_datapath('1') - self.agent.update_datapath('3') - expected_calls = [mock.call('1'), mock.call('3')] + self.agent.update_datapath('1', 'a') + self.agent.update_datapath('3', 'b') + expected_calls = [mock.call('1', 'a'), mock.call('3', 'b')] pdp.assert_has_calls(expected_calls) tdp.assert_not_called() def test_update_datapath_teardown(self): ports = [] for i in range(0, 3): - ports.append(makePort(datapath=DatapathInfo(uuid=str(i)))) + ports.append(makePort(datapath=DatapathInfo(uuid=str(i), + external_ids={'name': 'neutron-%d' % i}))) with mock.patch.object(self.agent, 'provision_datapath', return_value=None) as pdp,\ mock.patch.object(self.agent, 'teardown_datapath') as tdp,\ mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis', return_value=ports): - self.agent.update_datapath('5') - tdp.assert_called_once_with('5') + self.agent.update_datapath('5', 'a') + tdp.assert_called_once_with('5', 'a') pdp.assert_not_called() def test_teardown_datapath(self): @@ -234,13 +239,15 @@ 'update_chassis_metadata_networks') as update_chassis,\ mock.patch.object( driver.MetadataDriver, - 'spawn_monitored_metadata_proxy') as spawn_mdp: + 'spawn_monitored_metadata_proxy') as spawn_mdp, \ + mock.patch.object( + self.agent, '_ensure_datapath_checksum') as mock_checksum: # Simulate that the VETH pair was already present in 'br-fake'. # We need to assert that it was deleted first. self.agent.ovs_idl.list_br.return_value.execute.return_value = ( ['br-int', 'br-fake']) - self.agent.provision_datapath('1') + self.agent.provision_datapath('1', '1') # Check that the port was deleted from br-fake self.agent.ovs_idl.del_port.assert_called_once_with( @@ -267,6 +274,7 @@ bind_address='169.254.169.254', network_id='1') # Check that the chassis has been updated with the datapath. update_chassis.assert_called_once_with('1') + mock_checksum.assert_called_once_with('namespace') def _test_update_chassis_metadata_networks_helper( self, dp, remove, expected_dps, txn_called=True): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/ovn/metadata/test_server.py neutron-16.4.2/neutron/tests/unit/agent/ovn/metadata/test_server.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/ovn/metadata/test_server.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/ovn/metadata/test_server.py 2021-11-12 13:56:42.000000000 +0000 @@ -27,7 +27,8 @@ from neutron.conf.agent.ovn.metadata import config as ovn_meta_conf from neutron.tests import base -OvnPortInfo = collections.namedtuple('OvnPortInfo', 'external_ids') +OvnPortInfo = collections.namedtuple( + 'OvnPortInfo', ['external_ids', 'chassis']) class ConfFixture(config_fixture.Config): @@ -54,8 +55,9 @@ self.useFixture(self.fake_conf_fixture) self.log_p = mock.patch.object(agent, 'LOG') self.log = self.log_p.start() - self.handler = agent.MetadataProxyHandler(self.fake_conf) + self.handler = agent.MetadataProxyHandler(self.fake_conf, 'chassis1') self.handler.sb_idl = mock.Mock() + self.handler._post_fork_event.set() def test_call(self): req = mock.Mock() @@ -113,7 +115,8 @@ ovn_port = OvnPortInfo( external_ids={'neutron:device_id': 'device_id', - 'neutron:project_id': 'project_id'}) + 'neutron:project_id': 'project_id'}, + chassis=['chassis1']) ports = [[ovn_port]] self.assertEqual( @@ -220,14 +223,14 @@ @mock.patch.object(fileutils, 'ensure_tree') def test_init_doesnot_exists(self, ensure_dir): - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') ensure_dir.assert_called_once_with('/the', mode=0o755) def test_init_exists(self): with mock.patch('os.path.isdir') as isdir: with mock.patch('os.unlink') as unlink: isdir.return_value = True - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') unlink.assert_called_once_with('/the/path') def test_init_exists_unlink_no_file(self): @@ -238,7 +241,7 @@ exists.return_value = False unlink.side_effect = OSError - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') unlink.assert_called_once_with('/the/path') def test_init_exists_unlink_fails_file_still_exists(self): @@ -250,14 +253,14 @@ unlink.side_effect = OSError with testtools.ExpectedException(OSError): - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') unlink.assert_called_once_with('/the/path') @mock.patch.object(agent, 'MetadataProxyHandler') @mock.patch.object(agent_utils, 'UnixDomainWSGIServer') @mock.patch.object(fileutils, 'ensure_tree') def test_run(self, ensure_dir, server, handler): - p = agent.UnixDomainMetadataProxy(self.cfg.CONF) + p = agent.UnixDomainMetadataProxy(self.cfg.CONF, 'chassis1') p.run() ensure_dir.assert_called_once_with('/the', mode=0o755) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/test_securitygroups_rpc.py neutron-16.4.2/neutron/tests/unit/agent/test_securitygroups_rpc.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/test_securitygroups_rpc.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/test_securitygroups_rpc.py 2021-11-12 13:56:42.000000000 +0000 @@ -307,22 +307,48 @@ allowed_address_pairs=address_pairs) yield self.deserialize(self.fmt, res1) - def test_security_group_info_for_devices_ipv4_addr_pair(self): + def _test_security_group_info_for_devices_ipv4_addr_pair( + self, call_version=None): with self._port_with_addr_pairs_and_security_group() as port: port_id = port['port']['id'] sg_id = port['port']['security_groups'][0] devices = [port_id, 'no_exist_device'] ctx = context.get_admin_context() # verify that address pairs are included in remote SG IPs - sg_member_ips = self.rpc.security_group_info_for_devices( - ctx, devices=devices)['sg_member_ips'] - expected_member_ips = [ - '10.0.1.0/24', '11.0.0.1', - port['port']['fixed_ips'][0]['ip_address']] + if call_version is not None: + sg_member_ips = self.rpc.security_group_info_for_devices( + ctx, devices=devices, + call_version=call_version)['sg_member_ips'] + else: + sg_member_ips = self.rpc.security_group_info_for_devices( + ctx, devices=devices)['sg_member_ips'] + if call_version == '1.3': + expected_member_ips = [ + ('10.0.1.0/24', '00:00:00:00:00:01'), + ('11.0.0.1', '00:00:00:00:00:01'), + (port['port']['fixed_ips'][0]['ip_address'], + None)] + else: + expected_member_ips = [ + '10.0.1.0/24', + '11.0.0.1', + port['port']['fixed_ips'][0]['ip_address']] self.assertEqual(sorted(expected_member_ips), sorted(sg_member_ips[sg_id]['IPv4'])) self._delete('ports', port_id) + def test_security_group_info_for_devices_ipv4_addr_pair(self): + self._test_security_group_info_for_devices_ipv4_addr_pair( + call_version='1.3') + + def test_security_group_info_for_devices_ipv4_addr_pair_low_version(self): + self._test_security_group_info_for_devices_ipv4_addr_pair( + call_version='1.2') + + def test_security_group_info_for_devices_ipv4_addr_pair_backward_cmp( + self): + self._test_security_group_info_for_devices_ipv4_addr_pair() + def test_security_group_rules_for_devices_ipv4_ingress_addr_pair(self): fake_prefix = FAKE_PREFIX[const.IPv4] with self._port_with_addr_pairs_and_security_group() as port: @@ -510,7 +536,7 @@ port_ip2 = ports_rest2['port']['fixed_ips'][0]['ip_address'] ctx = context.get_admin_context() ports_rpc = self.rpc.security_group_info_for_devices( - ctx, devices=devices) + ctx, devices=devices, call_version='1.3') expected = { 'security_groups': {sg1_id: [ {'direction': 'egress', 'ethertype': const.IPv4, @@ -525,7 +551,7 @@ 'stateful': True} ]}, 'sg_member_ips': {sg2_id: { - 'IPv4': set([port_ip2]), + 'IPv4': set([(port_ip2, None), ]), 'IPv6': set(), }} } @@ -2922,7 +2948,9 @@ self.devices_info1 = {'security_groups': {'security_group1': rule1}, 'sg_member_ips': { 'security_group1': { - 'IPv4': ['10.0.0.3/32'], 'IPv6': []}}, + 'IPv4': [('10.0.0.3/32', + 'fa:16:3e:aa:bb:c1'), ], + 'IPv6': []}}, 'devices': devices_info1} devices_info2 = collections.OrderedDict([ ('tap_port1', self._device('tap_port1', @@ -2937,13 +2965,19 @@ self.devices_info2 = {'security_groups': {'security_group1': rule1}, 'sg_member_ips': { 'security_group1': { - 'IPv4': ['10.0.0.3/32', '10.0.0.4/32'], + 'IPv4': [('10.0.0.3/32', + 'fa:16:3e:aa:bb:c1'), + ('10.0.0.4/32', + 'fa:16:3e:aa:bb:c2')], 'IPv6': []}}, 'devices': devices_info2} self.devices_info3 = {'security_groups': {'security_group1': rule2}, 'sg_member_ips': { 'security_group1': { - 'IPv4': ['10.0.0.3/32', '10.0.0.4/32'], + 'IPv4': [('10.0.0.3/32', + 'fa:16:3e:aa:bb:c1'), + ('10.0.0.4/32', + 'fa:16:3e:aa:bb:c2')], 'IPv6': []}}, 'devices': devices_info2} diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/windows/test_utils.py neutron-16.4.2/neutron/tests/unit/agent/windows/test_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/agent/windows/test_utils.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/agent/windows/test_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,6 +26,13 @@ from neutron.tests import base +class x_wmi(Exception): + def __init__(self, info='', com_error=None): + super(x_wmi, self).__init__(info) + self.info = info + self.com_error = com_error + + @ddt.ddt class WindowsUtilsTestCase(base.BaseTestCase): @mock.patch('os.environ', {mock.sentinel.key0: mock.sentinel.val0}) @@ -87,6 +94,25 @@ if pid: mock_conn.Win32_Process.assert_called_once_with(ProcessId=pid) + @ddt.data({}, + {"hresult": 0xff, + "expect_exc": True}) + @ddt.unpack + @mock.patch.object(utils, 'wmi', create=True) + def test_get_wmi_process_exc(self, mock_wmi, expect_exc=False, + hresult=0x800703FA): + mock_conn = mock_wmi.WMI.return_value + mock_wmi.x_wmi = x_wmi + com_error = mock.Mock(hresult=hresult) + exc = x_wmi(com_error=com_error) + mock_conn.Win32_Process.side_effect = exc + + if expect_exc: + self.assertRaises( + x_wmi, utils._get_wmi_process, mock.sentinel.pid) + else: + self.assertIsNone(utils._get_wmi_process(mock.sentinel.pid)) + @ddt.data(True, False) @mock.patch.object(utils, '_get_wmi_process') def test_kill_process(self, process_exists, mock_get_process): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py neutron-16.4.2/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py 2021-11-12 13:56:42.000000000 +0000 @@ -85,12 +85,12 @@ new_agents=None, existing_agents=[], expected_casts=0, expected_warnings=1) - def _test__get_enabled_agents(self, network, + def _test__get_enabled_agents(self, network_id, agents=None, port_count=0, expected_warnings=0, expected_errors=0): self.notifier.plugin.get_ports_count.return_value = port_count enabled_agents = self.notifier._get_enabled_agents( - mock.ANY, network, agents, mock.ANY, mock.ANY) + mock.Mock(), network_id, None, agents, mock.ANY, mock.ANY) if not cfg.CONF.enable_services_on_agents_with_admin_state_down: agents = [x for x in agents if x.admin_state_up] self.assertEqual(agents, enabled_agents) @@ -104,8 +104,8 @@ agent2 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent2.admin_state_up = False agent2.heartbeat_timestamp = timeutils.utcnow() - network = {'id': 'foo_network_id'} - self._test__get_enabled_agents(network, agents=[agent1]) + self._test__get_enabled_agents(network_id='foo_network_id', + agents=[agent1]) def test__get_enabled_agents_with_inactive_ones(self): agent1 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) @@ -115,17 +115,18 @@ agent2.admin_state_up = True # This is effectively an inactive agent agent2.heartbeat_timestamp = datetime.datetime(2000, 1, 1, 0, 0) - network = {'id': 'foo_network_id'} - self._test__get_enabled_agents(network, + self._test__get_enabled_agents(network_id='foo_network_id', agents=[agent1, agent2], expected_warnings=1, expected_errors=0) def test__get_enabled_agents_with_notification_required(self): network = {'id': 'foo_network_id', 'subnets': ['foo_subnet_id']} + self.notifier.plugin.get_network.return_value = network agent = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent.admin_state_up = False agent.heartbeat_timestamp = timeutils.utcnow() - self._test__get_enabled_agents(network, [agent], port_count=20, + self._test__get_enabled_agents('foo_network_id', + [agent], port_count=20, expected_warnings=0, expected_errors=1) def test__get_enabled_agents_with_admin_state_down(self): @@ -137,8 +138,8 @@ agent2 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent2.admin_state_up = False agent2.heartbeat_timestamp = timeutils.utcnow() - network = {'id': 'foo_network_id'} - self._test__get_enabled_agents(network, agents=[agent1, agent2]) + self._test__get_enabled_agents(network_id='foo_network_id', + agents=[agent1, agent2]) def test__notify_agents_allocate_priority(self): mock_context = mock.MagicMock() @@ -247,7 +248,9 @@ self._test__notify_agents_with_function( lambda: self.notifier._after_router_interface_deleted( mock.ANY, mock.ANY, mock.ANY, context=mock.Mock(), - port={'id': 'foo_port_id', 'network_id': 'foo_network_id'}), + port={'id': 'foo_port_id', 'network_id': 'foo_network_id', + 'fixed_ips': {'subnet_id': 'subnet1', + 'ip_address': '10.0.0.1'}}), expected_scheduling=0, expected_casts=1) def test__fanout_message(self): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/api/rpc/handlers/test_securitygroups_rpc.py neutron-16.4.2/neutron/tests/unit/api/rpc/handlers/test_securitygroups_rpc.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/api/rpc/handlers/test_securitygroups_rpc.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/api/rpc/handlers/test_securitygroups_rpc.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,6 +13,7 @@ # under the License. import mock +import netaddr from neutron_lib import context from oslo_utils import uuidutils @@ -128,19 +129,30 @@ def test_security_group_info_for_devices(self): s1 = self._make_security_group_ovo() - p1 = self._make_port_ovo(ip='1.1.1.1', security_group_ids={s1.id}) + mac_1 = 'fa:16:3e:aa:bb:c1' + p1 = self._make_port_ovo(ip='1.1.1.1', + mac_address=netaddr.EUI(mac_1), + security_group_ids={s1.id}) + mac_2 = 'fa:16:3e:aa:bb:c2' p2 = self._make_port_ovo( ip='2.2.2.2', + mac_address=netaddr.EUI(mac_2), security_group_ids={s1.id}, security=psec.PortSecurity(port_security_enabled=False)) - p3 = self._make_port_ovo(ip='3.3.3.3', security_group_ids={s1.id}, + mac_3 = 'fa:16:3e:aa:bb:c3' + p3 = self._make_port_ovo(ip='3.3.3.3', + mac_address=netaddr.EUI(mac_3), + security_group_ids={s1.id}, device_owner='network:dhcp') ids = [p1.id, p2.id, p3.id] info = self.shim.security_group_info_for_devices(self.ctx, ids) - self.assertIn('1.1.1.1', info['sg_member_ips'][s1.id]['IPv4']) - self.assertIn('2.2.2.2', info['sg_member_ips'][s1.id]['IPv4']) - self.assertIn('3.3.3.3', info['sg_member_ips'][s1.id]['IPv4']) + self.assertIn(('1.1.1.1', str(netaddr.EUI(mac_1))), + info['sg_member_ips'][s1.id]['IPv4']) + self.assertIn(('2.2.2.2', str(netaddr.EUI(mac_2))), + info['sg_member_ips'][s1.id]['IPv4']) + self.assertIn(('3.3.3.3', str(netaddr.EUI(mac_3))), + info['sg_member_ips'][s1.id]['IPv4']) self.assertIn(p1.id, info['devices'].keys()) self.assertIn(p2.id, info['devices'].keys()) # P3 is a trusted port so it doesn't have rules diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/cmd/ovn/test_neutron_ovn_db_sync_util.py neutron-16.4.2/neutron/tests/unit/cmd/ovn/test_neutron_ovn_db_sync_util.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/cmd/ovn/test_neutron_ovn_db_sync_util.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/cmd/ovn/test_neutron_ovn_db_sync_util.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,31 @@ +# Copyright 2020 Canonical Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_config import cfg + +from neutron.cmd.ovn import neutron_ovn_db_sync_util as util +from neutron.tests import base + + +class TestNeutronOVNDBSyncUtil(base.BaseTestCase): + + def test_setup_conf(self): + # the code under test will fail because of the cfg.conf alredy being + # initialized by the BaseTestCase setUp method. Reset. + cfg.CONF.reset() + util.setup_conf() + # The sync tool will fail if these config options are at their default + # value. Validate that the setup code overrides them. LP: #1882020 + self.assertFalse(cfg.CONF.notify_nova_on_port_status_changes) + self.assertFalse(cfg.CONF.notify_nova_on_port_data_changes) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/cmd/test_netns_cleanup.py neutron-16.4.2/neutron/tests/unit/cmd/test_netns_cleanup.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/cmd/test_netns_cleanup.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/cmd/test_netns_cleanup.py 2021-11-12 13:56:42.000000000 +0000 @@ -19,41 +19,9 @@ import testtools from neutron.cmd import netns_cleanup as util +from neutron.privileged.agent.linux import utils as priv_utils from neutron.tests import base -NETSTAT_NETNS_OUTPUT = (""" -Active Internet connections (only servers) -Proto Recv-Q Send-Q Local Address Foreign Address State\ - PID/Program name -tcp 0 0 0.0.0.0:9697 0.0.0.0:* LISTEN\ - 1347/python -raw 0 0 0.0.0.0:112 0.0.0.0:* 7\ - 1279/keepalived -raw 0 0 0.0.0.0:112 0.0.0.0:* 7\ - 1279/keepalived -raw6 0 0 :::58 :::* 7\ - 1349/radvd -Active UNIX domain sockets (only servers) -Proto RefCnt Flags Type State I-Node PID/Program name\ - Path -unix 2 [ ACC ] STREAM LISTENING 82039530 1353/python\ - /tmp/rootwrap-VKSm8a/rootwrap.sock -""") - -NETSTAT_NO_NAMESPACE = (""" -Cannot open network namespace "qrouter-e6f206b2-4e8d-4597-a7e1-c3a20337e9c6":\ - No such file or directory -""") - -NETSTAT_NO_LISTEN_PROCS = (""" -Active Internet connections (only servers) -Proto Recv-Q Send-Q Local Address Foreign Address State\ - PID/Program name -Active UNIX domain sockets (only servers) -Proto RefCnt Flags Type State I-Node PID/Program name\ - Path -""") - class TestNetnsCleanup(base.BaseTestCase): def setUp(self): @@ -189,28 +157,6 @@ self.assertEqual([], ovs_br_cls.mock_calls) self.assertTrue(debug.called) - def _test_find_listen_pids_namespace_helper(self, expected, - netstat_output=None): - with mock.patch('neutron.agent.linux.ip_lib.IPWrapper') as ip_wrap: - ip_wrap.return_value.netns.execute.return_value = netstat_output - observed = util.find_listen_pids_namespace(mock.ANY) - self.assertEqual(expected, observed) - - def test_find_listen_pids_namespace_correct_output(self): - expected = set(['1347', '1279', '1349', '1353']) - self._test_find_listen_pids_namespace_helper(expected, - NETSTAT_NETNS_OUTPUT) - - def test_find_listen_pids_namespace_no_procs(self): - expected = set() - self._test_find_listen_pids_namespace_helper(expected, - NETSTAT_NO_LISTEN_PROCS) - - def test_find_listen_pids_namespace_no_namespace(self): - expected = set() - self._test_find_listen_pids_namespace_helper(expected, - NETSTAT_NO_NAMESPACE) - def _test__kill_listen_processes_helper(self, pids, parents, children, kills_expected, force): def _get_element(dct, x): @@ -234,7 +180,7 @@ mocks['find_fork_top_parent'].side_effect = _find_parent mocks['find_child_pids'].side_effect = _find_childs - with mock.patch.object(util, 'find_listen_pids_namespace', + with mock.patch.object(priv_utils, 'find_listen_pids_namespace', return_value=pids): calls = [] for pid, sig in kills_expected: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/cmd/test_sanity_check.py neutron-16.4.2/neutron/tests/unit/cmd/test_sanity_check.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/cmd/test_sanity_check.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/cmd/test_sanity_check.py 2021-11-12 13:56:42.000000000 +0000 @@ -22,8 +22,11 @@ class TestSanityCheck(base.BaseTestCase): - def test_setup_conf(self): - # verify that configuration can be successfully imported - with mock.patch.object(sanity_check.cfg, 'CONF', - return_value=cfg.ConfigOpts()): + def test_setup_conf_and_enable_test_from_config(self): + # Verify that configuration can be successfully imported and tests are + # correctly loaded, based on the registered configuration parameters. + with mock.patch.object(sanity_check, 'cfg') as mock_cfg: + mock_cfg.CONF = cfg.ConfigOpts() + sanity_check.cfg.CONF.register_cli_opts(sanity_check.OPTS) sanity_check.setup_conf() + sanity_check.enable_tests_from_config() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/ovn/test_acl.py neutron-16.4.2/neutron/tests/unit/common/ovn/test_acl.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/ovn/test_acl.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/common/ovn/test_acl.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,7 +15,6 @@ import mock from neutron_lib import constants as const -from oslo_config import cfg from ovsdbapp.backend.ovs_idl import idlutils from neutron.common.ovn import acl as ovn_acl @@ -540,6 +539,19 @@ expected_match = ' && %s.dst == %s' % (ip_version, remote_ip_prefix) self.assertEqual(expected_match, match) + def test_acl_remote_ip_prefix_not_normalized(self): + sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'direction': 'ingress', + 'remote_ip_prefix': '10.10.10.175/26' + }).info() + normalized_ip_prefix = '10.10.10.128/26' + ip_version = 'ip4' + + match = ovn_acl.acl_remote_ip_prefix(sg_rule, ip_version) + expected_match = ' && %s.src == %s' % (ip_version, + normalized_ip_prefix) + self.assertEqual(expected_match, match) + def test_acl_remote_group_id(self): sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({ 'direction': 'ingress', @@ -548,114 +560,15 @@ ip_version = 'ip4' sg_id = sg_rule['security_group_id'] - addrset_name = ovn_utils.ovn_addrset_name(sg_id, ip_version) + pg_name = ovn_utils.ovn_pg_addrset_name(sg_id, ip_version) match = ovn_acl.acl_remote_group_id(sg_rule, ip_version) self.assertEqual('', match) sg_rule['remote_group_id'] = sg_id match = ovn_acl.acl_remote_group_id(sg_rule, ip_version) - self.assertEqual(' && ip4.src == $' + addrset_name, match) + self.assertEqual(' && ip4.src == $' + pg_name, match) sg_rule['direction'] = 'egress' match = ovn_acl.acl_remote_group_id(sg_rule, ip_version) - self.assertEqual(' && ip4.dst == $' + addrset_name, match) - - def _test_update_acls_for_security_group(self, use_cache=True): - sg = fakes.FakeSecurityGroup.create_one_security_group().info() - remote_sg = fakes.FakeSecurityGroup.create_one_security_group().info() - sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({ - 'security_group_id': sg['id'], - 'remote_group_id': remote_sg['id'] - }).info() - port = fakes.FakePort.create_one_port({ - 'security_groups': [sg['id']] - }).info() - self.plugin.get_ports.return_value = [port] - if use_cache: - sg_ports_cache = {sg['id']: [{'port_id': port['id']}], - remote_sg['id']: []} - else: - sg_ports_cache = None - self.plugin._get_port_security_group_bindings.return_value = \ - [{'port_id': port['id']}] - - # Build ACL for validation. - expected_acl = ovn_acl._add_sg_rule_acl_for_port(port, sg_rule) - expected_acl.pop('lport') - expected_acl.pop('lswitch') - - # Validate ACLs when port has security groups. - ovn_acl.update_acls_for_security_group(self.plugin, - self.admin_context, - self.driver._nb_ovn, - sg['id'], - sg_rule, - sg_ports_cache=sg_ports_cache) - self.driver._nb_ovn.update_acls.assert_called_once_with( - [port['network_id']], - mock.ANY, - {port['id']: expected_acl}, - need_compare=False, - is_add_acl=True - ) - - def test_update_acls_for_security_group_cache(self): - self._test_update_acls_for_security_group(use_cache=True) - - def test_update_acls_for_security_group_no_cache(self): - self._test_update_acls_for_security_group(use_cache=False) - - def test_acl_port_ips(self): - port4 = fakes.FakePort.create_one_port({ - 'fixed_ips': [{'subnet_id': 'subnet-ipv4', - 'ip_address': '10.0.0.1'}], - }).info() - port46 = fakes.FakePort.create_one_port({ - 'fixed_ips': [{'subnet_id': 'subnet-ipv4', - 'ip_address': '10.0.0.2'}, - {'subnet_id': 'subnet-ipv6', - 'ip_address': 'fde3:d45:df72::1'}], - }).info() - port6 = fakes.FakePort.create_one_port({ - 'fixed_ips': [{'subnet_id': 'subnet-ipv6', - 'ip_address': '2001:db8::8'}], - }).info() - - addresses = ovn_acl.acl_port_ips(port4) - self.assertEqual({'ip4': [port4['fixed_ips'][0]['ip_address']], - 'ip6': []}, - addresses) - - addresses = ovn_acl.acl_port_ips(port46) - self.assertEqual({'ip4': [port46['fixed_ips'][0]['ip_address']], - 'ip6': [port46['fixed_ips'][1]['ip_address']]}, - addresses) - - addresses = ovn_acl.acl_port_ips(port6) - self.assertEqual({'ip4': [], - 'ip6': [port6['fixed_ips'][0]['ip_address']]}, - addresses) - - def test_sg_disabled(self): - sg = fakes.FakeSecurityGroup.create_one_security_group().info() - port = fakes.FakePort.create_one_port({ - 'security_groups': [sg['id']] - }).info() - - cfg.CONF.set_override('enable_security_group', 'False', - 'SECURITYGROUP') - acl_list = ovn_acl.add_acls(self.plugin, - self.admin_context, - port, {}, {}, self.driver._ovn) - self.assertEqual([], acl_list) - - ovn_acl.update_acls_for_security_group(self.plugin, - self.admin_context, - self.driver._ovn, - sg['id'], - None) - self.driver._ovn.update_acls.assert_not_called() - - addresses = ovn_acl.acl_port_ips(port) - self.assertEqual({'ip4': [], 'ip6': []}, addresses) + self.assertEqual(' && ip4.dst == $' + pg_name, match) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/ovn/test_hash_ring_manager.py neutron-16.4.2/neutron/tests/unit/common/ovn/test_hash_ring_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/ovn/test_hash_ring_manager.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/common/ovn/test_hash_ring_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -23,6 +23,7 @@ from neutron.common.ovn import exceptions from neutron.common.ovn import hash_ring_manager from neutron.db import ovn_hash_ring_db as db_hash_ring +from neutron import service from neutron.tests.unit import testlib_api HASH_RING_TEST_GROUP = 'test_group' @@ -109,23 +110,21 @@ # The ring should re-balance and as it was before self._verify_hashes(hash_dict_before) - def test__wait_startup_before_caching(self): + @mock.patch.object(service, '_get_api_workers', return_value=2) + def test__wait_startup_before_caching(self, api_workers): db_hash_ring.add_node(self.admin_ctx, HASH_RING_TEST_GROUP, 'node-1') - db_hash_ring.add_node(self.admin_ctx, HASH_RING_TEST_GROUP, 'node-2') - # Assert it will return True until created_at != updated_at + # Assert it will return True until until we equal api_workers self.assertTrue(self.hash_ring_manager._wait_startup_before_caching) - self.assertTrue(self.hash_ring_manager._cache_startup_timeout) + self.assertTrue(self.hash_ring_manager._check_hashring_startup) - # Touch the nodes (== update the updated_at column) - db_hash_ring.touch_nodes_from_host( - self.admin_ctx, HASH_RING_TEST_GROUP) + db_hash_ring.add_node(self.admin_ctx, HASH_RING_TEST_GROUP, 'node-2') # Assert it's now False. Waiting is not needed anymore self.assertFalse(self.hash_ring_manager._wait_startup_before_caching) - self.assertFalse(self.hash_ring_manager._cache_startup_timeout) + self.assertFalse(self.hash_ring_manager._check_hashring_startup) - # Now assert that since the _cache_startup_timeout has been + # Now assert that since the _check_hashring_startup has been # flipped, we no longer will read from the database with mock.patch.object(hash_ring_manager.db_hash_ring, 'get_active_nodes') as get_nodes_mock: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/ovn/test_utils.py neutron-16.4.2/neutron/tests/unit/common/ovn/test_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/ovn/test_utils.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/common/ovn/test_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,8 @@ # under the License. import fixtures +import mock +from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext from neutron.common.ovn import constants from neutron.common.ovn import utils @@ -58,6 +60,40 @@ self.assertFalse(utils.is_gateway_chassis(non_gw_chassis_1)) self.assertFalse(utils.is_gateway_chassis(non_gw_chassis_2)) + def test_get_chassis_availability_zones_no_azs(self): + chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={ + 'external_ids': {'ovn-cms-options': 'enable-chassis-as-gw'}}) + self.assertEqual([], utils.get_chassis_availability_zones(chassis)) + + def test_get_chassis_availability_zones_one_az(self): + chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={ + 'external_ids': {'ovn-cms-options': + 'enable-chassis-as-gw,availability-zones=az0'}}) + self.assertEqual( + ['az0'], utils.get_chassis_availability_zones(chassis)) + + def test_get_chassis_availability_zones_multiple_az(self): + chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={ + 'external_ids': { + 'ovn-cms-options': + 'enable-chassis-as-gw,availability-zones=az0:az1 :az2:: :'}}) + self.assertEqual( + ['az0', 'az1', 'az2'], + utils.get_chassis_availability_zones(chassis)) + + def test_get_chassis_availability_zones_malformed(self): + chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={ + 'external_ids': {'ovn-cms-options': + 'enable-chassis-as-gw,availability-zones:az0'}}) + self.assertEqual( + [], utils.get_chassis_availability_zones(chassis)) + + def test_is_security_groups_enabled(self): + self.assertTrue(utils.is_security_groups_enabled( + {constants.PORT_SECURITYGROUPS: ['fake']})) + self.assertFalse(utils.is_security_groups_enabled( + {})) + class TestGateWayChassisValidity(base.BaseTestCase): @@ -118,3 +154,130 @@ self.assertTrue(utils.is_gateway_chassis_invalid( self.chassis_name, self.gw_chassis, self.physnet, self.chassis_physnets)) + + +class TestDHCPUtils(base.BaseTestCase): + + def test_validate_port_extra_dhcp_opts_empty(self): + port = {edo_ext.EXTRADHCPOPTS: []} + result = utils.validate_port_extra_dhcp_opts(port) + self.assertFalse(result.failed) + self.assertEqual([], result.invalid_ipv4) + self.assertEqual([], result.invalid_ipv6) + + def test_validate_port_extra_dhcp_opts_dhcp_disabled(self): + opt0 = {'opt_name': 'not-valid-ipv4', + 'opt_value': 'joe rogan', + 'ip_version': 4} + opt1 = {'opt_name': 'dhcp_disabled', + 'opt_value': 'True', + 'ip_version': 4} + port = {edo_ext.EXTRADHCPOPTS: [opt0, opt1]} + + # Validation always succeeds if the "dhcp_disabled" option is enabled + result = utils.validate_port_extra_dhcp_opts(port) + self.assertFalse(result.failed) + self.assertEqual([], result.invalid_ipv4) + self.assertEqual([], result.invalid_ipv6) + + def test_validate_port_extra_dhcp_opts(self): + opt0 = {'opt_name': 'bootfile-name', + 'opt_value': 'homer_simpson.bin', + 'ip_version': 4} + opt1 = {'opt_name': 'dns-server', + 'opt_value': '2001:4860:4860::8888', + 'ip_version': 6} + port = {edo_ext.EXTRADHCPOPTS: [opt0, opt1]} + + result = utils.validate_port_extra_dhcp_opts(port) + self.assertFalse(result.failed) + self.assertEqual([], result.invalid_ipv4) + self.assertEqual([], result.invalid_ipv6) + + def test_validate_port_extra_dhcp_opts_invalid(self): + # Two value options and two invalid, assert the validation + # will fail and only the invalid options will be returned as + # not supported + opt0 = {'opt_name': 'bootfile-name', + 'opt_value': 'homer_simpson.bin', + 'ip_version': 4} + opt1 = {'opt_name': 'dns-server', + 'opt_value': '2001:4860:4860::8888', + 'ip_version': 6} + opt2 = {'opt_name': 'not-valid-ipv4', + 'opt_value': 'joe rogan', + 'ip_version': 4} + opt3 = {'opt_name': 'not-valid-ipv6', + 'opt_value': 'young jamie', + 'ip_version': 6} + port = {edo_ext.EXTRADHCPOPTS: [opt0, opt1, opt2, opt3]} + + result = utils.validate_port_extra_dhcp_opts(port) + self.assertTrue(result.failed) + self.assertEqual(['not-valid-ipv4'], result.invalid_ipv4) + self.assertEqual(['not-valid-ipv6'], result.invalid_ipv6) + + def test_get_lsp_dhcp_opts_empty(self): + port = {edo_ext.EXTRADHCPOPTS: []} + dhcp_disabled, options = utils.get_lsp_dhcp_opts(port, 4) + self.assertFalse(dhcp_disabled) + self.assertEqual({}, options) + + def test_get_lsp_dhcp_opts_empty_dhcp_disabled(self): + opt0 = {'opt_name': 'bootfile-name', + 'opt_value': 'homer_simpson.bin', + 'ip_version': 4} + opt1 = {'opt_name': 'dhcp_disabled', + 'opt_value': 'True', + 'ip_version': 4} + port = {edo_ext.EXTRADHCPOPTS: [opt0, opt1]} + + # Validation always succeeds if the "dhcp_disabled" option is enabled + dhcp_disabled, options = utils.get_lsp_dhcp_opts(port, 4) + self.assertTrue(dhcp_disabled) + self.assertEqual({}, options) + + @mock.patch.object(utils, 'is_network_device_port') + def test_get_lsp_dhcp_opts_is_network_device_port(self, mock_device_port): + mock_device_port.return_value = True + port = {} + dhcp_disabled, options = utils.get_lsp_dhcp_opts(port, 4) + # Assert OVN DHCP is disabled + self.assertTrue(dhcp_disabled) + self.assertEqual({}, options) + + def test_get_lsp_dhcp_opts(self): + opt0 = {'opt_name': 'bootfile-name', + 'opt_value': 'homer_simpson.bin', + 'ip_version': 4} + opt1 = {'opt_name': 'server-ip-address', + 'opt_value': '10.0.0.1', + 'ip_version': 4} + opt2 = {'opt_name': '42', + 'opt_value': '10.0.2.1', + 'ip_version': 4} + port = {edo_ext.EXTRADHCPOPTS: [opt0, opt1, opt2]} + + dhcp_disabled, options = utils.get_lsp_dhcp_opts(port, 4) + self.assertFalse(dhcp_disabled) + # Assert the names got translated to their OVN names + expected_options = {'tftp_server_address': '10.0.0.1', + 'ntp_server': '10.0.2.1', + 'bootfile_name': 'homer_simpson.bin'} + self.assertEqual(expected_options, options) + + +class TestConnectionConfigToTargetString(base.BaseTestCase): + + def test_strings(self): + config_target = ( + ('ssl:1.2.3.4:5678', 'pssl:5678:1.2.3.4'), + ('tcp:1.2.3.4:5678', 'ptcp:5678:1.2.3.4'), + ('ssl:[::1]:5678', 'pssl:5678:[::1]'), + ('tcp:[::1]:5678', 'ptcp:5678:[::1]'), + ('unix:/var/run/ovs/db.sock', 'punix:/var/run/ovs/db.sock'), + ('wrong_value', None)) + + for config, target in config_target: + output = utils.connection_config_to_target_string(config) + self.assertEqual(target, output) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/test_utils.py neutron-16.4.2/neutron/tests/unit/common/test_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/common/test_utils.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/common/test_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,7 +16,6 @@ import random import re import sys -import time import ddt import eventlet @@ -428,7 +427,7 @@ def sleep_mock(amount_to_sleep): sleep(amount_to_sleep) - self.assertGreater(threshold, amount_to_sleep) + self.assertGreaterEqual(threshold, amount_to_sleep) with mock.patch.object(utils.eventlet, "sleep", side_effect=sleep_mock): @@ -539,41 +538,6 @@ self.not_valid_rp_bandwidth, self.device_name_set) -class TimerTestCase(base.BaseTestCase): - - def test__getattr(self): - with utils.Timer() as timer: - time.sleep(1) - self.assertEqual(1, round(timer.total_seconds())) - self.assertEqual(1, timer.delta.seconds) - - def test__enter_with_timeout(self): - with utils.Timer(timeout=10) as timer: - time.sleep(1) - self.assertEqual(1, round(timer.total_seconds())) - - def test__enter_with_timeout_exception(self): - msg = r'Timer timeout expired after 1 second\(s\).' - with self.assertRaisesRegex(utils.TimerTimeout, msg): - with utils.Timer(timeout=1): - time.sleep(2) - - def test__enter_with_timeout_no_exception(self): - with utils.Timer(timeout=1, raise_exception=False): - time.sleep(2) - - def test__iter(self): - iterations = [] - for i in utils.Timer(timeout=2): - iterations.append(i) - time.sleep(1.1) - self.assertEqual(2, len(iterations)) - - def test_delta_time_sec(self): - with utils.Timer() as timer: - self.assertIsInstance(timer.delta_time_sec, float) - - class SpawnWithOrWithoutProfilerTestCase( testscenarios.WithScenarios, base.BaseTestCase): @@ -624,3 +588,43 @@ def test_spawn_without_profiler(self): self._compare_profilers_in_parent_and_in_child(init_profiler=False) + + +@utils.SingletonDecorator +class _TestSingletonClass(object): + + def __init__(self): + self.variable = None + + +class SingletonDecoratorTestCase(base.BaseTestCase): + + def test_singleton_instance_class(self): + instance_1 = _TestSingletonClass() + instance_1.variable = 'value1' + + instance_2 = _TestSingletonClass() + self.assertEqual(instance_1.__hash__(), instance_2.__hash__()) + self.assertEqual('value1', instance_2.variable) + + +class SkipDecoratorTestCase(base.BaseTestCase): + + def test_skip_exception(self): + @utils.skip_exceptions(AttributeError) + def raise_attribute_error_single_exception(): + raise AttributeError() + + @utils.skip_exceptions([AttributeError, IndexError]) + def raise_attribute_error_exception_list(): + raise AttributeError() + + raise_attribute_error_single_exception() + raise_attribute_error_exception_list() + + def test_skip_exception_fail(self): + @utils.skip_exceptions(IndexError) + def raise_attribute_error(): + raise AttributeError() + + self.assertRaises(AttributeError, raise_attribute_error) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_agentschedulers_db.py neutron-16.4.2/neutron/tests/unit/db/test_agentschedulers_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_agentschedulers_db.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_agentschedulers_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -13,11 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import datetime import mock from neutron_lib.api.definitions import dhcpagentscheduler as das_apidef from neutron_lib.api.definitions import portbindings +from neutron_lib.callbacks import events +from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context from neutron_lib.db import api as db_api @@ -1521,6 +1524,11 @@ return net, sub, port def _notification_mocks(self, hosts, net, subnet, port, port_priority): + subnet['subnet']['network'] = copy.deepcopy(net['network']) + # 'availability_zones' is empty at the time subnet_create_end + # notification is sent + subnet['subnet']['network']['availability_zones'] = [] + port['port']['network'] = net['network'] host_calls = {} for host in hosts: expected_calls = [ @@ -1588,6 +1596,47 @@ for expected in low_expecteds: self.assertIn(expected, self.dhcp_notifier_cast.call_args_list) + def _test_auto_schedule_new_network_segments(self, subnet_on_segment): + ctx = mock.Mock() + payload = events.DBEventPayload( + ctx, + metadata={'host': 'HOST A', + 'current_segment_ids': set(['segment-1'])}) + segments_plugin = mock.Mock() + segments_plugin.get_segments.return_value = [ + {'id': 'segment-1', 'hosts': ['HOST A']}] + dhcp_notifier = mock.Mock() + dhcp_mixin = agentschedulers_db.DhcpAgentSchedulerDbMixin() + with mock.patch( + 'neutron_lib.plugins.directory.get_plugin', + return_value=segments_plugin), \ + mock.patch( + 'neutron.objects.subnet.Subnet.get_objects') as get_subnets, \ + mock.patch.object( + dhcp_mixin, '_schedule_network') as schedule_network: + + get_subnets.return_value = ( + [subnet_on_segment] if subnet_on_segment else []) + + dhcp_mixin.agent_notifiers[constants.AGENT_TYPE_DHCP] = ( + dhcp_notifier) + dhcp_mixin.auto_schedule_new_network_segments( + resources.SEGMENT_HOST_MAPPING, events.AFTER_CREATE, + ctx, payload) + if subnet_on_segment: + schedule_network.assert_called_once_with( + ctx, subnet_on_segment.network_id, + dhcp_notifier, candidate_hosts=['HOST A']) + else: + schedule_network.assert_not_called() + + def test_auto_schedule_new_network_segments(self): + self._test_auto_schedule_new_network_segments( + subnet_on_segment=mock.Mock(network_id='net-1')) + + def test_auto_schedule_new_network_segments_no_networks_on_segment(self): + self._test_auto_schedule_new_network_segments(subnet_on_segment=None) + def _is_schedule_network_called(self, device_id): dhcp_notifier_schedule = mock.patch( 'neutron.api.rpc.agentnotifiers.dhcp_rpc_agent_api.' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_db_base_plugin_v2.py neutron-16.4.2/neutron/tests/unit/db/test_db_base_plugin_v2.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_db_base_plugin_v2.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_db_base_plugin_v2.py 2021-11-12 13:56:42.000000000 +0000 @@ -441,8 +441,8 @@ return subnetpool_res def _create_port(self, fmt, net_id, expected_res_status=None, - arg_list=None, set_context=False, tenant_id=None, - **kwargs): + arg_list=None, set_context=False, is_admin=False, + tenant_id=None, **kwargs): tenant_id = tenant_id or self._tenant_id data = {'port': {'network_id': net_id, 'tenant_id': tenant_id}} @@ -466,7 +466,7 @@ if set_context and tenant_id: # create a specific auth context for this request port_req.environ['neutron.context'] = context.Context( - '', tenant_id) + '', tenant_id, is_admin=is_admin) port_res = port_req.get_response(self.api) if expected_res_status: @@ -1613,17 +1613,25 @@ res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPConflict.code, res.status_int) - def test_delete_network_port_exists_owned_by_network(self): + def _test_delete_network_port_exists_owned_by_network(self, device_owner): res = self._create_network(fmt=self.fmt, name='net', admin_state_up=True) network = self.deserialize(self.fmt, res) network_id = network['network']['id'] self._create_port(self.fmt, network_id, - device_owner=constants.DEVICE_OWNER_DHCP) + device_owner=device_owner) req = self.new_delete_request('networks', network_id) res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int) + def test_test_delete_network_port_exists_dhcp(self): + self._test_delete_network_port_exists_owned_by_network( + constants.DEVICE_OWNER_DHCP) + + def test_test_delete_network_port_exists_fip_gw(self): + self._test_delete_network_port_exists_owned_by_network( + constants.DEVICE_OWNER_AGENT_GW) + def test_delete_network_port_exists_owned_by_network_race(self): res = self._create_network(fmt=self.fmt, name='net', admin_state_up=True) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_ipam_backend_mixin.py neutron-16.4.2/neutron/tests/unit/db/test_ipam_backend_mixin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_ipam_backend_mixin.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_ipam_backend_mixin.py 2021-11-12 13:56:42.000000000 +0000 @@ -86,6 +86,18 @@ self.mixin._get_subnet_object = mock.Mock( side_effect=_get_subnet_object) + def test__is_distributed_service(self): + port = {'device_owner': + '%snova' % constants.DEVICE_OWNER_COMPUTE_PREFIX, + 'device_id': uuidutils.generate_uuid()} + self.assertFalse(self.mixin._is_distributed_service(port)) + port = {'device_owner': constants.DEVICE_OWNER_DHCP, + 'device_id': uuidutils.generate_uuid()} + self.assertFalse(self.mixin._is_distributed_service(port)) + port = {'device_owner': constants.DEVICE_OWNER_DHCP, + 'device_id': 'ovnmeta-%s' % uuidutils.generate_uuid()} + self.assertTrue(self.mixin._is_distributed_service(port)) + def _test_get_changed_ips_for_port(self, expected, original_ips, new_ips, owner): change = self.mixin._get_changed_ips_for_port(self.ctx, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_ipam_pluggable_backend.py neutron-16.4.2/neutron/tests/unit/db/test_ipam_pluggable_backend.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_ipam_pluggable_backend.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_ipam_pluggable_backend.py 2021-11-12 13:56:42.000000000 +0000 @@ -692,13 +692,49 @@ mocks['ipam']._ipam_get_subnets.assert_called_once_with( context, network_id=port_dict['network_id'], fixed_configured=True, fixed_ips=[ip_dict], host=None, - service_type=port_dict['device_owner']) + service_type=port_dict['device_owner'], + distributed_service=False) # Validate port_dict is passed into address_factory address_factory.get_request.assert_called_once_with(context, port_dict, ip_dict) @mock.patch('neutron.ipam.driver.Pool') + def test_update_ips_for_port_ovn_distributed_svc(self, pool_mock): + address_factory = mock.Mock() + mocks = self._prepare_mocks_with_pool_mock( + pool_mock, address_factory=address_factory) + context = mock.Mock() + new_ips = mock.Mock() + original_ips = mock.Mock() + mac = mock.Mock() + + ip_dict = {'ip_address': '192.1.1.10', + 'subnet_id': uuidutils.generate_uuid()} + changes = ipam_pluggable_backend.IpamPluggableBackend.Changes( + add=[ip_dict], original=[], remove=[]) + changes_mock = mock.Mock(return_value=changes) + fixed_ips_mock = mock.Mock(return_value=changes.add) + mocks['ipam'] = ipam_pluggable_backend.IpamPluggableBackend() + mocks['ipam']._get_changed_ips_for_port = changes_mock + mocks['ipam']._ipam_get_subnets = mock.Mock(return_value=[]) + mocks['ipam']._test_fixed_ips_for_port = fixed_ips_mock + mocks['ipam']._update_ips_for_pd_subnet = mock.Mock(return_value=[]) + + port_dict = { + 'device_owner': constants.DEVICE_OWNER_DHCP, + 'device_id': 'ovnmeta-%s' % uuidutils.generate_uuid(), + 'network_id': uuidutils.generate_uuid()} + + mocks['ipam']._update_ips_for_port(context, port_dict, None, + original_ips, new_ips, mac) + mocks['ipam']._ipam_get_subnets.assert_called_once_with( + context, network_id=port_dict['network_id'], fixed_configured=True, + fixed_ips=[ip_dict], host=None, + service_type=port_dict['device_owner'], + distributed_service=True) + + @mock.patch('neutron.ipam.driver.Pool') def test_update_ips_for_port_passes_port_id_to_factory(self, pool_mock): port_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_l3_dvr_db.py neutron-16.4.2/neutron/tests/unit/db/test_l3_dvr_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_l3_dvr_db.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_l3_dvr_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -35,6 +35,7 @@ from neutron.db import models_v2 from neutron.objects import agent as agent_obj from neutron.objects import l3agent as rb_obj +from neutron.objects import ports as port_obj from neutron.objects import router as router_obj from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.extensions import test_l3 @@ -762,6 +763,42 @@ mock.call(self.ctx, network_id, 'host', fipagent['id'])]) self.assertIsNotNone(fport) + def test_create_fip_agent_gw_port_agent_binding_exists(self): + network_id = _uuid() + fport_db = {'id': _uuid()} + self.mixin._get_agent_gw_ports_exist_for_network = mock.Mock( + side_effect=[None, None]) + fipagent = agent_obj.Agent( + self.ctx, + id=_uuid(), + binary='foo-agent', + host='host', + agent_type='L3 agent', + topic='foo_topic', + configurations={"agent_mode": "dvr"}) + self.mixin._get_agent_by_type_and_host = mock.Mock( + return_value=fipagent) + self.mixin._populate_mtu_and_subnets_for_ports = mock.Mock() + + with mock.patch.object( + router_obj.DvrFipGatewayPortAgentBinding, 'create', + side_effect=o_exc.NeutronDbObjectDuplicateEntry( + mock.Mock(), mock.Mock()) + ) as dvr_fip_gateway_port_agent_binding_create,\ + mock.patch.object( + plugin_utils, "create_port", return_value=fport_db): + fport = self.mixin.create_fip_agent_gw_port_if_not_exists( + self.ctx, + network_id, + 'host') + dvr_fip_gateway_port_agent_binding_create.assert_called_once_with() + self.mixin._get_agent_gw_ports_exist_for_network.assert_has_calls([ + mock.call(self.ctx, network_id, 'host', fipagent['id']), + mock.call(self.ctx, network_id, 'host', fipagent['id'])]) + self.mixin._populate_mtu_and_subnets_for_ports.assert_has_calls([ + mock.call(self.ctx, [fport_db])]) + self.assertIsNotNone(fport) + def test_create_floatingip_agent_gw_port_with_non_dvr_router(self): floatingip = { 'id': _uuid(), @@ -778,6 +815,115 @@ fip, floatingip, router)) self.assertFalse(create_fip.called) + def test_get_ext_nets_by_host(self): + ports = [mock.Mock(id=_uuid()) for _ in range(3)] + fips = [mock.Mock(fixed_port_id=p.id, floating_network_id=_uuid()) + for p in ports] + expected_ext_nets = set([fip.floating_network_id for fip in fips]) + with mock.patch.object( + port_obj.Port, 'get_ports_by_host', + return_value=[p.id for p in ports] + ) as get_ports_by_host, mock.patch.object( + self.mixin, '_get_floatingips_by_port_id', return_value=fips + ) as get_floatingips_by_port_id: + self.assertEqual( + expected_ext_nets, + self.mixin._get_ext_nets_by_host(self.ctx, 'host')) + get_ports_by_host.assert_called_once_with(self.ctx, 'host') + get_floatingips_by_port_id.assert_has_calls( + [mock.call(self.ctx, p.id) for p in ports]) + + def _test_create_fip_agent_gw_ports(self, agent_type, agent_mode=None): + agent = { + 'id': _uuid(), + 'host': 'host', + 'agent_type': agent_type, + 'configurations': {'agent_mode': agent_mode}} + payload = events.DBEventPayload( + self.ctx, states=(agent,), resource_id=agent['id']) + + ext_nets = ['ext-net-1', 'ext-net-2'] + with mock.patch.object( + self.mixin, + 'create_fip_agent_gw_port_if_not_exists' + ) as create_fip_gw, mock.patch.object( + self.mixin, "_get_ext_nets_by_host", + return_value=ext_nets + ) as get_ext_nets_by_host: + + registry.publish(resources.AGENT, events.AFTER_CREATE, mock.Mock(), + payload=payload) + + if agent_type == 'L3 agent' and agent_mode in ['dvr', 'dvr_snat']: + get_ext_nets_by_host.assert_called_once_with( + mock.ANY, 'host') + create_fip_gw.assert_has_calls( + [mock.call(mock.ANY, ext_net, 'host') for + ext_net in ext_nets]) + else: + get_ext_nets_by_host.assert_not_called() + create_fip_gw.assert_not_called() + + def test_create_fip_agent_gw_ports(self): + self._test_create_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='dvr') + self._test_create_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='dvr_snat') + + def test_create_fip_agent_gw_ports_dvr_no_external_agent(self): + self._test_create_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='dvr_no_external') + + def test_create_fip_agent_gw_ports_non_dvr_agent(self): + self._test_create_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='legacy') + + def test_create_fip_agent_gw_ports_deleted_non_l3_agent(self): + self._test_create_fip_agent_gw_ports('Other agent type') + + def _test_delete_fip_agent_gw_ports(self, agent_type, agent_mode=None): + agent = agent_obj.Agent( + self.ctx, id=_uuid(), agent_type=agent_type, + configurations={"agent_mode": agent_mode}) + payload = events.DBEventPayload( + self.ctx, states=(agent,), resource_id=agent.id) + + gw_port = {'id': _uuid(), 'network_id': _uuid()} + with mock.patch.object( + self.mixin, '_get_agent_gw_ports', + return_value=[gw_port] + ) as get_agent_gw_ports, mock.patch.object( + self.core_plugin, 'delete_port' + ) as delete_port: + registry.publish(resources.AGENT, events.AFTER_DELETE, mock.Mock(), + payload=payload) + + if agent_type == 'L3 agent' and agent_mode in ['dvr', 'dvr_snat']: + get_agent_gw_ports.assert_called_once_with(payload.context, + agent['id']) + delete_port.assert_called_once_with(payload.context, + gw_port['id']) + else: + get_agent_gw_ports.assert_not_called() + delete_port.assert_not_called() + + def test_delete_fip_agent_gw_ports(self): + self._test_delete_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='dvr') + self._test_delete_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='dvr_snat') + + def test_delete_fip_agent_gw_ports_dvr_no_external_agent(self): + self._test_delete_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='dvr_no_external') + + def test_delete_fip_agent_gw_ports_non_dvr_agent(self): + self._test_delete_fip_agent_gw_ports( + agent_type='L3 agent', agent_mode='legacy') + + def test_delete_fip_agent_gw_ports_deleted_non_l3_agent(self): + self._test_delete_fip_agent_gw_ports('Other agent type') + def _test_update_router_gw_info_external_network_change(self): router_dict = {'name': 'test_router', 'admin_state_up': True, 'distributed': True} @@ -1136,6 +1282,72 @@ port=mock.ANY, interface_info=interface_info) + def test__generate_arp_table_and_notify_agent(self): + fixed_ip = { + 'ip_address': '1.2.3.4', + 'subnet_id': _uuid()} + mac_address = "00:11:22:33:44:55" + expected_arp_table = { + 'ip_address': fixed_ip['ip_address'], + 'subnet_id': fixed_ip['subnet_id'], + 'mac_address': mac_address} + notifier = mock.Mock() + ports = [{'id': _uuid(), 'device_id': 'router_1'}, + {'id': _uuid(), 'device_id': 'router_2'}] + with mock.patch.object(self.core_plugin, "get_ports", + return_value=ports): + self.mixin._generate_arp_table_and_notify_agent( + self.ctx, fixed_ip, mac_address, notifier) + notifier.assert_has_calls([ + mock.call(self.ctx, "router_1", expected_arp_table), + mock.call(self.ctx, "router_2", expected_arp_table)]) + + def _test_update_arp_entry_for_dvr_service_port( + self, device_owner, action): + router_dict = {'name': 'test_router', 'admin_state_up': True, + 'distributed': True} + router = self._create_router(router_dict) + plugin = mock.Mock() + directory.add_plugin(plugin_constants.CORE, plugin) + l3_notify = self.mixin.l3_rpc_notifier = mock.Mock() + port = { + 'id': 'my_port_id', + 'fixed_ips': [ + {'subnet_id': '51edc9e0-24f9-47f2-8e1e-2a41cb691323', + 'ip_address': '10.0.0.11'}, + {'subnet_id': '2b7c8a07-6f8e-4937-8701-f1d5da1a807c', + 'ip_address': '10.0.0.21'}, + {'subnet_id': '48534187-f077-4e81-93ff-81ec4cc0ad3b', + 'ip_address': 'fd45:1515:7e0:0:f816:3eff:fe1a:1111'}], + 'mac_address': 'my_mac', + 'device_owner': device_owner + } + dvr_port = { + 'id': 'dvr_port_id', + 'fixed_ips': mock.ANY, + 'device_owner': const.DEVICE_OWNER_DVR_INTERFACE, + 'device_id': router['id'] + } + plugin.get_ports.return_value = [dvr_port] + if action == 'add': + self.mixin.update_arp_entry_for_dvr_service_port( + self.ctx, port) + self.assertEqual(3, l3_notify.add_arp_entry.call_count) + elif action == 'del': + self.mixin.delete_arp_entry_for_dvr_service_port( + self.ctx, port) + self.assertEqual(3, l3_notify.del_arp_entry.call_count) + + def test_update_arp_entry_for_dvr_service_port_added(self): + action = 'add' + device_owner = const.DEVICE_OWNER_LOADBALANCER + self._test_update_arp_entry_for_dvr_service_port(device_owner, action) + + def test_update_arp_entry_for_dvr_service_port_deleted(self): + action = 'del' + device_owner = const.DEVICE_OWNER_LOADBALANCER + self._test_update_arp_entry_for_dvr_service_port(device_owner, action) + def test_add_router_interface_csnat_ports_failure(self): router_dict = {'name': 'test_router', 'admin_state_up': True, 'distributed': True} @@ -1289,6 +1501,8 @@ router = self._create_router(router_dict) with self.network() as network,\ self.subnet(network=network) as subnet: + self.mixin._core_plugin.get_allowed_address_pairs_for_ports = ( + mock.Mock()) fake_bound_ports_ids = [] def fake_is_port_bound(port): @@ -1310,6 +1524,8 @@ self.ctx, subnet['subnet']['id']) dvr_subnet_ports_ids = [p['id'] for p in dvr_subnet_ports] self.assertItemsEqual(fake_bound_ports_ids, dvr_subnet_ports_ids) + (self.mixin._core_plugin.get_allowed_address_pairs_for_ports. + assert_called_once_with(self.ctx, dvr_subnet_ports_ids)) @mock.patch.object(plugin_utils, 'can_port_be_bound_to_virtual_bridge', return_value=True) @@ -1343,3 +1559,27 @@ self.assertRaises( exceptions.BadRequest, self.mixin._get_assoc_data, self.ctx, mock.ANY, mock.Mock()) + + def test__delete_dvr_internal_ports(self): + payload = mock.Mock() + payload.context = mock.Mock() + payload.latest_state = {'distributed': True} + payload.metadata = {'new_network_id': 'fake-net-1', + 'network_id': 'fake-net-2'} + plugin = mock.Mock() + directory.add_plugin(plugin_constants.CORE, plugin) + plugin.get_ports.return_value = [] + with mock.patch.object(self.mixin, + 'delete_floatingip_agent_gateway_port') as \ + del_port, \ + mock.patch.object( + self.mixin.l3_rpc_notifier, + 'delete_fipnamespace_for_ext_net') as \ + del_fip_ns, \ + mock.patch.object(router_obj.DvrFipGatewayPortAgentBinding, + "delete_objects") as del_binding: + self.mixin._delete_dvr_internal_ports( + None, None, resources.ROUTER_GATEWAY, payload) + del_port.assert_called_once() + del_fip_ns.assert_called_once() + del_binding.assert_called_once() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_ovn_hash_ring_db.py neutron-16.4.2/neutron/tests/unit/db/test_ovn_hash_ring_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_ovn_hash_ring_db.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_ovn_hash_ring_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. import datetime +import time import mock from neutron_lib import context @@ -44,9 +45,13 @@ def _get_node_row(self, node_uuid): try: with db_api.CONTEXT_WRITER.using(self.admin_ctx): - return self.admin_ctx.session.query( + node = self.admin_ctx.session.query( ovn_models.OVNHashRing).filter_by( node_uuid=node_uuid).one() + # Ignore miliseconds + node.created_at = node.created_at.replace(microsecond=0) + node.updated_at = node.updated_at.replace(microsecond=0) + return node except exc.NoResultFound: return @@ -97,6 +102,7 @@ self.assertEqual(node_db.created_at, node_db.updated_at) # Touch the nodes from our host + time.sleep(1) ovn_hash_ring_db.touch_nodes_from_host(self.admin_ctx, HASH_RING_TEST_GROUP) @@ -123,6 +129,7 @@ self.admin_ctx, interval=60, group_name=HASH_RING_TEST_GROUP))) # Substract 60 seconds from utcnow() and touch the nodes from our host + time.sleep(1) fake_utcnow = timeutils.utcnow() - datetime.timedelta(seconds=60) with mock.patch.object(timeutils, 'utcnow') as mock_utcnow: mock_utcnow.return_value = fake_utcnow @@ -161,6 +168,7 @@ self.assertEqual(node_db.created_at, node_db.updated_at) # Touch one of the nodes + time.sleep(1) ovn_hash_ring_db.touch_node(self.admin_ctx, nodes[0]) # Assert it has been updated @@ -217,6 +225,7 @@ self.assertEqual(node_db.created_at, node_db.updated_at) # Touch the nodes from group1 + time.sleep(1) ovn_hash_ring_db.touch_nodes_from_host(self.admin_ctx, HASH_RING_TEST_GROUP) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_securitygroups_db.py neutron-16.4.2/neutron/tests/unit/db/test_securitygroups_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/db/test_securitygroups_db.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/db/test_securitygroups_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -20,6 +20,7 @@ from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context +from neutron_lib.objects import exceptions as obj_exc import sqlalchemy import testtools @@ -76,6 +77,10 @@ self.mock_quota_make_res = make_res.start() commit_res = mock.patch.object(quota.QuotaEngine, 'commit_reservation') self.mock_quota_commit_res = commit_res.start() + is_ext_supported = mock.patch( + 'neutron_lib.api.extensions.is_extension_supported') + self.is_ext_supported = is_ext_supported.start() + self.is_ext_supported.return_value = True def test_create_security_group_conflict(self): with mock.patch.object(registry, "publish") as mock_publish: @@ -371,7 +376,8 @@ mock.call('security_group', 'after_delete', mock.ANY, context=mock.ANY, security_group_id=sg_dict['id'], - security_group_rule_ids=[mock.ANY, mock.ANY])]) + security_group_rule_ids=[mock.ANY, mock.ANY], + name=sg_dict['name'])]) def test_security_group_rule_precommit_create_event_fail(self): registry.subscribe(fake_callback, resources.SECURITY_GROUP_RULE, @@ -553,3 +559,59 @@ for rule in (rule for rule in rules_after if rule not in rules_before): self.assertEqual('tenant_1', rule['tenant_id']) self.assertEqual(self.sg_user['id'], rule['security_group_id']) + + def test__ensure_default_security_group(self): + with mock.patch.object( + self.mixin, '_get_default_sg_id') as get_default_sg_id,\ + mock.patch.object( + self.mixin, 'create_security_group') as create_sg: + get_default_sg_id.return_value = None + self.mixin._ensure_default_security_group(self.ctx, 'tenant_1') + create_sg.assert_called_once_with( + self.ctx, + {'security_group': { + 'name': 'default', + 'tenant_id': 'tenant_1', + 'description': securitygroups_db.DEFAULT_SG_DESCRIPTION}}, + default_sg=True) + get_default_sg_id.assert_called_once_with(self.ctx, 'tenant_1') + + def test__ensure_default_security_group_already_exists(self): + with mock.patch.object( + self.mixin, '_get_default_sg_id') as get_default_sg_id,\ + mock.patch.object( + self.mixin, 'create_security_group') as create_sg: + get_default_sg_id.return_value = 'default_sg_id' + self.mixin._ensure_default_security_group(self.ctx, 'tenant_1') + create_sg.assert_not_called() + get_default_sg_id.assert_called_once_with(self.ctx, 'tenant_1') + + def test__ensure_default_security_group_created_in_parallel(self): + with mock.patch.object( + self.mixin, '_get_default_sg_id') as get_default_sg_id,\ + mock.patch.object( + self.mixin, 'create_security_group') as create_sg: + get_default_sg_id.side_effect = [None, 'default_sg_id'] + create_sg.side_effect = obj_exc.NeutronDbObjectDuplicateEntry( + mock.Mock(), mock.Mock()) + self.mixin._ensure_default_security_group(self.ctx, 'tenant_1') + create_sg.assert_called_once_with( + self.ctx, + {'security_group': { + 'name': 'default', + 'tenant_id': 'tenant_1', + 'description': securitygroups_db.DEFAULT_SG_DESCRIPTION}}, + default_sg=True) + get_default_sg_id.assert_has_calls([ + mock.call(self.ctx, 'tenant_1'), + mock.call(self.ctx, 'tenant_1')]) + + def test__ensure_default_security_group_when_disabled(self): + with mock.patch.object( + self.mixin, '_get_default_sg_id') as get_default_sg_id,\ + mock.patch.object( + self.mixin, 'create_security_group') as create_sg: + self.is_ext_supported.return_value = False + self.mixin._ensure_default_security_group(self.ctx, 'tenant_1') + create_sg.assert_not_called() + get_default_sg_id.assert_not_called() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_floating_ip_port_forwarding.py neutron-16.4.2/neutron/tests/unit/extensions/test_floating_ip_port_forwarding.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_floating_ip_port_forwarding.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/extensions/test_floating_ip_port_forwarding.py 2021-11-12 13:56:42.000000000 +0000 @@ -70,6 +70,21 @@ return fip_pf_req.get_response(self.ext_api) + def _update_fip_port_forwarding(self, fmt, floating_ip_id, + port_forwarding_id, **kwargs): + port_forwarding = {} + for k, v in kwargs.items(): + port_forwarding[k] = v + data = {'port_forwarding': port_forwarding} + + fip_pf_req = self._req( + 'PUT', 'floatingips', data, + fmt or self.fmt, id=floating_ip_id, + sub_id=port_forwarding_id, + subresource='port_forwardings') + + return fip_pf_req.get_response(self.ext_api) + def test_create_floatingip_port_forwarding_with_port_number_0(self): with self.network() as ext_net: network_id = ext_net['network']['id'] @@ -135,3 +150,46 @@ pf_body = self.deserialize(self.fmt, res) self.assertEqual( "blablablabla", pf_body['port_forwarding']['description']) + + def test_update_floatingip_port_forwarding_with_dup_internal_port(self): + with self.network() as ext_net: + network_id = ext_net['network']['id'] + self._set_net_external(network_id) + with self.subnet(ext_net, cidr='10.10.10.0/24'), \ + self.router() as router, \ + self.subnet(cidr='11.0.0.0/24') as private_subnet, \ + self.port(private_subnet) as port: + self._add_external_gateway_to_router( + router['router']['id'], + network_id) + self._router_interface_action( + 'add', router['router']['id'], + private_subnet['subnet']['id'], + None) + fip1 = self._make_floatingip( + self.fmt, + network_id) + self.assertIsNone(fip1['floatingip'].get('port_id')) + self._create_fip_port_forwarding( + self.fmt, fip1['floatingip']['id'], + 2222, 22, + 'tcp', + port['port']['fixed_ips'][0]['ip_address'], + port['port']['id'], + description="blablablabla") + fip2 = self._make_floatingip( + self.fmt, + network_id) + fip_pf_response = self._create_fip_port_forwarding( + self.fmt, fip2['floatingip']['id'], + 2222, 23, + 'tcp', + port['port']['fixed_ips'][0]['ip_address'], + port['port']['id'], + description="blablablabla") + update_res = self._update_fip_port_forwarding( + self.fmt, fip2['floatingip']['id'], + fip_pf_response.json['port_forwarding']['id'], + **{'internal_port': 22}) + self.assertEqual(exc.HTTPBadRequest.code, + update_res.status_int) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_l3_conntrack_helper.py neutron-16.4.2/neutron/tests/unit/extensions/test_l3_conntrack_helper.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_l3_conntrack_helper.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/extensions/test_l3_conntrack_helper.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,141 @@ +# Copyright 2021 Troila +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from webob import exc + +from neutron_lib.api.definitions import l3 as l3_apidef +from neutron_lib.api.definitions import l3_conntrack_helper as l3_ct +from neutron_lib import context +from oslo_utils import uuidutils + +from neutron.extensions import l3 +from neutron.extensions import l3_conntrack_helper +from neutron.tests.unit.api import test_extensions +from neutron.tests.unit.extensions import test_l3 + +_uuid = uuidutils.generate_uuid + + +class TestL3ConntrackHelperServicePlugin(test_l3.TestL3NatServicePlugin): + supported_extension_aliases = [l3_apidef.ALIAS, l3_ct.ALIAS] + + +class ExtendL3ConntrackHelperExtensionManager(object): + + def get_resources(self): + return (l3.L3.get_resources() + + l3_conntrack_helper.L3_conntrack_helper.get_resources()) + + def get_actions(self): + return [] + + def get_request_extensions(self): + return [] + + +class L3NConntrackHelperTestCase(test_l3.L3BaseForIntTests, + test_l3.L3NatTestCaseMixin): + tenant_id = _uuid() + fmt = "json" + + def setUp(self): + mock.patch('neutron.api.rpc.handlers.resources_rpc.' + 'ResourcesPushRpcApi').start() + svc_plugins = ('neutron.services.conntrack_helper.plugin.Plugin', + 'neutron.tests.unit.extensions.' + 'test_l3_conntrack_helper.' + 'TestL3ConntrackHelperServicePlugin') + plugin = ('neutron.tests.unit.extensions.test_l3.TestL3NatIntPlugin') + ext_mgr = ExtendL3ConntrackHelperExtensionManager() + super(L3NConntrackHelperTestCase, self).setUp( + ext_mgr=ext_mgr, service_plugins=svc_plugins, plugin=plugin) + self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr) + + def _create_router_conntrack_helper(self, fmt, router_id, + protocol, port, helper): + tenant_id = self.tenant_id or _uuid() + data = {'conntrack_helper': { + "protocol": protocol, + "port": port, + "helper": helper} + } + router_ct_req = self._req( + 'POST', 'routers', data, + fmt or self.fmt, id=router_id, + subresource='conntrack_helpers') + + router_ct_req.environ['neutron.context'] = context.Context( + '', tenant_id, is_admin=True) + + return router_ct_req.get_response(self.ext_api) + + def _update_router_conntrack_helper(self, fmt, router_id, + conntrack_helper_id, **kwargs): + conntrack_helper = {} + for k, v in kwargs.items(): + conntrack_helper[k] = v + data = {'conntrack_helper': conntrack_helper} + + router_ct_req = self._req( + 'PUT', 'routers', data, + fmt or self.fmt, id=router_id, + sub_id=conntrack_helper_id, + subresource='conntrack_helpers') + return router_ct_req.get_response(self.ext_api) + + def test_create_ct_with_duplicate_entry(self): + with self.router() as router: + ct1 = self._create_router_conntrack_helper( + self.fmt, router['router']['id'], + "udp", 69, "tftp") + self.assertEqual(exc.HTTPCreated.code, ct1.status_code) + ct2 = self._create_router_conntrack_helper( + self.fmt, router['router']['id'], + "udp", 69, "tftp") + self.assertEqual(exc.HTTPBadRequest.code, ct2.status_code) + expect_msg = ("Bad conntrack_helper request: A duplicate " + "conntrack helper entry with same attributes " + "already exists, conflicting values are " + "{'router_id': '%s', 'protocol': 'udp', " + "'port': 69, 'helper': " + "'tftp'}.") % router['router']['id'] + self.assertEqual( + expect_msg, ct2.json_body['NeutronError']['message']) + + def test_update_ct_with_duplicate_entry(self): + with self.router() as router: + ct1 = self._create_router_conntrack_helper( + self.fmt, router['router']['id'], + "udp", 69, "tftp") + self.assertEqual(exc.HTTPCreated.code, ct1.status_code) + ct2 = self._create_router_conntrack_helper( + self.fmt, router['router']['id'], + "udp", 68, "tftp") + self.assertEqual(exc.HTTPCreated.code, ct2.status_code) + result = self._update_router_conntrack_helper( + self.fmt, router['router']['id'], + ct1.json['conntrack_helper']['id'], + **{'port': 68}) + self.assertEqual(exc.HTTPBadRequest.code, result.status_code) + expect_msg = ("Bad conntrack_helper request: A duplicate " + "conntrack helper entry with same attributes " + "already exists, conflicting values are " + "{'router_id': '%s', 'protocol': 'udp', " + "'port': 68, 'helper': " + "'tftp'}.") % router['router']['id'] + self.assertEqual( + expect_msg, result.json_body['NeutronError']['message']) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_l3.py neutron-16.4.2/neutron/tests/unit/extensions/test_l3.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_l3.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/extensions/test_l3.py 2021-11-12 13:56:42.000000000 +0000 @@ -1489,6 +1489,33 @@ None, p['port']['id']) + def test_update_router_interface_port_ipv6_subnet_ext_ra(self): + use_cases = [{'msg': 'IPv6 Subnet Modes (none, slaac)', + 'ra_mode': None, + 'address_mode': lib_constants.IPV6_SLAAC}, + {'msg': 'IPv6 Subnet Modes (none, dhcpv6-stateful)', + 'ra_mode': None, + 'address_mode': lib_constants.DHCPV6_STATEFUL}, + {'msg': 'IPv6 Subnet Modes (none, dhcpv6-stateless)', + 'ra_mode': None, + 'address_mode': lib_constants.DHCPV6_STATELESS}] + for uc in use_cases: + with self.network() as network, self.router() as router: + with self.subnet( + network=network, cidr='fd00::/64', + ip_version=lib_constants.IP_VERSION_6, + ipv6_ra_mode=uc['ra_mode'], + ipv6_address_mode=uc['address_mode']) as subnet: + fixed_ips = [{'subnet_id': subnet['subnet']['id']}] + with self.port(subnet=subnet, fixed_ips=fixed_ips) as port: + self._router_interface_action( + 'add', + router['router']['id'], + None, + port['port']['id'], + expected_code=exc.HTTPBadRequest.code, + msg=uc['msg']) + def _assert_body_port_id_and_update_port(self, body, mock_update_port, port_id, device_id): self.assertNotIn('port_id', body) @@ -1725,18 +1752,61 @@ HTTPBadRequest.code) def test_router_add_interface_cidr_overlapped_with_gateway(self): - with self.router() as r: + with self.router() as r, self.network() as ext_net: with self.subnet(cidr='10.0.1.0/24') as s1, self.subnet( - cidr='10.0.0.0/16') as s2: - self._set_net_external(s2['subnet']['network_id']) + network=ext_net, cidr='10.0.0.0/16') as s2: + ext_net_id = ext_net['network']['id'] + self._set_net_external(ext_net_id) self._add_external_gateway_to_router( - r['router']['id'], - s2['subnet']['network_id']) - res = self._router_interface_action('add', - r['router']['id'], - s1['subnet']['id'], - None) - self.assertIn('port_id', res) + r['router']['id'], ext_net_id) + res = self._router_interface_action( + 'add', r['router']['id'], s1['subnet']['id'], None, + expected_code=exc.HTTPBadRequest.code) + expected_msg = ("Bad router request: Cidr 10.0.1.0/24 of " + "subnet %(internal_subnet_id)s overlaps with " + "cidr 10.0.0.0/16 of subnet " + "%(external_subnet_id)s.") % { + "external_subnet_id": s2['subnet']['id'], + "internal_subnet_id": s1['subnet']['id']} + self.assertEqual(expected_msg, res['NeutronError']['message']) + + # External network have multiple subnets. + with self.subnet(network=ext_net, + cidr='192.168.1.0/24') as s3, \ + self.subnet(cidr='192.168.1.0/24') as s4: + res = self._router_interface_action( + 'add', r['router']['id'], s4['subnet']['id'], None, + expected_code=exc.HTTPBadRequest.code) + expected_msg = ( + "Bad router request: Cidr 192.168.1.0/24 of subnet " + "%(internal_subnet_id)s overlaps with cidr " + "192.168.1.0/24 of subnet %(external_subnet_id)s.") % { + "external_subnet_id": s3['subnet']['id'], + "internal_subnet_id": s4['subnet']['id']} + self.assertEqual(expected_msg, + res['NeutronError']['message']) + + def test_router_set_gateway_cidr_overlapped_with_subnets(self): + with self.router() as r, self.network() as ext_net: + with self.subnet(network=ext_net, cidr='10.0.1.0/24') as s1, \ + self.subnet(network=ext_net, cidr='10.0.2.0/24') as s2, \ + self.subnet(cidr='10.0.2.0/24') as s3: + ext_net_id = ext_net['network']['id'] + self._set_net_external(ext_net_id) + self._router_interface_action( + 'add', r['router']['id'], + s3['subnet']['id'], None) + res = self._add_external_gateway_to_router( + r['router']['id'], ext_net_id, + ext_ips=[{'subnet_id': s1['subnet']['id']}], + expected_code=exc.HTTPBadRequest.code) + expected_msg = ( + "Bad router request: Cidr 10.0.2.0/24 of subnet " + "%(external_subnet_id)s overlaps with cidr 10.0.2.0/24 of " + "subnet %(internal_subnet_id)s.") % { + "external_subnet_id": s2["subnet"]["id"], + "internal_subnet_id": s3["subnet"]["id"]} + self.assertEqual(expected_msg, res['NeutronError']['message']) def test_router_add_interface_by_port_cidr_overlapped_with_gateway(self): with self.router() as r: @@ -1748,11 +1818,17 @@ r['router']['id'], s2['subnet']['network_id']) - res = self._router_interface_action('add', - r['router']['id'], - None, - p['port']['id']) - self.assertIn('port_id', res) + res = self._router_interface_action( + 'add', r['router']['id'], None, p['port']['id'], + expected_code=exc.HTTPBadRequest.code) + expected_msg = ( + "Bad router request: Cidr 10.0.1.0/24 of subnet " + "%(internal_subnet_id)s overlaps with cidr " + "10.0.0.0/16 of subnet %(external_subnet_id)s.") % { + "external_subnet_id": s2['subnet']['id'], + "internal_subnet_id": s1['subnet']['id']} + self.assertEqual(expected_msg, + res['NeutronError']['message']) def test_router_add_gateway_dup_subnet1_returns_400(self): with self.router() as r: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_portsecurity.py neutron-16.4.2/neutron/tests/unit/extensions/test_portsecurity.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_portsecurity.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/extensions/test_portsecurity.py 2021-11-12 13:56:42.000000000 +0000 @@ -293,6 +293,57 @@ self.assertEqual(port['port']['security_groups'], [security_group_id]) self._delete('ports', port['port']['id']) + def test_create_port_with_admin_use_other_tenant_security_group(self): + if self._skip_security_group: + self.skipTest("Plugin does not support security groups") + res = self._create_network('json', 'net1', True, + arg_list=('port_security_enabled',), + set_context=True, + tenant_id='admin_tenant', + port_security_enabled=False) + net = self.deserialize('json', res) + self._create_subnet('json', net['network']['id'], '10.0.0.0/24') + security_group = self.deserialize( + 'json', self._create_security_group(self.fmt, 'asdf', 'asdf', + tenant_id='other_tenant')) + security_group_id = security_group['security_group']['id'] + res = self._create_port('json', net['network']['id'], + arg_list=('security_groups', + 'port_security_enabled'), + set_context=True, + is_admin=True, + tenant_id='admin_tenant', + port_security_enabled=True, + security_groups=[security_group_id]) + port = self.deserialize('json', res) + self.assertTrue(port['port'][psec.PORTSECURITY]) + self.assertEqual(port['port']['security_groups'], [security_group_id]) + self._delete('ports', port['port']['id']) + + def test_create_port_with_no_admin_use_other_tenant_security_group(self): + if self._skip_security_group: + self.skipTest("Plugin does not support security groups") + res = self._create_network('json', 'net1', True, + arg_list=('port_security_enabled',), + set_context=True, + tenant_id='demo_tenant', + port_security_enabled=False) + net = self.deserialize('json', res) + self._create_subnet('json', net['network']['id'], '10.0.0.0/24', + set_context=True, tenant_id='demo_tenant') + security_group = self.deserialize( + 'json', self._create_security_group(self.fmt, 'asdf', 'asdf', + tenant_id='other_tenant')) + security_group_id = security_group['security_group']['id'] + res = self._create_port('json', net['network']['id'], + arg_list=('security_groups', + 'port_security_enabled'), + set_context=True, + tenant_id='demo_tenant', + port_security_enabled=True, + security_groups=[security_group_id]) + self.assertEqual(404, res.status_int) + def test_create_port_without_security_group_and_net_sec_false(self): res = self._create_network('json', 'net1', True, arg_list=('port_security_enabled',), @@ -326,6 +377,54 @@ self.deserialize('json', req.get_response(self.api)) self._delete('ports', port['port']['id']) + def test_update_port_with_admin_use_other_tenant_security_group(self): + if self._skip_security_group: + self.skipTest("Plugin does not support security groups") + with self.network() as net: + with self.subnet(network=net): + res = self._create_port('json', net['network']['id'], + set_context=True, is_admin=True, + tenant_id='admin_tenant',) + port = self.deserialize('json', res) + self.assertTrue(port['port'][psec.PORTSECURITY]) + + security_group = self.deserialize('json', + self._create_security_group(self.fmt, 'asdf', 'asdf', + tenant_id='other_tenant')) + security_group_id = security_group['security_group']['id'] + update_port = {'port': + {'security_groups': [security_group_id]}} + req = self.new_update_request('ports', update_port, + port['port']['id']) + port = self.deserialize('json', req.get_response(self.api)) + security_groups = port['port']['security_groups'] + self.assertIn(security_group_id, security_groups) + self._delete('ports', port['port']['id']) + + def test_update_port_with_no_admin_use_other_tenant_security_group(self): + if self._skip_security_group: + self.skipTest("Plugin does not support security groups") + with self.network(tenant_id='demo_tenant') as net: + with self.subnet(network=net, tenant_id='demo_tenant'): + res = self._create_port('json', net['network']['id'], + set_context=True, + tenant_id='demo_tenant',) + port = self.deserialize('json', res) + self.assertTrue(port['port'][psec.PORTSECURITY]) + + security_group = self.deserialize('json', + self._create_security_group(self.fmt, 'asdf', 'asdf', + tenant_id='other_tenant')) + security_group_id = security_group['security_group']['id'] + update_port = {'port': + {'security_groups': [security_group_id]}} + req = self.new_update_request('ports', update_port, + port['port']['id']) + req.environ['neutron.context'] = context.Context( + '', 'other_tenant') + res = req.get_response(self.api) + self.assertEqual(404, res.status_int) + def test_update_port_remove_port_security_security_group(self): if self._skip_security_group: self.skipTest("Plugin does not support security groups") diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_securitygroup.py neutron-16.4.2/neutron/tests/unit/extensions/test_securitygroup.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_securitygroup.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/extensions/test_securitygroup.py 2021-11-12 13:56:42.000000000 +0000 @@ -787,7 +787,7 @@ plugin = directory.get_plugin() if not hasattr(plugin, '_get_security_groups_on_port'): self.skipTest("plugin doesn't use the mixin with this method") - neutron_context = context.get_admin_context() + neutron_context = context.Context('user', 'tenant') res = self._create_security_group(self.fmt, 'webservers', 'webservers', tenant_id='bad_tenant') sg1 = self.deserialize(self.fmt, res) @@ -798,6 +798,22 @@ 'tenant_id': 'tenant'}} ) + def test_get_security_group_on_port_with_admin_from_other_tenant(self): + plugin = directory.get_plugin() + if not hasattr(plugin, '_get_security_groups_on_port'): + self.skipTest("plugin doesn't use the mixin with this method") + neutron_context = context.get_admin_context() + res = self._create_security_group(self.fmt, 'webservers', 'webservers', + tenant_id='other_tenant') + sg1 = self.deserialize(self.fmt, res) + sgs = plugin._get_security_groups_on_port( + neutron_context, + {'port': {'security_groups': [sg1['security_group']['id']], + 'tenant_id': 'tenant'}}) + sg1_id = sg1['security_group']['id'] + self.assertEqual(sg1_id, sgs[0].id) + self.assertEqual('other_tenant', sgs[0].project_id) + def test_delete_security_group(self): name = 'webservers' description = 'my webservers' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_segment.py neutron-16.4.2/neutron/tests/unit/extensions/test_segment.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/extensions/test_segment.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/extensions/test_segment.py 2021-11-12 13:56:42.000000000 +0000 @@ -419,7 +419,8 @@ events.PRECOMMIT_DELETE, mock.ANY, context=mock.ANY, - network_id=mock.ANY) + network_id=mock.ANY, + network=mock.ANY) class TestSegmentML2(SegmentTestCase): @@ -2478,6 +2479,33 @@ self.segments_plugin.nova_updater._send_notifications([event]) self.assertTrue(log.called) + def _test_create_network_and_segment(self, phys_net): + with self.network() as net: + network = net['network'] + segment = self._test_create_segment( + network_id=network['id'], physical_network=phys_net, + segmentation_id=200, network_type='vlan') + return network, segment['segment'] + + def test_delete_network_and_owned_segments(self): + db.subscribe() + aggregate = mock.MagicMock() + aggregate.uuid = uuidutils.generate_uuid() + aggregate.id = 1 + aggregate.hosts = ['fakehost1'] + self.mock_p_client.list_aggregates.return_value = { + 'aggregates': [aggregate.uuid]} + self.mock_n_client.aggregates.list.return_value = [aggregate] + self.mock_n_client.aggregates.get_details.return_value = aggregate + network, segment = self._test_create_network_and_segment('physnet') + self._delete('networks', network['id']) + self.mock_n_client.aggregates.remove_host.assert_has_calls( + [mock.call(aggregate.id, 'fakehost1')]) + self.mock_n_client.aggregates.delete.assert_has_calls( + [mock.call(aggregate.id)]) + self.mock_p_client.delete_resource_provider.assert_has_calls( + [mock.call(segment['id'])]) + class TestDhcpAgentSegmentScheduling(HostSegmentMappingTestCase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/fake_resources.py neutron-16.4.2/neutron/tests/unit/fake_resources.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/fake_resources.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/fake_resources.py 2021-11-12 13:56:42.000000000 +0000 @@ -71,10 +71,6 @@ self.idl = mock.Mock() self.add_static_route = mock.Mock() self.delete_static_route = mock.Mock() - self.create_address_set = mock.Mock() - self.update_address_set_ext_ids = mock.Mock() - self.delete_address_set = mock.Mock() - self.update_address_set = mock.Mock() self.get_all_chassis_gateway_bindings = mock.Mock() self.get_chassis_gateways = mock.Mock() self.get_gateway_chassis_binding = mock.Mock() @@ -99,12 +95,14 @@ self.get_parent_port.return_value = [] self.dns_add = mock.Mock() self.get_lswitch = mock.Mock() - fake_ovs_row = FakeOvsdbRow.create_one_ovsdb_row() - self.get_lswitch.return_value = fake_ovs_row + self.fake_ls_row = FakeOvsdbRow.create_one_ovsdb_row( + attrs={'ports': []}) + fake_lsp_row = FakeOvsdbRow.create_one_ovsdb_row() + self.get_lswitch.return_value = self.fake_ls_row self.get_lswitch_port = mock.Mock() - self.get_lswitch_port.return_value = fake_ovs_row + self.get_lswitch_port.return_value = fake_lsp_row self.get_ls_and_dns_record = mock.Mock() - self.get_ls_and_dns_record.return_value = (fake_ovs_row, None) + self.get_ls_and_dns_record.return_value = (self.fake_ls_row, None) self.ls_set_dns_records = mock.Mock() self.get_floatingip = mock.Mock() self.get_floatingip.return_value = None @@ -121,12 +119,6 @@ self.get_lrouter.return_value = None self.delete_lrouter_ext_gw = mock.Mock() self.delete_lrouter_ext_gw.return_value = None - self.is_port_groups_supported = mock.Mock() - # TODO(lucasagomes): Flip this return value to True at some point, - # port groups should be the default method used by networking-ovn - self.is_port_groups_supported.return_value = False - self.get_address_set = mock.Mock() - self.get_address_set.return_value = None self.pg_acl_add = mock.Mock() self.pg_acl_del = mock.Mock() self.pg_del = mock.Mock() @@ -148,6 +140,13 @@ self.ls_get = mock.Mock() self.check_liveness = mock.Mock() self.ha_chassis_group_get = mock.Mock() + self.qos_del_ext_ids = mock.Mock() + self.meter_add = mock.Mock() + self.meter_del = mock.Mock() + self.ha_chassis_group_add = mock.Mock() + self.ha_chassis_group_del = mock.Mock() + self.ha_chassis_group_add_chassis = mock.Mock() + self.ha_chassis_group_del_chassis = mock.Mock() class FakeOvsdbSbOvnIdl(object): @@ -169,6 +168,10 @@ self.is_col_present = mock.Mock() self.is_col_present.return_value = False self.db_set = mock.Mock() + self.lookup = mock.MagicMock() + self.chassis_list = mock.MagicMock() + self.is_table_present = mock.Mock() + self.is_table_present.return_value = False class FakeOvsdbTransaction(object): @@ -364,9 +367,29 @@ ovsdb_row_attrs.update(attrs) ovsdb_row_methods.update(methods) - return FakeResource(info=copy.deepcopy(ovsdb_row_attrs), - loaded=True, - methods=copy.deepcopy(ovsdb_row_methods)) + result = FakeResource(info=copy.deepcopy(ovsdb_row_attrs), + loaded=True, + methods=copy.deepcopy(ovsdb_row_methods)) + result.setkey.side_effect = lambda col, k, v: ( + getattr(result, col).__setitem__(k, v)) + + def fake_addvalue(col, val): + try: + getattr(result, col).append(val) + except AttributeError: + # Not all tests set up fake rows to have all used cols + pass + + def fake_delvalue(col, val): + try: + getattr(result, col).remove(val) + except (AttributeError, ValueError): + # Some tests also fake adding values + pass + + result.addvalue.side_effect = fake_addvalue + result.delvalue.side_effect = fake_delvalue + return result class FakeOvsdbTable(FakeResource): @@ -757,3 +780,33 @@ 'enabled': router.get('admin_state_up') or False, 'name': ovn_utils.ovn_name(router['id']), 'static_routes': routes}) + + +class FakeChassis(object): + + @staticmethod + def create(attrs=None, az_list=None, chassis_as_gw=False): + cms_opts = [] + if az_list: + cms_opts.append("%s=%s" % (ovn_const.CMS_OPT_AVAILABILITY_ZONES, + ':'.join(az_list))) + if chassis_as_gw: + cms_opts.append(ovn_const.CMS_OPT_CHASSIS_AS_GW) + + external_ids = {} + if cms_opts: + external_ids[ovn_const.OVN_CMS_OPTIONS] = ','.join(cms_opts) + + attrs = { + 'encaps': [], + 'external_ids': external_ids, + 'hostname': '', + 'name': uuidutils.generate_uuid(), + 'nb_cfg': 0, + 'other_config': {}, + 'transport_zones': [], + 'vtep_logical_switches': []} + + # Overwrite default attributes. + attrs.update(attrs) + return type('Chassis', (object, ), attrs) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/notifiers/test_nova.py neutron-16.4.2/neutron/tests/unit/notifiers/test_nova.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/notifiers/test_nova.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/notifiers/test_nova.py 2021-11-12 13:56:42.000000000 +0000 @@ -334,6 +334,7 @@ self.nova_notifier.send_events(response) mock_client.assert_called_once_with( api_versions.APIVersion(nova.NOVA_API_VERSION), + connect_retries=3, session=mock.ANY, region_name=cfg.CONF.nova.region_name, endpoint_type='public', @@ -347,6 +348,7 @@ self.nova_notifier.send_events(response) mock_client.assert_called_once_with( api_versions.APIVersion(nova.NOVA_API_VERSION), + connect_retries=3, session=mock.ANY, region_name=cfg.CONF.nova.region_name, endpoint_type='internal', diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/objects/plugins/ml2/test_base.py neutron-16.4.2/neutron/tests/unit/objects/plugins/ml2/test_base.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/objects/plugins/ml2/test_base.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/objects/plugins/ml2/test_base.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,19 +16,19 @@ class SegmentAllocationDbObjTestCase(object): - def test_get_unallocated_segments(self): - self.assertEqual( - [], self._test_class.get_unallocated_segments(self.context)) + def test_get_random_unallocated_segment(self): + self.assertIsNone( + self._test_class.get_random_unallocated_segment(self.context)) obj = self.objs[0] obj.allocated = True obj.create() - self.assertEqual( - [], self._test_class.get_unallocated_segments(self.context)) + self.assertIsNone( + self._test_class.get_random_unallocated_segment(self.context)) obj = self.objs[1] obj.allocated = False obj.create() - allocations = self._test_class.get_unallocated_segments(self.context) - self.assertEqual(1, len(allocations)) - self.assertEqual(obj.segmentation_id, allocations[0].segmentation_id) + allocations = self._test_class.get_random_unallocated_segment( + self.context) + self.assertEqual(obj.segmentation_id, allocations.segmentation_id) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/objects/test_network_segment_range.py neutron-16.4.2/neutron/tests/unit/objects/test_network_segment_range.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/objects/test_network_segment_range.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/objects/test_network_segment_range.py 2021-11-12 13:56:42.000000000 +0000 @@ -107,14 +107,14 @@ def _create_network_segment_range( self, minimum, maximum, network_type=None, physical_network=None, - project_id=None, default=False): + project_id=None, default=False, shared=False): kwargs = self.get_random_db_fields() kwargs.update({'network_type': network_type or constants.TYPE_VLAN, 'physical_network': physical_network or 'foo', 'minimum': minimum, 'maximum': maximum, 'default': default, - 'shared': default, + 'shared': shared, 'project_id': project_id}) db_obj = self._test_class.db_model(**kwargs) obj_fields = self._test_class.modify_fields_from_db(db_obj) @@ -180,7 +180,23 @@ obj.shared = False self.assertRaises(n_exc.ObjectActionError, obj.update) - def _create_environment(self): + def _create_vlan_environment_with_multiple_phynet( + self, physical_networks, project_id): + for phynet_name, vlan_range in physical_networks.items(): + self._create_network_segment_range( + vlan_range[0], vlan_range[1], + network_type=constants.TYPE_VLAN, + project_id=project_id, + physical_network=phynet_name, + default=True, shared=True).create() + + for segmentation_id in range(2, 4): + self._create_allocation( + vlan_alloc_obj.VlanAllocation, + segmentation_id=segmentation_id, + physical_network=phynet_name) + + def _create_environment(self, default_range=True): self.projects = [uuidutils.generate_uuid() for _ in range(3)] self.segment_ranges = { 'default': [100, 120], self.projects[0]: [90, 105], @@ -193,15 +209,31 @@ for name, ranges in self.segment_ranges.items(): default = True if name == 'default' else False project = name if not default else None + if default and not default_range: + continue + self._create_network_segment_range( ranges[0], ranges[1], network_type=subclass.network_type, - project_id=project, default=default).create() + project_id=project, default=default, + shared=default).create() # Build allocations (non allocated). for segmentation_id in range(self.seg_min, self.seg_max + 1): self._create_allocation(subclass, segmentation_id=segmentation_id) + def _create_shared_ranges(self): + self.shared_ranges = {0: [100, 105], 1: [110, 115]} + self.shared_ids = set(itertools.chain.from_iterable( + list(range(r[0], r[1] + 1)) for r in self.shared_ranges.values())) + for shared_range, subclass in itertools.product( + self.shared_ranges.values(), + ml2_base.SegmentAllocation.__subclasses__()): + self._create_network_segment_range( + shared_range[0], shared_range[1], + network_type=subclass.network_type, default=False, + shared=True).create() + def _default_range_set(self, project_id=None): range_set = set(range(self.segment_ranges['default'][0], self.segment_ranges['default'][1] + 1)) @@ -212,11 +244,13 @@ range_set.difference_update(prange_set) return range_set - def _allocate_random_allocations(self, allocations, subclass): + def _allocate_random_allocations(self, allocations, subclass, + num_of_allocations=None): pk_cols = subclass.db_model.__table__.primary_key.columns primary_keys = [col.name for col in pk_cols] allocated = [] - for allocation in random.sample(allocations, k=NUM_ALLOCATIONS): + for allocation in random.sample( + allocations, k=(num_of_allocations or NUM_ALLOCATIONS)): segment = dict((k, allocation[k]) for k in primary_keys) allocated.append(segment) self.assertEqual(1, subclass.allocate(self.context, **segment)) @@ -275,3 +309,92 @@ # a segmentation ID not belonging to any project. for alloc in allocated: self.assertEqual(1, subclass.deallocate(self.context, **alloc)) + + def test_get_segments_shared_without_physical_network_for_vlan(self): + phynet1_vlan_range = [2, 3] + phynet1_vlan_size = phynet1_vlan_range[1] - phynet1_vlan_range[0] + 1 + phynet2_vlan_range = [8, 9] + phynet2_vlan_size = phynet2_vlan_range[1] - phynet2_vlan_range[0] + 1 + phynets = {'phynet1': phynet1_vlan_range, + 'phynet2': phynet2_vlan_range} + project_id = uuidutils.generate_uuid() + self._create_vlan_environment_with_multiple_phynet( + phynets, project_id) + all_vlan_size = phynet1_vlan_size + phynet2_vlan_size + filters = {'project_id': project_id} + + # First allocation, the phynet1's vlan id will be exhausted. + allocations = network_segment_range.NetworkSegmentRange. \ + get_segments_shared( + self.context, vlan_alloc_obj.VlanAllocation.db_model, + constants.TYPE_VLAN, + vlan_alloc_obj.VlanAllocation.get_segmentation_id(), + **filters) + self.assertEqual(all_vlan_size, len(allocations)) + alloc_phynet = [] + for alloc in allocations: + alloc_phynet.append(alloc.physical_network) + alloc_phynet = set(alloc_phynet) + self.assertEqual(2, len(alloc_phynet)) + allocated = self._allocate_random_allocations( + allocations, vlan_alloc_obj.VlanAllocation) + remain_vlan_size = all_vlan_size - len(allocated) + + # Second allocation, all vlan id will be exhausted. + allocations = network_segment_range.NetworkSegmentRange. \ + get_segments_shared( + self.context, vlan_alloc_obj.VlanAllocation.db_model, + constants.TYPE_VLAN, + vlan_alloc_obj.VlanAllocation.get_segmentation_id(), + **filters) + self.assertEqual(len(allocations), all_vlan_size - NUM_ALLOCATIONS) + self._allocate_random_allocations(allocations, + vlan_alloc_obj.VlanAllocation, + remain_vlan_size) + alloc_phynet = [] + for alloc in allocations: + alloc_phynet.append(alloc.physical_network) + alloc_phynet = set(alloc_phynet) + self.assertEqual(1, len(alloc_phynet)) + + # Last allocation, we can't get any vlan segment. + allocations = network_segment_range.NetworkSegmentRange. \ + get_segments_shared( + self.context, vlan_alloc_obj.VlanAllocation.db_model, + constants.TYPE_VLAN, + vlan_alloc_obj.VlanAllocation.get_segmentation_id(), + **filters) + self.assertEqual(0, len(allocations)) + + def test_get_segments_shared_no_shared_ranges(self): + self._create_environment(default_range=False) + for project_id, subclass in itertools.product( + self.projects, ml2_base.SegmentAllocation.__subclasses__()): + filters = {'project_id': project_id, + 'physical_network': 'foo'} + allocations = network_segment_range.NetworkSegmentRange. \ + get_segments_shared( + self.context, subclass.db_model, subclass.network_type, + subclass.get_segmentation_id(), **filters) + + self.assertEqual([], allocations) + + def test_get_segments_shared_no_default_range_two_shared_ranges(self): + self._create_environment(default_range=False) + self.projects.append(None) + self._create_shared_ranges() + for project_id, subclass in itertools.product( + self.projects, ml2_base.SegmentAllocation.__subclasses__()): + + filters = {'project_id': project_id, + 'physical_network': 'foo'} + allocations = network_segment_range.NetworkSegmentRange. \ + get_segments_shared( + self.context, subclass.db_model, subclass.network_type, + subclass.get_segmentation_id(), **filters) + + prange = self._default_range_set(project_id) + available_ids = prange & self.shared_ids + self.assertEqual(len(available_ids), len(allocations)) + for alloc in allocations: + self.assertIn(alloc.segmentation_id, available_ids) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/objects/test_ports.py neutron-16.4.2/neutron/tests/unit/objects/test_ports.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/objects/test_ports.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/objects/test_ports.py 2021-11-12 13:56:42.000000000 +0000 @@ -11,6 +11,7 @@ # under the License. import mock +import netaddr from neutron_lib import constants from neutron_lib.tests import tools from oslo_utils import uuidutils @@ -545,3 +546,59 @@ self.context, network_id, binding_types=['vif_type1', 'vif_type2'], negative_search=True)) + + def test_get_ports_allocated_by_subnet_id(self): + network_id = self._create_test_network_id() + segment_id = self._create_test_segment_id(network_id) + subnet_id = self._create_test_subnet_id(network_id) + self.update_obj_fields( + {'network_id': network_id, + 'fixed_ips': {'subnet_id': subnet_id, + 'network_id': network_id}, + 'device_owner': 'not_a_router', + 'binding_levels': {'segment_id': segment_id}}, + db_objs=[self.db_objs[0]]) + + objs = [] + for idx in range(3): + objs.append(self._make_object(self.obj_fields[idx])) + objs[idx].create() + + ipa = ports.IPAllocation(self.context, port_id=objs[0].id, + subnet_id=subnet_id, network_id=network_id, + ip_address=netaddr.IPAddress('10.0.0.1')) + ipa.create() + + ports_alloc = ports.Port.get_ports_allocated_by_subnet_id(self.context, + subnet_id) + self.assertEqual(1, len(ports_alloc)) + self.assertEqual(objs[0].id, ports_alloc[0].id) + + def _test_get_auto_deletable_ports(self, device_owner): + network_id = self._create_test_network_id() + segment_id = self._create_test_segment_id(network_id) + port = self._create_test_port(device_owner=device_owner) + binding = ports.PortBindingLevel( + self.context, port_id=port.id, + host='host1', level=0, segment_id=segment_id) + binding.create() + return ( + ports.Port. + get_auto_deletable_port_ids_and_proper_port_count_by_segment( + self.context, segment_id)) + + def test_get_auto_deletable_ports_dhcp(self): + dhcp_ports, count = self._test_get_auto_deletable_ports( + 'network:dhcp') + self.assertEqual( + (1, 0), + (len(dhcp_ports), count), + ) + + def test_get_auto_deletable_ports_not_dhcp(self): + dhcp_ports, count = self._test_get_auto_deletable_ports( + 'not_network_dhcp') + self.assertEqual( + (0, 1), + (len(dhcp_ports), count), + ) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_arp_protect.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_arp_protect.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_arp_protect.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_arp_protect.py 2021-11-12 13:56:42.000000000 +0000 @@ -72,31 +72,35 @@ 'neutronMAC-%s' % vif, '-P', 'DROP'], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), - mock.ANY, mock.call(['ebtables', '-t', 'nat', '--concurrent', '-A', + 'neutronMAC-%s' % vif, '-j', 'DROP'], + check_exit_code=True, extra_ok_codes=None, + log_fail_as_error=True, run_as_root=True), + mock.ANY, + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-I', 'PREROUTING', '-i', vif, '-j', mac_chain], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), - mock.call(['ebtables', '-t', 'nat', '--concurrent', '-A', + mock.ANY, + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-I', mac_chain, '-i', vif, '--among-src', '%s' % ','.join(sorted(mac_addresses)), '-j', 'RETURN'], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), mock.ANY, - mock.ANY, mock.call(['ebtables', '-t', 'nat', '--concurrent', '-N', spoof_chain, '-P', 'DROP'], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), - mock.call(['ebtables', '-t', 'nat', '--concurrent', '-F', - spoof_chain], + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-A', + spoof_chain, '-j', 'DROP'], check_exit_code=True, extra_ok_codes=None, - log_fail_as_error=True, run_as_root=True), + log_fail_as_error=True, run_as_root=True) ] for addr in sorted(ip_addresses): expected.extend([ - mock.call(['ebtables', '-t', 'nat', '--concurrent', '-A', + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-I', spoof_chain, '-p', 'ARP', '--arp-ip-src', addr, '-j', 'ACCEPT'], check_exit_code=True, extra_ok_codes=None, @@ -135,30 +139,20 @@ '-p', 'ARP'], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), - mock.call(['ebtables', '-t', 'nat', '--concurrent', '-X', + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-F', spoof_chain], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), - mock.ANY, mock.call(['ebtables', '-t', 'nat', '--concurrent', '-X', - mac_chain], - check_exit_code=True, extra_ok_codes=None, - log_fail_as_error=True, run_as_root=True), - mock.call(['ebtables', '-t', 'filter', '--concurrent', '-L'], + spoof_chain], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), mock.ANY, - mock.call(['ebtables', '-t', 'filter', '--concurrent', '-D', - 'FORWARD', '-i', VIF, '-j', spoof_chain, - '-p', 'ARP'], - check_exit_code=True, extra_ok_codes=None, - log_fail_as_error=True, run_as_root=True), - mock.call(['ebtables', '-t', 'filter', '--concurrent', '-X', - spoof_chain], + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-F', + mac_chain], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), - mock.ANY, - mock.call(['ebtables', '-t', 'filter', '--concurrent', '-X', + mock.call(['ebtables', '-t', 'nat', '--concurrent', '-X', mac_chain], check_exit_code=True, extra_ok_codes=None, log_fail_as_error=True, run_as_root=True), diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -412,6 +412,7 @@ dstport=None, ttl=None, dev=self.lbm.local_int, + local=self.lbm.local_ip, proxy=expected_proxy) def test_ensure_vxlan_arp_responder_enabled(self): @@ -454,7 +455,7 @@ return_value=vxlan_dev) as add_vxlan_fn,\ mock.patch.object(vxlan_dev.link, 'set_mtu', side_effect=ip_lib.InvalidArgument( - parameter="MTU", value=mtu)),\ + device='device_exists', namespace='ns')),\ mock.patch.object(ip_lib, 'get_device_mtu', return_value=physical_mtu),\ mock.patch.object(vxlan_dev.link, 'delete') as delete_dev: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,9 +21,9 @@ from neutron.objects.qos import policy from neutron.objects.qos import rule -from neutron.plugins.ml2.drivers.mech_sriov.agent.common import exceptions from neutron.plugins.ml2.drivers.mech_sriov.agent.extension_drivers import ( qos_driver) +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.tests import base @@ -115,8 +115,16 @@ self.clear_max_rate_mock.assert_called_once_with(self.PCI_SLOT) def test__set_vf_max_rate_captures_sriov_failure(self): - self.max_rate_mock.side_effect = exceptions.SriovNicError() - self.qos_driver._set_vf_max_rate(self.ASSIGNED_MAC, self.PCI_SLOT) + msg = 'Failed to set device %s max rate' + with mock.patch.object(qos_driver, 'LOG') as mock_log: + for exc in (priv_ip_lib.InterfaceOperationNotSupported(), + priv_ip_lib.InvalidArgument()): + self.max_rate_mock.side_effect = exc + self.qos_driver._set_vf_max_rate(self.ASSIGNED_MAC, + self.PCI_SLOT) + mock_log.exception.assert_called_once_with(msg, + self.ASSIGNED_MAC) + mock_log.exception.reset_mock() def test__set_vf_max_rate_unknown_device(self): with mock.patch.object(self.qos_driver.eswitch_mgr, 'device_exists', diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -18,7 +18,6 @@ import mock -from neutron.agent.linux import ip_link_support from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm @@ -72,8 +71,8 @@ PCI_SLOT = '0000:06:00.1' WRONG_MAC = '00:00:00:00:00:67' WRONG_PCI = "0000:06:00.6" - MAX_RATE = ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE - MIN_RATE = ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE + MAX_RATE = esm.IP_LINK_CAPABILITY_RATE + MIN_RATE = esm.IP_LINK_CAPABILITY_MIN_TX_RATE def setUp(self): super(TestESwitchManagerApi, self).setUp() @@ -526,55 +525,48 @@ with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2000) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2000) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2) + 0, esm.IP_LINK_CAPABILITY_RATE, 2) def test_set_device_max_rate_ok2(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 99) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 99) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 1) + 0, esm.IP_LINK_CAPABILITY_RATE, 1) def test_set_device_max_rate_rounded_ok(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2001) - pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2001) + pci_lib_mock.assert_called_with(0, esm.IP_LINK_CAPABILITY_RATE, 2) def test_set_device_max_rate_rounded_ok2(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2499) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2499) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2) + 0, esm.IP_LINK_CAPABILITY_RATE, 2) def test_set_device_max_rate_rounded_ok3(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2500) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2500) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 3) + 0, esm.IP_LINK_CAPABILITY_RATE, 3) def test_set_device_max_rate_disable(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 0) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 0) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 0) + 0, esm.IP_LINK_CAPABILITY_RATE, 0) def test_set_device_max_rate_fail(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." @@ -583,7 +575,7 @@ exc.InvalidPciSlotError, self.emb_switch.set_device_rate, self.WRONG_PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 1000) + esm.IP_LINK_CAPABILITY_RATE, 1000) def test_get_pci_device(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,28 +16,18 @@ import mock -from neutron.agent.linux import ip_link_support -from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ - import exceptions as exc +from neutron.agent.linux import ip_lib from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib from neutron.tests import base class TestPciLib(base.BaseTestCase): DEV_NAME = "p7p1" + VF_INDEX = 1 - VF_INDEX_DISABLE = 0 - PF_LINK_SHOW = ('122: p7p1: mtu 1500 qdisc noop' - ' state DOWN mode DEFAULT group default qlen 1000') - PF_MAC = ' link/ether f4:52:14:2a:3e:c0 brd ff:ff:ff:ff:ff:ff' - VF_0_LINK_SHOW = (' vf 0 MAC fa:16:3e:b4:81:ac, vlan 4095, spoof' - ' checking off, link-state disable') - VF_1_LINK_SHOW = (' vf 1 MAC 00:00:00:00:00:11, vlan 4095, spoof' - ' checking off, link-state enable') - VF_2_LINK_SHOW = (' vf 2 MAC fa:16:3e:68:4e:79, vlan 4095, spoof' - ' checking off, link-state enable') - VF_LINK_SHOW = '\n'.join((PF_LINK_SHOW, PF_MAC, VF_0_LINK_SHOW, - VF_1_LINK_SHOW, VF_2_LINK_SHOW)) + VFS_LIST = {0: {'mac': 'fa:16:3e:b4:81:ac', 'link_state': 2}, + 1: {'mac': '00:00:00:00:00:11', 'link_state': 1}, + 2: {'mac': 'fa:16:3e:68:4e:79', 'link_state': 0}} MAC_MAPPING = { 0: "fa:16:3e:b4:81:ac", @@ -45,121 +35,79 @@ 2: "fa:16:3e:68:4e:79", } + STATE_MAPPING = { # VF index: state (string), according to VFS_LIST + 0: pci_lib.LinkState.disable.name, + 1: pci_lib.LinkState.enable.name, + 2: pci_lib.LinkState.auto.name, + } + def setUp(self): super(TestPciLib, self).setUp() self.pci_wrapper = pci_lib.PciDeviceIPWrapper(self.DEV_NAME) + self.mock_ip_device = mock.Mock() + self.mock_ip_device.link.get_vfs.return_value = self.VFS_LIST + mock.patch.object(ip_lib, 'IPDevice', + return_value=self.mock_ip_device).start() def test_get_assigned_macs(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.return_value = self.VF_LINK_SHOW - result = self.pci_wrapper.get_assigned_macs([self.VF_INDEX]) - self.assertEqual( - {self.VF_INDEX: self.MAC_MAPPING[self.VF_INDEX]}, result) - - def test_get_assigned_macs_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.get_assigned_macs, - [self.VF_INDEX]) - - def test_get_vf_state_enable(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.return_value = self.VF_LINK_SHOW - result = self.pci_wrapper.get_vf_state(self.VF_INDEX) - self.assertEqual('enable', result) - - def test_get_vf_state_disable(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.return_value = self.VF_LINK_SHOW - result = self.pci_wrapper.get_vf_state(self.VF_INDEX_DISABLE) - self.assertEqual('disable', result) - - def test_get_vf_state_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.get_vf_state, - self.VF_INDEX) + for idx in range(len(self.VFS_LIST)): + result = self.pci_wrapper.get_assigned_macs([idx]) + self.assertEqual({idx: self.MAC_MAPPING[idx]}, result) + + def test_get_assigned_macs_not_present(self): + result = self.pci_wrapper.get_assigned_macs([1000]) + self.assertEqual({}, result) + + def test_get_vf_state(self): + for idx in range(len(self.VFS_LIST)): + result = self.pci_wrapper.get_vf_state(idx) + self.assertEqual(self.STATE_MAPPING[idx], result) + + def test_get_vf_state_not_present(self): + result = self.pci_wrapper.get_vf_state(1000) + self.assertEqual(pci_lib.LinkState.disable.name, result) def test_set_vf_state(self): - with mock.patch.object(self.pci_wrapper, "_as_root"): - result = self.pci_wrapper.set_vf_state(self.VF_INDEX, - True) - self.assertIsNone(result) - - def test_set_vf_state_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.set_vf_state, - self.VF_INDEX, - True) + self.pci_wrapper.set_vf_state(self.VF_INDEX, True) + vf = {'vf': self.VF_INDEX, 'link_state': 1} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_state(self.VF_INDEX, False) + vf = {'vf': self.VF_INDEX, 'link_state': 2} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_state(self.VF_INDEX, False, auto=True) + vf = {'vf': self.VF_INDEX, 'link_state': 0} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) def test_set_vf_spoofcheck(self): - with mock.patch.object(self.pci_wrapper, "_as_root"): - result = self.pci_wrapper.set_vf_spoofcheck(self.VF_INDEX, - True) - self.assertIsNone(result) - - def test_set_vf_spoofcheck_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.set_vf_spoofcheck, - self.VF_INDEX, - True) - - def _set_vf_rate(self, rate, passed=True): - if passed: - with mock.patch.object(self.pci_wrapper, "_as_root") \ - as mock_as_root: - result = self.pci_wrapper.set_vf_rate( - self.VF_INDEX, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, - 1000) - self.assertIsNone(result) - mock_as_root.assert_called_once_with( - [], "link", ("set", self.DEV_NAME, "vf", - str(self.VF_INDEX), "rate", '1000')) - else: - with mock.patch.object(self.pci_wrapper, "_as_root", - side_effect=Exception()): - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.set_vf_rate, - self.VF_INDEX, - rate, - 1000) - - def test_set_vf_rate_max_rate(self): - self._set_vf_rate( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE) - - def test_set_vf_rate_max_rate_fail(self): - self._set_vf_rate('rate', passed=False) - - def test_set_vf_rate_min_tx_rate(self): - self._set_vf_rate( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE) - - def test_set_vf_rate_min_tx_rate_fail(self): - self._set_vf_rate( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, - passed=False) - - def test_set_vf_state_not_supported(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception( - pci_lib.PciDeviceIPWrapper.IP_LINK_OP_NOT_SUPPORTED) - self.assertRaises(exc.IpCommandOperationNotSupportedError, - self.pci_wrapper.set_vf_state, - self.VF_INDEX, - state=True) + self.pci_wrapper.set_vf_spoofcheck(self.VF_INDEX, True) + vf = {'vf': self.VF_INDEX, 'spoofchk': 1} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_spoofcheck(self.VF_INDEX, False) + vf = {'vf': self.VF_INDEX, 'spoofchk': 0} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + def test_set_vf_rate(self): + self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'max_tx_rate', 20) + vf = {'vf': self.VF_INDEX, 'rate': {'max_tx_rate': 20}} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'min_tx_rate', 10) + vf = {'vf': self.VF_INDEX, 'rate': {'min_tx_rate': 10}} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + @mock.patch.object(pci_lib, 'LOG') + def test_set_vf_rate_exception(self, mock_log): + self.mock_ip_device.link.set_vf_feature.side_effect = ( + ip_lib.InvalidArgument) + self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'min_tx_rate', 10) + mock_log.error.assert_called_once_with( + 'Device %(device)s does not support ip-link vf "%(rate_type)s" ' + 'parameter.', {'device': self.DEV_NAME, 'rate_type': 'min_tx_rate'} + ) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -19,12 +19,13 @@ from neutron_lib import constants from oslo_config import cfg from oslo_utils import uuidutils +import pyroute2 from neutron.agent.l2 import l2_agent_extensions_manager as l2_ext_manager from neutron.agent import rpc as agent_rpc from neutron.plugins.ml2.drivers.mech_sriov.agent.common import config # noqa -from neutron.plugins.ml2.drivers.mech_sriov.agent.common import exceptions from neutron.plugins.ml2.drivers.mech_sriov.agent import sriov_nic_agent +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.tests import base DEVICE_MAC = '11:22:33:44:55:66' @@ -423,8 +424,7 @@ agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.set_device_state.side_effect = ( - exceptions.IpCommandOperationNotSupportedError( - dev_name='aa:bb:cc:dd:ee:ff')) + priv_ip_lib.InterfaceOperationNotSupported()) self.assertTrue(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', admin_state_up=True)) @@ -435,7 +435,7 @@ agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.set_device_state.side_effect = ( - exceptions.SriovNicError()) + pyroute2.NetlinkError(22)) self.assertFalse(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', admin_state_up=True)) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py 2021-11-12 13:56:42.000000000 +0000 @@ -136,11 +136,12 @@ self.assertEqual('192.168.0.1', f('192.168.0.1/32')) self.assertEqual(('192.168.0.0', '255.255.255.0'), f('192.168.0.0/24')) - def test__setup_controllers__out_of_band(self): + def _test_setup_controllers(self, existing_controllers): cfg = mock.MagicMock() - cfg.OVS.of_listen_address = "" - cfg.OVS.of_listen_port = "" + cfg.OVS.of_listen_address = "127.0.0.1" + cfg.OVS.of_listen_port = "6633" + m_get_controller = mock.patch.object(self.br, 'get_controller') m_add_protocols = mock.patch.object(self.br, 'add_protocols') m_set_controller = mock.patch.object(self.br, 'set_controller') m_set_probe = mock.patch.object(self.br, @@ -148,10 +149,28 @@ m_set_ccm = mock.patch.object(self.br, 'set_controllers_connection_mode') - with m_set_ccm as set_ccm: - with m_set_controller, m_add_protocols, m_set_probe: - self.br.setup_controllers(cfg) - set_ccm.assert_called_once_with("out-of-band") + with m_set_ccm as set_ccm, \ + m_add_protocols as add_protocols, \ + m_set_controller as set_controller, \ + m_get_controller as get_controller, \ + m_set_probe: + get_controller.return_value = existing_controllers + + self.br.setup_controllers(cfg) + + if existing_controllers: + set_controller.assert_not_called() + else: + set_controller.assert_called_once_with(["tcp:127.0.0.1:6633"]) + set_ccm.assert_called_once_with("out-of-band") + add_protocols.assert_called_once_with(constants.OPENFLOW13) + + def test_setup_controllers(self): + self._test_setup_controllers(existing_controllers=[]) + + def test_setup_controllers_when_already_exists(self): + self._test_setup_controllers( + existing_controllers=["tcp:127.0.0.1:6633"]) class OVSDVRProcessTestMixin(object): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py 2021-11-12 13:56:42.000000000 +0000 @@ -87,7 +87,7 @@ ], match=ofpp.OFPMatch(), priority=3, - table_id=61), + table_id=constants.TRANSIENT_EGRESS_TABLE), active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -285,6 +285,48 @@ ] self.assertEqual(expected, self.mock.mock_calls) + def test_install_dvr_to_src_mac_flat(self): + network_type = 'flat' + gateway_mac = '08:60:6e:7f:74:e7' + dst_mac = '00:02:b3:13:fe:3d' + dst_port = 6666 + self.br.install_dvr_to_src_mac(network_type=network_type, + vlan_tag=None, + gateway_mac=gateway_mac, + dst_mac=dst_mac, + dst_port=dst_port) + (dp, ofp, ofpp) = self._get_dp() + expected = [ + call._send_msg(ofpp.OFPFlowMod(dp, + cookie=self.stamp, + instructions=[ + ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ + ofpp.OFPActionSetField(eth_src=gateway_mac), + ]), + ofpp.OFPInstructionGotoTable(table_id=60), + ], + match=ofpp.OFPMatch( + eth_dst=dst_mac, + vlan_vid=ofp.OFPVID_NONE), + priority=20, + table_id=2), + active_bundle=None), + call._send_msg(ofpp.OFPFlowMod(dp, + cookie=self.stamp, + instructions=[ + ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ + ofpp.OFPActionOutput(dst_port, 0), + ]), + ], + match=ofpp.OFPMatch( + eth_dst=dst_mac, + vlan_vid=ofp.OFPVID_NONE), + priority=20, + table_id=60), + active_bundle=None), + ] + self.assertEqual(expected, self.mock.mock_calls) + def test_delete_dvr_to_src_mac_vlan(self): network_type = 'vlan' vlan_tag = 1111 @@ -311,10 +353,36 @@ ] self.assertEqual(expected, self.mock.mock_calls) - def test_add_dvr_mac_vlan(self): + def test_delete_dvr_to_src_mac_flat(self): + network_type = 'flat' + vlan_tag = None + dst_mac = '00:02:b3:13:fe:3d' + self.br.delete_dvr_to_src_mac(network_type=network_type, + vlan_tag=vlan_tag, + dst_mac=dst_mac) + (dp, ofp, ofpp) = self._get_dp() + expected = [ + call.uninstall_flows( + strict=True, + priority=20, + table_id=2, + match=ofpp.OFPMatch( + eth_dst=dst_mac, + vlan_vid=ofp.OFPVID_NONE)), + call.uninstall_flows( + strict=True, + priority=20, + table_id=60, + match=ofpp.OFPMatch( + eth_dst=dst_mac, + vlan_vid=ofp.OFPVID_NONE)), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_add_dvr_mac_physical(self): mac = '00:02:b3:13:fe:3d' port = 8888 - self.br.add_dvr_mac_vlan(mac=mac, port=port) + self.br.add_dvr_mac_physical(mac=mac, port=port) (dp, ofp, ofpp) = self._get_dp() expected = [ call._send_msg(ofpp.OFPFlowMod(dp, @@ -487,12 +555,15 @@ self.assertEqual(expected, self.mock.mock_calls) def _test_delete_dvr_dst_mac_for_arp(self, network_type): - if network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN + if network_type in (p_const.TYPE_VLAN, p_const.TYPE_FLAT): + table_id = constants.DVR_TO_SRC_MAC_PHYSICAL else: table_id = constants.DVR_TO_SRC_MAC - vlan_tag = 1111 + if network_type == p_const.TYPE_FLAT: + vlan_tag = None + else: + vlan_tag = 1111 gateway_mac = '00:02:b3:13:fe:3e' dvr_mac = '00:02:b3:13:fe:3f' rtr_port = 8888 @@ -502,15 +573,26 @@ dvr_mac=dvr_mac, rtr_port=rtr_port) (dp, ofp, ofpp) = self._get_dp() - expected = [ - call.uninstall_flows( - strict=True, - priority=5, - table_id=table_id, - match=ofpp.OFPMatch( - eth_dst=dvr_mac, - vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)), - ] + if network_type == p_const.TYPE_FLAT: + expected = [ + call.uninstall_flows( + strict=True, + priority=5, + table_id=table_id, + match=ofpp.OFPMatch( + eth_dst=dvr_mac, + vlan_vid=ofp.OFPVID_NONE)), + ] + else: + expected = [ + call.uninstall_flows( + strict=True, + priority=5, + table_id=table_id, + match=ofpp.OFPMatch( + eth_dst=dvr_mac, + vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)), + ] self.assertEqual(expected, self.mock.mock_calls) def test_delete_dvr_dst_mac_for_arp_vlan(self): @@ -519,6 +601,9 @@ def test_delete_dvr_dst_mac_for_arp_tunnel(self): self._test_delete_dvr_dst_mac_for_arp(network_type='vxlan') + def test_delete_dvr_dst_mac_for_flat(self): + self._test_delete_dvr_dst_mac_for_arp(network_type='flat') + def test_install_dscp_marking_rule(self): test_port = 8888 test_mark = 38 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py 2021-11-12 13:56:42.000000000 +0000 @@ -27,7 +27,7 @@ class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ovs_bridge_test_base.OVSDVRProcessTestMixin): - dvr_process_table_id = ovs_const.DVR_PROCESS_VLAN + dvr_process_table_id = ovs_const.DVR_PROCESS_PHYSICAL dvr_process_next_table_id = ovs_const.LOCAL_VLAN_TRANSLATION def setUp(self): @@ -125,10 +125,10 @@ ] self.assertEqual(expected, self.mock.mock_calls) - def test_add_dvr_mac_vlan(self): + def test_add_dvr_mac_physical(self): mac = '00:02:b3:13:fe:3d' port = 8888 - self.br.add_dvr_mac_vlan(mac=mac, port=port) + self.br.add_dvr_mac_physical(mac=mac, port=port) (dp, ofp, ofpp) = self._get_dp() expected = [ call._send_msg(ofpp.OFPFlowMod(dp, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py 2021-11-12 13:56:42.000000000 +0000 @@ -66,6 +66,8 @@ TEST_NETWORK_ID1 = 'net-id-1' TEST_NETWORK_ID2 = 'net-id-2' +TEST_MTU = 7824 + DEVICE_OWNER_COMPUTE = n_const.DEVICE_OWNER_COMPUTE_PREFIX + 'fake' @@ -1280,13 +1282,19 @@ def test_port_update_smartnic(self): cfg.CONF.set_default('baremetal_smartnic', True, group='AGENT') - port_arg = {"id": TEST_PORT_ID1} + network_id = 'e850ed99-5f46-47bc-8c06-86d9d519c46b' + port_arg = {"id": TEST_PORT_ID1, "network_id": network_id} + network = {'id': network_id, 'mtu': TEST_MTU} with mock.patch.object(self.agent.plugin_rpc.remote_resource_cache, - "get_resource_by_id") as mocked_resource: + "get_resource_by_id") as mocked_resource,\ + mock.patch.object(self.agent.plugin_rpc, + "get_network_details", + return_value=network): port = Port() port['id'] = 'd850ed99-5f46-47bc-8c06-86d9d519c46a' port['mac_address'] = netaddr.EUI(FAKE_MAC) port['device_id'] = '0' + port['network_id'] = network_id bindings_data = PortBinding() bindings_data['host'] = 'host' bindings_data['vnic_type'] = portbindings.VNIC_SMARTNIC @@ -1302,7 +1310,8 @@ 'vm_uuid': port['device_id'], 'vif_name': 'rep_port', 'iface_id': port['id'], - 'vif_type': bindings_data['vif_type'] + 'vif_type': bindings_data['vif_type'], + 'mtu': TEST_MTU } self.assertEqual({TEST_PORT_ID1}, self.agent.updated_ports) self.assertEqual([expected_smartnic_port_data], @@ -1314,6 +1323,7 @@ vif_name = "rep0-0" vif_id = port_arg["id"] vif_mac = FAKE_MAC + default_mtu = 1500 self.agent.current_smartnic_ports_map = { vif_id: { 'vif_mac': vif_mac, @@ -1339,7 +1349,8 @@ 'vm_uuid': '', 'vif_name': 'rep0-0', 'iface_id': port['id'], - 'vif_type': portbindings.VIF_TYPE_UNBOUND + 'vif_type': portbindings.VIF_TYPE_UNBOUND, + 'mtu': default_mtu }] mocked_resource.assert_called_with(resources.PORT, port['id']) self.assertEqual({port['id']}, self.agent.updated_ports) @@ -1518,7 +1529,10 @@ self.assertIn('added_port_id', port_info['added']) self.assertNotIn('activated_port_id', port_info['added']) - def _test_setup_physical_bridges(self, port_exists=False): + def _test_setup_physical_bridges(self, port_exists=False, + dvr_enabled=False, + igmp_snooping_enabled=False): + self.agent.enable_distributed_routing = dvr_enabled with mock.patch.object(ip_lib.IPDevice, "exists") as devex_fn,\ mock.patch.object(sys, "exit"),\ mock.patch.object(self.agent, 'br_phys_cls') as phys_br_cls,\ @@ -1575,8 +1589,15 @@ 'phy-br-eth', constants.NONEXISTENT_PEER), ] expected_calls += [ - mock.call.int_br.drop_port(in_port='int_ofport'), - mock.call.phys_br.drop_port(in_port='phy_ofport'), + mock.call.int_br.set_igmp_snooping_flood( + 'int-br-eth', igmp_snooping_enabled), + mock.call.int_br.drop_port(in_port='int_ofport') + ] + if not dvr_enabled: + expected_calls += [ + mock.call.phys_br.drop_port(in_port='phy_ofport') + ] + expected_calls += [ mock.call.int_br.set_db_attribute('Interface', 'int-br-eth', 'options', {'peer': 'phy-br-eth'}), @@ -1596,6 +1617,9 @@ def test_setup_physical_bridges_port_exists(self): self._test_setup_physical_bridges(port_exists=True) + def test_setup_physical_bridges_dvr_enabled(self): + self._test_setup_physical_bridges(dvr_enabled=True) + def test_setup_physical_bridges_using_veth_interconnection(self): self.agent.use_veth_interconnection = True with mock.patch.object(ip_lib.IPDevice, "exists") as devex_fn,\ @@ -1636,6 +1660,10 @@ int_br.add_port.assert_called_with("int-br-eth") phys_br.add_port.assert_called_with("phy-br-eth") + def test_setup_physical_bridges_igmp_snooping_enabled(self): + cfg.CONF.set_override('igmp_snooping_enable', True, 'OVS') + self._test_setup_physical_bridges(igmp_snooping_enabled=True) + def _test_setup_physical_bridges_change_from_veth_to_patch_conf( self, port_exists=False): with mock.patch.object(sys, "exit"),\ @@ -1691,6 +1719,8 @@ 'phy-br-eth', constants.NONEXISTENT_PEER), ] expected_calls += [ + mock.call.int_br.set_igmp_snooping_flood( + 'int-br-eth', False), mock.call.int_br.drop_port(in_port='int_ofport'), mock.call.phys_br.drop_port(in_port='phy_ofport'), mock.call.int_br.set_db_attribute('Interface', 'int-br-eth', @@ -1731,6 +1761,8 @@ return_value=False),\ mock.patch.object(self.agent.int_br, 'port_exists', return_value=False),\ + mock.patch.object(self.agent.int_br, + 'set_igmp_snooping_flood'),\ mock.patch.object(sys, "exit"): self.agent.setup_tunnel_br(None) self.agent.setup_tunnel_br() @@ -1755,6 +1787,8 @@ "add_patch_port") as int_patch_port,\ mock.patch.object(self.agent.tun_br, "add_patch_port") as tun_patch_port,\ + mock.patch.object(self.agent.int_br, + 'set_igmp_snooping_flood'),\ mock.patch.object(sys, "exit"): self.agent.setup_tunnel_br(None) self.agent.setup_tunnel_br() @@ -2334,6 +2368,23 @@ self._test_ovs_status(constants.OVS_NORMAL, constants.OVS_RESTARTED) + def test_ovs_restart_for_ingress_direct_goto_flows(self): + with mock.patch.object(self.agent, + 'check_ovs_status', + return_value=constants.OVS_RESTARTED), \ + mock.patch.object(self.agent, + '_agent_has_updates', + side_effect=TypeError('loop exit')), \ + mock.patch.object(self.agent, 'setup_integration_br'), \ + mock.patch.object(self.agent, + 'install_ingress_direct_goto_flows') as \ + install_ingress_direct_goto_flows: + try: + self.agent.rpc_loop(polling_manager=mock.Mock()) + except TypeError: + pass + install_ingress_direct_goto_flows.assert_called_once_with() + def test_rpc_loop_fail_to_process_network_ports_keep_flows(self): with mock.patch.object(async_process.AsyncProcess, "_spawn"),\ mock.patch.object(async_process.AsyncProcess, "start"),\ @@ -2643,12 +2694,13 @@ 'vm_uuid': vm_uuid, 'vif_name': rep_port, 'iface_id': iface_id, - 'vif_type': vif_type} + 'vif_type': vif_type, + 'mtu': TEST_MTU} cfg.CONF.set_default('baremetal_smartnic', True, group='AGENT') agent = self._make_agent() instance_info = vif_instance_object.InstanceInfo(uuid=vm_uuid) - vif = agent._get_vif_object(iface_id, rep_port, mac) + vif = agent._get_vif_object(iface_id, rep_port, mac, TEST_MTU) with mock.patch.object(os_vif, 'plug') as plug_mock, \ mock.patch.object(os_vif, 'unplug') as unplug_mock, \ mock.patch('os_vif.objects.instance_info.InstanceInfo', @@ -2678,12 +2730,14 @@ ovs_port.port_name = rep_port ovs_port.vif_id = port_id ports_int_br = [ovs_port] + default_mtu = 1500 expected_smartnic_ports_processed_list = [ {'iface_id': port_id, 'vif_name': rep_port, 'mac': mac, 'vif_type': portbindings.VIF_TYPE_UNBOUND, - 'vm_uuid': ''}] + 'vm_uuid': '', + 'mtu': default_mtu}] expected_current_smartnic_ports_map = { port_id: { 'vif_mac': mac, @@ -2709,6 +2763,8 @@ ovs_port.port_name = rep_port ovs_port.vif_id = port_id ports_int_br = [ovs_port] + default_mtu = 1500 + network = {'id': TEST_NETWORK_ID1, 'mtu': TEST_MTU} PORT_TO_PROCESS = { 'binding:profile': {'local_link_information': [ @@ -2716,19 +2772,22 @@ 'mac_address': FAKE_MAC, 'device_id': "407a79e0-e0be-4b7d-92a6-513b2161011e", 'id': "407a79e0-e0be-4b7d-92a6-513b2161011c", - 'binding:vif_type': portbindings.VIF_TYPE_OVS + 'binding:vif_type': portbindings.VIF_TYPE_OVS, + 'network_id': TEST_NETWORK_ID1 } expected_smartnic_ports_processed_list = [ {'iface_id': port_id, 'vif_name': rep_port, 'mac': mac, 'vif_type': portbindings.VIF_TYPE_UNBOUND, - 'vm_uuid': ''}, + 'vm_uuid': '', + 'mtu': default_mtu}, {'iface_id': "407a79e0-e0be-4b7d-92a6-513b2161011c", 'vif_name': rep_port, 'mac': mac, 'vif_type': portbindings.VIF_TYPE_OVS, - 'vm_uuid': "407a79e0-e0be-4b7d-92a6-513b2161011e"}] + 'vm_uuid': "407a79e0-e0be-4b7d-92a6-513b2161011e", + 'mtu': TEST_MTU}] expected_current_smartnic_ports_map = { port_id: { 'vif_mac': mac, @@ -2738,7 +2797,10 @@ return_value=[PORT_TO_PROCESS]),\ mock.patch.object(self.agent.int_br, "get_vif_ports", - return_value=ports_int_br): + return_value=ports_int_br),\ + mock.patch.object(self.agent.plugin_rpc, + "get_network_details", + return_value=network): self.agent.process_smartnic_ports() self.assertEqual(expected_smartnic_ports_processed_list, self.agent.updated_smartnic_ports) @@ -2755,14 +2817,16 @@ rep_port, iface_id, portbindings.VIF_TYPE_OVS, - vm_uuid,) + vm_uuid, + TEST_MTU) smartnic_data = { 'mac': mac, 'vm_uuid': vm_uuid, 'vif_name': rep_port, 'iface_id': iface_id, - 'vif_type': portbindings.VIF_TYPE_OVS} + 'vif_type': portbindings.VIF_TYPE_OVS, + 'mtu': TEST_MTU} self.assertEqual([smartnic_data], self.agent.updated_smartnic_ports) @@ -2774,13 +2838,15 @@ vif_mac, vif_name, vif_id, - portbindings.VIF_TYPE_UNBOUND) + portbindings.VIF_TYPE_UNBOUND, + mtu=TEST_MTU) smartnic_data = { 'mac': vif_mac, 'vm_uuid': '', 'vif_name': vif_name, 'iface_id': vif_id, - 'vif_type': portbindings.VIF_TYPE_UNBOUND} + 'vif_type': portbindings.VIF_TYPE_UNBOUND, + 'mtu': TEST_MTU} self.assertEqual([smartnic_data], self.agent.updated_smartnic_ports) @@ -3033,6 +3099,15 @@ self._compute_fixed_ips = [{'subnet_id': 'my-subnet-uuid', 'ip_address': '1.1.1.3'}] + def _setup_extra_port_for_dvr(self): + self._port_nongateway = mock.Mock() + self._port_nongateway.ofport = 11 + self._port_nongateway.vif_id = "1234-5678-99" + self._port_nongateway.vif_mac = 'aa:bb:cc:11:22:44' + self._port_nongateway.dvr_mac = self.agent.dvr_agent.dvr_mac_address + self._fixed_ips_nongateway = [{'subnet_id': 'my-subnet-uuid', + 'ip_address': '1.1.1.11'}] + @staticmethod def _expected_port_bound(port, lvid, is_dvr=True, network_type=n_const.TYPE_VXLAN): @@ -3090,8 +3165,8 @@ ), ] - def _test_port_bound_for_dvr_on_vlan_network( - self, device_owner, ip_version=n_const.IP_VERSION_4): + def _test_port_bound_for_dvr_on_physical_network( + self, device_owner, network_type, ip_version=n_const.IP_VERSION_4): self._setup_for_dvr_test() if ip_version == n_const.IP_VERSION_4: gateway_ip = '1.1.1.10' @@ -3105,7 +3180,8 @@ self._compute_port.vif_mac = '77:88:99:00:11:22' physical_network = self._physical_network segmentation_id = self._segmentation_id - network_type = n_const.TYPE_VLAN + if network_type == n_const.TYPE_FLAT: + segmentation_id = None int_br = mock.create_autospec(self.agent.int_br) tun_br = mock.create_autospec(self.agent.tun_br) phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) @@ -3268,10 +3344,19 @@ phys_br.assert_not_called() def test_port_bound_for_dvr_with_compute_ports(self): - self._test_port_bound_for_dvr_on_vlan_network( - device_owner=DEVICE_OWNER_COMPUTE) - self._test_port_bound_for_dvr_on_vlan_network( + self._test_port_bound_for_dvr_on_physical_network( + device_owner=DEVICE_OWNER_COMPUTE, + network_type=n_const.TYPE_VLAN) + self._test_port_bound_for_dvr_on_physical_network( device_owner=DEVICE_OWNER_COMPUTE, + network_type=n_const.TYPE_VLAN, + ip_version=n_const.IP_VERSION_6) + self._test_port_bound_for_dvr_on_physical_network( + device_owner=DEVICE_OWNER_COMPUTE, + network_type=n_const.TYPE_FLAT) + self._test_port_bound_for_dvr_on_physical_network( + device_owner=DEVICE_OWNER_COMPUTE, + network_type=n_const.TYPE_FLAT, ip_version=n_const.IP_VERSION_6) self._test_port_bound_for_dvr_on_vxlan_network( device_owner=DEVICE_OWNER_COMPUTE) @@ -3280,10 +3365,19 @@ ip_version=n_const.IP_VERSION_6) def test_port_bound_for_dvr_with_dhcp_ports(self): - self._test_port_bound_for_dvr_on_vlan_network( - device_owner=n_const.DEVICE_OWNER_DHCP) - self._test_port_bound_for_dvr_on_vlan_network( + self._test_port_bound_for_dvr_on_physical_network( + device_owner=n_const.DEVICE_OWNER_DHCP, + network_type=n_const.TYPE_VLAN) + self._test_port_bound_for_dvr_on_physical_network( + device_owner=n_const.DEVICE_OWNER_DHCP, + network_type=n_const.TYPE_VLAN, + ip_version=n_const.IP_VERSION_6) + self._test_port_bound_for_dvr_on_physical_network( + device_owner=n_const.DEVICE_OWNER_DHCP, + network_type=n_const.TYPE_FLAT) + self._test_port_bound_for_dvr_on_physical_network( device_owner=n_const.DEVICE_OWNER_DHCP, + network_type=n_const.TYPE_FLAT, ip_version=n_const.IP_VERSION_6) self._test_port_bound_for_dvr_on_vxlan_network( device_owner=n_const.DEVICE_OWNER_DHCP) @@ -3418,6 +3512,155 @@ False) int_br.install_dvr_to_src_mac.assert_not_called() + def _test_port_unbound_dvr_router_port_on_vxlan_network( + self, port_type='gateway', ip_version=n_const.IP_VERSION_4): + """Test DVR ovs flows when router port is unbound. + + Setup 2 ports from same subnet to a DVR router. Add an instance port. + Remove one of the router ports based on port_type passed to the + function. port_type='gateway' removes the gateway port, port_type + nongateway removes the non-gateway port. port_type='all' removes + both the ports. Verify if the corresponding ovs flows are updated. + """ + self._setup_for_dvr_test() + self._setup_extra_port_for_dvr() + + if ip_version == n_const.IP_VERSION_4: + gateway_ip = '1.1.1.1' + cidr = '1.1.1.0/24' + else: + gateway_ip = '2001:db8:100::1' + cidr = '2001:db8:100::0/64' + self._fixed_ips = [{'subnet_id': 'my-subnet-uuid', + 'ip_address': '2001:db8:100::1'}] + self._fixed_ips_nongateway = [{'subnet_id': 'my-subnet-uuid', + 'ip_address': '2001:db8:100::10'}] + network_type = n_const.TYPE_VXLAN + self._port.vif_mac = gateway_mac = 'aa:bb:cc:11:22:33' + self._port.dvr_mac = self.agent.dvr_agent.dvr_mac_address + self._compute_port.vif_mac = '77:88:99:00:11:22' + physical_network = self._physical_network + segmentation_id = self._segmentation_id + + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} + with mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_subnet_for_dvr', + return_value={'gateway_ip': gateway_ip, + 'cidr': cidr, + 'ip_version': ip_version, + 'gateway_mac': gateway_mac}),\ + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]),\ + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port),\ + mock.patch.object(self.agent, 'int_br', new=int_br),\ + mock.patch.object(self.agent, 'tun_br', new=tun_br),\ + mock.patch.dict(self.agent.phys_brs, + {physical_network: phys_br}),\ + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),\ + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),\ + mock.patch.dict(self.agent.dvr_agent.phys_brs, + {physical_network: phys_br}): + self.agent.port_bound( + self._port, self._net_uuid, network_type, + physical_network, segmentation_id, self._fixed_ips, + n_const.DEVICE_OWNER_DVR_INTERFACE, False) + + lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + + # Bound non-gateway port + self.agent.port_bound( + self._port_nongateway, self._net_uuid, network_type, + physical_network, segmentation_id, self._fixed_ips_nongateway, + n_const.DEVICE_OWNER_DVR_INTERFACE, False) + + # Bound compute port + self.agent.port_bound(self._compute_port, self._net_uuid, + network_type, physical_network, + segmentation_id, + self._compute_fixed_ips, + DEVICE_OWNER_COMPUTE, False) + + int_br.reset_mock() + tun_br.reset_mock() + phys_br.reset_mock() + + ports_to_unbind = {} + expected_br_int_mock_calls_gateway_port = 2 + expected_br_int_mock_calls_nongateway_port = 1 + if port_type == 'gateway': + ports_to_unbind = { + self._port: expected_br_int_mock_calls_gateway_port + } + elif port_type == 'nongateway': + ports_to_unbind = { + self._port_nongateway: + expected_br_int_mock_calls_nongateway_port + } + else: + ports_to_unbind = { + self._port: + expected_br_int_mock_calls_gateway_port, + self._port_nongateway: + expected_br_int_mock_calls_nongateway_port + } + + for port_to_unbind, br_int_mock_calls in ports_to_unbind.items(): + self.agent.port_unbound(port_to_unbind.vif_id) + expected_on_int_br = self._expected_port_unbound( + port_to_unbind, lvid, True, network_type) + + if ip_version == n_const.IP_VERSION_4: + expected_on_tun_br = [ + mock.call.delete_dvr_process_ipv4( + vlan_tag=lvid, + gateway_ip=gateway_ip), + ] + else: + expected_on_tun_br = [ + mock.call.delete_dvr_process_ipv6( + vlan_tag=lvid, + gateway_mac=gateway_mac), + ] + expected_on_tun_br.extend([ + mock.call.delete_dvr_process( + vlan_tag=lvid, + vif_mac=port_to_unbind.vif_mac), + ]) + + int_br.assert_has_calls(expected_on_int_br) + self.assertEqual(br_int_mock_calls, len(int_br.mock_calls)) + tun_br.assert_has_calls(expected_on_tun_br) + int_br.reset_mock() + tun_br.reset_mock() + phys_br.assert_not_called() + phys_br.reset_mock() + + def test_port_unbound_dvr_router_port(self): + self._test_port_unbound_dvr_router_port_on_vxlan_network( + port_type='gateway') + self._test_port_unbound_dvr_router_port_on_vxlan_network( + port_type='nongateway') + # Unbound all the dvr router ports + self._test_port_unbound_dvr_router_port_on_vxlan_network( + port_type='all') + self._test_port_unbound_dvr_router_port_on_vxlan_network( + port_type='gateway', + ip_version=n_const.IP_VERSION_6) + self._test_port_unbound_dvr_router_port_on_vxlan_network( + port_type='nongateway', + ip_version=n_const.IP_VERSION_6) + # Unbound all the dvr router ports + self._test_port_unbound_dvr_router_port_on_vxlan_network( + port_type='all', + ip_version=n_const.IP_VERSION_6) + def test_treat_devices_removed_for_dvr_interface(self): self._test_treat_devices_removed_for_dvr_interface() self._test_treat_devices_removed_for_dvr_interface( @@ -3749,8 +3992,9 @@ mock.call.setup_canary_table(), mock.call.install_drop(table_id=constants.DVR_TO_SRC_MAC, priority=1), - mock.call.install_drop(table_id=constants.DVR_TO_SRC_MAC_VLAN, - priority=1), + mock.call.install_drop( + table_id=constants.DVR_TO_SRC_MAC_PHYSICAL, + priority=1), mock.call.install_drop(table_id=constants.LOCAL_SWITCHING, priority=2, in_port=ioport), @@ -3841,7 +4085,7 @@ dvr_macs=[{'host': newhost, 'mac_address': newmac}]) expected_on_int_br = [ - mock.call.add_dvr_mac_vlan( + mock.call.add_dvr_mac_physical( mac=newmac, port=self.agent.int_ofports[physical_network]), mock.call.add_dvr_mac_tun( @@ -3854,7 +4098,7 @@ port=self.agent.patch_int_ofport), ] expected_on_phys_br = [ - mock.call.add_dvr_mac_vlan( + mock.call.add_dvr_mac_physical( mac=newmac, port=self.agent.phys_ofports[physical_network]), ] @@ -3953,6 +4197,56 @@ self.agent.ancillary_brs = mock.Mock() self._test_scan_ports_failure('scan_ancillary_ports') + def test_ext_br_recreated(self): + self._setup_for_dvr_test() + reset_methods = ( + 'reset_ovs_parameters', 'reset_dvr_parameters', + 'setup_dvr_flows_on_integ_br', 'setup_dvr_flows_on_tun_br', + 'setup_dvr_flows_on_phys_br', 'setup_dvr_mac_flows_on_all_brs') + for method in reset_methods: + mock.patch.object(self.agent.dvr_agent, method).start() + bridge_mappings = {'physnet0': 'br-ex0', + 'physnet1': 'br-ex1'} + ex_br_mocks = [mock.Mock(br_name='br-ex0'), + mock.Mock(br_name='br-ex1')] + phys_bridges = {'physnet0': ex_br_mocks[0], + 'physnet1': ex_br_mocks[1]}, + bridges_added = ['br-ex0'] + with mock.patch.object(self.agent, 'check_ovs_status', + return_value=constants.OVS_NORMAL), \ + mock.patch.object(self.agent, '_agent_has_updates', + side_effect=TypeError('loop exit')), \ + mock.patch.dict(self.agent.bridge_mappings, bridge_mappings, + clear=True), \ + mock.patch.dict(self.agent.phys_brs, phys_bridges, + clear=True), \ + mock.patch.object(self.agent, 'setup_physical_bridges') as \ + setup_physical_bridges, \ + mock.patch.object(self.agent.ovs.ovsdb, 'idl_monitor') as \ + mock_idl_monitor: + mock_idl_monitor.bridges_added = bridges_added + try: + self.agent.rpc_loop(polling_manager=mock.Mock()) + except TypeError: + pass + # Setup bridges should be called once even if it will raise Runtime + # Error because TypeError is raised in _agent_has_updates to stop + # agent after first loop iteration + setup_physical_bridges.assert_called_once_with({'physnet0': 'br-ex0'}) + # Ensure dvr_agent methods were called correctly + self.agent.dvr_agent.reset_ovs_parameters.assert_called_once_with( + self.agent.int_br, self.agent.tun_br, self.agent.phys_brs, + self.agent.patch_int_ofport, self.agent.patch_tun_ofport) + self.agent.dvr_agent.reset_dvr_parameters.assert_called_once_with() + (self.agent.dvr_agent.setup_dvr_flows_on_phys_br. + assert_called_once_with({'physnet0': 'br-ex0'})) + (self.agent.dvr_agent.setup_dvr_flows_on_integ_br. + assert_called_once_with()) + (self.agent.dvr_agent.setup_dvr_flows_on_tun_br. + assert_called_once_with()) + (self.agent.dvr_agent.setup_dvr_mac_flows_on_all_brs. + assert_called_once_with()) + class TestOvsDvrNeutronAgentOSKen(TestOvsDvrNeutronAgent, ovs_test_base.OVSOSKenTestBase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py 2021-11-12 13:56:42.000000000 +0000 @@ -226,6 +226,8 @@ mock.call.port_exists('int-%s' % self.MAP_TUN_BRIDGE), mock.call.add_patch_port('int-%s' % self.MAP_TUN_BRIDGE, constants.NONEXISTENT_PEER), + mock.call.set_igmp_snooping_flood('int-%s' % self.MAP_TUN_BRIDGE, + igmp_snooping), ] self.mock_int_bridge_expected += [ @@ -255,6 +257,7 @@ self.mock_int_bridge_expected += [ mock.call.port_exists('patch-tun'), mock.call.add_patch_port('patch-tun', 'patch-int'), + mock.call.set_igmp_snooping_flood('patch-tun', igmp_snooping), ] self.mock_int_bridge_expected += [ mock.call.get_vif_ports((ovs_lib.INVALID_OFPORT, @@ -276,6 +279,20 @@ self.intb_expected = [] self.execute_expected = [] + self.mock_int_bridge_expected += [ + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.MAP_TUN_INT_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.TUN_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.TRANSIENT_EGRESS_TABLE, + table_id=constants.LOCAL_MAC_DIRECT), + ] + def _build_agent(self, **config_opts_agent): """Configure and initialize OVS agent. @@ -693,7 +710,9 @@ self.mock_int_bridge_expected += [ mock.call.db_get_val('Interface', 'int-%s' % self.MAP_TUN_BRIDGE, 'type', log_errors=False), - mock.call.add_port('int-%s' % self.MAP_TUN_BRIDGE) + mock.call.add_port('int-%s' % self.MAP_TUN_BRIDGE), + mock.call.set_igmp_snooping_flood('int-%s' % self.MAP_TUN_BRIDGE, + igmp_snooping), ] self.mock_int_bridge_expected += [ @@ -716,7 +735,8 @@ ] self.mock_int_bridge_expected += [ mock.call.port_exists('patch-tun'), - mock.call.add_patch_port('patch-tun', 'patch-int') + mock.call.add_patch_port('patch-tun', 'patch-int'), + mock.call.set_igmp_snooping_flood('patch-tun', igmp_snooping), ] self.mock_int_bridge_expected += [ mock.call.get_vif_ports((ovs_lib.INVALID_OFPORT, @@ -747,6 +767,20 @@ self.execute_expected = [mock.call(['udevadm', 'settle', '--timeout=10'])] + self.mock_int_bridge_expected += [ + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.MAP_TUN_INT_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.TUN_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.TRANSIENT_EGRESS_TABLE, + table_id=constants.LOCAL_MAC_DIRECT), + ] + class TunnelTestUseVethIntercoOSKen(TunnelTestUseVethInterco, ovs_test_base.OVSOSKenTestBase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,381 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +import netaddr +from neutron_lib import constants +from neutron_lib import context +from neutron_lib.services.qos import constants as qos_constants +from oslo_config import cfg +from oslo_utils import uuidutils + +from neutron.common.ovn import constants as ovn_const +from neutron.core_extensions import qos as core_qos +from neutron import manager +from neutron.objects import network as network_obj +from neutron.objects import ports as port_obj +from neutron.objects.qos import policy as policy_obj +from neutron.objects.qos import rule as rule_obj +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ + import qos as qos_extension +from neutron.tests.unit.plugins.ml2 import test_plugin + + +QOS_RULE_BW_1 = {'max_kbps': 200, 'max_burst_kbps': 100} +QOS_RULE_BW_2 = {'max_kbps': 300} +QOS_RULE_DSCP_1 = {'dscp_mark': 16} +QOS_RULE_DSCP_2 = {'dscp_mark': 20} +QOS_RULE_MINBW_1 = {'min_kbps': 500} + + +class _Context(object): + + def __enter__(self): + return self + + def __exit__(self, *args): + return + + +class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): + + CORE_PLUGIN_CLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin' + _extension_drivers = ['qos'] + + def setUp(self): + cfg.CONF.set_override('extension_drivers', self._extension_drivers, + group='ml2') + cfg.CONF.set_override('service_plugins', self._extension_drivers) + super(TestOVNClientQosExtension, self).setUp() + self.setup_coreplugin(self.CORE_PLUGIN_CLASS, load_plugins=True) + manager.init() + self._mock_qos_loaded = mock.patch.object( + core_qos.QosCoreResourceExtension, 'plugin_loaded') + self.mock_qos_loaded = self._mock_qos_loaded.start() + + self.txn = _Context() + mock_driver = mock.Mock() + mock_driver._nb_idl.transaction.return_value = self.txn + self.qos_driver = qos_extension.OVNClientQosExtension(mock_driver) + self._mock_rules = mock.patch.object(self.qos_driver, + '_update_port_qos_rules') + self.mock_rules = self._mock_rules.start() + self.addCleanup(self._mock_rules.stop) + self.ctx = context.get_admin_context() + self.project_id = uuidutils.generate_uuid() + self._initialize_objs() + + def _get_random_db_fields(self, obj_cls=None): + obj_cls = obj_cls or self._test_class + return obj_cls.modify_fields_to_db( + self.get_random_object_fields(obj_cls)) + + def _initialize_objs(self): + self.qos_policies = [] + self.ports = [] + self.networks = [] + for net_idx in range(2): + qos_policy = policy_obj.QosPolicy( + self.ctx, id=uuidutils.generate_uuid(), + project_id=self.project_id) + qos_policy.create() + self.qos_policies.append(qos_policy) + + # Any QoS policy should have at least one rule, in order to have + # the port dictionary extended with the QoS policy information; see + # QoSPlugin._extend_port_resource_request + qos_rule = rule_obj.QosDscpMarkingRule( + self.ctx, dscp=20, id=uuidutils.generate_uuid(), + qos_policy_id=qos_policy.id) + qos_rule.create() + + network = network_obj.Network( + self.ctx, id=uuidutils.generate_uuid(), + project_id=self.project_id) + network.create() + self.networks.append(network) + + for port_idx in range(3): + mac_address = netaddr.EUI(net_idx * 16 + port_idx) + port = port_obj.Port( + self.ctx, project_id=self.project_id, + network_id=network.id, device_owner='', + admin_state_up=True, status='DOWN', device_id='2', + mac_address=mac_address) + port.create() + self.ports.append(port) + + @mock.patch.object(qos_extension.LOG, 'warning') + @mock.patch.object(rule_obj, 'get_rules') + def test__qos_rules(self, mock_get_rules, mock_warning): + rules = [ + rule_obj.QosBandwidthLimitRule( + direction=constants.EGRESS_DIRECTION, **QOS_RULE_BW_1), + rule_obj.QosBandwidthLimitRule( + direction=constants.INGRESS_DIRECTION, **QOS_RULE_BW_2), + rule_obj.QosDscpMarkingRule(**QOS_RULE_DSCP_1), + rule_obj.QosMinimumBandwidthRule(**QOS_RULE_MINBW_1)] + mock_get_rules.return_value = rules + expected = { + constants.EGRESS_DIRECTION: { + qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1, + qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1}, + constants.INGRESS_DIRECTION: { + qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2} + } + self.assertEqual(expected, self.qos_driver._qos_rules(mock.ANY, + 'policy_id1')) + msg = ('Rule type %(rule_type)s from QoS policy %(policy_id)s is not ' + 'supported in OVN') + mock_warning.assert_called_once_with( + msg, {'rule_type': qos_constants.RULE_TYPE_MINIMUM_BANDWIDTH, + 'policy_id': 'policy_id1'}) + + @mock.patch.object(rule_obj, 'get_rules') + def test__qos_rules_no_rules(self, mock_get_rules): + mock_get_rules.return_value = [] + expected = {constants.EGRESS_DIRECTION: {}, + constants.INGRESS_DIRECTION: {}} + self.assertEqual(expected, + self.qos_driver._qos_rules(mock.ANY, mock.ANY)) + + def test__ovn_qos_rule_ingress(self): + direction = constants.INGRESS_DIRECTION + rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1} + expected = {'burst': 100, 'rate': 200, 'direction': 'to-lport', + 'match': 'outport == "port_id"', + 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY, + 'switch': 'neutron-network_id'} + result = self.qos_driver._ovn_qos_rule( + direction, rule, 'port_id', 'network_id') + self.assertEqual(expected, result) + + def test__ovn_qos_rule_egress(self): + direction = constants.EGRESS_DIRECTION + rule = {qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1} + expected = {'direction': 'from-lport', 'match': 'inport == "port_id"', + 'dscp': 16, 'switch': 'neutron-network_id', + 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY} + result = self.qos_driver._ovn_qos_rule( + direction, rule, 'port_id', 'network_id') + self.assertEqual(expected, result) + + rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2, + qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_2} + expected = {'direction': 'from-lport', 'match': 'inport == "port_id"', + 'rate': 300, 'dscp': 20, 'switch': 'neutron-network_id', + 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY} + result = self.qos_driver._ovn_qos_rule( + direction, rule, 'port_id', 'network_id') + self.assertEqual(expected, result) + + def test__port_effective_qos_policy_id(self): + port = {'qos_policy_id': 'qos1'} + self.assertEqual(('qos1', 'port'), + self.qos_driver._port_effective_qos_policy_id(port)) + + port = {'qos_network_policy_id': 'qos1'} + self.assertEqual(('qos1', 'network'), + self.qos_driver._port_effective_qos_policy_id(port)) + + port = {'qos_policy_id': 'qos_port', + 'qos_network_policy_id': 'qos_network'} + self.assertEqual(('qos_port', 'port'), + self.qos_driver._port_effective_qos_policy_id(port)) + + port = {} + self.assertEqual((None, None), + self.qos_driver._port_effective_qos_policy_id(port)) + + port = {'qos_policy_id': None, 'qos_network_policy_id': None} + self.assertEqual((None, None), + self.qos_driver._port_effective_qos_policy_id(port)) + + port = {'qos_policy_id': 'qos1', 'device_owner': 'neutron:port'} + self.assertEqual((None, None), + self.qos_driver._port_effective_qos_policy_id(port)) + + def test_update_port(self): + port = self.ports[0] + original_port = self.ports[1] + + # Remove QoS policy + original_port.qos_policy_id = self.qos_policies[0].id + self.qos_driver.update_port(mock.ANY, port, original_port) + self.mock_rules.assert_called_once_with( + mock.ANY, port.id, port.network_id, None, None) + + # Change from port policy (qos_policy0) to network policy (qos_policy1) + self.mock_rules.reset_mock() + port.qos_network_policy_id = self.qos_policies[1].id + self.qos_driver.update_port(mock.ANY, port, original_port) + self.mock_rules.assert_called_once_with( + mock.ANY, port.id, port.network_id, self.qos_policies[1].id, None) + + # No change (qos_policy0) + self.mock_rules.reset_mock() + port.qos_policy_id = self.qos_policies[0].id + original_port.qos_policy_id = self.qos_policies[0].id + self.qos_driver.update_port(mock.ANY, port, original_port) + self.mock_rules.assert_not_called() + + # No change (no policy) + self.mock_rules.reset_mock() + port.qos_policy_id = None + port.qos_network_policy_id = None + original_port.qos_policy_id = None + original_port.qos_network_policy_id = None + self.qos_driver.update_port(mock.ANY, port, original_port) + self.mock_rules.assert_not_called() + + # Reset (no policy) + self.qos_driver.update_port(mock.ANY, port, original_port, reset=True) + self.mock_rules.assert_called_once_with( + mock.ANY, port.id, port.network_id, None, None) + + # Reset (qos_policy0, regardless of being the same a in the previous + # state) + self.mock_rules.reset_mock() + port.qos_policy_id = self.qos_policies[0].id + original_port.qos_policy_id = self.qos_policies[1].id + self.qos_driver.update_port(mock.ANY, port, original_port, reset=True) + self.mock_rules.assert_called_once_with( + mock.ANY, port.id, port.network_id, self.qos_policies[0].id, None) + + # External port, OVN QoS extension does not apply. + self.mock_rules.reset_mock() + port.qos_policy_id = self.qos_policies[0].id + self.qos_driver.update_port(mock.ANY, port, original_port, + port_type=ovn_const.LSP_TYPE_EXTERNAL) + self.mock_rules.assert_not_called() + + def test_delete_port(self): + self.mock_rules.reset_mock() + self.qos_driver.delete_port(mock.ANY, self.ports[1]) + + # Assert that rules are deleted + self.mock_rules.assert_called_once_with( + mock.ANY, self.ports[1].id, self.ports[1].network_id, None, None) + + def test_update_network(self): + """Test update network. + + net1: [(1) from qos_policy0 to no QoS policy, + (2) from qos_policy0 to qos_policy1] + - port10: no QoS port policy + - port11: qos_policy0 + - port12: qos_policy1 + """ + policies_ports = [ + (None, {self.ports[0].id}), + (self.qos_policies[1].id, {self.ports[0].id})] + + self.ports[1].qos_policy_id = self.qos_policies[0].id + self.ports[1].update() + self.ports[2].qos_policy_id = self.qos_policies[1].id + self.ports[2].update() + for qos_policy_id, reference_ports in policies_ports: + self.networks[0].qos_policy_id = qos_policy_id + self.networks[0].update() + original_network = {'qos_policy_id': self.qos_policies[0]} + reviewed_port_ids = self.qos_driver.update_network( + mock.ANY, self.networks[0], original_network) + self.assertEqual(reference_ports, reviewed_port_ids) + calls = [mock.call(mock.ANY, self.ports[0].id, + self.ports[0].network_id, qos_policy_id, + None)] + self.mock_rules.assert_has_calls(calls) + self.mock_rules.reset_mock() + + def test_update_network_no_policy_change(self): + """Test update network if the QoS policy is the same. + + net1: [(1) from qos_policy0 to qos_policy0, + (2) from no QoS policy to no QoS policy] + """ + for qos_policy_id in (self.qos_policies[0].id, None): + self.networks[0].qos_policy_id = qos_policy_id + self.networks[0].update() + original_network = {'qos_policy_id': qos_policy_id} + reviewed_port_ids = self.qos_driver.update_network( + mock.ANY, self.networks[0], original_network) + self.assertEqual(set([]), reviewed_port_ids) + self.mock_rules.assert_not_called() + + def test_update_network_reset(self): + """Test update network. + + net1: [(1) from qos_policy1 to qos_policy1, + (2) from no QoS policy to no QoS policy] + - port10: no QoS port policy + - port11: qos_policy0 + - port12: qos_policy1 + """ + policies_ports = [ + (self.qos_policies[1].id, {self.ports[0].id}), + (None, {self.ports[0].id})] + + self.ports[1].qos_policy_id = self.qos_policies[0].id + self.ports[1].update() + self.ports[2].qos_policy_id = self.qos_policies[1].id + self.ports[2].update() + for qos_policy_id, reference_ports in policies_ports: + self.networks[0].qos_policy_id = qos_policy_id + self.networks[0].update() + original_network = {'qos_policy_id': self.qos_policies[0]} + reviewed_port_ids = self.qos_driver.update_network( + mock.ANY, self.networks[0], original_network, reset=True) + self.assertEqual(reference_ports, reviewed_port_ids) + calls = [mock.call(mock.ANY, self.ports[0].id, + self.ports[0].network_id, qos_policy_id, None)] + self.mock_rules.assert_has_calls(calls) + self.mock_rules.reset_mock() + + def test_update_policy(self): + """Test update QoS policy, networks and ports bound are updated. + + QoS policy updated: qos_policy0 + net1: no QoS policy + - port10: no port QoS policy + - port11: qos_policy0 --> handled during "update_port" and updated + - port12: qos_policy1 + net2: qos_policy0 + - port20: no port QoS policy --> handled during "update_network" + and updated + - port21: qos_policy0 --> handled during "update_network", not updated + handled during "update_port" and updated + - port22: qos_policy1 --> handled during "update_network", not updated + """ + self.ports[1].qos_policy_id = self.qos_policies[0].id + self.ports[1].update() + self.ports[2].qos_policy_id = self.qos_policies[1].id + self.ports[2].update() + self.ports[4].qos_policy_id = self.qos_policies[0].id + self.ports[4].update() + self.ports[5].qos_policy_id = self.qos_policies[1].id + self.ports[5].update() + self.networks[1].qos_policy_id = self.qos_policies[0].id + self.networks[1].update() + mock_qos_rules = mock.Mock() + with mock.patch.object(self.qos_driver, '_qos_rules', + return_value=mock_qos_rules): + self.qos_driver.update_policy(self.ctx, self.qos_policies[0]) + updated_ports = [self.ports[1], self.ports[3], self.ports[4]] + calls = [mock.call(self.txn, port.id, port.network_id, + self.qos_policies[0].id, mock_qos_rules) + for port in updated_ports] + # We can't ensure the call order because we are not enforcing any order + # when retrieving the port and the network list. + self.mock_rules.assert_has_calls(calls, any_order=True) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py 2021-11-12 13:56:42.000000000 +0000 @@ -935,171 +935,6 @@ self.assertEqual([mock.ANY], fake_lrouter.static_routes) -class TestAddAddrSetCommand(TestBaseCommand): - - def test_addrset_exists(self): - with mock.patch.object(idlutils, 'row_by_value', - return_value=mock.ANY): - cmd = commands.AddAddrSetCommand( - self.ovn_api, 'fake-addrset', may_exist=True) - cmd.run_idl(self.transaction) - self.transaction.insert.assert_not_called() - - def test_addrset_add_exists(self): - fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row() - self.ovn_api._tables['Address_Set'].rows[fake_addrset.uuid] = \ - fake_addrset - self.transaction.insert.return_value = fake_addrset - cmd = commands.AddAddrSetCommand( - self.ovn_api, fake_addrset.name, may_exist=False) - cmd.run_idl(self.transaction) - # NOTE(rtheis): Mocking the transaction allows this insert - # to succeed when it normally would fail due the duplicate name. - self.transaction.insert.assert_called_once_with( - self.ovn_api._tables['Address_Set']) - - def _test_addrset_add(self, may_exist=True): - with mock.patch.object(idlutils, 'row_by_value', - return_value=None): - fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row( - attrs={'foo': ''}) - self.transaction.insert.return_value = fake_addrset - cmd = commands.AddAddrSetCommand( - self.ovn_api, 'fake-addrset', may_exist=may_exist, - foo='bar') - cmd.run_idl(self.transaction) - self.transaction.insert.assert_called_once_with( - self.ovn_api._tables['Address_Set']) - self.assertEqual('fake-addrset', fake_addrset.name) - self.assertEqual('bar', fake_addrset.foo) - - def test_addrset_add_may_exist(self): - self._test_addrset_add(may_exist=True) - - def test_addrset_add_ignore_exists(self): - self._test_addrset_add(may_exist=False) - - -class TestDelAddrSetCommand(TestBaseCommand): - - def _test_addrset_del_no_exist(self, if_exists=True): - with mock.patch.object(idlutils, 'row_by_value', - side_effect=idlutils.RowNotFound): - cmd = commands.DelAddrSetCommand( - self.ovn_api, 'fake-addrset', if_exists=if_exists) - if if_exists: - cmd.run_idl(self.transaction) - else: - self.assertRaises(RuntimeError, cmd.run_idl, self.transaction) - - def test_addrset_no_exist_ignore(self): - self._test_addrset_del_no_exist(if_exists=True) - - def test_addrset_no_exist_fail(self): - self._test_addrset_del_no_exist(if_exists=False) - - def test_addrset_del(self): - fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row() - self.ovn_api._tables['Address_Set'].rows[fake_addrset.uuid] = \ - fake_addrset - with mock.patch.object(idlutils, 'row_by_value', - return_value=fake_addrset): - cmd = commands.DelAddrSetCommand( - self.ovn_api, fake_addrset.name, if_exists=True) - cmd.run_idl(self.transaction) - fake_addrset.delete.assert_called_once_with() - - -class TestUpdateAddrSetCommand(TestBaseCommand): - - def _test_addrset_update_no_exist(self, if_exists=True): - with mock.patch.object(idlutils, 'row_by_value', - side_effect=idlutils.RowNotFound): - cmd = commands.UpdateAddrSetCommand( - self.ovn_api, 'fake-addrset', - addrs_add=[], addrs_remove=[], - if_exists=if_exists) - if if_exists: - cmd.run_idl(self.transaction) - else: - self.assertRaises(RuntimeError, cmd.run_idl, self.transaction) - - def test_addrset_no_exist_ignore(self): - self._test_addrset_update_no_exist(if_exists=True) - - def test_addrset_no_exist_fail(self): - self._test_addrset_update_no_exist(if_exists=False) - - def _test_addrset_update(self, addrs_add=None, addrs_del=None): - save_address = '10.0.0.1' - initial_addresses = [save_address] - final_addresses = [save_address] - expected_addvalue_calls = [] - expected_delvalue_calls = [] - if addrs_add: - for addr_add in addrs_add: - final_addresses.append(addr_add) - expected_addvalue_calls.append( - mock.call('addresses', addr_add)) - if addrs_del: - for addr_del in addrs_del: - initial_addresses.append(addr_del) - expected_delvalue_calls.append( - mock.call('addresses', addr_del)) - fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row( - attrs={'addresses': initial_addresses}) - with mock.patch.object(idlutils, 'row_by_value', - return_value=fake_addrset): - cmd = commands.UpdateAddrSetCommand( - self.ovn_api, fake_addrset.name, - addrs_add=addrs_add, addrs_remove=addrs_del, - if_exists=True) - cmd.run_idl(self.transaction) - fake_addrset.addvalue.assert_has_calls(expected_addvalue_calls) - fake_addrset.delvalue.assert_has_calls(expected_delvalue_calls) - - def test_addrset_update_add(self): - self._test_addrset_update(addrs_add=['10.0.0.4']) - - def test_addrset_update_del(self): - self._test_addrset_update(addrs_del=['10.0.0.2']) - - -class TestUpdateAddrSetExtIdsCommand(TestBaseCommand): - def setUp(self): - super(TestUpdateAddrSetExtIdsCommand, self).setUp() - self.ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: 'default'} - - def _test_addrset_extids_update_no_exist(self, if_exists=True): - with mock.patch.object(idlutils, 'row_by_value', - side_effect=idlutils.RowNotFound): - cmd = commands.UpdateAddrSetExtIdsCommand( - self.ovn_api, 'fake-addrset', self.ext_ids, - if_exists=if_exists) - if if_exists: - cmd.run_idl(self.transaction) - else: - self.assertRaises(RuntimeError, cmd.run_idl, self.transaction) - - def test_addrset_no_exist_ignore(self): - self._test_addrset_extids_update_no_exist(if_exists=True) - - def test_addrset_no_exist_fail(self): - self._test_addrset_extids_update_no_exist(if_exists=False) - - def test_addrset_extids_update(self): - new_ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: 'default-new'} - fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row( - attrs={'external_ids': self.ext_ids}) - with mock.patch.object(idlutils, 'row_by_value', - return_value=fake_addrset): - cmd = commands.UpdateAddrSetExtIdsCommand( - self.ovn_api, fake_addrset.name, - new_ext_ids, if_exists=True) - cmd.run_idl(self.transaction) - self.assertEqual(new_ext_ids, fake_addrset.external_ids) - - class TestUpdateChassisExtIdsCommand(TestBaseCommand): def setUp(self): super(TestUpdateChassisExtIdsCommand, self).setUp() @@ -1484,7 +1319,8 @@ fake_route_2 = fakes.FakeOvsdbRow.create_one_ovsdb_row( attrs={'ip_prefix': '50.0.0.0/24', 'nexthop': '40.0.0.101'}) fake_lrouter = fakes.FakeOvsdbRow.create_one_ovsdb_row( - attrs={'static_routes': [fake_route_1, fake_route_2]}) + attrs={'static_routes': [fake_route_1, fake_route_2], + 'nat': []}) with mock.patch.object(self.ovn_api, "is_col_present", return_value=True): with mock.patch.object(idlutils, 'row_by_value', @@ -1503,7 +1339,8 @@ attrs={'external_ip': '192.168.1.8', 'logical_ip': '10.0.0.5', 'type': 'badtype'}) fake_lrouter = fakes.FakeOvsdbRow.create_one_ovsdb_row( - attrs={'nat': [fake_nat_1, fake_nat_2]}) + attrs={'nat': [fake_nat_1, fake_nat_2], + 'static_routes': []}) with mock.patch.object(self.ovn_api, "is_col_present", return_value=True): with mock.patch.object(idlutils, 'row_by_value', @@ -1518,7 +1355,8 @@ port_id = 'fake-port-id' fake_lrouter = fakes.FakeOvsdbRow.create_one_ovsdb_row( attrs={'external_ids': - {ovn_const.OVN_GW_PORT_EXT_ID_KEY: port_id}}) + {ovn_const.OVN_GW_PORT_EXT_ID_KEY: port_id}, + 'static_routes': [], 'nat': []}) with mock.patch.object(self.ovn_api, "is_col_present", return_value=True): with mock.patch.object(idlutils, 'row_by_value', @@ -1533,7 +1371,8 @@ port_id = 'fake-port-id' fake_lrouter = fakes.FakeOvsdbRow.create_one_ovsdb_row( attrs={'external_ids': - {ovn_const.OVN_GW_PORT_EXT_ID_KEY: port_id}}) + {ovn_const.OVN_GW_PORT_EXT_ID_KEY: port_id}, + 'static_routes': [], 'nat': []}) with mock.patch.object(self.ovn_api, "is_col_present", return_value=True): with mock.patch.object(idlutils, 'row_by_value', diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py 2021-11-12 13:56:42.000000000 +0000 @@ -19,7 +19,6 @@ from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils -from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn from neutron.tests import base from neutron.tests.unit import fake_resources as fakes @@ -338,7 +337,7 @@ self._tables['DHCP_Options'] = self.dhcp_table self._tables['Address_Set'] = self.address_set_table - with mock.patch.object(impl_idl_ovn, 'get_connection', + with mock.patch.object(impl_idl_ovn.OvsdbNbOvnIdl, 'from_worker', return_value=mock.Mock()): impl_idl_ovn.OvsdbNbOvnIdl.ovsdb_connection = None self.nb_ovn_idl = impl_idl_ovn.OvsdbNbOvnIdl(mock.Mock()) @@ -397,21 +396,6 @@ fake_address_sets = TestNBImplIdlOvn.fake_set['address_sets'] self._load_ovsdb_fake_rows(self.address_set_table, fake_address_sets) - @mock.patch.object(impl_idl_ovn.OvsdbNbOvnIdl, 'ovsdb_connection', None) - @mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock()) - def test_setting_ovsdb_probe_timeout_default_value(self): - inst = impl_idl_ovn.OvsdbNbOvnIdl(mock.Mock()) - inst.idl._session.reconnect.set_probe_interval.assert_called_with( - 60000) - - @mock.patch.object(impl_idl_ovn.OvsdbNbOvnIdl, 'ovsdb_connection', None) - @mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock()) - @mock.patch.object(ovn_conf, 'get_ovn_ovsdb_probe_interval') - def test_setting_ovsdb_probe_timeout(self, mock_get_probe_interval): - mock_get_probe_interval.return_value = 5000 - inst = impl_idl_ovn.OvsdbNbOvnIdl(mock.Mock()) - inst.idl._session.reconnect.set_probe_interval.assert_called_with(5000) - def test_get_all_logical_switches_with_ports(self): # Test empty mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports() @@ -421,18 +405,18 @@ mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports() expected = [{'name': utils.ovn_name('ls-id-1'), 'ports': ['lsp-id-11', 'lsp-id-12', 'lsp-rp-id-1'], - 'provnet_port': 'provnet-ls-id-1'}, + 'provnet_ports': ['provnet-ls-id-1']}, {'name': utils.ovn_name('ls-id-2'), 'ports': ['lsp-id-21', 'lsp-rp-id-2'], - 'provnet_port': 'provnet-ls-id-2'}, + 'provnet_ports': ['provnet-ls-id-2']}, {'name': utils.ovn_name('ls-id-3'), 'ports': ['lsp-id-31', 'lsp-id-32', 'lsp-rp-id-3', 'lsp-vpn-id-3'], - 'provnet_port': None}, + 'provnet_ports': []}, {'name': utils.ovn_name('ls-id-5'), 'ports': ['lsp-id-51', 'lsp-id-52', 'lsp-rp-id-5', 'lsp-vpn-id-5'], - 'provnet_port': None}] + 'provnet_ports': []}] self.assertItemsEqual(mapping, expected) def test_get_all_logical_routers_with_rports(self): @@ -685,10 +669,10 @@ 'ports': []}, subnet_options) subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options( 'subnet-id-11-0-2-0')['subnet'] - self.assertIsNone(subnet_options) + self.assertEqual({}, subnet_options) subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options( 'port-id-30-0-1-0')['subnet'] - self.assertIsNone(subnet_options) + self.assertEqual({}, subnet_options) def test_get_subnet_dhcp_options_with_ports(self): # Test empty @@ -774,53 +758,3 @@ self._tables.pop('Port_Group', None) port_groups = self.nb_ovn_idl.get_port_groups() self.assertEqual({}, port_groups) - - -class TestSBImplIdlOvn(TestDBImplIdlOvn): - - fake_set = { - 'chassis': [ - {'name': 'host-1', 'hostname': 'host-1.localdomain.com', - 'external_ids': {'ovn-bridge-mappings': - 'public:br-ex,private:br-0'}}, - {'name': 'host-2', 'hostname': 'host-2.localdomain.com', - 'external_ids': {'ovn-bridge-mappings': - 'public:br-ex,public2:br-ex'}}, - {'name': 'host-3', 'hostname': 'host-3.localdomain.com', - 'external_ids': {'ovn-bridge-mappings': - 'public:br-ex'}}], - } - - def setUp(self): - super(TestSBImplIdlOvn, self).setUp() - - self.chassis_table = fakes.FakeOvsdbTable.create_one_ovsdb_table() - self._tables = {} - self._tables['Chassis'] = self.chassis_table - - with mock.patch.object(impl_idl_ovn, 'get_connection', - return_value=mock.Mock()): - impl_idl_ovn.OvsdbSbOvnIdl.ovsdb_connection = None - self.sb_ovn_idl = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock()) - - self.sb_ovn_idl.idl.tables = self._tables - - def _load_sb_db(self): - # Load Chassis - fake_chassis = TestSBImplIdlOvn.fake_set['chassis'] - self._load_ovsdb_fake_rows(self.chassis_table, fake_chassis) - - @mock.patch.object(impl_idl_ovn.OvsdbSbOvnIdl, 'ovsdb_connection', None) - @mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock()) - def test_setting_ovsdb_probe_timeout_default_value(self): - inst = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock()) - inst.idl._session.reconnect.set_probe_interval.assert_called_with( - 60000) - - @mock.patch.object(impl_idl_ovn.OvsdbSbOvnIdl, 'ovsdb_connection', None) - @mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock()) - @mock.patch.object(ovn_conf, 'get_ovn_ovsdb_probe_interval') - def test_setting_ovsdb_probe_timeout(self, mock_get_probe_interval): - mock_get_probe_interval.return_value = 5000 - inst = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock()) - inst.idl._session.reconnect.set_probe_interval.assert_called_with(5000) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py 2021-11-12 13:56:42.000000000 +0000 @@ -100,10 +100,8 @@ self.periodic.check_for_inconsistencies() mock_fix_net.assert_called_once_with(mock.ANY, fake_row) - def _test_migrate_to_port_groups_helper(self, pg_supported, a_sets, - migration_expected, never_again): - self.fake_ovn_client._nb_idl.is_port_groups_supported.return_value = ( - pg_supported) + def _test_migrate_to_port_groups_helper(self, a_sets, migration_expected, + never_again): self.fake_ovn_client._nb_idl.get_address_sets.return_value = a_sets with mock.patch.object(ovn_db_sync.OvnNbSynchronizer, 'migrate_to_port_groups') as mtpg: @@ -118,24 +116,15 @@ else: mtpg.assert_not_called() - def test_migrate_to_port_groups_port_groups_not_supported(self): - self._test_migrate_to_port_groups_helper(pg_supported=False, - a_sets=None, - migration_expected=False, - never_again=True) - def test_migrate_to_port_groups_not_needed(self): - self._test_migrate_to_port_groups_helper(pg_supported=True, - a_sets=None, + self._test_migrate_to_port_groups_helper(a_sets=None, migration_expected=False, never_again=True) def test_migrate_to_port_groups(self): - # Check normal migration path: if port groups are supported by the - # schema and the migration has to be done, it will take place and - # won't be attempted in the future. - self._test_migrate_to_port_groups_helper(pg_supported=True, - a_sets=['as1', 'as2'], + # Check normal migration path: if the migration has to be done, it will + # take place and won't be attempted in the future. + self._test_migrate_to_port_groups_helper(a_sets=['as1', 'as2'], migration_expected=True, never_again=True) @@ -145,8 +134,7 @@ return_value=False)): # Check that if this worker doesn't have the lock, it won't # perform the migration and it will try again later. - self._test_migrate_to_port_groups_helper(pg_supported=True, - a_sets=['as1', 'as2'], + self._test_migrate_to_port_groups_helper(a_sets=['as1', 'as2'], migration_expected=False, never_again=False) @@ -274,10 +262,9 @@ port = {'id': 'port-id', 'device_id': 'router-id'} self.periodic._create_lrouter_port(self.ctx, port) - l3_mock = self.periodic._ovn_client._l3_plugin - l3_mock.add_router_interface.assert_called_once_with( - self.ctx, port['device_id'], {'port_id': port['id']}, - may_exist=True) + ovn_client_mock = self.periodic._ovn_client + ovn_client_mock.create_router_port.assert_called_once_with( + self.ctx, port['device_id'], mock.ANY) @mock.patch.object(maintenance.LOG, 'debug') def test__log_maintenance_inconsistencies(self, mock_log): @@ -332,7 +319,7 @@ attrs={'name': 'ls2', 'other_config': { constants.MCAST_SNOOP: 'true', - constants.MCAST_FLOOD_UNREGISTERED: 'true'}}) + constants.MCAST_FLOOD_UNREGISTERED: 'false'}}) nb_idl.ls_list.return_value.execute.return_value = [ls0, ls1, ls2] @@ -345,11 +332,11 @@ mock.call('Logical_Switch', 'ls0', ('other_config', { constants.MCAST_SNOOP: 'true', - constants.MCAST_FLOOD_UNREGISTERED: 'true'})), + constants.MCAST_FLOOD_UNREGISTERED: 'false'})), mock.call('Logical_Switch', 'ls1', ('other_config', { constants.MCAST_SNOOP: 'true', - constants.MCAST_FLOOD_UNREGISTERED: 'true'})), + constants.MCAST_FLOOD_UNREGISTERED: 'false'})), ] nb_idl.db_set.assert_has_calls(expected_calls) @@ -408,3 +395,68 @@ priority=constants.HA_CHASSIS_GROUP_HIGHEST_PRIORITY - 1) ] nb_idl.ha_chassis_group_add_chassis.assert_has_calls(expected_calls) + + def test_check_for_mcast_flood_reports(self): + nb_idl = self.fake_ovn_client._nb_idl + lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lsp0', + 'options': {'mcast_flood_reports': 'true'}, + 'type': ""}) + lsp1 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lsp1', 'options': {}, 'type': ""}) + lsp2 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lsp2', 'options': {}, + 'type': "vtep"}) + lsp3 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lsp3', 'options': {}, + 'type': "localport"}) + lsp4 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lsp4', 'options': {}, + 'type': "router"}) + lsp5 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lsp5', 'options': {}, 'type': 'localnet'}) + + nb_idl.lsp_list.return_value.execute.return_value = [ + lsp0, lsp1, lsp2, lsp3, lsp4, lsp5] + + # Invoke the periodic method, it meant to run only once at startup + # so NeverAgain will be raised at the end + self.assertRaises(periodics.NeverAgain, + self.periodic.check_for_mcast_flood_reports) + + # Assert only lsp1 and lsp5 were called because they are the only + # ones meeting the criteria ("mcast_flood_reports" not yet set, + # and type "" or localnet) + expected_calls = [ + mock.call('lsp1', mcast_flood_reports='true'), + mock.call('lsp5', mcast_flood_reports='true', mcast_flood='false')] + + nb_idl.lsp_set_options.assert_has_calls(expected_calls) + + def test_check_router_mac_binding_options(self): + nb_idl = self.fake_ovn_client._nb_idl + lr0 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lr0', + 'options': {'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'}}) + lr1 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lr1', 'options': {}}) + lr2 = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lr2', 'options': {}}) + nb_idl.lr_list.return_value.execute.return_value = [lr0, lr1, lr2] + + # Invoke the periodic method, it meant to run only once at startup + # so NeverAgain will be raised at the end + self.assertRaises(periodics.NeverAgain, + self.periodic.check_router_mac_binding_options) + + # Assert lr1 and lr2 had their options updated since the values + # were not set + expected_calls = [ + mock.call('lr1', + options={'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'}), + mock.call('lr2', + options={'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'})] + nb_idl.update_lrouter.assert_has_calls(expected_calls) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py 2021-11-12 13:56:42.000000000 +0000 @@ -60,6 +60,23 @@ 'mtu': 1450, 'provider:physical_network': 'physnet2'}] + self.segments = [{'id': 'seg1', + 'network_id': 'n1', + 'physical_network': 'physnet1', + 'network_type': 'vlan', + 'segmentation_id': 1000}, + {'id': 'seg2', + 'network_id': 'n2', + 'physical_network': None, + 'network_type': 'geneve'}, + {'id': 'seg4', + 'network_id': 'n4', + 'physical_network': 'physnet2', + 'network_type': 'flat'}] + self.segments_map = { + k['network_id']: k + for k in self.segments} + self.subnets = [{'id': 'n1-s1', 'network_id': 'n1', 'enable_dhcp': True, @@ -213,25 +230,6 @@ 'match': 'outport == "p2n2" && ip4 && ' 'ip4.src == 10.0.0.0/24 && udp && ' 'udp.src == 67 && udp.dst == 68'}]} - self.address_sets_ovn = { - 'as_ip4_sg1': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: - 'all-tcp'}, - 'name': 'as_ip4_sg1', - 'addresses': ['10.0.0.4']}, - 'as_ip4_sg2': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: - 'all-tcpe'}, - 'name': 'as_ip4_sg2', - 'addresses': []}, - 'as_ip6_sg2': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: - 'all-tcpe'}, - 'name': 'as_ip6_sg2', - 'addresses': ['fd79:e1c:a55::816:eff:eff:ff2', - 'fd79:e1c:a55::816:eff:eff:ff3']}, - 'as_ip4_del': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: - 'all-delete'}, - 'name': 'as_ip4_delete', - 'addresses': ['10.0.0.4']}, - } self.routers = [{'id': 'r1', 'routes': [{'nexthop': '20.0.0.100', 'destination': '11.0.0.0/24'}, { @@ -324,16 +322,23 @@ self.lswitches_with_ports = [{'name': 'neutron-n1', 'ports': ['p1n1', 'p3n1'], - 'provnet_port': None}, + 'provnet_ports': []}, {'name': 'neutron-n3', 'ports': ['p1n3', 'p2n3'], - 'provnet_port': None}, + 'provnet_ports': []}, {'name': 'neutron-n4', 'ports': [], - 'provnet_port': 'provnet-n4'}] + 'provnet_ports': [ + 'provnet-seg4', + 'provnet-orphaned-segment']}] self.lrport_networks = ['fdad:123:456::1/64', 'fdad:cafe:a1b2::1/64'] + def get_additional_service_plugins(self): + p = super(TestOvnNbSyncML2, self).get_additional_service_plugins() + p.update({'segments': 'neutron.services.segments.plugin.Plugin'}) + return p + def _fake_get_ovn_dhcp_options(self, subnet, network, server_mac=None): if subnet['id'] == 'n1-s1': return {'cidr': '10.0.0.0/24', @@ -368,11 +373,24 @@ ovn_api = ovn_nb_synchronizer.ovn_api ovn_driver = ovn_nb_synchronizer.ovn_driver l3_plugin = ovn_nb_synchronizer.l3_plugin + segments_plugin = ovn_nb_synchronizer.segments_plugin core_plugin.get_networks = mock.Mock() core_plugin.get_networks.return_value = self.networks core_plugin.get_subnets = mock.Mock() core_plugin.get_subnets.return_value = self.subnets + + def get_segments(self, filters): + segs = [] + for segment in self.segments: + if segment['network_id'] == filters['network_id'][0]: + segs.append(segment) + return segs + + segments_plugin.get_segments = mock.Mock() + segments_plugin.get_segments.side_effect = ( + lambda x, filters: get_segments(self, filters)) + # following block is used for acl syncing unit-test # With the given set of values in the unit testing, @@ -392,9 +410,6 @@ ovn_nb_synchronizer.get_acls.return_value = self.acls_ovn core_plugin.get_security_groups = mock.MagicMock( return_value=self.security_groups) - ovn_nb_synchronizer.get_address_sets = mock.Mock() - ovn_nb_synchronizer.get_address_sets.return_value =\ - self.address_sets_ovn get_port_groups = mock.MagicMock() get_port_groups.execute.return_value = self.port_groups_ovn ovn_api.db_list_rows.return_value = get_port_groups @@ -421,7 +436,7 @@ ovn_nb_synchronizer._ovn_client = mock.Mock() ovn_nb_synchronizer._ovn_client.\ _get_nets_and_ipv6_ra_confs_for_router_port.return_value = ( - self.lrport_networks, {}) + self.lrport_networks, {'fixed_ips': {}}) ovn_nb_synchronizer._ovn_client._get_v4_network_of_all_router_ports. \ side_effect = self._fake_get_v4_network_of_all_router_ports ovn_nb_synchronizer._ovn_client._get_gw_info = mock.Mock() @@ -445,7 +460,7 @@ ovn_driver.validate_and_get_data_from_binding_profile = mock.Mock() ovn_nb_synchronizer._ovn_client.create_port = mock.Mock() ovn_nb_synchronizer._ovn_client.create_port.return_value = mock.ANY - ovn_nb_synchronizer._ovn_client._create_provnet_port = mock.Mock() + ovn_nb_synchronizer._ovn_client.create_provnet_port = mock.Mock() ovn_api.ls_del = mock.Mock() ovn_api.delete_lswitch_port = mock.Mock() @@ -505,9 +520,6 @@ 'port_id': 'p1n2'}, 'uuid': 'UUID6'}}} - ovn_api.create_address_set = mock.Mock() - ovn_api.delete_address_set = mock.Mock() - ovn_api.update_address_set = mock.Mock() ovn_nb_synchronizer._ovn_client._add_subnet_dhcp_options = mock.Mock() ovn_nb_synchronizer._ovn_client._get_ovn_dhcp_options = mock.Mock() ovn_nb_synchronizer._ovn_client._get_ovn_dhcp_options.side_effect = ( @@ -528,54 +540,17 @@ add_static_route_list, del_static_route_list, add_snat_list, del_snat_list, add_floating_ip_list, del_floating_ip_list, - add_address_set_list, del_address_set_list, - update_address_set_list, add_subnet_dhcp_options_list, delete_dhcp_options_list, add_port_groups_list, - del_port_groups_list, - port_groups_supported=False): + del_port_groups_list): self._test_mocks_helper(ovn_nb_synchronizer) - core_plugin = ovn_nb_synchronizer.core_plugin ovn_api = ovn_nb_synchronizer.ovn_api - ovn_api.is_port_groups_supported.return_value = port_groups_supported - mock.patch.object(impl_idl_ovn, 'get_connection').start() + mock.patch.object(impl_idl_ovn.OvsdbNbOvnIdl, 'from_worker').start() ovn_nb_synchronizer.do_sync() - if not ovn_api.is_port_groups_supported(): - get_security_group_calls = [mock.call(mock.ANY, sg['id']) - for sg in self.security_groups] - self.assertEqual(len(self.security_groups), - core_plugin.get_security_group.call_count) - core_plugin.get_security_group.assert_has_calls( - get_security_group_calls, any_order=True) - - create_address_set_calls = [mock.call(**a) - for a in add_address_set_list] - self.assertEqual( - len(add_address_set_list), - ovn_api.create_address_set.call_count) - ovn_api.create_address_set.assert_has_calls( - create_address_set_calls, any_order=True) - - del_address_set_calls = [mock.call(**d) - for d in del_address_set_list] - self.assertEqual( - len(del_address_set_list), - ovn_api.delete_address_set.call_count) - ovn_api.delete_address_set.assert_has_calls( - del_address_set_calls, any_order=True) - - update_address_set_calls = [mock.call(**u) - for u in update_address_set_list] - self.assertEqual( - len(update_address_set_list), - ovn_api.update_address_set.call_count) - ovn_api.update_address_set.assert_has_calls( - update_address_set_calls, any_order=True) - create_port_groups_calls = [mock.call(**a) for a in add_port_groups_list] self.assertEqual( @@ -609,14 +584,16 @@ create_port_calls, any_order=True) create_provnet_port_calls = [ - mock.call(mock.ANY, mock.ANY, - network['provider:physical_network'], - network['provider:segmentation_id']) - for network in create_provnet_port_list] + mock.call( + network['id'], + self.segments_map[network['id']], + txn=mock.ANY) + for network in create_provnet_port_list + if network.get('provider:physical_network')] self.assertEqual( len(create_provnet_port_list), - ovn_nb_synchronizer._ovn_client._create_provnet_port.call_count) - ovn_nb_synchronizer._ovn_client._create_provnet_port.assert_has_calls( + ovn_nb_synchronizer._ovn_client.create_provnet_port.call_count) + ovn_nb_synchronizer._ovn_client.create_provnet_port.assert_has_calls( create_provnet_port_calls, any_order=True) self.assertEqual(len(del_network_list), @@ -687,9 +664,9 @@ ovn_nb_synchronizer._ovn_client.create_router.assert_has_calls( create_router_calls, any_order=True) - create_router_port_calls = [mock.call(mock.ANY, p['device_id'], - mock.ANY) - for p in create_router_port_list] + create_router_port_calls = [ + mock.call(mock.ANY, self.routers[i], mock.ANY) + for i, p in enumerate(create_router_port_list)] self.assertEqual( len(create_router_port_list), ovn_nb_synchronizer._ovn_client._create_lrouter_port.call_count) @@ -740,13 +717,15 @@ ovn_api.delete_dhcp_options.assert_has_calls( delete_dhcp_options_calls, any_order=True) - def _test_ovn_nb_sync_mode_repair_helper(self, port_groups_supported=True): + def test_ovn_nb_sync_mode_repair(self): create_network_list = [{'net': {'id': 'n2', 'mtu': 1450}, 'ext_ids': {}}] del_network_list = ['neutron-n3'] del_port_list = [{'id': 'p3n1', 'lswitch': 'neutron-n1'}, - {'id': 'p1n1', 'lswitch': 'neutron-n1'}] + {'id': 'p1n1', 'lswitch': 'neutron-n1'}, + {'id': 'provnet-orphaned-segment', + 'lswitch': 'neutron-n4'}] create_port_list = self.ports for port in create_port_list: if port['id'] in ['p1n1', 'fp1']: @@ -818,37 +797,11 @@ update_router_port_list[0].update( {'networks': self.lrport_networks}) - if not port_groups_supported: - add_address_set_list = [ - {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg1'}, - 'name': 'as_ip6_sg1', - 'addresses': ['fd79:e1c:a55::816:eff:eff:ff2']}] - del_address_set_list = [{'name': 'as_ip4_del'}] - update_address_set_list = [ - {'addrs_remove': [], - 'addrs_add': ['10.0.0.4'], - 'name': 'as_ip4_sg2'}, - {'addrs_remove': ['fd79:e1c:a55::816:eff:eff:ff3'], - 'addrs_add': [], - 'name': 'as_ip6_sg2'}] - # If Port Groups are not supported, we don't expect any of those - # to be created/deleted. - add_port_groups_list = [] - del_port_groups_list = [] - else: - add_port_groups_list = [ - {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg2'}, - 'name': 'pg_sg2', - 'acls': []}] - del_port_groups_list = ['pg_unknown_del'] - # If using Port Groups, no Address Set shall be created/updated - # and all the existing ones have to be removed. - add_address_set_list = [] - update_address_set_list = [] - del_address_set_list = [{'name': 'as_ip4_sg1'}, - {'name': 'as_ip4_sg2'}, - {'name': 'as_ip6_sg2'}, - {'name': 'as_ip4_del'}] + add_port_groups_list = [ + {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg2'}, + 'name': 'pg_sg2', + 'acls': []}] + del_port_groups_list = ['pg_unknown_del'] add_subnet_dhcp_options_list = [(self.subnets[2], self.networks[1]), (self.subnets[1], self.networks[0])] @@ -875,22 +828,12 @@ del_snat_list, add_floating_ip_list, del_floating_ip_list, - add_address_set_list, - del_address_set_list, - update_address_set_list, add_subnet_dhcp_options_list, delete_dhcp_options_list, add_port_groups_list, - del_port_groups_list, - port_groups_supported) - - def test_ovn_nb_sync_mode_repair_no_pgs(self): - self._test_ovn_nb_sync_mode_repair_helper(port_groups_supported=False) - - def test_ovn_nb_sync_mode_repair_pgs(self): - self._test_ovn_nb_sync_mode_repair_helper(port_groups_supported=True) + del_port_groups_list) - def _test_ovn_nb_sync_mode_log_helper(self, port_groups_supported=True): + def test_ovn_nb_sync_mode_log(self): create_network_list = [] create_port_list = [] create_provnet_port_list = [] @@ -907,9 +850,6 @@ del_snat_list = [] add_floating_ip_list = [] del_floating_ip_list = [] - add_address_set_list = [] - del_address_set_list = [] - update_address_set_list = [] add_subnet_dhcp_options_list = [] delete_dhcp_options_list = [] add_port_groups_list = [] @@ -936,20 +876,10 @@ del_snat_list, add_floating_ip_list, del_floating_ip_list, - add_address_set_list, - del_address_set_list, - update_address_set_list, add_subnet_dhcp_options_list, delete_dhcp_options_list, add_port_groups_list, - del_port_groups_list, - port_groups_supported) - - def test_ovn_nb_sync_mode_log_pgs(self): - self._test_ovn_nb_sync_mode_log_helper(port_groups_supported=True) - - def test_ovn_nb_sync_mode_log_no_pgs(self): - self._test_ovn_nb_sync_mode_log_helper(port_groups_supported=False) + del_port_groups_list) class TestOvnSbSyncML2(test_mech_driver.OVNMechanismDriverTestCase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py 2021-11-12 13:56:42.000000000 +0000 @@ -26,6 +26,7 @@ from ovs.stream import Stream from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils +import testtools from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import hash_ring_manager @@ -98,7 +99,12 @@ def setUp(self): super(TestOvnDbNotifyHandler, self).setUp() self.handler = ovsdb_monitor.OvnDbNotifyHandler(mock.ANY) - self.watched_events = self.handler._RowEventHandler__watched_events + # NOTE(ralonsoh): once the ovsdbapp library version is bumped beyond + # 1.5.0, the first assignation (using name mangling) can be deleted. + try: + self.watched_events = self.handler._RowEventHandler__watched_events + except AttributeError: + self.watched_events = self.handler._watched_events def test_watch_and_unwatch_events(self): expected_events = set() @@ -601,3 +607,32 @@ # after it became a Gateway chassis self._test_handle_ha_chassis_group_changes_create( self.event.ROW_UPDATE) + + +class TestShortLivingOvsdbApi(base.BaseTestCase): + def test_context(self): + api_class = mock.Mock() + idl = mock.Mock() + with ovsdb_monitor.short_living_ovsdb_api(api_class, idl) as api: + self.assertEqual(api_class.return_value, api) + api.ovsdb_connection.stop.assert_called_once_with() + + def test_context_error(self): + api_class = mock.Mock() + idl = mock.Mock() + exc = RuntimeError() + try: + with ovsdb_monitor.short_living_ovsdb_api(api_class, idl) as api: + self.assertEqual(api_class.return_value, api) + raise exc + except RuntimeError as re: + self.assertIs(exc, re) + api.ovsdb_connection.stop.assert_called_once_with() + + def test_api_class_error(self): + api_class = mock.Mock(side_effect=RuntimeError()) + idl = mock.Mock() + with testtools.ExpectedException(RuntimeError): + with ovsdb_monitor.short_living_ovsdb_api(api_class, idl): + # Make sure it never enter the api context + raise Exception("API class instantiated but it should not") diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -14,10 +14,12 @@ import copy import datetime +import shlex import uuid import mock from neutron_lib.api.definitions import external_net +from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net as pnet from neutron_lib.callbacks import events @@ -29,6 +31,7 @@ from neutron_lib.plugins import directory from neutron_lib.tests import tools from neutron_lib.utils import net as n_net +from oslo_concurrency import processutils from oslo_config import cfg from oslo_db import exception as os_db_exc from oslo_serialization import jsonutils @@ -46,8 +49,11 @@ from neutron.db import ovn_revision_numbers_db from neutron.db import provisioning_blocks from neutron.db import securitygroups_db +from neutron.db import segments_db +from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent from neutron.plugins.ml2.drivers.ovn.mech_driver import mech_driver from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client +from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor from neutron.plugins.ml2.drivers import type_geneve # noqa from neutron.services.revisions import revision_plugin from neutron.tests.unit.extensions import test_segment @@ -84,6 +90,7 @@ self.mech_driver = mm.mech_drivers['ovn'].obj self.mech_driver._nb_ovn = fakes.FakeOvsdbNbOvnIdl() self.mech_driver._sb_ovn = fakes.FakeOvsdbSbOvnIdl() + self.mech_driver._ovn_client._qos_driver = mock.Mock() self.nb_ovn = self.mech_driver._nb_ovn self.sb_ovn = self.mech_driver._sb_ovn @@ -107,43 +114,131 @@ p.start() self.addCleanup(p.stop) + def test_delete_mac_binding_entries(self): + self.config(group='ovn', ovn_sb_private_key=None) + expected = ('ovsdb-client transact tcp:127.0.0.1:6642 ' + '\'["OVN_Southbound", {"op": "delete", "table": ' + '"MAC_Binding", "where": [["ip", "==", "1.1.1.1"]]}]\'') + with mock.patch.object(processutils, 'execute') as mock_execute: + self.mech_driver.delete_mac_binding_entries('1.1.1.1') + mock_execute.assert_called_once_with(*shlex.split(expected), + log_errors=processutils.LOG_FINAL_ERROR) + + def test_delete_mac_binding_entries_ssl(self): + self.config(group='ovn', ovn_sb_private_key='pk') + self.config(group='ovn', ovn_sb_certificate='cert') + self.config(group='ovn', ovn_sb_ca_cert='ca') + expected = ('ovsdb-client transact tcp:127.0.0.1:6642 ' + '-p pk -c cert -C ca ' + '\'["OVN_Southbound", {"op": "delete", "table": ' + '"MAC_Binding", "where": [["ip", "==", "1.1.1.1"]]}]\'') + with mock.patch.object(processutils, 'execute') as mock_execute: + self.mech_driver.delete_mac_binding_entries('1.1.1.1') + mock_execute.assert_called_once_with(*shlex.split(expected), + log_errors=processutils.LOG_FINAL_ERROR) + + @mock.patch.object(ovsdb_monitor.OvnInitPGNbIdl, 'from_server') + @mock.patch.object(ovsdb_monitor, 'short_living_ovsdb_api') + def test__create_neutron_pg_drop_non_existing( + self, m_ovsdb_api_con, m_from_server): + m_ovsdb_api = m_ovsdb_api_con.return_value.__enter__.return_value + m_ovsdb_api.get_port_group.return_value = None + self.mech_driver._create_neutron_pg_drop() + self.assertEqual(1, m_ovsdb_api.get_port_group.call_count) + self.assertTrue(m_ovsdb_api.transaction.return_value.__enter__.called) + + @mock.patch.object(ovsdb_monitor.OvnInitPGNbIdl, 'from_server') + @mock.patch.object(ovsdb_monitor, 'short_living_ovsdb_api') + def test__create_neutron_pg_drop_existing( + self, m_ovsdb_api_con, m_from_server): + m_ovsdb_api = m_ovsdb_api_con.return_value.__enter__.return_value + m_ovsdb_api.get_port_group.return_value = 'foo' + self.mech_driver._create_neutron_pg_drop() + self.assertEqual(1, m_ovsdb_api.get_port_group.call_count) + self.assertFalse(m_ovsdb_api.transaction.return_value.__enter__.called) + + @mock.patch.object(ovsdb_monitor.OvnInitPGNbIdl, 'from_server') + @mock.patch.object(ovsdb_monitor, 'short_living_ovsdb_api') + def test__create_neutron_pg_drop_created_meanwhile( + self, m_ovsdb_api_con, m_from_server): + m_ovsdb_api = m_ovsdb_api_con.return_value.__enter__.return_value + m_ovsdb_api.get_port_group.side_effect = [None, 'foo'] + m_ovsdb_api.transaction.return_value.__exit__.side_effect = ( + RuntimeError()) + self.mech_driver._create_neutron_pg_drop() + self.assertEqual(2, m_ovsdb_api.get_port_group.call_count) + + @mock.patch.object(ovsdb_monitor.OvnInitPGNbIdl, 'from_server') + @mock.patch.object(ovsdb_monitor, 'short_living_ovsdb_api') + def test__create_neutron_pg_drop_error( + self, m_ovsdb_api_con, m_from_server): + m_ovsdb_api = m_ovsdb_api_con.return_value.__enter__.return_value + m_ovsdb_api.get_port_group.side_effect = [None, None] + m_ovsdb_api.transaction.return_value.__exit__.side_effect = ( + RuntimeError()) + self.assertRaises(RuntimeError, + self.mech_driver._create_neutron_pg_drop) + self.assertEqual(2, m_ovsdb_api.get_port_group.call_count) + @mock.patch.object(ovn_revision_numbers_db, 'bump_revision') def test__create_security_group(self, mock_bump): self.mech_driver._create_security_group( resources.SECURITY_GROUP, events.AFTER_CREATE, {}, security_group=self.fake_sg, context=self.context) external_ids = {ovn_const.OVN_SG_EXT_ID_KEY: self.fake_sg['id']} - ip4_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip4') - ip6_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip6') - create_address_set_calls = [mock.call(name=name, - external_ids=external_ids) - for name in [ip4_name, ip6_name]] + pg_name = ovn_utils.ovn_port_group_name(self.fake_sg['id']) + + self.nb_ovn.pg_add.assert_called_once_with( + name=pg_name, acls=[], external_ids=external_ids) - self.nb_ovn.create_address_set.assert_has_calls( - create_address_set_calls, any_order=True) mock_bump.assert_called_once_with( mock.ANY, self.fake_sg, ovn_const.TYPE_SECURITY_GROUPS) - def test__delete_security_group(self): + @mock.patch.object(ovn_revision_numbers_db, 'delete_revision') + def test__delete_security_group(self, mock_del_rev): self.mech_driver._delete_security_group( resources.SECURITY_GROUP, events.AFTER_CREATE, {}, security_group_id=self.fake_sg['id'], context=self.context) - ip4_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip4') - ip6_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip6') - delete_address_set_calls = [mock.call(name=name) - for name in [ip4_name, ip6_name]] + pg_name = ovn_utils.ovn_port_group_name(self.fake_sg['id']) - self.nb_ovn.delete_address_set.assert_has_calls( - delete_address_set_calls, any_order=True) + self.nb_ovn.pg_del.assert_called_once_with( + name=pg_name) + + mock_del_rev.assert_called_once_with( + mock.ANY, self.fake_sg['id'], ovn_const.TYPE_SECURITY_GROUPS) @mock.patch.object(ovn_revision_numbers_db, 'bump_revision') def test__process_sg_rule_notifications_sgr_create(self, mock_bump): - with mock.patch.object(ovn_acl, 'update_acls_for_security_group') \ - as ovn_acl_up: + with mock.patch.object( + self.mech_driver, + '_sg_has_rules_with_same_normalized_cidr') as has_same_rules, \ + mock.patch.object( + ovn_acl, 'update_acls_for_security_group') as ovn_acl_up: rule = {'security_group_id': 'sg_id'} self.mech_driver._process_sg_rule_notification( resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, {}, security_group_rule=rule, context=self.context) + has_same_rules.assert_not_called() + ovn_acl_up.assert_called_once_with( + mock.ANY, mock.ANY, mock.ANY, + 'sg_id', rule, is_add_acl=True) + mock_bump.assert_called_once_with( + mock.ANY, rule, ovn_const.TYPE_SECURITY_GROUP_RULES) + + @mock.patch.object(ovn_revision_numbers_db, 'bump_revision') + def test__process_sg_rule_notifications_sgr_create_with_remote_ip_prefix( + self, mock_bump): + with mock.patch.object( + self.mech_driver, + '_sg_has_rules_with_same_normalized_cidr') as has_same_rules, \ + mock.patch.object( + ovn_acl, 'update_acls_for_security_group') as ovn_acl_up: + rule = {'security_group_id': 'sg_id', + 'remote_ip_prefix': '1.0.0.0/24'} + self.mech_driver._process_sg_rule_notification( + resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, {}, + security_group_rule=rule, context=self.context) + has_same_rules.assert_not_called() ovn_acl_up.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, 'sg_id', rule, is_add_acl=True) @@ -167,87 +262,58 @@ mock_delrev.assert_called_once_with( mock.ANY, rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES) - def test_add_acls_no_sec_group(self): - fake_port_no_sg = fakes.FakePort.create_one_port().info() - expected_acls = ovn_acl.drop_all_ip_traffic_for_port(fake_port_no_sg) - acls = ovn_acl.add_acls(self.mech_driver._plugin, - mock.Mock(), - fake_port_no_sg, - {}, {}, self.mech_driver._nb_ovn) - self.assertEqual(expected_acls, acls) - - def test_add_acls_no_sec_group_no_port_security(self): - fake_port_no_sg_no_ps = fakes.FakePort.create_one_port( - attrs={'port_security_enabled': False}).info() - acls = ovn_acl.add_acls(self.mech_driver._plugin, - mock.Mock(), - fake_port_no_sg_no_ps, - {}, {}, self.mech_driver._nb_ovn) - self.assertEqual([], acls) - - def _test_add_acls_with_sec_group_helper(self, native_dhcp=True): - fake_port_sg = fakes.FakePort.create_one_port( - attrs={'security_groups': [self.fake_sg['id']], - 'fixed_ips': [{'subnet_id': self.fake_subnet['id'], - 'ip_address': '10.10.10.20'}]} - ).info() - - expected_acls = [] - expected_acls += ovn_acl.drop_all_ip_traffic_for_port( - fake_port_sg) - expected_acls += ovn_acl.add_acl_dhcp( - fake_port_sg, self.fake_subnet, native_dhcp) - sg_rule_acl = ovn_acl.add_sg_rule_acl_for_port( - fake_port_sg, self.fake_sg_rule, - 'outport == "' + fake_port_sg['id'] + '" ' + - '&& ip4 && ip4.src == 0.0.0.0/0 ' + - '&& tcp && tcp.dst == 22') - expected_acls.append(sg_rule_acl) - - # Test with caches - acls = ovn_acl.add_acls(self.mech_driver._plugin, - mock.Mock(), - fake_port_sg, - self.sg_cache, - self.subnet_cache, - self.mech_driver._nb_ovn) - self.assertEqual(expected_acls, acls) - - # Test without caches - with mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, - 'get_subnet', return_value=self.fake_subnet), \ - mock.patch.object(securitygroups_db.SecurityGroupDbMixin, - 'get_security_group', - return_value=self.fake_sg): - acls = ovn_acl.add_acls(self.mech_driver._plugin, - mock.Mock(), - fake_port_sg, - {}, {}, self.mech_driver._nb_ovn) - self.assertEqual(expected_acls, acls) - - # Test with security groups disabled - with mock.patch.object(ovn_acl, 'is_sg_enabled', return_value=False): - acls = ovn_acl.add_acls(self.mech_driver._plugin, - mock.Mock(), - fake_port_sg, - self.sg_cache, - self.subnet_cache, - self.mech_driver._nb_ovn) - self.assertEqual([], acls) - - # Test with multiple fixed IPs on the same subnet. - fake_port_sg['fixed_ips'].append({'subnet_id': self.fake_subnet['id'], - 'ip_address': '10.10.10.21'}) - acls = ovn_acl.add_acls(self.mech_driver._plugin, - mock.Mock(), - fake_port_sg, - self.sg_cache, - self.subnet_cache, - self.mech_driver._nb_ovn) - self.assertEqual(expected_acls, acls) - - def test_add_acls_with_sec_group_native_dhcp_enabled(self): - self._test_add_acls_with_sec_group_helper() + def test__sg_has_rules_with_same_normalized_cidr(self): + scenarios = [ + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '10.10.10.175/26', + 'protocol': 'tcp'}, False), + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '10.10.10.175/26', + 'protocol': 'udp'}, False), + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '10.10.10.175/26', + 'protocol': 'tcp'}, False), + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '10.10.10.175/26', + 'protocol': 'tcp', + 'port_range_min': '2000', 'port_range_max': '2100'}, False), + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '192.168.0.0/24', + 'protocol': 'tcp', + 'port_range_min': '2000', 'port_range_max': '3000', + 'direction': 'ingress'}, False), + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '10.10.10.175/26', + 'protocol': 'tcp', + 'port_range_min': '2000', 'port_range_max': '3000', + 'direction': 'egress'}, False), + ({'id': 'rule-id', 'security_group_id': 'sec-group-uuid', + 'remote_ip_prefix': '10.10.10.175/26', + 'protocol': 'tcp', + 'port_range_min': '2000', 'port_range_max': '3000', + 'direction': 'ingress'}, True)] + + rules = [ + { + 'id': 'rule-1-id', + 'protocol': 'udp', + }, { + 'id': 'rule-2-id', + 'remote_ip_prefix': '10.10.10.128/26', + 'protocol': 'tcp', + 'port_range_min': '2000', + 'port_range_max': '3000', + 'direction': 'ingress' + }] + + with mock.patch.object(securitygroups_db.SecurityGroupDbMixin, + 'get_security_group_rules', + return_value=rules): + for rule, expected_result in scenarios: + self.assertEqual( + expected_result, + self.mech_driver._sg_has_rules_with_same_normalized_cidr( + rule)) def test_port_invalid_binding_profile(self): invalid_binding_profiles = [ @@ -304,6 +370,68 @@ self.mech_driver._validate_ignored_port, p, ori_p) + def test__validate_port_extra_dhcp_opts(self): + opt = {'opt_name': 'bootfile-name', + 'opt_value': 'homer_simpson.bin', + 'ip_version': 4} + port = {edo_ext.EXTRADHCPOPTS: [opt], 'id': 'fake-port'} + self.assertIsNone( + self.mech_driver._validate_port_extra_dhcp_opts(port)) + + @mock.patch.object(mech_driver.LOG, 'info') + def test__validate_port_extra_dhcp_opts_invalid(self, mock_log): + port_id = 'fake-port' + opt = {'opt_name': 'not-valid', + 'opt_value': 'spongebob squarepants', + 'ip_version': 4} + port = {edo_ext.EXTRADHCPOPTS: [opt], 'id': port_id} + self.mech_driver._validate_port_extra_dhcp_opts(port) + # Assert the log message contained the invalid DHCP options + expected_call = mock.call( + mock.ANY, {'port_id': port_id, 'ipv4_opts': 'not-valid', + 'ipv6_opts': ''}) + mock_log.assert_has_calls([expected_call]) + + @mock.patch.object(mech_driver.LOG, 'info') + def test_create_port_invalid_extra_dhcp_opts(self, mock_log): + extra_dhcp_opts = { + 'extra_dhcp_opts': [{'ip_version': 4, 'opt_name': 'banana', + 'opt_value': 'banana'}, + {'ip_version': 6, 'opt_name': 'orange', + 'opt_value': 'orange'}] + } + with self.network() as n: + with self.subnet(n): + res = self._create_port(self.fmt, n['network']['id'], + arg_list=('extra_dhcp_opts',), + **extra_dhcp_opts) + port_id = self.deserialize(self.fmt, res)['port']['id'] + # Assert the log message contained the invalid DHCP options + expected_call = mock.call( + mock.ANY, {'port_id': port_id, 'ipv4_opts': 'banana', + 'ipv6_opts': 'orange'}) + mock_log.assert_has_calls([expected_call]) + + @mock.patch.object(mech_driver.LOG, 'info') + def test_update_port_invalid_extra_dhcp_opts(self, mock_log): + data = { + 'port': {'extra_dhcp_opts': [{'ip_version': 4, 'opt_name': 'apple', + 'opt_value': 'apple'}, + {'ip_version': 6, 'opt_name': 'grape', + 'opt_value': 'grape'}]}} + with self.network(set_context=True, tenant_id='test') as net: + with self.subnet(network=net) as subnet: + with self.port(subnet=subnet, + set_context=True, tenant_id='test') as port: + port_id = port['port']['id'] + self._update('ports', port_id, data) + + # Assert the log message contained the invalid DHCP options + expected_call = mock.call( + mock.ANY, {'port_id': port_id, 'ipv4_opts': 'apple', + 'ipv6_opts': 'grape'}) + mock_log.assert_has_calls([expected_call]) + def test_create_and_update_ignored_fip_port(self): with self.network(set_context=True, tenant_id='test') as net1: with self.subnet(network=net1) as subnet1: @@ -405,8 +533,8 @@ self.assertEqual([], called_args_dict.get('port_security')) - self.assertEqual(ovn_const.UNKNOWN_ADDR, - called_args_dict.get('addresses')[1]) + self.assertIn(ovn_const.UNKNOWN_ADDR, + called_args_dict.get('addresses')) data = {'port': {'mac_address': '00:00:00:00:00:01'}} req = self.new_update_request( 'ports', @@ -418,9 +546,8 @@ ).call_args_list[0][1]) self.assertEqual([], called_args_dict.get('port_security')) - self.assertEqual(2, len(called_args_dict.get('addresses'))) - self.assertEqual(ovn_const.UNKNOWN_ADDR, - called_args_dict.get('addresses')[1]) + self.assertIn(ovn_const.UNKNOWN_ADDR, + called_args_dict.get('addresses')) # Enable port security data = {'port': {'port_security_enabled': 'True'}} @@ -580,7 +707,7 @@ ovn_utils.ovn_name(net['id']), external_ids=mock.ANY, may_exist=True, other_config={ovn_const.MCAST_SNOOP: value, - ovn_const.MCAST_FLOOD_UNREGISTERED: value}) + ovn_const.MCAST_FLOOD_UNREGISTERED: 'false'}) def test_create_network_igmp_snoop_enabled(self): self._create_network_igmp_snoop(enabled=True) @@ -588,6 +715,36 @@ def test_create_network_igmp_snoop_disabled(self): self._create_network_igmp_snoop(enabled=False) + def test_create_network_create_localnet_port_tunnel_network_type(self): + nb_idl = self.mech_driver._ovn_client._nb_idl + self._make_network(self.fmt, name='net1', + admin_state_up=True)['network'] + # net1 is not physical network + nb_idl.create_lswitch_port.assert_not_called() + + def test_create_network_create_localnet_port_physical_network_type(self): + nb_idl = self.mech_driver._ovn_client._nb_idl + net_arg = {pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: '2'} + net = self._make_network(self.fmt, 'net1', True, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID,), + **net_arg)['network'] + segments = segments_db.get_network_segments( + self.context, net['id']) + nb_idl.create_lswitch_port.assert_called_once_with( + addresses=[ovn_const.UNKNOWN_ADDR], + external_ids={}, + lport_name=ovn_utils.ovn_provnet_port_name(segments[0]['id']), + lswitch_name=ovn_utils.ovn_name(net['id']), + options={'network_name': 'physnet1', + ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true', + ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'}, + tag=2, + type='localnet') + def test_create_port_without_security_groups(self): kwargs = {'security_groups': []} with self.network(set_context=True, tenant_id='test') as net1: @@ -598,8 +755,7 @@ **kwargs): self.assertEqual( 1, self.nb_ovn.create_lswitch_port.call_count) - self.assertEqual(2, self.nb_ovn.add_acl.call_count) - self.nb_ovn.update_address_set.assert_not_called() + self.assertFalse(self.nb_ovn.add_acl.called) def test_create_port_without_security_groups_no_ps(self): kwargs = {'security_groups': [], 'port_security_enabled': False} @@ -613,23 +769,6 @@ self.assertEqual( 1, self.nb_ovn.create_lswitch_port.call_count) self.nb_ovn.add_acl.assert_not_called() - self.nb_ovn.update_address_set.assert_not_called() - - def _test_create_port_with_security_groups_helper(self, - add_acl_call_count): - with self.network(set_context=True, tenant_id='test') as net1: - with self.subnet(network=net1) as subnet1: - with self.port(subnet=subnet1, - set_context=True, tenant_id='test'): - self.assertEqual( - 1, self.nb_ovn.create_lswitch_port.call_count) - self.assertEqual( - add_acl_call_count, self.nb_ovn.add_acl.call_count) - self.assertEqual( - 1, self.nb_ovn.update_address_set.call_count) - - def test_create_port_with_security_groups_native_dhcp_enabled(self): - self._test_create_port_with_security_groups_helper(7) def test_update_port_changed_security_groups(self): with self.network(set_context=True, tenant_id='test') as net1: @@ -645,29 +784,21 @@ # Remove the default security group. self.nb_ovn.set_lswitch_port.reset_mock() self.nb_ovn.update_acls.reset_mock() - self.nb_ovn.update_address_set.reset_mock() data = {'port': {'security_groups': []}} self._update('ports', port1['port']['id'], data) self.assertEqual( 1, self.nb_ovn.set_lswitch_port.call_count) - self.assertEqual( - 1, self.nb_ovn.update_acls.call_count) - self.assertEqual( - 1, self.nb_ovn.update_address_set.call_count) + self.assertFalse(self.nb_ovn.update_acls.called) + self.assertTrue(self.nb_ovn.pg_add_ports.called) # Add the default security group. self.nb_ovn.set_lswitch_port.reset_mock() self.nb_ovn.update_acls.reset_mock() - self.nb_ovn.update_address_set.reset_mock() fake_lsp.external_ids.pop(ovn_const.OVN_SG_IDS_EXT_ID_KEY) data = {'port': {'security_groups': [sg_id]}} self._update('ports', port1['port']['id'], data) - self.assertEqual( - 1, self.nb_ovn.set_lswitch_port.call_count) - self.assertEqual( - 1, self.nb_ovn.update_acls.call_count) - self.assertEqual( - 1, self.nb_ovn.update_address_set.call_count) + self.assertFalse(self.nb_ovn.update_acls.called) + self.assertTrue(self.nb_ovn.pg_add_ports.called) def test_update_port_unchanged_security_groups(self): with self.network(set_context=True, tenant_id='test') as net1: @@ -682,26 +813,20 @@ # Update the port name. self.nb_ovn.set_lswitch_port.reset_mock() self.nb_ovn.update_acls.reset_mock() - self.nb_ovn.update_address_set.reset_mock() data = {'port': {'name': 'rtheis'}} self._update('ports', port1['port']['id'], data) self.assertEqual( 1, self.nb_ovn.set_lswitch_port.call_count) self.nb_ovn.update_acls.assert_not_called() - self.nb_ovn.update_address_set.assert_not_called() # Update the port fixed IPs self.nb_ovn.set_lswitch_port.reset_mock() self.nb_ovn.update_acls.reset_mock() - self.nb_ovn.update_address_set.reset_mock() data = {'port': {'fixed_ips': []}} self._update('ports', port1['port']['id'], data) self.assertEqual( 1, self.nb_ovn.set_lswitch_port.call_count) - self.assertEqual( - 1, self.nb_ovn.update_acls.call_count) - self.assertEqual( - 1, self.nb_ovn.update_address_set.call_count) + self.assertFalse(self.nb_ovn.update_acls.called) def _test_update_port_vip(self, is_vip=True): kwargs = {} @@ -751,33 +876,9 @@ self.nb_ovn.lookup.return_value = fake_lsp self.nb_ovn.delete_lswitch_port.reset_mock() self.nb_ovn.delete_acl.reset_mock() - self.nb_ovn.update_address_set.reset_mock() self._delete('ports', port1['port']['id']) self.assertEqual( 1, self.nb_ovn.delete_lswitch_port.call_count) - self.assertEqual( - 1, self.nb_ovn.delete_acl.call_count) - self.nb_ovn.update_address_set.assert_not_called() - - def test_delete_port_with_security_groups(self): - with self.network(set_context=True, tenant_id='test') as net1: - with self.subnet(network=net1) as subnet1: - with self.port(subnet=subnet1, - set_context=True, tenant_id='test') as port1: - fake_lsp = ( - fakes.FakeOVNPort.from_neutron_port( - port1['port'])) - self.nb_ovn.lookup.return_value = fake_lsp - self.nb_ovn.delete_lswitch_port.reset_mock() - self.nb_ovn.delete_acl.reset_mock() - self.nb_ovn.update_address_set.reset_mock() - self._delete('ports', port1['port']['id']) - self.assertEqual( - 1, self.nb_ovn.delete_lswitch_port.call_count) - self.assertEqual( - 1, self.nb_ovn.delete_acl.call_count) - self.assertEqual( - 1, self.nb_ovn.update_address_set.call_count) def _test_set_port_status_up(self, is_compute_port=False): port_device_owner = 'compute:nova' if is_compute_port else '' @@ -1403,10 +1504,9 @@ umd.assert_not_called() def test_update_subnet_postcommit_enable_dhcp(self): - context = fakes.FakeSubnetContext( - subnet={'enable_dhcp': True, 'ip_version': 4, 'network_id': 'id', - 'id': 'subnet_id'}, - network={'id': 'id'}) + subnet = {'enable_dhcp': True, 'ip_version': 4, 'network_id': 'id', + 'id': 'subnet_id'} + context = fakes.FakeSubnetContext(subnet=subnet, network={'id': 'id'}) with mock.patch.object( self.mech_driver._ovn_client, '_enable_subnet_dhcp_options') as esd,\ @@ -1416,15 +1516,14 @@ self.mech_driver.update_subnet_postcommit(context) esd.assert_called_once_with( context.current, context.network.current, mock.ANY) - umd.assert_called_once_with(mock.ANY, 'id') + umd.assert_called_once_with(mock.ANY, 'id', subnet=subnet) def test_update_subnet_postcommit_disable_dhcp(self): self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = { 'subnet': mock.sentinel.subnet, 'ports': []} - context = fakes.FakeSubnetContext( - subnet={'enable_dhcp': False, 'id': 'fake_id', 'ip_version': 4, - 'network_id': 'id'}, - network={'id': 'id'}) + subnet = {'enable_dhcp': False, 'id': 'subnet_id', 'ip_version': 4, + 'network_id': 'id'} + context = fakes.FakeSubnetContext(subnet=subnet, network={'id': 'id'}) with mock.patch.object( self.mech_driver._ovn_client, '_remove_subnet_dhcp_options') as dsd,\ @@ -1433,15 +1532,14 @@ 'update_metadata_port') as umd: self.mech_driver.update_subnet_postcommit(context) dsd.assert_called_once_with(context.current['id'], mock.ANY) - umd.assert_called_once_with(mock.ANY, 'id') + umd.assert_called_once_with(mock.ANY, 'id', subnet=subnet) def test_update_subnet_postcommit_update_dhcp(self): self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = { 'subnet': mock.sentinel.subnet, 'ports': []} - context = fakes.FakeSubnetContext( - subnet={'enable_dhcp': True, 'ip_version': 4, 'network_id': 'id', - 'id': 'subnet_id'}, - network={'id': 'id'}) + subnet = {'enable_dhcp': True, 'id': 'subnet_id', 'ip_version': 4, + 'network_id': 'id'} + context = fakes.FakeSubnetContext(subnet=subnet, network={'id': 'id'}) with mock.patch.object( self.mech_driver._ovn_client, '_update_subnet_dhcp_options') as usd,\ @@ -1451,7 +1549,112 @@ self.mech_driver.update_subnet_postcommit(context) usd.assert_called_once_with( context.current, context.network.current, mock.ANY) - umd.assert_called_once_with(mock.ANY, 'id') + umd.assert_called_once_with(mock.ANY, 'id', subnet=subnet) + + def test_update_metadata_port_with_subnet(self): + ovn_conf.cfg.CONF.set_override('ovn_metadata_enabled', True, + group='ovn') + + with mock.patch.object( + self.mech_driver._ovn_client, '_find_metadata_port') as \ + mock_metaport, \ + mock.patch.object(self.mech_driver._plugin, 'get_subnets') as \ + mock_get_subnets, \ + mock.patch.object(self.mech_driver._plugin, 'update_port') as \ + mock_update_port: + # Subnet with DHCP, present in port. + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + mock_metaport.return_value = {'fixed_ips': fixed_ips, + 'id': 'metadata_id'} + mock_get_subnets.return_value = [{'id': 'subnet1'}] + subnet = {'id': 'subnet1', 'enable_dhcp': True} + self.mech_driver._ovn_client.update_metadata_port( + self.context, 'net_id', subnet=subnet) + mock_update_port.assert_not_called() + + # Subnet without DHCP, present in port. + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + mock_metaport.return_value = {'fixed_ips': fixed_ips, + 'id': 'metadata_id'} + mock_get_subnets.return_value = [{'id': 'subnet1'}] + subnet = {'id': 'subnet1', 'enable_dhcp': False} + self.mech_driver._ovn_client.update_metadata_port( + self.context, 'net_id', subnet=subnet) + port = {'id': 'metadata_id', + 'port': {'network_id': 'net_id', 'fixed_ips': []}} + mock_update_port.assert_called_once_with(mock.ANY, 'metadata_id', + port) + mock_update_port.reset_mock() + + # Subnet with DHCP, not present in port. + mock_metaport.return_value = {'fixed_ips': [], + 'id': 'metadata_id'} + mock_get_subnets.return_value = [] + subnet = {'id': 'subnet1', 'enable_dhcp': True} + self.mech_driver._ovn_client.update_metadata_port( + self.context, 'net_id', subnet=subnet) + fixed_ips = [{'subnet_id': 'subnet1'}] + port = {'id': 'metadata_id', + 'port': {'network_id': 'net_id', 'fixed_ips': fixed_ips}} + mock_update_port.assert_called_once_with(mock.ANY, 'metadata_id', + port) + mock_update_port.reset_mock() + + # Subnet without DHCP, not present in port. + mock_metaport.return_value = {'fixed_ips': [], + 'id': 'metadata_id'} + mock_get_subnets.return_value = [] + subnet = {'id': 'subnet1', 'enable_dhcp': False} + self.mech_driver._ovn_client.update_metadata_port( + self.context, 'net_id', subnet=subnet) + mock_update_port.assert_not_called() + + def test_update_metadata_port_no_subnet(self): + ovn_conf.cfg.CONF.set_override('ovn_metadata_enabled', True, + group='ovn') + with mock.patch.object( + self.mech_driver._ovn_client, '_find_metadata_port') as \ + mock_metaport, \ + mock.patch.object(self.mech_driver._plugin, 'get_subnets') as \ + mock_get_subnets, \ + mock.patch.object(self.mech_driver._plugin, 'update_port') as \ + mock_update_port: + # Port with IP in subnet1; subnet1 and subnet2 with DHCP. + mock_get_subnets.return_value = [{'id': 'subnet1'}, + {'id': 'subnet2'}] + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + mock_metaport.return_value = {'fixed_ips': fixed_ips, + 'id': 'metadata_id'} + self.mech_driver._ovn_client.update_metadata_port(self.context, + 'net_id') + port = {'id': 'metadata_id', + 'port': {'network_id': 'net_id', 'fixed_ips': fixed_ips}} + fixed_ips.append({'subnet_id': 'subnet2'}) + mock_update_port.assert_called_once_with( + mock.ANY, 'metadata_id', port) + mock_update_port.reset_mock() + + # Port with IP in subnet1; subnet1 with DHCP, subnet2 without DHCP. + mock_get_subnets.return_value = [{'id': 'subnet1'}] + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + mock_metaport.return_value = {'fixed_ips': fixed_ips, + 'id': 'metadata_id'} + self.mech_driver._ovn_client.update_metadata_port(self.context, + 'net_id') + mock_update_port.assert_not_called() + + # Port with IP in subnet1; subnet1 without DHCP. + mock_get_subnets.return_value = [] + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + mock_metaport.return_value = {'fixed_ips': fixed_ips, + 'id': 'metadata_id'} + self.mech_driver._ovn_client.update_metadata_port(self.context, + 'net_id') + port = {'id': 'metadata_id', + 'port': {'network_id': 'net_id', 'fixed_ips': []}} + mock_update_port.assert_called_once_with( + mock.ANY, 'metadata_id', port) + mock_update_port.reset_mock() @mock.patch.object(provisioning_blocks, 'is_object_blocked') @mock.patch.object(provisioning_blocks, 'provisioning_complete') @@ -1527,25 +1730,31 @@ def _add_chassis_agent(self, nb_cfg, agent_type, updated_at=None): updated_at = updated_at or datetime.datetime.utcnow() - chassis = mock.Mock() - chassis.nb_cfg = nb_cfg - chassis.uuid = uuid.uuid4() - chassis.external_ids = {ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY: - datetime.datetime.isoformat(updated_at)} + chassis_private = mock.Mock() + chassis_private.nb_cfg = nb_cfg + chassis_private.uuid = uuid.uuid4() + chassis_private.external_ids = { + ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY: + datetime.datetime.isoformat(updated_at)} if agent_type == ovn_const.OVN_METADATA_AGENT: - chassis.external_ids.update({ + chassis_private.external_ids.update({ ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg, ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY: datetime.datetime.isoformat(updated_at)}) + chassis_private.chassis = [chassis_private] - return chassis + return neutron_agent.NeutronAgent.from_type( + agent_type, chassis_private) def test_agent_alive_true(self): for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_METADATA_AGENT): self.mech_driver._nb_ovn.nb_global.nb_cfg = 5 - chassis = self._add_chassis_agent(5, agent_type) - self.assertTrue(self.mech_driver.agent_alive(chassis, agent_type)) + agent = self._add_chassis_agent(5, agent_type) + self.assertTrue(self.mech_driver.agent_alive(agent, + update_db=True)) + # Assert that each Chassis has been updated in the SB database + self.assertEqual(2, self.sb_ovn.db_set.call_count) def test_agent_alive_true_one_diff(self): # Agent should be reported as alive when the nb_cfg delta is 1 @@ -1555,15 +1764,17 @@ self.mech_driver._nb_ovn.nb_global.nb_cfg = 5 now = timeutils.utcnow() updated_at = now - datetime.timedelta(cfg.CONF.agent_down_time + 1) - chassis = self._add_chassis_agent(4, agent_type, updated_at) - self.assertTrue(self.mech_driver.agent_alive(chassis, agent_type)) + agent = self._add_chassis_agent(4, agent_type, updated_at) + self.assertTrue(self.mech_driver.agent_alive(agent, + update_db=True)) def test_agent_alive_not_timed_out(self): for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_METADATA_AGENT): self.mech_driver._nb_ovn.nb_global.nb_cfg = 5 - chassis = self._add_chassis_agent(3, agent_type) - self.assertTrue(self.mech_driver.agent_alive(chassis, agent_type), + agent = self._add_chassis_agent(3, agent_type) + self.assertTrue(self.mech_driver.agent_alive( + agent, update_db=True), "Agent type %s is not alive" % agent_type) def test_agent_alive_timed_out(self): @@ -1572,8 +1783,18 @@ self.mech_driver._nb_ovn.nb_global.nb_cfg = 5 now = timeutils.utcnow() updated_at = now - datetime.timedelta(cfg.CONF.agent_down_time + 1) - chassis = self._add_chassis_agent(3, agent_type, updated_at) - self.assertFalse(self.mech_driver.agent_alive(chassis, agent_type)) + agent = self._add_chassis_agent(3, agent_type, updated_at) + self.assertFalse(self.mech_driver.agent_alive(agent, + update_db=True)) + + def test_agent_alive_true_skip_db_update(self): + for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, + ovn_const.OVN_METADATA_AGENT): + self.mech_driver._nb_ovn.nb_global.nb_cfg = 5 + agent = self._add_chassis_agent(5, agent_type) + self.assertTrue(self.mech_driver.agent_alive(agent, + update_db=False)) + self.sb_ovn.db_set.assert_not_called() def _test__update_dnat_entry_if_needed(self, up=True): ovn_conf.cfg.CONF.set_override( @@ -1583,7 +1804,8 @@ fake_nat_uuid = uuidutils.generate_uuid() nat_row = fakes.FakeOvsdbRow.create_one_ovsdb_row( attrs={'_uuid': fake_nat_uuid, 'external_ids': { - ovn_const.OVN_FIP_EXT_MAC_KEY: fake_ext_mac_key}}) + ovn_const.OVN_FIP_EXT_MAC_KEY: fake_ext_mac_key}, + 'external_mac': 'aa:aa:aa:aa:aa:aa'}) fake_db_find = mock.Mock() fake_db_find.execute.return_value = [nat_row] @@ -1643,12 +1865,12 @@ expected_opts = {} self._test_update_network_fragmentation(new_mtu, expected_opts) - def test_ping_chassis(self): + def test_ping_all_chassis(self): self.nb_ovn.nb_global.external_ids = {} - self.mech_driver.ping_chassis() + self.mech_driver.ping_all_chassis() self.nb_ovn.check_liveness.assert_called_once_with() - def test_ping_chassis_interval_expired(self): + def test_ping_all_chassis_interval_expired(self): timeout = 10 ovn_conf.cfg.CONF.set_override('agent_down_time', timeout) @@ -1658,12 +1880,14 @@ self.nb_ovn.nb_global.external_ids = { ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY: str(time)} + update_db = self.mech_driver.ping_all_chassis() # Since the interval has expired, assert that the "check_liveness" # command has been invoked - self.mech_driver.ping_chassis() self.nb_ovn.check_liveness.assert_called_once_with() + # Assert that ping_all_chassis returned True as it updated the db + self.assertTrue(update_db) - def test_ping_chassis_interval_not_expired(self): + def test_ping_all_chassis_interval_not_expired(self): ovn_conf.cfg.CONF.set_override('agent_down_time', 10) # Pretend the interval has NOT yet expired @@ -1671,9 +1895,48 @@ self.nb_ovn.nb_global.external_ids = { ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY: str(time)} + update_db = self.mech_driver.ping_all_chassis() # Assert that "check_liveness" wasn't invoked - self.mech_driver.ping_chassis() self.assertFalse(self.nb_ovn.check_liveness.called) + # Assert ping_all_chassis returned False as it didn't update the db + self.assertFalse(update_db) + + def test_get_candidates_for_scheduling_availability_zones(self): + ovn_client = self.mech_driver._ovn_client + ch0 = fakes.FakeChassis.create(az_list=['az0', 'az1'], + chassis_as_gw=True) + ch1 = fakes.FakeChassis.create(az_list=['az3', 'az4'], + chassis_as_gw=True) + ch2 = fakes.FakeChassis.create(az_list=['az2'], chassis_as_gw=True) + ch3 = fakes.FakeChassis.create(az_list=[], chassis_as_gw=True) + ch4 = fakes.FakeChassis.create(az_list=['az0'], chassis_as_gw=True) + ch5 = fakes.FakeChassis.create(az_list=['az2'], chassis_as_gw=False) + + # Fake ovsdbapp lookup + def fake_lookup(table, chassis_name, default): + for ch in [ch0, ch1, ch2, ch3, ch4, ch5]: + if ch.name == chassis_name: + return ch + ovn_client._sb_idl.lookup = fake_lookup + + # The target physnet and availability zones + physnet = 'public' + az_hints = ['az0', 'az2'] + + cms = [ch0.name, ch1.name, ch2.name, ch3.name, ch4.name, ch5.name] + ch_physnet = {ch0.name: [physnet], ch1.name: [physnet], + ch2.name: [physnet], ch3.name: [physnet], + ch4.name: ['another-physnet'], + ch5.name: ['yet-another-physnet']} + + candidates = ovn_client.get_candidates_for_scheduling( + physnet, cms=cms, chassis_physnets=ch_physnet, + availability_zone_hints=az_hints) + + # Only chassis ch0 and ch2 should match the availability zones + # hints and physnet we passed to get_candidates_for_scheduling() + expected_candidates = [ch0.name, ch2.name] + self.assertEqual(sorted(expected_candidates), sorted(candidates)) class OVNMechanismDriverTestCase(test_plugin.Ml2PluginV2TestCase): @@ -1705,6 +1968,7 @@ sb_ovn = fakes.FakeOvsdbSbOvnIdl() self.mech_driver._nb_ovn = nb_ovn self.mech_driver._sb_ovn = sb_ovn + self.mech_driver._ovn_client._qos_driver = mock.Mock() self.mech_driver._insert_port_provisioning_block = mock.Mock() p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1) p.start() @@ -1843,9 +2107,11 @@ sb_ovn = fakes.FakeOvsdbSbOvnIdl() self.mech_driver._nb_ovn = nb_ovn self.mech_driver._sb_ovn = sb_ovn + self.mech_driver._ovn_client._qos_driver = mock.Mock() p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1) p.start() self.addCleanup(p.stop) + self.context = context.get_admin_context() def _test_segment_host_mapping(self): # Disable the callback to update SegmentHostMapping by default, so @@ -1913,6 +2179,201 @@ segments_host_db2 = self._get_segments_for_host('hostname2') self.assertFalse(set(segments_host_db2)) + def test_create_segment_create_localnet_port(self): + ovn_nb_api = self.mech_driver._nb_ovn + with self.network() as network: + net = network['network'] + new_segment = self._test_create_segment( + network_id=net['id'], physical_network='phys_net1', + segmentation_id=200, network_type='vlan')['segment'] + ovn_nb_api.create_lswitch_port.assert_called_once_with( + addresses=[ovn_const.UNKNOWN_ADDR], + external_ids={}, + lport_name=ovn_utils.ovn_provnet_port_name(new_segment['id']), + lswitch_name=ovn_utils.ovn_name(net['id']), + options={'network_name': 'phys_net1', + ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true', + ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'}, + tag=200, + type='localnet') + ovn_nb_api.create_lswitch_port.reset_mock() + new_segment = self._test_create_segment( + network_id=net['id'], physical_network='phys_net2', + segmentation_id=300, network_type='vlan')['segment'] + ovn_nb_api.create_lswitch_port.assert_called_once_with( + addresses=[ovn_const.UNKNOWN_ADDR], + external_ids={}, + lport_name=ovn_utils.ovn_provnet_port_name(new_segment['id']), + lswitch_name=ovn_utils.ovn_name(net['id']), + options={'network_name': 'phys_net2', + ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true', + ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'}, + tag=300, + type='localnet') + segments = segments_db.get_network_segments( + self.context, net['id']) + self.assertEqual(len(segments), 3) + + def test_delete_segment_delete_localnet_port(self): + ovn_nb_api = self.mech_driver._nb_ovn + with self.network() as network: + net = network['network'] + segment = self._test_create_segment( + network_id=net['id'], physical_network='phys_net1', + segmentation_id=200, network_type='vlan')['segment'] + self._delete('segments', segment['id']) + ovn_nb_api.delete_lswitch_port.assert_called_once_with( + lport_name=ovn_utils.ovn_provnet_port_name(segment['id']), + lswitch_name=ovn_utils.ovn_name(net['id'])) + + def test_delete_segment_delete_localnet_port_compat_name(self): + ovn_nb_api = self.mech_driver._nb_ovn + with self.network() as network: + net = network['network'] + seg_1 = self._test_create_segment( + network_id=net['id'], physical_network='phys_net1', + segmentation_id=200, network_type='vlan')['segment'] + seg_2 = self._test_create_segment( + network_id=net['id'], physical_network='phys_net2', + segmentation_id=300, network_type='vlan')['segment'] + # Lets pretend that segment_1 is old and its localnet + # port is based on neutron network id. + ovn_nb_api.fake_ls_row.ports = [ + fakes.FakeOVNPort.create_one_port( + attrs={ + 'options': {'network_name': 'phys_net1'}, + 'tag': 200, + 'name': ovn_utils.ovn_provnet_port_name(net['id'])}), + fakes.FakeOVNPort.create_one_port( + attrs={ + 'options': {'network_name': 'phys_net2'}, + 'tag': 300, + 'name': ovn_utils.ovn_provnet_port_name(seg_2['id'])})] + self._delete('segments', seg_1['id']) + ovn_nb_api.delete_lswitch_port.assert_called_once_with( + lport_name=ovn_utils.ovn_provnet_port_name(net['id']), + lswitch_name=ovn_utils.ovn_name(net['id'])) + ovn_nb_api.delete_lswitch_port.reset_mock() + self._delete('segments', seg_2['id']) + ovn_nb_api.delete_lswitch_port.assert_called_once_with( + lport_name=ovn_utils.ovn_provnet_port_name(seg_2['id']), + lswitch_name=ovn_utils.ovn_name(net['id'])) + + def _test_segments_helper(self): + self.mech_driver.nb_ovn.get_subnet_dhcp_options.return_value = { + 'subnet': {'uuid': 'foo-uuid', + 'options': {'server_mac': 'ca:fe:ca:fe:ca:fe'}, + 'cidr': '1.2.3.4/5'}, + 'ports': {}} + ovn_conf.cfg.CONF.set_override('ovn_metadata_enabled', True, + group='ovn') + + # Create first segment and associate subnet to it. + with self.network() as n: + self.net = n + self.seg_1 = self._test_create_segment( + network_id=self.net['network']['id'], physical_network='phys_net1', + segmentation_id=200, network_type='vlan')['segment'] + with self.subnet(network=self.net, cidr='10.0.1.0/24', + segment_id=self.seg_1['id']) as subnet: + self.sub_1 = subnet + + # Create second segment and subnet linked to it + self.seg_2 = self._test_create_segment( + network_id=self.net['network']['id'], physical_network='phys_net2', + segmentation_id=300, network_type='vlan')['segment'] + with self.subnet(network=self.net, cidr='10.0.2.0/24', + segment_id=self.seg_2['id']) as subnet: + self.sub_2 = subnet + + def test_create_segments_subnet_metadata_ip_allocation(self): + self._test_segments_helper() + ovn_nb_api = self.mech_driver._nb_ovn + + # Assert that metadata address has been allocated from previously + # created subnet. + self.assertIn( + '10.0.1.2', + ovn_nb_api.set_lswitch_port.call_args_list[0][1]['addresses'][0]) + + # Assert that the second subnet address also has been allocated for + # metadata port. + self.assertIn( + '10.0.2.2', + ovn_nb_api.set_lswitch_port.call_args_list[1][1]['addresses'][0]) + # Assert also that the first subnet address is in this update + self.assertIn( + '10.0.1.2', + ovn_nb_api.set_lswitch_port.call_args_list[1][1]['addresses'][0]) + self.assertEqual( + ovn_nb_api.set_lswitch_port.call_count, 2) + + # Make sure both updates where on same metadata port + args_list = ovn_nb_api.set_lswitch_port.call_args_list + self.assertEqual( + 'ovnmeta-%s' % self.net['network']['id'], + args_list[1][1]['external_ids']['neutron:device_id']) + self.assertEqual( + args_list[1][1]['external_ids']['neutron:device_id'], + args_list[0][1]['external_ids']['neutron:device_id']) + self.assertEqual( + args_list[1][1]['external_ids']['neutron:device_owner'], + args_list[0][1]['external_ids']['neutron:device_owner']) + self.assertEqual( + const.DEVICE_OWNER_DHCP, + args_list[1][1]['external_ids']['neutron:device_owner']) + + def test_create_segments_mixed_allocation_prohibited(self): + self._test_segments_helper() + + # Try to create 'normal' port with ip address + # allocations from multiple segments + kwargs = {'fixed_ips': [{'ip_address': '10.0.1.100', + 'subnet_id': self.sub_1['subnet']['id']}, + {'ip_address': '10.0.2.100', + 'subnet_id': self.sub_2['subnet']['id']}]} + + # Make sure that this allocation is prohibited. + self._create_port( + self.fmt, self.net['network']['id'], + arg_list=('fixed_ips',), **kwargs, + expected_res_status=400) + + def test_create_delete_segment_distributed_service_port_not_touched(self): + self._test_segments_helper() + ovn_nb_api = self.mech_driver._nb_ovn + + # Delete second subnet + self._delete('subnets', self.sub_2['subnet']['id']) + # Make sure that shared metadata port wasn't deleted. + ovn_nb_api.delete_lswitch_port.assert_not_called() + + # Delete first subnet + self._delete('subnets', self.sub_1['subnet']['id']) + # Make sure that the metadata port wasn't deleted. + ovn_nb_api.delete_lswitch_port.assert_not_called() + + # Delete both segments + self._delete('segments', self.seg_2['id']) + self._delete('segments', self.seg_1['id']) + + # Make sure that the metadata port wasn't deleted. + deleted_ports = [ + port[1]['lport_name'] + for port in ovn_nb_api.delete_lswitch_port.call_args_list] + self.assertNotIn( + 'ovnmeta-%s' % self.net['network']['id'], + deleted_ports) + self.assertEqual( + 2, + ovn_nb_api.delete_lswitch_port.call_count) + + # Only on network deletion the metadata port is deleted. + self._delete('networks', self.net['network']['id']) + self.assertEqual( + 3, + ovn_nb_api.delete_lswitch_port.call_count) + @mock.patch.object(n_net, 'get_random_mac', lambda *_: '01:02:03:04:05:06') class TestOVNMechanismDriverDHCPOptions(OVNMechanismDriverTestCase): @@ -2452,6 +2913,7 @@ sb_ovn = fakes.FakeOvsdbSbOvnIdl() self.mech_driver._nb_ovn = nb_ovn self.mech_driver._sb_ovn = sb_ovn + self.mech_driver._ovn_client._qos_driver = mock.Mock() self.ctx = context.get_admin_context() revision_plugin.RevisionPlugin() @@ -2488,8 +2950,7 @@ def _delete_sg_rule(self, rule_id): self._delete('security-group-rules', rule_id) - def test_create_security_group_with_port_group(self): - self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True + def test_create_security_group(self): sg = self._create_sg('sg') expected_pg_name = ovn_utils.ovn_port_group_name(sg['id']) @@ -2501,8 +2962,7 @@ self.mech_driver._nb_ovn.pg_add.assert_has_calls( expected_pg_add_calls) - def test_delete_security_group_with_port_group(self): - self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True + def test_delete_security_group(self): sg = self._create_sg('sg') self._delete('security-groups', sg['id']) @@ -2513,8 +2973,7 @@ self.mech_driver._nb_ovn.pg_del.assert_has_calls( expected_pg_del_calls) - def test_create_port_with_port_group(self): - self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True + def test_create_port(self): with self.network() as n, self.subnet(n): sg = self._create_empty_sg('sg') self._make_port(self.fmt, n['network']['id'], @@ -2534,61 +2993,58 @@ def test_create_port_with_sg_default_rules(self): with self.network() as n, self.subnet(n): + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() sg = self._create_sg('sg') self._make_port(self.fmt, n['network']['id'], security_groups=[sg['id']]) - - # One DHCP rule, one IPv6 rule, one IPv4 rule and - # two default dropping rules. + # egress traffic for ipv4 and ipv6 is allowed by default self.assertEqual( - 5, self.mech_driver._nb_ovn.add_acl.call_count) + 2, self.mech_driver._nb_ovn.pg_acl_add.call_count) def test_create_port_with_empty_sg(self): with self.network() as n, self.subnet(n): + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() sg = self._create_empty_sg('sg') + # Implicit egress rules for ipv4 and ipv6 + self.assertEqual(2, self.mech_driver._nb_ovn.pg_acl_add.call_count) + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() + self.mech_driver._nb_ovn.pg_add.reset_mock() + self.mech_driver._nb_ovn.pg_add_ports.reset_mock() + self._make_port(self.fmt, n['network']['id'], security_groups=[sg['id']]) - # One DHCP rule and two default dropping rules. - self.assertEqual( - 3, self.mech_driver._nb_ovn.add_acl.call_count) + self.assertFalse(self.mech_driver._nb_ovn.pg_acl_add.called) + self.assertFalse(self.mech_driver._nb_ovn.pg_add.called) + self.assertEqual(1, self.mech_driver._nb_ovn.pg_add_ports.called) - def test_create_port_with_multi_sgs(self): + def test_create_port_with_multi_sgs_duplicate_rules(self): with self.network() as n, self.subnet(n): + self.mech_driver._nb_ovn.pg_add.reset_mock() sg1 = self._create_empty_sg('sg1') sg2 = self._create_empty_sg('sg2') - self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP, - port_range_min=22, port_range_max=23) - self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_UDP, - remote_ip_prefix='0.0.0.0/0') - self._make_port(self.fmt, n['network']['id'], - security_groups=[sg1['id'], sg2['id']]) - - # One DHCP rule, one TCP rule, one UDP rule and - # two default dropping rules. self.assertEqual( - 5, self.mech_driver._nb_ovn.add_acl.call_count) + 2, self.mech_driver._nb_ovn.pg_add.call_count) - def test_create_port_with_multi_sgs_duplicate_rules(self): - with self.network() as n, self.subnet(n): - sg1 = self._create_empty_sg('sg1') - sg2 = self._create_empty_sg('sg2') + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP, port_range_min=22, port_range_max=23, remote_ip_prefix='20.0.0.0/24') self._create_sg_rule(sg2['id'], 'ingress', const.PROTO_NAME_TCP, port_range_min=22, port_range_max=23, remote_ip_prefix='20.0.0.0/24') + self.assertEqual( + 2, self.mech_driver._nb_ovn.pg_acl_add.call_count) + self._make_port(self.fmt, n['network']['id'], security_groups=[sg1['id'], sg2['id']]) - - # One DHCP rule, two TCP rule and two default dropping rules. + # Default drop group, two security groups self.assertEqual( - 5, self.mech_driver._nb_ovn.add_acl.call_count) + 3, self.mech_driver._nb_ovn.pg_add_ports.call_count) @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' 'ovn_client.OVNClient.is_external_ports_supported', lambda *_: True) - def test_create_port_with_vnic_direct(self): + def _test_create_port_with_vnic_type(self, vnic_type): fake_grp = 'fake-default-ha-group-uuid' row = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={'uuid': fake_grp}) self.mech_driver._nb_ovn.ha_chassis_group_get.return_value.\ @@ -2598,7 +3054,7 @@ self._create_port( self.fmt, n['network']['id'], arg_list=(portbindings.VNIC_TYPE,), - **{portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT}) + **{portbindings.VNIC_TYPE: vnic_type}) # Assert create_lswitch_port was called with the relevant # parameters @@ -2608,6 +3064,17 @@ self.assertEqual(ovn_const.LSP_TYPE_EXTERNAL, kwargs['type']) self.assertEqual(fake_grp, kwargs['ha_chassis_group']) + def test_create_port_with_vnic_direct(self): + self._test_create_port_with_vnic_type(portbindings.VNIC_DIRECT) + + def test_create_port_with_vnic_direct_physical(self): + self._test_create_port_with_vnic_type( + portbindings.VNIC_DIRECT_PHYSICAL) + + def test_create_port_with_vnic_macvtap(self): + self._test_create_port_with_vnic_type( + portbindings.VNIC_MACVTAP) + def test_update_port_with_sgs(self): with self.network() as n, self.subnet(n): sg1 = self._create_empty_sg('sg1') @@ -2616,18 +3083,18 @@ p = self._make_port(self.fmt, n['network']['id'], security_groups=[sg1['id']])['port'] - # One DHCP rule, one TCP rule and two default dropping rules. - self.assertEqual( - 4, self.mech_driver._nb_ovn.add_acl.call_count) sg2 = self._create_empty_sg('sg2') self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_UDP, remote_ip_prefix='30.0.0.0/24') data = {'port': {'security_groups': [sg1['id'], sg2['id']]}} + + self.mech_driver._nb_ovn.pg_add_ports.reset_mock() req = self.new_update_request('ports', data, p['id']) req.get_response(self.api) + self.assertEqual( - 1, self.mech_driver._nb_ovn.update_acls.call_count) + 2, self.mech_driver._nb_ovn.pg_add_ports.call_count) def test_update_sg_change_rule(self): with self.network() as n, self.subnet(n): @@ -2635,39 +3102,18 @@ self._make_port(self.fmt, n['network']['id'], security_groups=[sg['id']]) - # One DHCP rule and two default dropping rules. - self.assertEqual( - 3, self.mech_driver._nb_ovn.add_acl.call_count) + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() sg_r = self._create_sg_rule(sg['id'], 'ingress', const.PROTO_NAME_UDP, ethertype=const.IPv6) self.assertEqual( - 1, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_add.call_count) + self.mech_driver._nb_ovn.pg_acl_del.reset_mock() self._delete_sg_rule(sg_r['id']) self.assertEqual( - 2, self.mech_driver._nb_ovn.update_acls.call_count) - - def test_update_sg_change_rule_unrelated_port(self): - with self.network() as n, self.subnet(n): - sg1 = self._create_empty_sg('sg1') - sg2 = self._create_empty_sg('sg2') - self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP, - remote_group_id=sg2['id']) - - self._make_port(self.fmt, n['network']['id'], - security_groups=[sg1['id']]) - # One DHCP rule, one TCP rule and two default dropping rules. - self.assertEqual( - 4, self.mech_driver._nb_ovn.add_acl.call_count) - - sg2_r = self._create_sg_rule(sg2['id'], 'egress', - const.PROTO_NAME_UDP) - self.mech_driver._nb_ovn.update_acls.assert_not_called() - - self._delete_sg_rule(sg2_r['id']) - self.mech_driver._nb_ovn.update_acls.assert_not_called() + 1, self.mech_driver._nb_ovn.pg_acl_del.call_count) def test_update_sg_duplicate_rule(self): with self.network() as n, self.subnet(n): @@ -2678,27 +3124,31 @@ port_range_min=22, port_range_max=23) self._make_port(self.fmt, n['network']['id'], security_groups=[sg1['id'], sg2['id']]) - # One DHCP rule, one UDP rule and two default dropping rules. + # One default drop rule, two SGs self.assertEqual( - 4, self.mech_driver._nb_ovn.add_acl.call_count) + 3, self.mech_driver._nb_ovn.pg_add_ports.call_count) + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() # Add a new duplicate rule to sg2. It's expected to be added. sg2_r = self._create_sg_rule(sg2['id'], 'ingress', const.PROTO_NAME_UDP, port_range_min=22, port_range_max=23) self.assertEqual( - 1, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_add.call_count) + self.mech_driver._nb_ovn.pg_acl_del.reset_mock() # Delete the duplicate rule. It's expected to be deleted. self._delete_sg_rule(sg2_r['id']) self.assertEqual( - 2, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_del.call_count) def test_update_sg_duplicate_rule_multi_ports(self): with self.network() as n, self.subnet(n): sg1 = self._create_empty_sg('sg1') sg2 = self._create_empty_sg('sg2') sg3 = self._create_empty_sg('sg3') + + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_UDP, remote_group_id=sg3['id']) @@ -2711,34 +3161,56 @@ security_groups=[sg1['id'], sg2['id']]) self._make_port(self.fmt, n['network']['id'], security_groups=[sg2['id'], sg3['id']]) - # Rules include 5 + 5 + 4 + + # No matter how many ports are there, there are two rules only self.assertEqual( - 14, self.mech_driver._nb_ovn.add_acl.call_count) + 2, self.mech_driver._nb_ovn.pg_acl_add.call_count) # Add a rule to sg1 duplicate with sg2. It's expected to be added. + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() sg1_r = self._create_sg_rule(sg1['id'], 'egress', const.PROTO_NAME_TCP, port_range_min=60, port_range_max=70) self.assertEqual( - 1, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_add.call_count) # Add a rule to sg2 duplicate with sg1 but not duplicate with sg3. # It's expected to be added as well. + self.mech_driver._nb_ovn.pg_acl_add.reset_mock() sg2_r = self._create_sg_rule(sg2['id'], 'ingress', const.PROTO_NAME_UDP, remote_group_id=sg3['id']) self.assertEqual( - 2, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_add.call_count) # Delete the duplicate rule in sg1. It's expected to be deleted. + self.mech_driver._nb_ovn.pg_acl_del.reset_mock() self._delete_sg_rule(sg1_r['id']) self.assertEqual( - 3, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_del.call_count) # Delete the duplicate rule in sg2. It's expected to be deleted. + self.mech_driver._nb_ovn.pg_acl_del.reset_mock() self._delete_sg_rule(sg2_r['id']) self.assertEqual( - 4, self.mech_driver._nb_ovn.update_acls.call_count) + 1, self.mech_driver._nb_ovn.pg_acl_del.call_count) + + def test_delete_port_with_security_groups_port_doesnt_remove_pg(self): + with self.network(set_context=True, tenant_id='test') as net1: + with self.subnet(network=net1): + sg = self._create_sg('sg') + port = self._make_port( + self.fmt, net1['network']['id'], + security_groups=[sg['id']])['port'] + fake_lsp = fakes.FakeOVNPort.from_neutron_port(port) + self.mech_driver._nb_ovn.lookup.return_value = fake_lsp + self.mech_driver._nb_ovn.delete_lswitch_port.reset_mock() + self.mech_driver._nb_ovn.delete_acl.reset_mock() + self._delete('ports', port['id']) + self.assertEqual( + 1, self.mech_driver._nb_ovn.delete_lswitch_port.call_count) + self.assertFalse(self.mech_driver._nb_ovn.pg_del.called) + self.assertFalse(self.mech_driver._nb_ovn.delete_acl.called) class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase): @@ -2751,6 +3223,7 @@ self.mech_driver = mm.mech_drivers['ovn'].obj self.mech_driver._nb_ovn = fakes.FakeOvsdbNbOvnIdl() self.mech_driver._sb_ovn = fakes.FakeOvsdbSbOvnIdl() + self.mech_driver._ovn_client._qos_driver = mock.Mock() self.nb_ovn = self.mech_driver._nb_ovn self.sb_ovn = self.mech_driver._sb_ovn self.ctx = context.get_admin_context() @@ -2807,6 +3280,8 @@ Check that the metadata port is updated with a new IP address when a subnet is created. """ + self.mech_driver.nb_ovn.get_subnet_dhcp_options.return_value = { + 'subnet': {}, 'ports': {}} with self.network(set_context=True, tenant_id='test') as net1: with self.subnet(network=net1, cidr='10.0.0.0/24') as subnet1: # Create a network:dhcp owner port just as how Neutron DHCP @@ -3032,3 +3507,38 @@ self.mech_driver._ovn_client.delete_port(self.context, parent['id']) self.nb_idl.unset_lswitch_port_to_virtual_type.assert_called_once_with( virt_port['id'], parent['id'], if_exists=True) + + +class TestOVNAvailabilityZone(OVNMechanismDriverTestCase): + + def setUp(self): + super(TestOVNAvailabilityZone, self).setUp() + self.context = context.get_admin_context() + self.sb_idl = self.mech_driver._ovn_client._sb_idl + + def test_list_availability_zones(self): + ch0 = fakes.FakeChassis.create(az_list=['az0', 'az1'], + chassis_as_gw=True) + ch1 = fakes.FakeChassis.create(az_list=[], chassis_as_gw=False) + ch2 = fakes.FakeChassis.create(az_list=['az2'], chassis_as_gw=True) + ch3 = fakes.FakeChassis.create(az_list=[], chassis_as_gw=True) + self.sb_idl.chassis_list.return_value.execute.return_value = [ + ch0, ch1, ch2, ch3] + + azs = self.mech_driver.list_availability_zones(self.context) + expected_azs = {'az0': {'name': 'az0', 'resource': 'router', + 'state': 'available', 'tenant_id': mock.ANY}, + 'az1': {'name': 'az1', 'resource': 'router', + 'state': 'available', 'tenant_id': mock.ANY}, + 'az2': {'name': 'az2', 'resource': 'router', + 'state': 'available', 'tenant_id': mock.ANY}} + self.assertEqual(expected_azs, azs) + + def test_list_availability_zones_no_azs(self): + ch0 = fakes.FakeChassis.create(az_list=[], chassis_as_gw=True) + ch1 = fakes.FakeChassis.create(az_list=[], chassis_as_gw=True) + self.sb_idl.chassis_list.return_value.execute.return_value = [ + ch0, ch1] + + azs = self.mech_driver.list_availability_zones(mock.Mock()) + self.assertEqual({}, azs) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/test_type_vlan.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/test_type_vlan.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/drivers/test_type_vlan.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/drivers/test_type_vlan.py 2021-11-12 13:56:42.000000000 +0000 @@ -251,10 +251,16 @@ observed = self.driver.reserve_provider_segment(self.context, segment) alloc = self._get_allocation(self.context, observed) self.assertTrue(alloc.allocated) - vlan_id = observed[api.SEGMENTATION_ID] - self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1)) - self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1)) - self.assertEqual(TENANT_NET, observed[api.PHYSICAL_NETWORK]) + if observed[api.PHYSICAL_NETWORK] == PROVIDER_NET: + self.assertIn(observed[api.SEGMENTATION_ID], + range(p_const.MIN_VLAN_TAG, + p_const.MAX_VLAN_TAG + 1)) + elif observed[api.PHYSICAL_NETWORK] == TENANT_NET: + self.assertIn(observed[api.SEGMENTATION_ID], + range(VLAN_MIN, VLAN_MAX + 1)) + else: + self.fail('The observed physical network %s does not match with ' + 'any configured' % [api.PHYSICAL_NETWORK]) def test_reserve_provider_segment_all_allocateds(self): for __ in range(VLAN_MIN, VLAN_MAX + 1): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/test_db.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/test_db.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/test_db.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/test_db.py 2021-11-12 13:56:42.000000000 +0000 @@ -22,6 +22,7 @@ from neutron_lib import constants from neutron_lib import context from neutron_lib.db import api as db_api +from neutron_lib.plugins import directory from neutron_lib.plugins.ml2 import api from oslo_utils import uuidutils from sqlalchemy.orm import exc @@ -34,6 +35,7 @@ from neutron.objects import ports as port_obj from neutron.plugins.ml2 import db as ml2_db from neutron.plugins.ml2 import models +from neutron.services.segments import exceptions as seg_exc from neutron.tests.unit import testlib_api @@ -300,6 +302,43 @@ for mac in macs: self.assertIsNotNone(re.search(mac_regex, mac)) + def test__prevent_segment_delete_with_port_bound_raise(self): + payload_mock = mock.Mock() + payload_mock.metadata.get.return_value = False + payload_mock.context = self.ctx + with mock.patch.object( + port_obj.Port, + 'get_auto_deletable_port_ids_and_proper_port_count_by_segment' + ) as mock_get: + mock_get.return_value = ([], 1) + self.assertRaises( + seg_exc.SegmentInUse, + ml2_db._prevent_segment_delete_with_port_bound, + resource=mock.Mock(), + event=mock.Mock(), + trigger=mock.Mock(), + payload=payload_mock, + ) + + def test__prevent_segment_delete_with_port_bound_auto_delete(self): + payload_mock = mock.Mock() + payload_mock.metadata.get.return_value = False + payload_mock.context = self.ctx + plugin = directory.get_plugin() + with mock.patch.object( + port_obj.Port, + 'get_auto_deletable_port_ids_and_proper_port_count_by_segment' + ) as mock_get, \ + mock.patch.object(plugin, 'delete_port') as mock_delete_port: + mock_get.return_value = (['fake-port'], 0) + ml2_db._prevent_segment_delete_with_port_bound( + resource=mock.Mock(), + event=mock.Mock(), + trigger=mock.Mock(), + payload=payload_mock, + ) + mock_delete_port.assert_called_with(mock.ANY, 'fake-port') + class Ml2DvrDBTestCase(testlib_api.SqlTestCase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/test_plugin.py neutron-16.4.2/neutron/tests/unit/plugins/ml2/test_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/plugins/ml2/test_plugin.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/plugins/ml2/test_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -698,7 +698,7 @@ kwargs = after_create.mock_calls[0][2] self.assertEqual(s['subnet']['id'], kwargs['subnet']['id']) - def test_port_update_subnetnotfound(self): + def test_port_create_subnetnotfound(self): with self.network() as n: with self.subnet(network=n, cidr='1.1.1.0/24') as s1,\ self.subnet(network=n, cidr='1.1.2.0/24') as s2,\ @@ -706,27 +706,23 @@ fixed_ips = [{'subnet_id': s1['subnet']['id']}, {'subnet_id': s2['subnet']['id']}, {'subnet_id': s3['subnet']['id']}] - with self.port(subnet=s1, fixed_ips=fixed_ips, - device_owner=constants.DEVICE_OWNER_DHCP) as p: - plugin = directory.get_plugin() - orig_update = plugin.update_port + plugin = directory.get_plugin() + origin_create_port_db = plugin.create_port_db - def delete_before_update(ctx, *args, **kwargs): - # swap back out with original so only called once - plugin.update_port = orig_update - # delete s2 in the middle of s1 port_update - plugin.delete_subnet(ctx, s2['subnet']['id']) - return plugin.update_port(ctx, *args, **kwargs) - plugin.update_port = delete_before_update - req = self.new_delete_request('subnets', - s1['subnet']['id']) - res = req.get_response(self.api) - self.assertEqual(204, res.status_int) - # ensure port only has 1 IP on s3 - port = self._show('ports', p['port']['id'])['port'] - self.assertEqual(1, len(port['fixed_ips'])) - self.assertEqual(s3['subnet']['id'], - port['fixed_ips'][0]['subnet_id']) + def create_port(ctx, port): + plugin.create_port_db = origin_create_port_db + second_ctx = context.get_admin_context() + with db_api.CONTEXT_WRITER.using(second_ctx): + setattr(second_ctx, 'GUARD_TRANSACTION', False) + plugin.delete_subnet(second_ctx, s2['subnet']['id']) + return plugin.create_port_db(ctx, port) + + plugin.create_port_db = create_port + res = self._create_port( + self.fmt, s2['subnet']['network_id'], fixed_ips=fixed_ips, + device_owner=constants.DEVICE_OWNER_DHCP, subnet=s1) + self.assertIn('Subnet %s could not be found.' + % s2['subnet']['id'], res.text) def test_update_subnet_with_empty_body(self): with self.subnet() as subnet: @@ -3286,23 +3282,21 @@ driver_mock().remove_subnet.assert_called_with(request.subnet_id) def test_delete_subnet_deallocates_slaac_correctly(self): - driver = 'neutron.ipam.drivers.neutrondb_ipam.driver.NeutronDbPool' + cidr = '2001:db8:100::0/64' with self.network() as network: - with self.subnet(network=network, - cidr='2001:100::0/64', + with self.subnet(network=network, cidr=cidr, ip_version=constants.IP_VERSION_6, ipv6_ra_mode=constants.IPV6_SLAAC) as subnet: with self.port(subnet=subnet) as port: - with mock.patch(driver) as driver_mock: - # Validate that deletion of SLAAC allocation happens - # via IPAM interface, i.e. ipam_subnet.deallocate is - # called prior to subnet deletiong from db. - self._delete('subnets', subnet['subnet']['id']) - dealloc = driver_mock().get_subnet().deallocate - dealloc.assert_called_with( - port['port']['fixed_ips'][0]['ip_address']) - driver_mock().remove_subnet.assert_called_with( - subnet['subnet']['id']) + ipallocs = port_obj.IPAllocation.get_objects(self.context) + self.assertEqual(1, len(ipallocs)) + self.assertEqual( + port['port']['fixed_ips'][0]['ip_address'], + str(ipallocs[0].ip_address)) + + self._delete('subnets', subnet['subnet']['id']) + ipallocs = port_obj.IPAllocation.get_objects(self.context) + self.assertEqual(0, len(ipallocs)) class TestTransactionGuard(Ml2PluginV2TestCase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py neutron-16.4.2/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py 2021-11-12 13:56:42.000000000 +0000 @@ -226,6 +226,39 @@ except OSError as e: self.assertEqual(errno.EINVAL, e.errno) + def _clean(self, client_mode): + priv_lib.privileged.default.client_mode = client_mode + + def test_get_link_vfs(self): + # NOTE(ralonsoh): there should be a functional test checking this + # method, but this is not possible due to the lack of SR-IOV capable + # NICs in the CI servers. + vf_info = [] + for idx in range(3): + vf_info.append(pyroute2.netlink.nlmsg_base()) + mac_info = {'mac': 'mac_%s' % idx, 'vf': idx} + link_state = {'link_state': idx} # see SR-IOV pci_lib.LinkState + vf_info[idx].setvalue( + {'attrs': [('IFLA_VF_MAC', mac_info), + ('IFLA_VF_LINK_STATE', link_state)]}) + vfinfo_list = pyroute2.netlink.nlmsg_base() + vfinfo_list.setvalue({'attrs': [('IFLA_VF_INFO', vf_info[0]), + ('IFLA_VF_INFO', vf_info[1]), + ('IFLA_VF_INFO', vf_info[2])]}) + value = pyroute2.netlink.nlmsg_base() + value.setvalue({'attrs': [('IFLA_NUM_VF', 3), + ('IFLA_VFINFO_LIST', vfinfo_list)]}) + client_mode = priv_lib.privileged.default.client_mode + priv_lib.privileged.default.client_mode = False + self.addCleanup(self._clean, client_mode) + with mock.patch.object(priv_lib, '_run_iproute_link') as mock_iplink: + mock_iplink.return_value = [value] + result = priv_lib.get_link_vfs('device', 'namespace') + self.assertEqual({0: {'mac': 'mac_0', 'link_state': 0}, + 1: {'mac': 'mac_1', 'link_state': 1}, + 2: {'mac': 'mac_2', 'link_state': 2}}, + result) + class MakeSerializableTestCase(base.BaseTestCase): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/privileged/agent/linux/test_utils.py neutron-16.4.2/neutron/tests/unit/privileged/agent/linux/test_utils.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/privileged/agent/linux/test_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/privileged/agent/linux/test_utils.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,76 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from oslo_concurrency import processutils + +from neutron.privileged.agent.linux import utils as priv_utils +from neutron.tests import base + + +NETSTAT_NETNS_OUTPUT = (""" +Active Internet connections (only servers) +Proto Recv-Q Send-Q Local Address Foreign Address State\ + PID/Program name +tcp 0 0 0.0.0.0:9697 0.0.0.0:* LISTEN\ + 1347/python +raw 0 0 0.0.0.0:112 0.0.0.0:* 7\ + 1279/keepalived +raw 0 0 0.0.0.0:112 0.0.0.0:* 7\ + 1279/keepalived +raw6 0 0 :::58 :::* 7\ + 1349/radvd +Active UNIX domain sockets (only servers) +Proto RefCnt Flags Type State I-Node PID/Program name\ + Path +unix 2 [ ACC ] STREAM LISTENING 82039530 1353/python\ + /tmp/rootwrap-VKSm8a/rootwrap.sock +""") + +NETSTAT_NO_NAMESPACE = (""" +Cannot open network namespace "qrouter-e6f206b2-4e8d-4597-a7e1-c3a20337e9c6":\ + No such file or directory +""") + +NETSTAT_NO_LISTEN_PROCS = (""" +Active Internet connections (only servers) +Proto Recv-Q Send-Q Local Address Foreign Address State\ + PID/Program name +Active UNIX domain sockets (only servers) +Proto RefCnt Flags Type State I-Node PID/Program name\ + Path +""") + + +class FindListenPidsNamespaceTestCase(base.BaseTestCase): + + def _test_find_listen_pids_namespace_helper(self, expected, + netstat_output=None): + with mock.patch.object(processutils, 'execute') as mock_execute: + mock_execute.return_value = (netstat_output, mock.ANY) + observed = priv_utils._find_listen_pids_namespace(mock.ANY) + self.assertEqual(sorted(expected), sorted(observed)) + + def test_find_listen_pids_namespace_correct_output(self): + expected = ['1347', '1279', '1349', '1353'] + self._test_find_listen_pids_namespace_helper(expected, + NETSTAT_NETNS_OUTPUT) + + def test_find_listen_pids_namespace_no_procs(self): + self._test_find_listen_pids_namespace_helper([], + NETSTAT_NO_LISTEN_PROCS) + + def test_find_listen_pids_namespace_no_namespace(self): + self._test_find_listen_pids_namespace_helper([], NETSTAT_NO_NAMESPACE) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/scheduler/test_base_scheduler.py neutron-16.4.2/neutron/tests/unit/scheduler/test_base_scheduler.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/scheduler/test_base_scheduler.py 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/scheduler/test_base_scheduler.py 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,52 @@ +# Copyright 2020 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +from neutron.scheduler import base_scheduler + +from neutron.tests import base + + +class GetVacantBindingFilterCase(base.BaseTestCase): + + def test_get_vacant_binding_index_no_agents(self): + ret = base_scheduler.get_vacant_binding_index(0, [], 1) + self.assertEqual(-1, ret) + + def test_get_vacant_binding_index_several_agents(self): + ret = base_scheduler.get_vacant_binding_index(1, [], 1) + self.assertEqual(1, ret) + + ret = base_scheduler.get_vacant_binding_index( + 1, [mock.Mock(binding_index=1)], 1) + self.assertEqual(-1, ret) + + ret = base_scheduler.get_vacant_binding_index( + 3, [mock.Mock(binding_index=1), mock.Mock(binding_index=3)], 1) + self.assertEqual(2, ret) + + def test_get_vacant_binding_index_force_scheduling(self): + ret = base_scheduler.get_vacant_binding_index( + 3, [mock.Mock(binding_index=1), mock.Mock(binding_index=2), + mock.Mock(binding_index=3), mock.Mock(binding_index=5), + mock.Mock(binding_index=7)], 1, force_scheduling=True) + self.assertEqual(4, ret) + + ret = base_scheduler.get_vacant_binding_index( + 3, [mock.Mock(binding_index=1), mock.Mock(binding_index=2), + mock.Mock(binding_index=3), mock.Mock(binding_index=4), + mock.Mock(binding_index=5)], 1, force_scheduling=True) + self.assertEqual(6, ret) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py neutron-16.4.2/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py 2021-11-12 13:56:42.000000000 +0000 @@ -841,12 +841,20 @@ 'device_owner': DEVICE_OWNER_COMPUTE, }, } + port = kwargs.get('original_port') l3plugin = mock.Mock() directory.add_plugin(plugin_constants.L3, l3plugin) l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', mock.ANY, **kwargs) l3plugin._get_allowed_address_pair_fixed_ips.return_value = ( ['10.1.0.21']) + self.assertFalse( + l3plugin.update_arp_entry_for_dvr_service_port.called) + l3plugin.delete_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, + port, + fixed_ips_to_delete=mock.ANY) def test__notify_l3_agent_update_port_with_allowed_address_pairs(self): port_id = uuidutils.generate_uuid() @@ -874,6 +882,8 @@ directory.add_plugin(plugin_constants.L3, l3plugin) l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', mock.ANY, **kwargs) + self.assertTrue( + l3plugin.update_arp_entry_for_dvr_service_port.called) def test__notify_l3_agent_when_unbound_port_migrates_to_bound_host(self): port_id = 'fake-port' @@ -930,6 +940,8 @@ l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', plugin, **kwargs) self.assertFalse( + l3plugin.update_arp_entry_for_dvr_service_port.called) + self.assertFalse( l3plugin.dvr_handle_new_service_port.called) self.assertFalse(l3plugin.remove_router_from_l3_agent.called) self.assertFalse(l3plugin.get_dvr_routers_to_remove.called) @@ -946,6 +958,9 @@ directory.add_plugin(plugin_constants.L3, l3plugin) l3_dvrscheduler_db._notify_l3_agent_new_port( 'port', 'after_create', mock.ANY, **kwargs) + l3plugin.update_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, kwargs.get('port')) l3plugin.dvr_handle_new_service_port.assert_called_once_with( self.adminContext, kwargs.get('port')) @@ -962,6 +977,8 @@ l3_dvrscheduler_db._notify_l3_agent_new_port( 'port', 'after_create', mock.ANY, **kwargs) self.assertFalse( + l3plugin.update_arp_entry_for_dvr_service_port.called) + self.assertFalse( l3plugin.dvr_handle_new_service_port.called) def test__notify_l3_agent_update_port_with_migration_port_profile(self): @@ -987,6 +1004,9 @@ l3plugin.dvr_handle_new_service_port.assert_called_once_with( self.adminContext, kwargs.get('port'), dest_host='vm-host2', router_id=None) + l3plugin.update_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, kwargs.get('port')) def test__notify_l3_agent_update_port_no_action(self): kwargs = { @@ -1006,6 +1026,8 @@ 'port', 'after_update', mock.ANY, **kwargs) self.assertFalse( + l3plugin.update_arp_entry_for_dvr_service_port.called) + self.assertFalse( l3plugin.dvr_handle_new_service_port.called) self.assertFalse(l3plugin.remove_router_from_l3_agent.called) self.assertFalse(l3plugin.get_dvr_routers_to_remove.called) @@ -1029,6 +1051,10 @@ directory.add_plugin(plugin_constants.L3, l3plugin) l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', mock.ANY, **kwargs) + + l3plugin.update_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, kwargs.get('port')) self.assertFalse(l3plugin.dvr_handle_new_service_port.called) def test__notify_l3_agent_update_port_with_ip_update(self): @@ -1053,6 +1079,9 @@ l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', mock.ANY, **kwargs) + l3plugin.update_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, kwargs.get('port')) self.assertFalse(l3plugin.dvr_handle_new_service_port.called) def test__notify_l3_agent_update_port_without_ip_change(self): @@ -1074,6 +1103,7 @@ l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', mock.ANY, **kwargs) + self.assertFalse(l3plugin.update_arp_entry_for_dvr_service_port.called) self.assertFalse(l3plugin.dvr_handle_new_service_port.called) def test__notify_l3_agent_port_binding_change(self): @@ -1159,10 +1189,15 @@ if routers_to_remove: (l3plugin.l3_rpc_notifier.router_removed_from_agent. assert_called_once_with(mock.ANY, 'foo_id', source_host)) + self.assertEqual( + 1, + l3plugin.delete_arp_entry_for_dvr_service_port.call_count) if fip and is_distributed and not (routers_to_remove and fip['router_id'] is routers_to_remove[0]['router_id']): (l3plugin.l3_rpc_notifier.routers_updated_on_host. assert_called_once_with(mock.ANY, ['router_id'], source_host)) + self.assertEqual( + 1, l3plugin.update_arp_entry_for_dvr_service_port.call_count) l3plugin.dvr_handle_new_service_port.assert_called_once_with( self.adminContext, kwargs.get('port'), dest_host=None, router_id=router_id) @@ -1203,6 +1238,12 @@ l3_dvrscheduler_db._notify_l3_agent_port_update( 'port', 'after_update', plugin, **kwargs) + self.assertEqual( + 1, l3plugin.delete_arp_entry_for_dvr_service_port.call_count) + l3plugin.delete_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, mock.ANY) + self.assertFalse( l3plugin.dvr_handle_new_service_port.called) (l3plugin.l3_rpc_notifier.router_removed_from_agent. @@ -1236,6 +1277,9 @@ l3plugin.get_dvr_routers_to_remove.return_value = removed_routers l3_dvrscheduler_db._notify_port_delete( 'port', 'after_delete', plugin, **kwargs) + l3plugin.delete_arp_entry_for_dvr_service_port.\ + assert_called_once_with( + self.adminContext, mock.ANY) (l3plugin.l3_rpc_notifier.router_removed_from_agent. assert_called_once_with(mock.ANY, 'foo_id', 'foo_host')) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py neutron-16.4.2/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py 2021-11-12 13:56:42.000000000 +0000 @@ -25,6 +25,7 @@ import testtools from neutron.services.l3_router.service_providers import driver_controller +from neutron.services.l3_router.service_providers import single_node from neutron.services import provider_configuration from neutron.tests import base from neutron.tests.unit import testlib_api @@ -114,6 +115,18 @@ states=({'flavor_id': 'old_fid'},))) mock_cb.assert_not_called() + def test___attrs_to_driver(self): + test_dc = driver_controller.DriverController(self.fake_l3) + test_dc.default_provider = 'single_node' + self.assertIsInstance(test_dc._attrs_to_driver({}), + single_node.SingleNodeDriver) + + test_dc.default_provider = 'ha' + with mock.patch.object(driver_controller, + "_is_driver_compatible", return_value=False): + self.assertRaises(NotImplementedError, test_dc._attrs_to_driver, + {}) + def test__update_router_provider_with_flags(self): test_dc = driver_controller.DriverController(self.fake_l3) with mock.patch.object(registry, "publish"): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/logapi/common/test_db_api.py neutron-16.4.2/neutron/tests/unit/services/logapi/common/test_db_api.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/logapi/common/test_db_api.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/logapi/common/test_db_api.py 2021-11-12 13:56:42.000000000 +0000 @@ -16,6 +16,7 @@ import mock from neutron_lib import constants as const from neutron_lib import context +from neutron_lib.db import api as n_db_api from neutron_lib.services.logapi import constants as log_const from neutron_lib.utils import net as net_utils from oslo_utils import uuidutils @@ -27,21 +28,24 @@ from neutron.tests.unit.extensions import test_securitygroup as test_sg -def _create_log(tenant_id, resource_id=None, +def _create_log(context, project_id, resource_id=None, target_id=None, event='ALL', enabled=True,): log_data = { 'id': uuidutils.generate_uuid(), 'name': 'test', 'resource_type': 'security_group', - 'project_id': tenant_id, + 'project_id': project_id, 'event': event, 'enabled': enabled} if resource_id: log_data['resource_id'] = resource_id if target_id: log_data['target_id'] = target_id - return log_object.Log(**log_data) + with n_db_api.CONTEXT_WRITER.using(context): + _log_obj = log_object.Log(context, **log_data) + _log_obj.create() + return _log_obj class LoggingDBApiTestCase(test_sg.SecurityGroupDBTestCase): @@ -49,8 +53,8 @@ def setUp(self): super(LoggingDBApiTestCase, self).setUp() self.context = context.get_admin_context() - self.sg_id, self.port_id, self.tenant_id = self._create_sg_and_port() - self.context.tenant_id = self.tenant_id + self.sg_id, self.port_id, self._tenant_id = self._create_sg_and_port() + self.context.tenant_id = self._tenant_id def _create_sg_and_port(self): with self.network() as network, \ @@ -67,44 +71,66 @@ return sg_id, port_id, tenant_id def test_get_logs_bound_port(self): - log = _create_log(target_id=self.port_id, tenant_id=self.tenant_id) + log = _create_log(self.context, self._tenant_id, + target_id=self.port_id) with mock.patch.object(log_object.Log, 'get_objects', return_value=[log]): self.assertEqual( [log], db_api.get_logs_bound_port(self.context, self.port_id)) # Test get log objects with required resource type - calls = [mock.call(self.context, project_id=self.tenant_id, + calls = [mock.call(self.context, project_id=self._tenant_id, resource_type=log_const.SECURITY_GROUP, enabled=True)] log_object.Log.get_objects.assert_has_calls(calls) def test_get_logs_not_bound_port(self): fake_sg_id = uuidutils.generate_uuid() - log = _create_log(resource_id=fake_sg_id, tenant_id=self.tenant_id) + log = _create_log(self.context, self._tenant_id, + resource_id=fake_sg_id) with mock.patch.object(log_object.Log, 'get_objects', return_value=[log]): self.assertEqual( [], db_api.get_logs_bound_port(self.context, self.port_id)) # Test get log objects with required resource type - calls = [mock.call(self.context, project_id=self.tenant_id, + calls = [mock.call(self.context, project_id=self._tenant_id, resource_type=log_const.SECURITY_GROUP, enabled=True)] log_object.Log.get_objects.assert_has_calls(calls) def test_get_logs_bound_sg(self): - log = _create_log(resource_id=self.sg_id, tenant_id=self.tenant_id) - with mock.patch.object(log_object.Log, 'get_objects', - return_value=[log]): + with self.network() as network, \ + self.subnet(network=network) as subnet, \ + self.port(subnet=subnet) as p1, \ + self.port(subnet=subnet, security_groups=[self.sg_id]) as p2: + + log = _create_log(self.context, self._tenant_id) + log_sg = _create_log(self.context, self._tenant_id, + resource_id=self.sg_id) + log_port_no_sg = _create_log(self.context, self._tenant_id, + target_id=p1['port']['id']) + log_port_sg = _create_log(self.context, self._tenant_id, + target_id=p2['port']['id']) self.assertEqual( - [log], db_api.get_logs_bound_sg(self.context, self.sg_id)) - - # Test get log objects with required resource type - calls = [mock.call(self.context, project_id=self.tenant_id, - resource_type=log_const.SECURITY_GROUP, - enabled=True)] - log_object.Log.get_objects.assert_has_calls(calls) + [log, log_sg, log_port_sg], + db_api.get_logs_bound_sg(self.context, sg_id=self.sg_id, + project_id=self._tenant_id)) + self.assertEqual( + [log_sg, log_port_sg], + db_api.get_logs_bound_sg(self.context, sg_id=self.sg_id, + project_id=self._tenant_id, + exclusive=True)) + self.assertEqual( + [log_port_no_sg], + db_api.get_logs_bound_sg( + self.context, project_id=self._tenant_id, + port_id=p1['port']['id'])) + self.assertEqual( + [log_port_sg], + db_api.get_logs_bound_sg( + self.context, project_id=self._tenant_id, + port_id=p2['port']['id'])) def test_get_logs_not_bound_sg(self): with self.network() as network, \ @@ -115,27 +141,28 @@ self.fmt, network['network']['id'], security_groups=[sg2_id]) port2_id = self.deserialize(self.fmt, res)['port']['id'] - log = _create_log(target_id=port2_id, tenant_id=self.tenant_id) + log = _create_log(self.context, self._tenant_id, + target_id=port2_id) with mock.patch.object(log_object.Log, 'get_objects', return_value=[log]): self.assertEqual( - [], db_api.get_logs_bound_sg(self.context, self.sg_id)) + [], db_api.get_logs_bound_sg( + self.context, self.sg_id, project_id=self._tenant_id)) # Test get log objects with required resource type - calls = [mock.call(self.context, project_id=self.tenant_id, + calls = [mock.call(self.context, project_id=self._tenant_id, resource_type=log_const.SECURITY_GROUP, enabled=True)] log_object.Log.get_objects.assert_has_calls(calls) def test__get_ports_being_logged(self): - log1 = _create_log(target_id=self.port_id, - tenant_id=self.tenant_id) - log2 = _create_log(resource_id=self.sg_id, - tenant_id=self.tenant_id) - log3 = _create_log(target_id=self.port_id, - resource_id=self.tenant_id, - tenant_id=self.tenant_id) - log4 = _create_log(tenant_id=self.tenant_id) + log1 = _create_log(self.context, self._tenant_id, + target_id=self.port_id) + log2 = _create_log(self.context, self._tenant_id, + resource_id=self.sg_id) + log3 = _create_log(self.context, self._tenant_id, + target_id=self.port_id, resource_id=self.sg_id) + log4 = _create_log(self.context, self._tenant_id) with mock.patch.object( validators, 'validate_log_type_for_port', return_value=True): ports_log1 = db_api._get_ports_being_logged(self.context, log1) @@ -149,7 +176,7 @@ self.assertEqual([self.port_id], ports_log4) def test__get_ports_being_logged_not_supported_log_type(self): - log = _create_log(tenant_id=self.tenant_id) + log = _create_log(self.context, self._tenant_id) with mock.patch.object( validators, 'validate_log_type_for_port', return_value=False): ports_log = db_api._get_ports_being_logged(self.context, log) @@ -187,7 +214,7 @@ security_groups=[sg_id]) ports_rest = self.deserialize(self.fmt, res) port_id = ports_rest['port']['id'] - log = _create_log(resource_id=sg_id, tenant_id=tenant_id) + log = _create_log(self.context, self._tenant_id, resource_id=sg_id) with mock.patch.object( server_rpc, 'get_rpc_method', @@ -259,7 +286,7 @@ ) ports_rest = self.deserialize(self.fmt, res) port_id = ports_rest['port']['id'] - log = _create_log(tenant_id=tenant_id) + log = _create_log(self.context, tenant_id) with mock.patch.object( log_object.Log, 'get_objects', return_value=[log]): with mock.patch.object( diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/ovn_l3/test_plugin.py neutron-16.4.2/neutron/tests/unit/services/ovn_l3/test_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/ovn_l3/test_plugin.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/ovn_l3/test_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -22,6 +22,8 @@ from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import exceptions as n_exc +from neutron_lib.exceptions import availability_zone as az_exc +from neutron_lib.exceptions import l3 as l3_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from oslo_config import cfg @@ -43,6 +45,7 @@ # Ml2PluginV2TestCase. class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): + _mechanism_drivers = ['ovn'] l3_plugin = 'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin' def _start_mock(self, path, return_value, new_callable=None): @@ -284,6 +287,23 @@ return_value=False) self.mock_is_lb_member_fip.start() + def test__plugin_driver(self): + # No valid mech drivers should raise an exception. + self._mechanism_drivers = None + self.l3_inst._plugin.mechanism_manager.mech_drivers = {} + self.l3_inst._mech = None + self.assertRaises(n_exc.NotFound, lambda: self.l3_inst._plugin_driver) + # Populate the mechanism driver map with keys the code under test looks + # for and validate it finds them. + fake_mech_driver = mock.MagicMock() + for driver in ('ovn', 'ovn-sync'): + self.l3_inst._plugin.mechanism_manager.mech_drivers[ + driver] = fake_mech_driver + result = self.l3_inst._plugin_driver + self.l3_inst._plugin.mechanism_manager.mech_drivers.pop( + driver, None) + self.assertEqual(fake_mech_driver.obj, result) + @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface') def test_add_router_interface(self, func): router_id = 'router-id' @@ -368,6 +388,23 @@ ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: utils.ovn_name(self.fake_network['id'])}) + def test_remove_router_interface_router_not_found(self): + router_id = 'router-id' + interface_info = {'port_id': 'router-port-id'} + self.get_port.side_effect = n_exc.PortNotFound( + port_id='router-port-id') + self.get_router.side_effect = l3_exc.RouterNotFound( + router_id='router-id') + + self.l3_inst.remove_router_interface( + self.context, router_id, interface_info) + + self.get_router.assert_called_once_with(self.context, 'router-id') + self.l3_inst._ovn.lrp_del.assert_called_once_with( + 'lrp-router-port-id', 'neutron-router-id', if_exists=True) + self.del_rev_p.assert_called_once_with( + self.context, 'router-port-id', ovn_const.TYPE_ROUTER_PORTS) + @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.' 'update_router') @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb' @@ -384,7 +421,8 @@ 'neutron-router-id', enabled=True, external_ids={ ovn_const.OVN_GW_PORT_EXT_ID_KEY: '', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', - ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router'}) + ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router', + ovn_const.OVN_ROUTER_AZ_HINTS_EXT_ID_KEY: ''}) @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.' 'update_router') @@ -402,7 +440,8 @@ 'neutron-router-id', enabled=False, external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', - ovn_const.OVN_GW_PORT_EXT_ID_KEY: ''}) + ovn_const.OVN_GW_PORT_EXT_ID_KEY: '', + ovn_const.OVN_ROUTER_AZ_HINTS_EXT_ID_KEY: ''}) @mock.patch.object(utils, 'get_lrouter_non_gw_routes') @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.update_router') @@ -495,10 +534,12 @@ external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', - ovn_const.OVN_GW_PORT_EXT_ID_KEY: 'gw-port-id'} + ovn_const.OVN_GW_PORT_EXT_ID_KEY: 'gw-port-id', + ovn_const.OVN_ROUTER_AZ_HINTS_EXT_ID_KEY: ''} self.l3_inst._ovn.create_lrouter.assert_called_once_with( 'neutron-router-id', external_ids=external_ids, - enabled=True, options={}) + enabled=True, options={'always_learn_from_arp_request': 'false', + 'dynamic_neigh_routers': 'true'}) self.l3_inst._ovn.add_lrouter_port.assert_called_once_with( **self.fake_ext_gw_port_assert) expected_calls = [ @@ -849,8 +890,6 @@ 'ovn_client.OVNClient._check_external_ips_changed') @mock.patch.object(utils, 'get_lrouter_snats') @mock.patch.object(utils, 'get_lrouter_ext_gw_static_route') - @mock.patch('neutron.common.ovn.utils.is_snat_enabled', - mock.Mock(return_value=True)) @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' 'ovn_client.OVNClient._get_router_ports') @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.' @@ -888,12 +927,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip['router_id'])} + self.fake_floating_ip['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10', external_ip='192.168.0.10', + logical_port='port_id', external_ids=expected_ext_ids) self.l3_inst._ovn.delete_lswitch_port.assert_called_once_with( 'fip-port-id', 'neutron-fip-net-id') @@ -959,12 +1000,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip['router_id'])} + self.fake_floating_ip['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10', external_ip='192.168.0.10', + logical_port='port_id', external_ids=expected_ext_ids) self.l3_inst._ovn.delete_lswitch_port.assert_called_once_with( 'fip-port-id', 'neutron-fip-net-id') @@ -983,12 +1026,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip['router_id'])} + self.fake_floating_ip['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10', external_ip='192.168.0.10', + logical_port='port_id', external_ids=expected_ext_ids) self.l3_inst._ovn.delete_lswitch_port.assert_called_once_with( 'fip-port-id', 'neutron-fip-net-id') @@ -1138,12 +1183,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip_new['router_id'])} + self.fake_floating_ip_new['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', logical_ip='10.10.10.10', external_ip='192.168.0.10', + logical_port='new-port_id', external_ids=expected_ext_ids) @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.' @@ -1160,12 +1207,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip_new['router_id'])} + self.fake_floating_ip_new['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', logical_ip='10.10.10.10', external_ip='192.168.0.10', + logical_port='new-port_id', external_ids=expected_ext_ids) @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_network') @@ -1221,12 +1270,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip_new['router_id'])} + self.fake_floating_ip_new['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', logical_ip='10.10.10.10', external_ip='192.168.0.10', + logical_port='foo', external_ids=expected_ext_ids) @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.' @@ -1252,12 +1303,14 @@ ovn_const.OVN_FIP_PORT_EXT_ID_KEY: self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( - self.fake_floating_ip_new['router_id'])} + self.fake_floating_ip_new['router_id']), + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', logical_ip='10.10.10.10', external_ip='192.168.0.10', + logical_port='port_id', external_ids=expected_ext_ids) @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_floatingips') @@ -1351,6 +1404,8 @@ self.l3_inst.schedule_unhosted_gateways() self.nb_idl().update_lrouter_port.assert_not_called() + @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.mech_driver.' + 'OVNMechanismDriver.list_availability_zones', lambda *_: []) @mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.' '_get_gateway_port_physnet_mapping') def test_schedule_unhosted_gateways(self, get_gppm): @@ -1389,7 +1444,7 @@ self.mock_candidates.assert_has_calls([ mock.call(mock.ANY, chassis_physnets=chassis_mappings, - cms=chassis)] * 3) + cms=chassis, availability_zone_hints=[])] * 3) self.mock_schedule.assert_has_calls([ mock.call(self.nb_idl(), self.sb_idl(), 'lrp-foo-1', [], ['chassis1', 'chassis2']), @@ -1473,6 +1528,55 @@ mock.ANY, self.fake_router_port, ovn_const.TYPE_ROUTER_PORTS) + def _test_get_router_availability_zones(self, azs, expected): + lr = fake_resources.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'id': 'fake-router', 'external_ids': { + ovn_const.OVN_ROUTER_AZ_HINTS_EXT_ID_KEY: azs}}) + self.l3_inst._ovn.get_lrouter.return_value = lr + azs_list = self.l3_inst.get_router_availability_zones(lr) + self.assertEqual(sorted(expected), sorted(azs_list)) + + def test_get_router_availability_zones_one(self): + self._test_get_router_availability_zones('az0', ['az0']) + + def test_get_router_availability_zones_multiple(self): + self._test_get_router_availability_zones( + 'az0,az1,az2', ['az0', 'az1', 'az2']) + + def test_get_router_availability_zones_none(self): + self._test_get_router_availability_zones('', []) + + @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.mech_driver.' + 'OVNMechanismDriver.list_availability_zones') + def test_validate_availability_zones(self, mock_list_azs): + mock_list_azs.return_value = {'az0': {'name': 'az0'}, + 'az1': {'name': 'az1'}, + 'az2': {'name': 'az2'}} + self.assertIsNone( + self.l3_inst.validate_availability_zones( + self.context, 'router', ['az0', 'az2'])) + + @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.mech_driver.' + 'OVNMechanismDriver.list_availability_zones') + def test_validate_availability_zones_fail_non_exist(self, mock_list_azs): + mock_list_azs.return_value = {'az0': {'name': 'az0'}, + 'az1': {'name': 'az1'}, + 'az2': {'name': 'az2'}} + # Fails validation if the az does not exist + self.assertRaises( + az_exc.AvailabilityZoneNotFound, + self.l3_inst.validate_availability_zones, self.context, 'router', + ['az0', 'non-existent']) + + @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.mech_driver.' + 'OVNMechanismDriver.list_availability_zones') + def test_validate_availability_zones_no_azs(self, mock_list_azs): + # When no AZs are requested validation should just succeed + self.assertIsNone( + self.l3_inst.validate_availability_zones( + self.context, 'router', [])) + mock_list_azs.assert_not_called() + class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase, test_l3.L3NatDBIntTestCase, diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/portforwarding/test_pf_plugin.py neutron-16.4.2/neutron/tests/unit/services/portforwarding/test_pf_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/portforwarding/test_pf_plugin.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/portforwarding/test_pf_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -15,6 +15,7 @@ import mock +from neutron_lib.callbacks import registry from neutron_lib import context from neutron_lib import exceptions as lib_exc from neutron_lib.exceptions import l3 as lib_l3_exc @@ -154,10 +155,14 @@ self.pf_plugin.delete_floatingip_port_forwarding, self.ctxt, 'pf_id', floatingip_id='fip_id') + @mock.patch.object(port_forwarding.PortForwarding, 'get_objects') + @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port') + @mock.patch.object(registry, 'notify') @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') @mock.patch.object(port_forwarding.PortForwarding, 'get_object') def test_update_floatingip_port_forwarding( - self, mock_pf_get_object, mock_rpc_push): + self, mock_pf_get_object, mock_rpc_push, mock_registry_notify, + mock_get_port, mock_pf_get_objects): pf_input = { 'port_forwarding': {'port_forwarding': { @@ -165,7 +170,12 @@ 'floatingip_id': 'fip_id'}}, 'floatingip_id': 'fip_id'} pf_obj = mock.Mock() + pf_obj.internal_ip_address = "10.0.0.1" mock_pf_get_object.return_value = pf_obj + port_dict = {'id': 'ID', 'fixed_ips': [{"subnet_id": "test-subnet-id", + "ip_address": "10.0.0.1"}]} + mock_get_port.return_value = port_dict + mock_pf_get_objects.return_value = [] self.pf_plugin.update_floatingip_port_forwarding( self.ctxt, 'pf_id', **pf_input) mock_pf_get_object.assert_called_once_with(self.ctxt, id='pf_id') @@ -174,6 +184,81 @@ mock_rpc_push.assert_called_once_with( self.ctxt, mock.ANY, rpc_events.UPDATED) + @mock.patch.object(port_forwarding.PortForwarding, 'get_objects') + @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port') + def test_check_port_forwarding_update(self, mock_get_port, + mock_pf_get_objects): + port_dict = {'fixed_ips': [{"ip_address": "10.0.0.1"}]} + mock_get_port.return_value = port_dict + other_pf_obj = mock.Mock(id='cmp_obj_id') + pf_obj = mock.Mock(id='pf_obj_id', internal_port_id='int_port_id', + internal_ip_address='10.0.0.1') + mock_pf_get_objects.return_value = [pf_obj, other_pf_obj] + self.pf_plugin._check_port_forwarding_update(self.ctxt, pf_obj) + mock_get_port.assert_called_once_with(self.ctxt, 'int_port_id') + mock_pf_get_objects.assert_called_once_with(self.ctxt, + floatingip_id=pf_obj.floatingip_id, protocol=pf_obj.protocol) + + @mock.patch.object(port_forwarding.PortForwarding, 'get_objects') + @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port') + def test_check_port_forwarding_update_invalid_address( + self, mock_get_port, mock_pf_get_objects): + port_dict = {'fixed_ips': [{"ip_address": "10.0.0.1"}]} + mock_get_port.return_value = port_dict + pf_obj = mock.Mock(id='pf_obj_id', internal_port_id='int_port_id', + internal_ip_address="99.99.99.99") + self.assertRaisesRegex( + lib_exc.BadRequest, + "not correspond to an address on the internal port", + self.pf_plugin._check_port_forwarding_update, + self.ctxt, pf_obj) + mock_get_port.assert_called_once_with(self.ctxt, 'int_port_id') + mock_pf_get_objects.assert_not_called() + + @mock.patch.object(port_forwarding.PortForwarding, 'get_objects') + @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port') + def test_check_port_forwarding_update_invalid_external( + self, mock_get_port, mock_pf_get_objects): + port_dict = {'fixed_ips': [{"ip_address": "10.0.0.1"}]} + mock_get_port.return_value = port_dict + pf_obj_dict = {'id': 'pf_obj_one', + 'floating_ip_address': 'same_fip_addr', + 'external_port': 'same_ext_port'} + other_pf_obj = mock.Mock(**pf_obj_dict) + pf_obj_dict.update(id='pf_obj_two', internal_ip_address='10.0.0.1') + pf_obj = mock.Mock(**pf_obj_dict) + mock_pf_get_objects.return_value = [pf_obj, other_pf_obj] + self.assertRaisesRegex( + lib_exc.BadRequest, + "already exist.*same_fip_addr", + self.pf_plugin._check_port_forwarding_update, + self.ctxt, pf_obj) + mock_get_port.assert_called_once_with(self.ctxt, mock.ANY) + mock_pf_get_objects.assert_called_once() + + @mock.patch.object(port_forwarding.PortForwarding, 'get_objects') + @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port') + def test_check_port_forwarding_update_invalid_internal( + self, mock_get_port, mock_pf_get_objects): + same_internal_ip = "10.0.0.10" + port_dict = {'fixed_ips': [{"ip_address": same_internal_ip}]} + mock_get_port.return_value = port_dict + pf_obj_dict = {'id': 'pf_obj_one', + 'internal_port_id': 'same_int_port_id', + 'internal_ip_address': same_internal_ip, + 'internal_port': 'same_int_port'} + other_pf_obj = mock.Mock(**pf_obj_dict) + pf_obj_dict.update(id='pf_obj_two') + pf_obj = mock.Mock(**pf_obj_dict) + mock_pf_get_objects.return_value = [pf_obj, other_pf_obj] + self.assertRaisesRegex( + lib_exc.BadRequest, + "already exist.*{}".format(same_internal_ip), + self.pf_plugin._check_port_forwarding_update, + self.ctxt, pf_obj) + mock_get_port.assert_called_once_with(self.ctxt, 'same_int_port_id') + mock_pf_get_objects.assert_called_once() + @mock.patch.object(port_forwarding.PortForwarding, 'get_object') def test_negative_update_floatingip_port_forwarding( self, mock_pf_get_object): diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/drivers/openvswitch/test_driver.py neutron-16.4.2/neutron/tests/unit/services/qos/drivers/openvswitch/test_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/drivers/openvswitch/test_driver.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/qos/drivers/openvswitch/test_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -43,3 +43,5 @@ return_value=net): test_method(self.driver.validate_rule_for_port( mock.Mock(), rule, port)) + test_method(self.driver.validate_rule_for_network( + mock.Mock(), rule, network_id=mock.Mock())) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/drivers/ovn/test_driver.py neutron-16.4.2/neutron/tests/unit/services/qos/drivers/ovn/test_driver.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/drivers/ovn/test_driver.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/qos/drivers/ovn/test_driver.py 2021-11-12 13:56:42.000000000 +0000 @@ -12,27 +12,22 @@ import mock -from neutron.objects.qos import policy as qos_policy -from neutron.objects.qos import rule as qos_rule from neutron.tests import base -from neutron_lib import constants -from oslo_utils import uuidutils -from neutron.common.ovn import utils from neutron.services.qos.drivers.ovn import driver + context = 'context' -class TestOVNQosNotificationDriver(base.BaseTestCase): +class TestOVNQosDriver(base.BaseTestCase): def setUp(self): - super(TestOVNQosNotificationDriver, self).setUp() + super(TestOVNQosDriver, self).setUp() self.mech_driver = mock.Mock() self.mech_driver._ovn_client = mock.Mock() self.mech_driver._ovn_client._qos_driver = mock.Mock() - self.driver = driver.OVNQosNotificationDriver.create( - self.mech_driver) + self.driver = driver.OVNQosDriver.create(self.mech_driver) self.policy = "policy" def test_create_policy(self): @@ -49,210 +44,3 @@ self.driver.delete_policy(context, self.policy) self.driver._driver._ovn_client._qos_driver.delete_policy.\ assert_not_called() - - -class TestOVNQosDriver(base.BaseTestCase): - - def setUp(self): - super(TestOVNQosDriver, self).setUp() - self.plugin = mock.Mock() - self.ovn_client = mock.Mock() - self.driver = driver.OVNQosDriver(self.ovn_client) - self.driver._plugin_property = self.plugin - self.port_id = uuidutils.generate_uuid() - self.policy_id = uuidutils.generate_uuid() - self.network_id = uuidutils.generate_uuid() - self.network_policy_id = uuidutils.generate_uuid() - self.policy = self._create_fake_policy() - self.port = self._create_fake_port() - self.bw_rule = self._create_bw_limit_rule() - self.bw_expected = {'qos_max_rate': 1000, 'qos_burst': 100000, - 'direction': constants.EGRESS_DIRECTION} - self.dscp_rule = self._create_dscp_rule() - self.dscp_expected = {'dscp_mark': 16, - 'direction': constants.EGRESS_DIRECTION} - - def _create_bw_limit_rule(self): - rule_obj = qos_rule.QosBandwidthLimitRule() - rule_obj.id = uuidutils.generate_uuid() - rule_obj.max_kbps = 1000 - rule_obj.max_burst_kbps = 100000 - rule_obj.obj_reset_changes() - return rule_obj - - def _create_dscp_rule(self): - rule_obj = qos_rule.QosDscpMarkingRule() - rule_obj.id = uuidutils.generate_uuid() - rule_obj.dscp_mark = 16 - rule_obj.obj_reset_changes() - return rule_obj - - def _create_fake_policy(self): - policy_dict = {'id': self.network_policy_id} - policy_obj = qos_policy.QosPolicy(context, **policy_dict) - policy_obj.obj_reset_changes() - return policy_obj - - def _create_fake_port(self): - return {'id': self.port_id, - 'qos_policy_id': self.policy_id, - 'network_id': self.network_id, - 'device_owner': 'compute:fake'} - - def _create_fake_network(self): - return {'id': self.network_id, - 'qos_policy_id': self.network_policy_id} - - def test__is_network_device_port(self): - self.assertFalse(utils.is_network_device_port(self.port)) - port = self._create_fake_port() - port['device_owner'] = constants.DEVICE_OWNER_DHCP - self.assertTrue(utils.is_network_device_port(port)) - port['device_owner'] = 'neutron:LOADBALANCERV2' - self.assertTrue(utils.is_network_device_port(port)) - - def _generate_port_options(self, policy_id, return_val, expected_result): - with mock.patch.object(qos_rule, 'get_rules', - return_value=return_val) as get_rules: - options = self.driver._generate_port_options(context, policy_id) - if policy_id: - get_rules.assert_called_once_with(qos_policy.QosPolicy, - context, policy_id) - else: - get_rules.assert_not_called() - self.assertEqual(expected_result, options) - - def test__generate_port_options_no_policy_id(self): - self._generate_port_options(None, [], {}) - - def test__generate_port_options_no_rules(self): - self._generate_port_options(self.policy_id, [], {}) - - def test__generate_port_options_with_bw_rule(self): - self._generate_port_options(self.policy_id, [self.bw_rule], - self.bw_expected) - - def test__generate_port_options_with_dscp_rule(self): - self._generate_port_options(self.policy_id, [self.dscp_rule], - self.dscp_expected) - - def _get_qos_options(self, port, port_policy, network_policy): - with mock.patch.object(qos_policy.QosPolicy, 'get_network_policy', - return_value=self.policy) as get_network_policy: - with mock.patch.object(self.driver, '_generate_port_options', - return_value={}) as generate_port_options: - options = self.driver.get_qos_options(port) - if network_policy: - get_network_policy.\ - assert_called_once_with(context, self.network_id) - generate_port_options. \ - assert_called_once_with(context, - self.network_policy_id) - elif port_policy: - get_network_policy.assert_not_called() - generate_port_options.\ - assert_called_once_with(context, self.policy_id) - else: - get_network_policy.assert_not_called() - generate_port_options.assert_not_called() - self.assertEqual({}, options) - - def test_get_qos_options_no_qos(self): - port = self._create_fake_port() - port.pop('qos_policy_id') - self._get_qos_options(port, False, False) - - def test_get_qos_options_network_port(self): - port = self._create_fake_port() - port['device_owner'] = constants.DEVICE_OWNER_DHCP - self._get_qos_options(port, False, False) - - @mock.patch('neutron_lib.context.get_admin_context', return_value=context) - def test_get_qos_options_port_policy(self, *mocks): - self._get_qos_options(self.port, True, False) - - @mock.patch('neutron_lib.context.get_admin_context', return_value=context) - def test_get_qos_options_network_policy(self, *mocks): - port = self._create_fake_port() - port['qos_policy_id'] = None - self._get_qos_options(port, False, True) - - def _update_network_ports(self, port, called): - with mock.patch.object(self.plugin, 'get_ports', - return_value=[port]) as get_ports: - with mock.patch.object(self.ovn_client, - 'update_port') as update_port: - self.driver._update_network_ports( - context, self.network_id, {}) - get_ports.assert_called_once_with( - context, filters={'network_id': [self.network_id]}) - if called: - update_port.assert_called() - else: - update_port.assert_not_called() - - def test__update_network_ports_port_policy(self): - self._update_network_ports(self.port, False) - - def test__update_network_ports_network_device(self): - port = self._create_fake_port() - port['device_owner'] = constants.DEVICE_OWNER_DHCP - self._update_network_ports(port, False) - - def test__update_network_ports(self): - port = self._create_fake_port() - port['qos_policy_id'] = None - self._update_network_ports(port, True) - - def _update_network(self, network, called): - with mock.patch.object(self.driver, '_generate_port_options', - return_value={}) as generate_port_options: - with mock.patch.object(self.driver, '_update_network_ports' - ) as update_network_ports: - self.driver.update_network(network) - if called: - generate_port_options.assert_called_once_with( - context, self.network_policy_id) - update_network_ports.assert_called_once_with( - context, self.network_id, {}) - else: - generate_port_options.assert_not_called() - update_network_ports.assert_not_called() - - @mock.patch('neutron_lib.context.get_admin_context', return_value=context) - def test_update_network_no_qos(self, *mocks): - network = self._create_fake_network() - network.pop('qos_policy_id') - self._update_network(network, False) - - @mock.patch('neutron_lib.context.get_admin_context', return_value=context) - def test_update_network_policy_change(self, *mocks): - network = self._create_fake_network() - self._update_network(network, True) - - def test_update_policy(self): - with mock.patch.object(self.driver, '_generate_port_options', - return_value={}) as generate_port_options, \ - mock.patch.object(self.policy, 'get_bound_networks', - return_value=[self.network_id] - ) as get_bound_networks, \ - mock.patch.object(self.driver, '_update_network_ports' - ) as update_network_ports, \ - mock.patch.object(self.policy, 'get_bound_ports', - return_value=[self.port_id] - ) as get_bound_ports, \ - mock.patch.object(self.plugin, 'get_port', - return_value=self.port) as get_port, \ - mock.patch.object(self.ovn_client, 'update_port', - ) as update_port: - - self.driver.update_policy(context, self.policy) - - generate_port_options.assert_called_once_with( - context, self.network_policy_id) - get_bound_networks.assert_called_once_with() - update_network_ports.assert_called_once_with( - context, self.network_id, {}) - get_bound_ports.assert_called_once_with() - get_port.assert_called_once_with(context, self.port_id) - update_port.assert_called_once_with(self.port, qos_options={}) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/drivers/test_manager.py neutron-16.4.2/neutron/tests/unit/services/qos/drivers/test_manager.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/drivers/test_manager.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/qos/drivers/test_manager.py 2021-11-12 13:56:42.000000000 +0000 @@ -118,6 +118,29 @@ else: is_rule_supported_mock.assert_not_called() + def test_validate_rule_for_network(self): + driver_manager = self._create_manager_with_drivers({ + 'driver-A': { + 'is_loaded': True, + 'rules': { + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: { + "min_kbps": {'type:values': None}, + 'direction': { + 'type:values': lib_consts.VALID_DIRECTIONS} + } + } + } + }) + rule = rule_object.QosMinimumBandwidthRule( + self.ctxt, id=uuidutils.generate_uuid()) + + is_rule_supported_mock = mock.Mock() + is_rule_supported_mock.return_value = True + driver_manager._drivers[0].is_rule_supported = is_rule_supported_mock + self.assertTrue(driver_manager.validate_rule_for_network( + mock.Mock(), rule, mock.Mock())) + is_rule_supported_mock.assert_called_once_with(rule) + def test_validate_rule_for_port_rule_vif_type_supported(self): port = self._get_port( portbindings.VIF_TYPE_OVS, portbindings.VNIC_NORMAL) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/test_qos_plugin.py neutron-16.4.2/neutron/tests/unit/services/qos/test_qos_plugin.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/qos/test_qos_plugin.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/qos/test_qos_plugin.py 2021-11-12 13:56:42.000000000 +0000 @@ -128,10 +128,8 @@ if has_qos_policy: self.port_data['port']['qos_policy_id'] = self.policy.id - self.policy.rules = bw_rules elif has_net_qos_policy: self.port_data['port']['qos_network_policy_id'] = self.policy.id - self.policy.rules = bw_rules self.port = ports_object.Port( self.ctxt, **self.port_data['port']) @@ -142,8 +140,10 @@ with mock.patch('neutron.objects.network.NetworkSegment.get_objects', return_value=[segment_mock]), \ - mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', - return_value=self.policy): + mock.patch( + 'neutron.objects.qos.rule.QosMinimumBandwidthRule.' + 'get_objects', + return_value=bw_rules): return qos_plugin.QoSPlugin._extend_port_resource_request( port_res, self.port) @@ -184,7 +184,7 @@ ) def test__extend_port_resource_request_non_min_bw_rule(self): - port = self._create_and_extend_port([self.rule]) + port = self._create_and_extend_port([]) self.assertIsNone(port.get('resource_request')) @@ -202,7 +202,6 @@ def test__extend_port_resource_request_inherited_policy(self): self.min_rule.direction = lib_constants.EGRESS_DIRECTION - self.policy.rules = [self.min_rule] self.min_rule.qos_policy_id = self.policy.id port = self._create_and_extend_port([self.min_rule], @@ -327,6 +326,8 @@ 'neutron.objects.qos.policy.QosPolicy.get_object', return_value=policy_mock ) as get_policy, mock.patch.object( + self.qos_plugin, "validate_policy_for_network" + ) as validate_policy_for_network, mock.patch.object( self.qos_plugin, "validate_policy_for_ports" ) as validate_policy_for_ports, mock.patch.object( self.ctxt, "elevated", return_value=admin_ctxt @@ -338,6 +339,7 @@ states=(kwargs['original_network'],))) if policy_id is None or policy_id == original_policy_id: get_policy.assert_not_called() + validate_policy_for_network.assert_not_called() get_ports.assert_not_called() validate_policy_for_ports.assert_not_called() else: @@ -385,6 +387,20 @@ except qos_exc.QosRuleNotSupported: self.fail("QosRuleNotSupported exception unexpectedly raised") + def test_validate_policy_for_network(self): + network = uuidutils.generate_uuid() + with mock.patch.object( + self.qos_plugin.driver_manager, "validate_rule_for_network", + return_value=True + ): + self.policy.rules = [self.rule] + try: + self.qos_plugin.validate_policy_for_network( + self.ctxt, self.policy, network_id=network) + except qos_exc.QosRuleNotSupportedByNetwork: + self.fail("QosRuleNotSupportedByNetwork " + "exception unexpectedly raised") + def test_create_min_bw_rule_on_bound_port(self): policy = self._get_policy() policy.rules = [self.min_rule] @@ -1237,6 +1253,35 @@ network.create() return network + def _test_validate_create_network_callback(self, network_qos=False): + net_qos_obj = self._make_qos_policy() + net_qos_id = net_qos_obj.id if network_qos else None + network = self._make_network(qos_policy_id=net_qos_id) + kwargs = {"context": self.context, + "network": network} + + with mock.patch.object(self.qos_plugin, + 'validate_policy_for_network') \ + as mock_validate_policy: + self.qos_plugin._validate_create_network_callback( + 'NETWORK', 'precommit_create', 'test_plugin', **kwargs) + + qos_policy = None + if network_qos: + qos_policy = net_qos_obj + + if qos_policy: + mock_validate_policy.assert_called_once_with( + self.context, qos_policy, network.id) + else: + mock_validate_policy.assert_not_called() + + def test_validate_create_network_callback(self): + self._test_validate_create_network_callback(network_qos=True) + + def test_validate_create_network_callback_no_qos(self): + self._test_validate_create_network_callback(network_qos=False) + def _test_validate_create_port_callback(self, port_qos=False, network_qos=False): net_qos_obj = self._make_qos_policy() diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/trunk/rpc/test_server.py neutron-16.4.2/neutron/tests/unit/services/trunk/rpc/test_server.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/services/trunk/rpc/test_server.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/services/trunk/rpc/test_server.py 2021-11-12 13:56:42.000000000 +0000 @@ -103,6 +103,43 @@ for port in updated_subports[trunk['id']]: self.assertEqual('trunk_host_id', port[portbindings.HOST_ID]) + def test_update_subport_bindings_during_migration(self): + with self.port() as _parent_port: + parent_port = _parent_port + trunk = self._create_test_trunk(parent_port) + subports = [] + for vid in range(0, 3): + with self.port() as new_port: + obj = trunk_obj.SubPort( + context=self.context, + trunk_id=trunk['id'], + port_id=new_port['port']['id'], + segmentation_type='vlan', + segmentation_id=vid) + subports.append(obj) + + expected_calls = [ + mock.call( + mock.ANY, subport['port_id'], + {'port': {portbindings.HOST_ID: 'new_trunk_host_id', + 'device_owner': constants.TRUNK_SUBPORT_OWNER}}) + for subport in subports] + + test_obj = server.TrunkSkeleton() + test_obj._trunk_plugin = self.trunk_plugin + test_obj._core_plugin = self.core_plugin + port_data = { + portbindings.HOST_ID: 'trunk_host_id', + portbindings.PROFILE: {'migrating_to': 'new_trunk_host_id'}} + with mock.patch.object( + self.core_plugin, "get_port", + return_value=port_data), \ + mock.patch.object( + test_obj, "_safe_update_trunk"): + test_obj.update_subport_bindings(self.context, subports=subports) + for expected_call in expected_calls: + self.assertIn(expected_call, self.mock_update_port.mock_calls) + def test__handle_port_binding_binding_error(self): with self.port() as _trunk_port: trunk = self._create_test_trunk(_trunk_port) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/test_opts.py neutron-16.4.2/neutron/tests/unit/test_opts.py --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron/tests/unit/test_opts.py 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/neutron/tests/unit/test_opts.py 2021-11-12 13:56:42.000000000 +0000 @@ -21,4 +21,7 @@ class OptsTestCase(base.BaseTestCase): def test_list_sriov_agent_opts(self): - self.assertEqual('sriov_nic', opts.list_sriov_agent_opts()[0][0]) + sriov_agent_opts = opts.list_sriov_agent_opts() + self.assertEqual('DEFAULT', sriov_agent_opts[0][0]) + self.assertEqual('sriov_nic', sriov_agent_opts[1][0]) + self.assertEqual('agent', sriov_agent_opts[2][0]) diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/pbr.json neutron-16.4.2/neutron.egg-info/pbr.json --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/pbr.json 2020-04-15 20:24:50.000000000 +0000 +++ neutron-16.4.2/neutron.egg-info/pbr.json 2021-11-12 13:57:34.000000000 +0000 @@ -1 +1 @@ -{"git_version": "5f42488a9a", "is_release": false} \ No newline at end of file +{"git_version": "ee67324c17", "is_release": true} \ No newline at end of file diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/PKG-INFO neutron-16.4.2/neutron.egg-info/PKG-INFO --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/PKG-INFO 2020-04-15 20:24:50.000000000 +0000 +++ neutron-16.4.2/neutron.egg-info/PKG-INFO 2021-11-12 13:57:34.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: neutron -Version: 16.0.0.0b2.dev214 +Version: 16.4.2 Summary: OpenStack Networking Home-page: https://docs.openstack.org/neutron/latest/ Author: OpenStack diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/requires.txt neutron-16.4.2/neutron.egg-info/requires.txt --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/requires.txt 2020-04-15 20:24:50.000000000 +0000 +++ neutron-16.4.2/neutron.egg-info/requires.txt 2021-11-12 13:57:34.000000000 +0000 @@ -44,7 +44,6 @@ pecan>=1.3.2 psutil>=3.2.2 pyOpenSSL>=17.1.0 -pyroute2>=0.5.7 python-designateclient>=2.7.0 python-neutronclient>=6.7.0 python-novaclient>=9.1.0 @@ -53,3 +52,6 @@ stevedore>=1.20.0 tenacity>=4.4.0 tooz>=1.58.0 + +[:(sys_platform!='win32')] +pyroute2>=0.5.7 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/SOURCES.txt neutron-16.4.2/neutron.egg-info/SOURCES.txt --- neutron-16.0.0~b3~git2020041516.5f42488a9a/neutron.egg-info/SOURCES.txt 2020-04-15 20:24:50.000000000 +0000 +++ neutron-16.4.2/neutron.egg-info/SOURCES.txt 2021-11-12 13:57:35.000000000 +0000 @@ -11,7 +11,6 @@ TESTING.rst babel.cfg bindep.txt -lower-constraints.txt plugin.spec requirements.txt setup.cfg @@ -36,6 +35,7 @@ devstack/lib/macvtap_agent devstack/lib/ml2 devstack/lib/network_segment_range +devstack/lib/octavia devstack/lib/ovn_agent devstack/lib/ovs devstack/lib/placement @@ -405,7 +405,6 @@ doc/source/contributor/internals/images/under-the-hood-scenario-1-ovs-compute.png doc/source/contributor/internals/images/under-the-hood-scenario-1-ovs-netns.png doc/source/contributor/internals/images/under-the-hood-scenario-1-ovs-network.png -doc/source/contributor/internals/ovn/acl_optimizations.rst doc/source/contributor/internals/ovn/data_model.rst doc/source/contributor/internals/ovn/database_consistency.rst doc/source/contributor/internals/ovn/distributed_ovsdb_events.rst @@ -510,6 +509,7 @@ doc/source/install/ovn/figures/tripleo-ovn-arch.svg doc/source/install/shared/edit_hosts_file.txt doc/source/install/shared/note_configuration_vary_by_distribution.rst +doc/source/ovn/dhcp_opts.rst doc/source/ovn/gaps.rst doc/source/ovn/index.rst doc/source/ovn/migration.rst @@ -528,7 +528,6 @@ etc/neutron/rootwrap.d/iptables-firewall.filters etc/neutron/rootwrap.d/l3.filters etc/neutron/rootwrap.d/linuxbridge-plugin.filters -etc/neutron/rootwrap.d/netns-cleanup.filters etc/neutron/rootwrap.d/openvswitch-plugin.filters etc/neutron/rootwrap.d/privsep.filters etc/oslo-config-generator/dhcp_agent.ini @@ -624,11 +623,11 @@ neutron/agent/linux/daemon.py neutron/agent/linux/dhcp.py neutron/agent/linux/dibbler.py +neutron/agent/linux/ethtool.py neutron/agent/linux/external_process.py neutron/agent/linux/interface.py neutron/agent/linux/ip_conntrack.py neutron/agent/linux/ip_lib.py -neutron/agent/linux/ip_link_support.py neutron/agent/linux/ipset_manager.py neutron/agent/linux/iptables_comments.py neutron/agent/linux/iptables_firewall.py @@ -1034,6 +1033,7 @@ neutron/db/migration/alembic_migrations/versions/ussuri/expand/Ibac91d24da2_port_forwarding_description.py neutron/db/migration/alembic_migrations/versions/ussuri/expand/a010322604bc_network_subnet_update_lock.py neutron/db/migration/alembic_migrations/versions/ussuri/expand/c3e9d13c4367_add_binding_index_to_.py +neutron/db/migration/alembic_migrations/versions/ussuri/expand/d8bdf05313f4_add_in_use_to_subnet.py neutron/db/migration/alembic_migrations/versions/ussuri/expand/e4e236b0e1ff_add_rbac_support_for_address_scope.py neutron/db/migration/alembic_migrations/versions/ussuri/expand/e88badaa9591_add_rbac_support_for_subnetpool.py neutron/db/migration/alembic_migrations/versions/ussuri/expand/f4b9654dd40c_ovn_backend.py @@ -1389,10 +1389,13 @@ neutron/plugins/ml2/drivers/openvswitch/mech_driver/__init__.py neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py neutron/plugins/ml2/drivers/ovn/__init__.py +neutron/plugins/ml2/drivers/ovn/agent/__init__.py +neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py neutron/plugins/ml2/drivers/ovn/mech_driver/__init__.py neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/__init__.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py +neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/backports.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py @@ -1400,6 +1403,8 @@ neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/worker.py +neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/__init__.py +neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py neutron/plugins/ml2/extensions/__init__.py neutron/plugins/ml2/extensions/data_plane_status.py neutron/plugins/ml2/extensions/dns_integration.py @@ -1415,6 +1420,7 @@ neutron/privileged/agent/linux/netlink_constants.py neutron/privileged/agent/linux/netlink_lib.py neutron/privileged/agent/linux/tc_lib.py +neutron/privileged/agent/linux/utils.py neutron/profiling/__init__.py neutron/profiling/profiled_decorator.py neutron/quota/__init__.py @@ -1499,6 +1505,7 @@ neutron/services/network_segment_range/__init__.py neutron/services/network_segment_range/plugin.py neutron/services/ovn_l3/__init__.py +neutron/services/ovn_l3/exceptions.py neutron/services/ovn_l3/plugin.py neutron/services/placement_report/__init__.py neutron/services/placement_report/plugin.py @@ -1620,6 +1627,7 @@ neutron/tests/fullstack/test_logging.py neutron/tests/fullstack/test_mtu.py neutron/tests/fullstack/test_port_shut_down.py +neutron/tests/fullstack/test_ports_api.py neutron/tests/fullstack/test_ports_binding.py neutron/tests/fullstack/test_ports_rebind.py neutron/tests/fullstack/test_qos.py @@ -1734,6 +1742,15 @@ neutron/tests/functional/db/migrations/test_a8b517cff8ab_add_routerport_bindings_for_ha.py neutron/tests/functional/db/migrations/test_b12a3ef66e62_add_standardattr_to_qos_policies.py neutron/tests/functional/db/migrations/test_c3e9d13c4367_add_binding_index_to_.py +neutron/tests/functional/objects/__init__.py +neutron/tests/functional/objects/test_quota.py +neutron/tests/functional/objects/plugins/__init__.py +neutron/tests/functional/objects/plugins/ml2/__init__.py +neutron/tests/functional/objects/plugins/ml2/test_base.py +neutron/tests/functional/objects/plugins/ml2/test_geneveallocation.py +neutron/tests/functional/objects/plugins/ml2/test_greallocation.py +neutron/tests/functional/objects/plugins/ml2/test_vlanallocation.py +neutron/tests/functional/objects/plugins/ml2/test_vxlanallocation.py neutron/tests/functional/pecan_wsgi/__init__.py neutron/tests/functional/pecan_wsgi/config.py neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -1756,11 +1773,14 @@ neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/__init__.py +neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py neutron/tests/functional/privileged/__init__.py neutron/tests/functional/privileged/agent/__init__.py neutron/tests/functional/privileged/agent/linux/__init__.py neutron/tests/functional/privileged/agent/linux/test_ip_lib.py neutron/tests/functional/privileged/agent/linux/test_tc_lib.py +neutron/tests/functional/privileged/agent/linux/test_utils.py neutron/tests/functional/resources/__init__.py neutron/tests/functional/resources/process.py neutron/tests/functional/resources/ovsdb/__init__.py @@ -1866,7 +1886,6 @@ neutron/tests/unit/agent/linux/test_interface.py neutron/tests/unit/agent/linux/test_ip_conntrack.py neutron/tests/unit/agent/linux/test_ip_lib.py -neutron/tests/unit/agent/linux/test_ip_link_support.py neutron/tests/unit/agent/linux/test_ipset_manager.py neutron/tests/unit/agent/linux/test_iptables_firewall.py neutron/tests/unit/agent/linux/test_iptables_manager.py @@ -1925,6 +1944,8 @@ neutron/tests/unit/cmd/test_ovs_cleanup.py neutron/tests/unit/cmd/test_sanity_check.py neutron/tests/unit/cmd/test_status.py +neutron/tests/unit/cmd/ovn/__init__.py +neutron/tests/unit/cmd/ovn/test_neutron_ovn_db_sync_util.py neutron/tests/unit/cmd/upgrade_checks/__init__.py neutron/tests/unit/cmd/upgrade_checks/test_checks.py neutron/tests/unit/common/__init__.py @@ -1994,6 +2015,7 @@ neutron/tests/unit/extensions/test_floating_ip_port_forwarding.py neutron/tests/unit/extensions/test_floatingip_pools.py neutron/tests/unit/extensions/test_l3.py +neutron/tests/unit/extensions/test_l3_conntrack_helper.py neutron/tests/unit/extensions/test_l3_ext_gw_mode.py neutron/tests/unit/extensions/test_network_ip_availability.py neutron/tests/unit/extensions/test_network_segment_range.py @@ -2180,6 +2202,8 @@ neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/__init__.py +neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/schemas/ovn-nb.ovsschema neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/schemas/ovn-sb.ovsschema neutron/tests/unit/plugins/ml2/extensions/__init__.py @@ -2194,10 +2218,12 @@ neutron/tests/unit/privileged/agent/linux/__init__.py neutron/tests/unit/privileged/agent/linux/test_ip_lib.py neutron/tests/unit/privileged/agent/linux/test_netlink_lib.py +neutron/tests/unit/privileged/agent/linux/test_utils.py neutron/tests/unit/quota/__init__.py neutron/tests/unit/quota/test_resource.py neutron/tests/unit/quota/test_resource_registry.py neutron/tests/unit/scheduler/__init__.py +neutron/tests/unit/scheduler/test_base_scheduler.py neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py neutron/tests/unit/scheduler/test_l3_agent_scheduler.py neutron/tests/unit/scheduler/test_l3_ovn_scheduler.py @@ -2304,10 +2330,6 @@ playbooks/multinode-setup.yaml playbooks/post_functional_job.yaml playbooks/run_functional_job.yaml -playbooks/legacy/neutron-grenade-dvr-multinode/post.yaml -playbooks/legacy/neutron-grenade-dvr-multinode/run.yaml -playbooks/legacy/neutron-grenade-multinode/post.yaml -playbooks/legacy/neutron-grenade-multinode/run.yaml playbooks/legacy/neutron-ovn-grenade/post.yaml playbooks/legacy/neutron-ovn-grenade/run.yaml rally-jobs/README.rst @@ -2320,8 +2342,10 @@ releasenotes/notes/1500-default-mtu-b0d6e4ab193b62a4.yaml releasenotes/notes/1500-default-segment-mtu-54e2cf6aea9602d5.yaml releasenotes/notes/404-for-quota-tenant-2c09c16759269b21.yaml +releasenotes/notes/Add-http_retries-config-option-b81dd29c03ba8c6a.yaml releasenotes/notes/Add-support-for-direct-ports-with-QoS-in-OVS-48c78c156606e724.yaml releasenotes/notes/Adds-http_proxy_to_wsgi-middleware-24e8271cbd94ffdf.yaml +releasenotes/notes/Deprecate-plug_new-method-without-link_up-parameter-27f8310eb1e1910a.yaml releasenotes/notes/Dscp-marking-for-linuxbridge-agent-e765d0d934fa4017.yaml releasenotes/notes/Ingress-bandwidth-limit-in-openvswitch-agent-51cda9bb6b511885.yaml releasenotes/notes/Minimizing-lock-granularity-8bc2f893d9389cf8.yaml @@ -2329,6 +2353,7 @@ releasenotes/notes/QoS-for-linuxbridge-agent-bdb13515aac4e555.yaml releasenotes/notes/QoS-ingress-bandwidth-limit-54cea12dbea71172.yaml releasenotes/notes/Remove-neutron-lbaas-5cbedd7e8033610f.yaml +releasenotes/notes/Ussuri-prelude-ca4d793da2d0bc63.yaml releasenotes/notes/accepted_egress_direct-cc23873e213c6919.yaml releasenotes/notes/access_as_external_rbac-455dc74b9fa22761.yaml releasenotes/notes/add-address-scope-rbac-a903ff28f6457606.yaml @@ -2347,6 +2372,7 @@ releasenotes/notes/add-integration-with-external-dns-f56ec8a4993b1fc4.yaml releasenotes/notes/add-ip-protocols-in-sg-60467a073e771aee.yaml releasenotes/notes/add-keepalived-vrrp-healt-check-f23ed7c853151484.yaml +releasenotes/notes/add-keepalived_use_no_track-config-option-4fa10304ee2960e6.yaml releasenotes/notes/add-log-for-keepalived-state-change-e6d0c4f663776233.yaml releasenotes/notes/add-minimum-bandwidth-support-sriov-63664b89f4dd1c1b.yaml releasenotes/notes/add-multiple-port-bindings-f16eb47ebdddff2d.yaml @@ -2389,6 +2415,8 @@ releasenotes/notes/bug-1311040-dhcp-no-dns-09291c23e2ce800a.yaml releasenotes/notes/bug-1811166-314d4b89de1cc0f1.yaml releasenotes/notes/bug-1843428-mac-addres-case-insensitivity-750299c11b49a9a8.yaml +releasenotes/notes/bug-1875981-ec32d8c3918b0dd4.yaml +releasenotes/notes/bug-1926693-55406915708d59ec.yaml releasenotes/notes/bump-default-quotas-810570badb378c50.yaml releasenotes/notes/change-of-default-timeout-b09d11683526e27d.yaml releasenotes/notes/change-oslo-db-defaults-f94df09c30767f95.yaml @@ -2437,12 +2465,14 @@ releasenotes/notes/dns_domain-1799b939e7248247.yaml releasenotes/notes/dnsmasq-local-service-c8eaa91894a7d6d4.yaml releasenotes/notes/dnsmasq_dns_servers-d729c04887ce67b4.yaml +releasenotes/notes/do-not-create-dhcp-entries-for-all-types-of-ports-39c03b3782d2753e.yaml releasenotes/notes/drop-python-2-7-9707a901c7d8eab6.yaml releasenotes/notes/dscp-qos-77ea9b27d3762e48.yaml releasenotes/notes/dvr-configure-centralized-floatingip-with-new-agent-type-05361f1f78853cf7.yaml releasenotes/notes/dvr-fip-namespace-on-all-nodes-c4da7ccd60ee62f5.yaml releasenotes/notes/dvr-ha-support-cc67e84d9380cd0b.yaml releasenotes/notes/dvr-ovs-agent-6052a8d60fddde22.yaml +releasenotes/notes/dvr-support-flat-network-for-ovs-fdf8c3eb461426ec.yaml releasenotes/notes/dvr-support-live-migration-b818b12bd9cbb518.yaml releasenotes/notes/dvr_handle_unbound_floatingip_port-f12ae806b8be2065.yaml releasenotes/notes/dynamically-resize-agent-greenthreads-c163ab37d36fcafe.yaml @@ -2464,9 +2494,12 @@ releasenotes/notes/firewall_driver_not_needed_on_server-4159669ad834dea6.yaml releasenotes/notes/fix-co-existence-bug-between-sg-logging-and-fwg-logging-ef16077880d76449.yaml releasenotes/notes/fix-deferred-alloction-when-new-mac-in-same-request-as-binding-data-2a01c1ed1a8eff66.yaml +releasenotes/notes/fix-dual-stack-issue-with-dnsmasq-2.81-c95a46e4f4459bd1.yaml releasenotes/notes/fix-ipv6-auto-allocation-with-segments-b90e99a30d096c9d.yaml +releasenotes/notes/fix-mac-learning-in-case--ovs-offload-26193bf1638fd673.yaml releasenotes/notes/fix-mtu-for-existing-networks-5a476cde9bc46a53.yaml releasenotes/notes/fix-net-delete-race-f2fa5bac3ab35a5b.yaml +releasenotes/notes/fix-newline-chars-in-dhcp-extra-options-bf86d30371556d63.yaml releasenotes/notes/fix-ovsdb-ssl-connection-4058caf4fdcb33ab.yaml releasenotes/notes/fix-remote-security-group-no-port-on-host-9177e66d4b16e90c.yaml releasenotes/notes/fix-security-group-protocol-by-numbers-48afb97ede961716.yaml @@ -2518,7 +2551,11 @@ releasenotes/notes/oslo-reports-166a169037bf64f2.yaml releasenotes/notes/oslo.messaging.notify.drivers-abb0d17b9e1bd470.yaml releasenotes/notes/overlay_ip_version-ml2-e6438b570844ef5c.yaml +releasenotes/notes/ovn-igmp-flood-unregistered-82aeb640f5dded84.yaml releasenotes/notes/ovn-igmp-snooping-support-1a6ec8e703311fce.yaml +releasenotes/notes/ovn-mcast-flood-reports-80fb529120f2af1c.yaml +releasenotes/notes/ovn-router-availability-zones-03a802ee19689474.yaml +releasenotes/notes/ovnmeta-namespaces-include-network-name-e6e4e5f6ff69e7ed.yaml releasenotes/notes/ovs-ct-firewall-driver-52a70a6a16d06f59.yaml releasenotes/notes/ovs-dpdk-rep-port-40fe628974040786.yaml releasenotes/notes/ovs-ipv6-tunnel-endpoints-f41b4954a04c43f6.yaml @@ -2563,6 +2600,7 @@ releasenotes/notes/rename-ovs-vsctl-timeout-9df1967c47f394c0.yaml releasenotes/notes/rename-tenant-to-project-b19a4068f8625969.yaml releasenotes/notes/rename-to-nova-metadata-ip-685fd81618c16d9d.yaml +releasenotes/notes/resource_priovider_default_hypervisor-b92cff207dfb94c0.yaml releasenotes/notes/rm-notify-entry-points-aa442134a780469a.yaml releasenotes/notes/routed-networks-hostroutes-a13a9885f0db4f69.yaml releasenotes/notes/security-group-ipv6-icmp-221c59dcaf2caa3c.yaml @@ -2615,7 +2653,6 @@ releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder -releasenotes/source/locale/ja/LC_MESSAGES/releasenotes.po roles/add_mariadb_repo/tasks/main.yaml roles/configure_functional_tests/README.rst roles/configure_functional_tests/defaults/main.yaml @@ -2685,6 +2722,10 @@ tools/ovn_migration/tripleo_environment/playbooks/roles/resources/validate/defaults/main.yml tools/ovn_migration/tripleo_environment/playbooks/roles/resources/validate/tasks/main.yml tools/ovn_migration/tripleo_environment/playbooks/roles/resources/validate/templates/validate-resources.sh.j2 +tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/defaults/main.yml +tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/cleanup.yml +tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/main.yml +tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/vars/main.yml tools/ovn_migration/tripleo_environment/playbooks/roles/tripleo-update/defaults/main.yml tools/ovn_migration/tripleo_environment/playbooks/roles/tripleo-update/tasks/main.yml tools/ovn_migration/tripleo_environment/playbooks/roles/tripleo-update/templates/generate-ovn-extras.sh.j2 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/PKG-INFO neutron-16.4.2/PKG-INFO --- neutron-16.0.0~b3~git2020041516.5f42488a9a/PKG-INFO 2020-04-15 20:24:51.555046800 +0000 +++ neutron-16.4.2/PKG-INFO 2021-11-12 13:57:35.698911000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: neutron -Version: 16.0.0.0b2.dev214 +Version: 16.4.2 Summary: OpenStack Networking Home-page: https://docs.openstack.org/neutron/latest/ Author: OpenStack diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/configure_functional_job.yaml neutron-16.4.2/playbooks/configure_functional_job.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/configure_functional_job.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/playbooks/configure_functional_job.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -1,4 +1,5 @@ - hosts: all roles: + - ensure-tox - setup_logdir - configure_functional_tests diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-dvr-multinode/post.yaml neutron-16.4.2/playbooks/legacy/neutron-grenade-dvr-multinode/post.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-dvr-multinode/post.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/playbooks/legacy/neutron-grenade-dvr-multinode/post.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -- hosts: primary - tasks: - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/logs/** - - --include=*/ - - --exclude=* - - --prune-empty-dirs diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-dvr-multinode/run.yaml neutron-16.4.2/playbooks/legacy/neutron-grenade-dvr-multinode/run.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-dvr-multinode/run.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/playbooks/legacy/neutron-grenade-dvr-multinode/run.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -- hosts: primary - name: Autoconverted job legacy-grenade-dsvm-neutron-dvr-multinode from old job gate-grenade-dsvm-neutron-dvr-multinode-ubuntu-xenial-nv - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -e - set -x - cat > clonemap.yaml << EOF - clonemap: - - name: openstack/devstack-gate - dest: devstack-gate - EOF - /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ - https://opendev.org \ - openstack/devstack-gate - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - export PYTHONUNBUFFERED=true - export DEVSTACK_GATE_NEUTRON=1 - export DEVSTACK_GATE_CONFIGDRIVE=0 - export DEVSTACK_GATE_GRENADE=pullup - export DEVSTACK_GATE_USE_PYTHON3=True - # Test DVR upgrade on multinode - export PROJECTS="openstack/grenade $PROJECTS" - export DEVSTACK_GATE_NEUTRON_DVR=1 - export BRANCH_OVERRIDE=default - if [ "$BRANCH_OVERRIDE" != "default" ] ; then - export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE - fi - export DEVSTACK_GATE_TOPOLOGY="multinode" - - # Disable some services to use less memory - # Cinder-backup - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service c-bak" - # Etcd - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service etcd3" - # Swift - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-account" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-container" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-container-sync" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-object" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-proxy" - - cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh - ./safe-devstack-vm-gate-wrap.sh - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-multinode/post.yaml neutron-16.4.2/playbooks/legacy/neutron-grenade-multinode/post.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-multinode/post.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/playbooks/legacy/neutron-grenade-multinode/post.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -- hosts: primary - tasks: - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/logs/** - - --include=*/ - - --exclude=* - - --prune-empty-dirs diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-multinode/run.yaml neutron-16.4.2/playbooks/legacy/neutron-grenade-multinode/run.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/playbooks/legacy/neutron-grenade-multinode/run.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/playbooks/legacy/neutron-grenade-multinode/run.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -- hosts: primary - name: Autoconverted job legacy-grenade-dsvm-neutron-multinode from old job gate-grenade-dsvm-neutron-multinode-ubuntu-xenial - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -e - set -x - cat > clonemap.yaml << EOF - clonemap: - - name: openstack/devstack-gate - dest: devstack-gate - EOF - /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ - https://opendev.org \ - openstack/devstack-gate - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - export PYTHONUNBUFFERED=true - export DEVSTACK_GATE_NEUTRON=1 - export DEVSTACK_GATE_CONFIGDRIVE=0 - export DEVSTACK_GATE_GRENADE=pullup - export DEVSTACK_GATE_USE_PYTHON3=True - export PROJECTS="openstack/grenade $PROJECTS" - # Default to non DVR - export DEVSTACK_GATE_NEUTRON_DVR=0 - export BRANCH_OVERRIDE=default - if [ "$BRANCH_OVERRIDE" != "default" ] ; then - export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE - fi - export DEVSTACK_GATE_TOPOLOGY="multinode" - - # Disable some services to use less memory - # Cinder - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service c-bak" - # Etcd - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service etcd3" - # Swift - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-account" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-container" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-container-sync" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-object" - export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service s-proxy" - - cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh - ./safe-devstack-vm-gate-wrap.sh - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/rally-jobs/task-neutron.yaml neutron-16.4.2/rally-jobs/task-neutron.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/rally-jobs/task-neutron.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/rally-jobs/task-neutron.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -44,7 +44,6 @@ network_create_args: {} network_update_args: admin_state_up: False - name: "_updated" runner: constant: times: 40 @@ -99,7 +98,6 @@ subnets_per_network: 2 subnet_update_args: enable_dhcp: True - name: "_subnet_updated" runner: constant: times: 100 @@ -166,7 +164,6 @@ router_create_args: {} router_update_args: admin_state_up: False - name: "_router_updated" runner: constant: times: 40 @@ -209,8 +206,8 @@ network quotas are not exceeded scenario: NeutronNetworks.create_and_list_ports: - network_create_args: - port_create_args: + network_create_args: {} + port_create_args: {} ports_per_network: 50 runner: constant: @@ -243,7 +240,6 @@ admin_state_up: False device_id: "dummy_id" device_owner: "dummy_owner" - name: "_port_updated" runner: constant: times: 40 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/Add-http_retries-config-option-b81dd29c03ba8c6a.yaml neutron-16.4.2/releasenotes/notes/Add-http_retries-config-option-b81dd29c03ba8c6a.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/Add-http_retries-config-option-b81dd29c03ba8c6a.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/Add-http_retries-config-option-b81dd29c03ba8c6a.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,6 @@ +--- +features: + - | + A new configuration option ``http_retries`` was added. This option allows + configuring the number of times the nova or ironic client should retry on + a failed HTTP call. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/add-keepalived_use_no_track-config-option-4fa10304ee2960e6.yaml neutron-16.4.2/releasenotes/notes/add-keepalived_use_no_track-config-option-4fa10304ee2960e6.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/add-keepalived_use_no_track-config-option-4fa10304ee2960e6.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/add-keepalived_use_no_track-config-option-4fa10304ee2960e6.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,7 @@ +--- +features: + - | + New config option ``keepalived_use_no_track`` was added. If keepalived + version used on the deployment does not support ``no_track`` flag in its + config file (e.g. keepalived 1.x), this option should be set to ``False``. + Default value of this option is ``True``. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/bug-1875981-ec32d8c3918b0dd4.yaml neutron-16.4.2/releasenotes/notes/bug-1875981-ec32d8c3918b0dd4.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/bug-1875981-ec32d8c3918b0dd4.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/bug-1875981-ec32d8c3918b0dd4.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,6 @@ +--- +fixes: + - | + `1875981 `_ + Neutron now correctly removes associated DNS records when an admin + deletes ports, servers or floation IPs. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/bug-1926693-55406915708d59ec.yaml neutron-16.4.2/releasenotes/notes/bug-1926693-55406915708d59ec.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/bug-1926693-55406915708d59ec.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/bug-1926693-55406915708d59ec.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,7 @@ +--- +fixes: + - | + `1926693 `_ + The logic to detect the hypervisor hostname, which was introduced by + `change 69660 `_, + has been fixed and now returns the result consistent with libvirt. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/Deprecate-plug_new-method-without-link_up-parameter-27f8310eb1e1910a.yaml neutron-16.4.2/releasenotes/notes/Deprecate-plug_new-method-without-link_up-parameter-27f8310eb1e1910a.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/Deprecate-plug_new-method-without-link_up-parameter-27f8310eb1e1910a.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/Deprecate-plug_new-method-without-link_up-parameter-27f8310eb1e1910a.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,10 @@ +--- +deprecations: + - | + Abstract method ``plug_new`` from the + neutron.agent.linux.interface.LinuxInterfaceDriver class now accepts + an optional parameter ``link_up``. + Usage of this method, which takes from 5 to 9 positional arguments, without + ``link_up`` is now deprecated and will not be possible starting in the W + release. Third-party drivers which inherit from this base class should update + the implementation of their ``plug_new`` method. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/do-not-create-dhcp-entries-for-all-types-of-ports-39c03b3782d2753e.yaml neutron-16.4.2/releasenotes/notes/do-not-create-dhcp-entries-for-all-types-of-ports-39c03b3782d2753e.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/do-not-create-dhcp-entries-for-all-types-of-ports-39c03b3782d2753e.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/do-not-create-dhcp-entries-for-all-types-of-ports-39c03b3782d2753e.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,6 @@ +--- +other: + - | + To improve performance of the DHCP agent, it will no longer configure the DHCP server + for every port type created in Neutron. For example, for floating IP or router HA + interfaces there is no need since a client will not make a DHCP request for them diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/dvr-support-flat-network-for-ovs-fdf8c3eb461426ec.yaml neutron-16.4.2/releasenotes/notes/dvr-support-flat-network-for-ovs-fdf8c3eb461426ec.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/dvr-support-flat-network-for-ovs-fdf8c3eb461426ec.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/dvr-support-flat-network-for-ovs-fdf8c3eb461426ec.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,9 @@ +--- +features: + - | + ``DVR`` routers now support ``flat`` networks. +fixes: + - | + Fixed bug `1876092 `_ + which caused DUP ICMP replies on the ``flat`` networks used with ``DVR`` + routers. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/fix-dual-stack-issue-with-dnsmasq-2.81-c95a46e4f4459bd1.yaml neutron-16.4.2/releasenotes/notes/fix-dual-stack-issue-with-dnsmasq-2.81-c95a46e4f4459bd1.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/fix-dual-stack-issue-with-dnsmasq-2.81-c95a46e4f4459bd1.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/fix-dual-stack-issue-with-dnsmasq-2.81-c95a46e4f4459bd1.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed an issue where the client on a dual-stack (IPv4 + IPv6) network failed + to get configuration from the dnsmasq DHCP server. See bug: `1876094 + `_. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/fix-mac-learning-in-case--ovs-offload-26193bf1638fd673.yaml neutron-16.4.2/releasenotes/notes/fix-mac-learning-in-case--ovs-offload-26193bf1638fd673.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/fix-mac-learning-in-case--ovs-offload-26193bf1638fd673.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/fix-mac-learning-in-case--ovs-offload-26193bf1638fd673.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixed MAC learning issue when ovs offload enabled. OVS firewall reduce + the usage of normal actions to reduce cpu utilization. This causing flood + rule because there is no MAC learning on ingress traffic. While this ok + for none offload case, when using ovs offload flood rule is not + offloaded. This fix the MAC learning in the offload, so we avoid flood + rule. + `#1897637 `_. \ No newline at end of file diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/fix-newline-chars-in-dhcp-extra-options-bf86d30371556d63.yaml neutron-16.4.2/releasenotes/notes/fix-newline-chars-in-dhcp-extra-options-bf86d30371556d63.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/fix-newline-chars-in-dhcp-extra-options-bf86d30371556d63.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/fix-newline-chars-in-dhcp-extra-options-bf86d30371556d63.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,6 @@ +--- +security: + - | + Fix `bug 1939733 `_ by + dropping from the dhcp extra option values everything what is after first + newline (``\n``) character before passing them to the dnsmasq. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovn-igmp-flood-unregistered-82aeb640f5dded84.yaml neutron-16.4.2/releasenotes/notes/ovn-igmp-flood-unregistered-82aeb640f5dded84.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovn-igmp-flood-unregistered-82aeb640f5dded84.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/ovn-igmp-flood-unregistered-82aeb640f5dded84.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,8 @@ +--- +issues: + - | + Even with the "igmp_snooping_enable" configuration option stating that + traffic would not be flooded to unregistered VMs when this option was + enabled, the ML2/OVN driver didn't follow that behavior. This + has now been fixed and ML2/OVN will no longer flood traffic to + unregistered VMs when this configuration option is set to True. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovn-mcast-flood-reports-80fb529120f2af1c.yaml neutron-16.4.2/releasenotes/notes/ovn-mcast-flood-reports-80fb529120f2af1c.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovn-mcast-flood-reports-80fb529120f2af1c.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/ovn-mcast-flood-reports-80fb529120f2af1c.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes a configuration problem in the OVN driver that prevented + external IGMP queries from reaching the Virtual Machines. See + `bug 1918108 `_ + for details. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovnmeta-namespaces-include-network-name-e6e4e5f6ff69e7ed.yaml neutron-16.4.2/releasenotes/notes/ovnmeta-namespaces-include-network-name-e6e4e5f6ff69e7ed.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovnmeta-namespaces-include-network-name-e6e4e5f6ff69e7ed.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/ovnmeta-namespaces-include-network-name-e6e4e5f6ff69e7ed.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,7 @@ +--- +other: + - | + The ``OVN Metadata Agent`` now creates the network namespaces including the + Neutron network UUID in its name. Previously, the OVN datapath UUID was used + and it was not obvious for operators and during debugging to figure out which + namespace corresponded to what Neutron network. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovn-router-availability-zones-03a802ee19689474.yaml neutron-16.4.2/releasenotes/notes/ovn-router-availability-zones-03a802ee19689474.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/ovn-router-availability-zones-03a802ee19689474.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/ovn-router-availability-zones-03a802ee19689474.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,6 @@ +--- +features: + - | + Added support for router availability zones in OVN. The OVN driver + can now read from the router's availability_zone_hints field and + schedule router ports accordingly with the given availability zones. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/resource_priovider_default_hypervisor-b92cff207dfb94c0.yaml neutron-16.4.2/releasenotes/notes/resource_priovider_default_hypervisor-b92cff207dfb94c0.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/resource_priovider_default_hypervisor-b92cff207dfb94c0.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/resource_priovider_default_hypervisor-b92cff207dfb94c0.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,9 @@ +--- +fixes: + - | + The new ``resource_provider_defualt_hypervisor`` option has been added, + to replace the default hypervisor name to locates the root resource + provider without giving a complete list of interfaces or bridges in + the ``resource_provider_hypervisors`` option. This option is located in + the ``[ovs]`` ini-section for ``ovs-agent`` and ``[sriov_nic]`` ini-section + for ``sriov-agent``. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/Ussuri-prelude-ca4d793da2d0bc63.yaml neutron-16.4.2/releasenotes/notes/Ussuri-prelude-ca4d793da2d0bc63.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/notes/Ussuri-prelude-ca4d793da2d0bc63.yaml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/releasenotes/notes/Ussuri-prelude-ca4d793da2d0bc63.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,28 @@ +--- +prelude: | + The 16.0.0 release includes many bug fixes and new features. + + The most important improvements worth mentioning are: + + - Python 2 is no longer supported by Neutron, Python 3.6 and 3.7 are. + + - Address scopes and subnetpools can now be shared with other tenants using + the Role Based Access Control (``RBAC``) mechanism. + + - Security groups can now be set as ``stateful``. Conntrack will not be + used for any rules from such a group. This is currently supported only by + the ``iptables`` and ``iptables_hybrid`` drivers. + + - Neutron API now allows tagging resources directly in the ``POST`` request. + + - IGMP snooping (multicast) can now be enabled in the ``OVS`` and ``OVN`` + drivers. + + - A list of IPv6 addresses for a dhcp-host entry in the dnsmasq DHCP agent + driver can be configured. This solves problems with failing boot process + when only one IP address is available. See `#1861032 + `_) for details about + the issue. + + - The networking-ovn mechanism driver has been merged into the neutron + repository and is now an in-tree driver for ML2. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/source/locale/ja/LC_MESSAGES/releasenotes.po neutron-16.4.2/releasenotes/source/locale/ja/LC_MESSAGES/releasenotes.po --- neutron-16.0.0~b3~git2020041516.5f42488a9a/releasenotes/source/locale/ja/LC_MESSAGES/releasenotes.po 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/releasenotes/source/locale/ja/LC_MESSAGES/releasenotes.po 1970-01-01 00:00:00.000000000 +0000 @@ -1,301 +0,0 @@ -# Akihiro Motoki , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: Neutron Release Notes\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-10 09:07+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2016-10-10 09:28+0000\n" -"Last-Translator: Akihiro Motoki \n" -"Language-Team: Japanese\n" -"Language: ja\n" -"X-Generator: Zanata 3.9.6\n" -"Plural-Forms: nplurals=1; plural=0\n" - -msgid "7.0.1" -msgstr "7.0.1" - -msgid "7.0.3" -msgstr "7.0.3" - -msgid "7.0.4" -msgstr "7.0.4" - -msgid "7.1.0" -msgstr "7.1.0" - -msgid "8.0.0" -msgstr "8.0.0" - -msgid "8.1.0" -msgstr "8.1.0" - -msgid "8.2.0" -msgstr "8.2.0" - -msgid "9.0.0" -msgstr "9.0.0" - -msgid "" -"A DHCP agent is assigned to an availability zone; the network will be hosted " -"by the DHCP agent with availability zone specified by the user." -msgstr "" -"DHCP エージェントをアベイラビリティーゾーンに割り当てられます。ネットワークは" -"ユーザーが指定したアベイラビリティーゾーンの DHCP エージェントが担当します。" - -msgid "" -"A new rule has been added to the API that allows for tagging traffic with " -"DSCP values. This is currently supported by the Open vSwitch QoS driver." -msgstr "" -"新しいルールが追加され、トラフィックへの DSCP 値のタグ付けを API でできるよう" -"になりました。この機能は現在のところ Open vSwitch QoS ドライバーでサポートさ" -"れています。" - -msgid "Bug Fixes" -msgstr "バグ修正" - -msgid "" -"Creating Neutron-LBaaS load balancers in environments without hardware " -"virtualization may be slow when using the Octavia driver. This is due to " -"QEMU using the TCG accelerator instead of the KVM accelerator in " -"environments without hardware virtualization available. We recommend " -"enabling hardware virtualization on your compute nodes, or enabling nested " -"virtualization when using the Octavia driver inside a virtual environment. " -"See `this link explaining devstack with nested KVM `_ for details " -"on setting up nested virtualization for DevStack running inside KVM." -msgstr "" -"Octavia ドライバー使用時には、ハードウェア仮想化のない環境での Neutron-LBaaS " -"ロードバランサーの作成が遅くなる可能性があります。これは、ハードウェア仮想化" -"なしの環境では QEMU が KVM アクセラレーターではなく TCG アクセラレーターを使" -"用するためです。Octavia ドライバーを仮想環境内で使用する場合、コンピュート" -"ノード上でハードウェア仮想化を有効にするか、nested virtualization (訳注:仮想" -"化環境上の仮想化支援機構) を有効にすることをお勧めします。KVM 内で実行される " -"DevStack で nested virtualization をセットアップする詳細な方法は、 `nested " -"KVM での DevStack に関するリンク `_ を参照してください。" - -msgid "Current Series Release Notes" -msgstr "開発中バージョンのリリースノート" - -msgid "Deprecation Notes" -msgstr "廃止予定の機能" - -msgid "" -"During Liberty, some plugins and drivers have been deprecated, including the " -"metaplugin, the IBM SDN-VE plugin, the Cisco N1KV monolithic plugin, and the " -"Embrane plugin." -msgstr "" -"Liberty では、いくつかのプラグインやドライバーが廃止予定となりました。 " -"metaplugin、 IBM SDN-VE プラグイン、 Cisco N1KV 一体型プラグイン、 Embrane プ" -"ラグインが該当します。" - -msgid "Fixes bug 1537734" -msgstr "バグ 1537734 を修正しました。" - -msgid "Fixes bug 1572670" -msgstr "バグ 1572670 を修正しました。" - -msgid "IPv6 prefix delegation support was added to Neutron." -msgstr "" -"IPv6 プレフィックスデリゲーションのサポートが Neutron に追加されました。" - -msgid "Known Issues" -msgstr "既知の問題" - -msgid "" -"LBaaS V2 reference driver is now based on Octavia, an operator grade " -"scalable, reliable Load Balancer platform." -msgstr "" -"LBaaS V2 リファレンスドライバーが Octavia (事業者品質のスケーラブルで高信頼な" -"ロードバランサプラットフォーム) ベースになりました。" - -msgid "Liberty Series Release Notes" -msgstr "Liberty バージョンのリリースノート" - -msgid "Mitaka Series Release Notes" -msgstr "Mitaka バージョンのリリースノート" - -msgid "" -"Networks used for VRRP traffic for HA routers may now be configured to use a " -"specific segmentation type or physical network tag." -msgstr "" -"HA ルータの VRRP トラフィック用に使用されるネットワークを、特定のセグメントタ" -"イプまたは物理ネットワークタグを使用して設定できるようになりました。" - -msgid "" -"Neutron now exposes a QoS API, initially offering bandwidth limitation on " -"the port level. See the `QoS devref `_ for additional information." -msgstr "" -"Neutron で QoS API が利用できるようになりました。最初の機能としてポートレベル" -"の帯域幅制限機能が提供されます。この機能の API、CLI、設定、追加情報は `QoS " -"devref `_ にあります。" - -msgid "Neutron now has a pluggable IP address management framework." -msgstr "プラグイン型の IP アドレス管理 (IPAM) フレームワークが実装されました。" - -msgid "Neutron now offers role base access control (RBAC) for networks." -msgstr "" -"Neutron で、ネットワークに対してロールベースアクセス制御 (RBAC) が利用できる" -"ようになりました。" - -msgid "" -"Neutron now provides a way for admins to manually schedule agents, allowing " -"host resources to be tested before they are enabled for tenant use." -msgstr "" -"Neutron は、管理者が手動でエージェントをスケジューリングする方法を提供するよ" -"うになりました。これにより、テナントが利用できるようにする前に、ホストのリ" -"ソースをテストできるようになりました。" - -msgid "" -"Neutron now supports IPv6 Prefix Delegation for the automatic assignment of " -"CIDRs to IPv6 subnets. For more information on the usage and configuration " -"of this feature, see the `OpenStack Networking Guide `_." -msgstr "" -"Neutron は IPv6 サブネットに対する CIDR の自動割当において IPv6 プレフィック" -"スデリゲーションに対応しました。この機能の利用、設定に関する詳細な情報は、 " -"`OpenStack Networking Guide `_ を参照してください。" - -msgid "New Features" -msgstr "新機能" - -msgid "OFAgent has been removed in the Newton cycle." -msgstr "Newton サイクルで OFAgent は削除されました。" - -msgid "OFAgent is decomposed and deprecated in the Mitaka cycle." -msgstr "" -"Mitaka サイクルで OFAgent は別リポジトリーに分離され、廃止予定となりました。" - -msgid "Other Notes" -msgstr "その他の注意点" - -msgid "Pluggable IPAM enables the use of alternate or third-party IPAM." -msgstr "" -"プラグイン型の IPAM により、サードパーティー製 IPAM や代替機能を利用可能にな" -"ります。" - -msgid "" -"Router high availability (L3 HA / VRRP) now works when layer 2 population " -"(l2pop) is enabled." -msgstr "" -"ルータ高可用性(L3 HA/VRRP)が、レイヤー2ポピュレーション(l2pop)が有効の場" -"合にも機能するようになりました。" - -msgid "Start using reno to manage release notes." -msgstr "リリースノートの管理に reno を使い始めました。" - -msgid "Support for IPv6 addresses as tunnel endpoints in OVS." -msgstr "" -"OVS のトンネルエンドポイントとして IPv6 アドレスが使用できるようになりまし" -"た。" - -msgid "Support integration with external DNS service." -msgstr "外部 DNS サービスとの連携に対応しました。" - -msgid "" -"The 'external_network_bridge' option for the L3 agent has been deprecated in " -"favor of a bridge_mapping with a physnet. For more information, see the " -"`Network Node `_ section of this scenario in the networking guide." -msgstr "" -"L3 エージェントの 'external_network_bridge' オプションは廃止予定となりまし" -"た。 bridge_mapping の physnet 指定を使用してください。詳細な情報は、 `ネット" -"ワーキングガイド `_ の Network Node の節を参照してください。" - -msgid "" -"The Cisco N1kV monolithic plugin is removed in the Liberty release (replaced " -"by the ML2 mechanism driver)." -msgstr "" -"Cisco N1kV 一体型プラグインは Liberty リリースで削除されました (ML2 メカニズ" -"ムドライバーで置き換えられました)。" - -msgid "" -"The Embrane plugin is deprecated and will be removed in the Mitaka release." -msgstr "Embrane プラグインは廃止予定になり、Mitaka リリースで廃止されます。" - -msgid "" -"The FWaaS API is marked as experimental for Liberty. Further, the current " -"API will be removed in Mitaka and replaced with a new FWaaS API, which the " -"team is in the process of developing." -msgstr "" -"FWaaS API は Liberty では実験的機能 (experimental) の扱いです。今後、現在の " -"API が Mitaka で削除され、 (Neutron チームが開発中の) 新しい FWaaS API で置換" -"される予定です。" - -msgid "The IBM SDN-VE monolithic plugin is removed in the Liberty release." -msgstr "IBM SDN-VE 一体型プラグインは Liberty リリースで削除されました。" - -msgid "" -"The LBaaS V1 API is marked as deprecated and is planned to be removed in a " -"future release. Going forward, the LBaaS V2 API should be used." -msgstr "" -"LBaaS V1 API は廃止予定となり、将来のリリースで削除される予定です。今後は " -"LBaaS V2 API を使用すべきです。" - -msgid "The LBaaS V2 API is no longer experimental. It is now stable." -msgstr "" -"LBaaS V2 API は今回のリリースで実験的機能 (experimental) でなくなりました。安" -"定版 (stable) となりました。" - -msgid "" -"The OVS agent may now be restarted without affecting data plane connectivity." -msgstr "" -"データプレーンの接続性に影響を与えずに OVS エージェントを再起動できるようにな" -"りました。" - -msgid "" -"The Openflow Agent(OFAgent) mechanism driver and its agent have been removed " -"in favor of OpenvSwitch mechanism driver with \"native\" of_interface in the " -"Newton cycle." -msgstr "" -"Newton サイクルで OpenFlow Agent (OFAgent) メカニズムドライバーとエージェント" -"が削除されました。 Open vSwitch メカニズムドライバーの \"native\" インター" -"フェースを使ってください。" - -msgid "" -"The Openflow Agent(OFAgent) mechanism driver is decomposed completely from " -"neutron tree in the Mitaka. The OFAgent driver and its agent also are " -"deprecated in favor of OpenvSwitch mechanism driver with \"native\" " -"of_interface in the Mitaka and will be removed in the next release." -msgstr "" -"Mitaka リリースで、OpenFlow Agent (OFAgent) メカニズムドライバーは neutron " -"コードツリーから完全に分離されました。また、 Open vSwitch メカニズムドライ" -"バーの \"native\" インターフェースが利用できるようになったため、 OFAgent ド" -"ライバーとエージェントは Mitaka サイクルで廃止予定扱いになりました。次のリ" -"リースで削除される予定です。" - -msgid "The metaplugin is removed in the Liberty release." -msgstr "metaplugin は Liberty リリースで削除されました。" - -msgid "The original, non-pluggable version of IPAM is enabled by default." -msgstr "デフォルトでは、以前からの非プラグイン版の IPAM が有効になります。" - -msgid "" -"The stock Ubuntu Trusty Tahr kernel (3.13) shows linear performance " -"degradation when running \"ip netns exec\" as the number of namespaces " -"increases. In cases where scale is important, a later version kernel (e.g. " -"3.19) should be used. This regression should be fixed in Trusty Tahr since " -"3.13.0-36.63 and later kernel versions. For more information, please see " -"`Launchpad bug 1328088. `_" -msgstr "" -"Trusty Tahr に含まれるカーネル (3.13) は、ネームスペース数の増加に伴い「ip " -"netns exec」の実行時の性能が線形に劣化します。スケーラビリティーが重要な場" -"合、より後のバージョンのカーネル (例えば 3.19) を使うべきです。この問題は " -"Trusty Thar の 3.13.0-36.63 以降のカーネルバージョンで修正済みです。詳細は " -"`Launchpad バグ 1328088 `_ を参照してください。" - -msgid "Upgrade Notes" -msgstr "アップグレード時の注意" - -msgid "VPNaaS reference drivers now work with HA routers." -msgstr "" -"VPNaaS リファレンスドライバーが HA ルータ上で動作するようになりました。" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/roles/configure_functional_tests/tasks/main.yaml neutron-16.4.2/roles/configure_functional_tests/tasks/main.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/roles/configure_functional_tests/tasks/main.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/roles/configure_functional_tests/tasks/main.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -8,13 +8,18 @@ BASE_DIR={{ base_dir }} GATE_DEST={{ gate_dest_dir }} PROJECT_NAME={{ project_name }} - NEUTRON_PATH={{ neutron_dir }} + NEUTRON_DIR={{ neutron_dir }} DEVSTACK_PATH={{ devstack_dir }} + TOP_DIR={{ devstack_dir }} VENV={{ tests_venv }} + STACK_USER=stack + OVS_BRANCH={{ OVS_BRANCH }} + OVN_BRANCH={{ OVN_BRANCH }} source $DEVSTACK_PATH/functions - source $NEUTRON_PATH/devstack/lib/ovs - source $NEUTRON_PATH/tools/configure_for_func_testing.sh + source $NEUTRON_DIR/devstack/lib/ovs + source $NEUTRON_DIR/devstack/lib/ovn_agent + source $NEUTRON_DIR/tools/configure_for_func_testing.sh configure_host_for_func_testing executable: /bin/bash diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/TESTING.rst neutron-16.4.2/TESTING.rst --- neutron-16.0.0~b3~git2020041516.5f42488a9a/TESTING.rst 2020-04-15 20:24:41.000000000 +0000 +++ neutron-16.4.2/TESTING.rst 2021-11-12 13:56:42.000000000 +0000 @@ -511,9 +511,9 @@ Many changes span across both the neutron and neutron-lib repos, and tox will always build the test environment using the published module versions -specified in requirements.txt and lower-constraints.txt. To run tox tests -against a different version of neutron-lib, use the TOX_ENV_SRC_MODULES -environment variable to point at a local package repo. +specified in requirements.txt. To run tox tests against a different version of +neutron-lib, use the TOX_ENV_SRC_MODULES environment variable to point at a +local package repo. For example, to run against the 'master' branch of neutron-lib:: diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/test-requirements.txt neutron-16.4.2/test-requirements.txt --- neutron-16.0.0~b3~git2020041516.5f42488a9a/test-requirements.txt 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/test-requirements.txt 2021-11-12 13:56:42.000000000 +0000 @@ -6,7 +6,7 @@ coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.12 # LGPLv3 -pycodestyle>=2.0.0 # MIT +pycodestyle>=2.0.0,<2.6.0 # MIT mock>=3.0.0 # BSD python-subunit>=1.0.0 # Apache-2.0/BSD testtools>=2.2.0 # MIT @@ -21,6 +21,7 @@ astroid==2.1.0;python_version>="3.0" # LGPLv2.1 pylint==1.9.2;python_version<"3.0" # GPLv2 pylint==2.2.0;python_version>="3.0" # GPLv2 +isort==4.3.21 # MIT # Needed to run DB commands in virtualenvs PyMySQL>=0.7.6 # MIT License bashate>=0.5.1 # Apache-2.0 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/configure_for_func_testing.sh neutron-16.4.2/tools/configure_for_func_testing.sh --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/configure_for_func_testing.sh 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/configure_for_func_testing.sh 2021-11-12 13:56:42.000000000 +0000 @@ -58,11 +58,13 @@ DEVSTACK_PATH=${DEVSTACK_PATH:-$1} PROJECT_NAME=${PROJECT_NAME:-neutron} REPO_BASE=${GATE_DEST:-$(cd $(dirname "$0")/../.. && pwd)} -NEUTRON_PATH=${NEUTRON_PATH:=$REPO_BASE/$PROJECT_NAME} +NEUTRON_DIR=${NEUTRON_DIR:=$REPO_BASE/$PROJECT_NAME} INSTALL_MYSQL_ONLY=${INSTALL_MYSQL_ONLY:-False} # The gate should automatically install dependencies. INSTALL_BASE_DEPENDENCIES=${INSTALL_BASE_DEPENDENCIES:-$IS_GATE} BUILD_OVS_FROM_SOURCE=${BUILD_OVS_FROM_SOURCE:-True} +OVN_BRANCH=${OVN_BRANCH:-master} +OVS_BRANCH=${OVS_BRANCH:-master} if [ ! -f "$DEVSTACK_PATH/stack.sh" ]; then @@ -81,7 +83,7 @@ TOP_DIR=$DEVSTACK_PATH if [ -f $DEVSTACK_PATH/local.conf ]; then - source $DEVSTACK_PATH/local.conf 2> /dev/null + source $DEVSTACK_PATH/local.conf 2> /dev/null || true fi source $DEVSTACK_PATH/stackrc @@ -108,10 +110,11 @@ PACKAGES=$(echo $PACKAGES | perl -pe 's|python-(?!dev)[^ ]*||g') install_package $PACKAGES - source $NEUTRON_PATH/devstack/lib/ovs - remove_ovs_packages - OVS_BRANCH="v2.12.0" + source $NEUTRON_DIR/devstack/lib/ovs + source $NEUTRON_DIR/devstack/lib/ovn_agent + echo_summary "OVN_BRANCH: ${OVN_BRANCH} OVS_BRANCH: ${OVS_BRANCH}" compile_ovs False /usr /var + compile_ovn False /usr /var else PACKAGES=$(get_packages general,neutron,q-agt,q-l3,openvswitch) PACKAGES=$(echo $PACKAGES | perl -pe 's|python-(?!dev)[^ ]*||g') diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/infrared/tripleo-ovn-migration/main.yml neutron-16.4.2/tools/ovn_migration/infrared/tripleo-ovn-migration/main.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/infrared/tripleo-ovn-migration/main.yml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/infrared/tripleo-ovn-migration/main.yml 2021-11-12 13:56:42.000000000 +0000 @@ -35,15 +35,15 @@ state: directory path: "{{ ovn_migration_working_dir }}" - - name: Set the docker registry information + - name: Set the image registry information block: - - name: Get the docker registry info (infrared deployment) + - name: Get the image registry info (infrared deployment) block: - name: Set is_infrard deployment set_fact: is_infrared: True - - name: Save the docker reg + - name: Save the image reg set_fact: container_image_prepare: namespace: "{{ install.get('registry', {}).namespace|default(False)|ternary(install.get('registry', {}).namespace, install.get('registry', {}).mirror + '/' + 'rhosp' + install.version) }}" @@ -54,13 +54,13 @@ when: - install is defined - - name: Get the docker registry info (tripleo deployment) + - name: Get the image registry info (tripleo deployment) block: - name: Set is_infrard deployment set_fact: is_infrared: False - - name: Save the docker reg + - name: Save the image reg set_fact: container_image_prepare: namespace: "{{ registry_namespace }}" diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/create-resources/templates/create-resources.sh.j2 neutron-16.4.2/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/create-resources/templates/create-resources.sh.j2 --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/create-resources/templates/create-resources.sh.j2 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/create-resources/templates/create-resources.sh.j2 2021-11-12 13:56:42.000000000 +0000 @@ -8,12 +8,12 @@ openstack image show $image_name if [ "$?" != "0" ] then - if [ ! -f cirros-0.4.0-x86_64-disk.img ] + if [ ! -f cirros-0.5.2-x86_64-disk.img ] then - curl -Lo cirros-0.4.0-x86_64-disk.img https://github.com/cirros-dev/cirros/releases/download/0.4.0/cirros-0.4.0-x86_64-disk.img + curl -Lo cirros-0.5.2-x86_64-disk.img https://github.com/cirros-dev/cirros/releases/download/0.5.2/cirros-0.5.2-x86_64-disk.img fi - openstack image create "cirros-ovn-migration-{{ resource_suffix }}" --file cirros-0.4.0-x86_64-disk.img \ + openstack image create "cirros-ovn-migration-{{ resource_suffix }}" --file cirros-0.5.2-x86_64-disk.img \ --disk-format qcow2 --container-format bare --public image_name="cirros-ovn-migration-{{ resource_suffix }}" fi diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/prepare-migration/tasks/main.yml neutron-16.4.2/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/prepare-migration/tasks/main.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/prepare-migration/tasks/main.yml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/infrared/tripleo-ovn-migration/roles/prepare-migration/tasks/main.yml 2021-11-12 13:56:42.000000000 +0000 @@ -31,9 +31,9 @@ set_fact: overcloud_deploy_ovn_script: '~/overcloud-deploy-ovn.sh' -- name: Set docker images environment file +- name: Set container images environment file set_fact: - output_env_file: /home/stack/docker-images-ovn.yaml + output_env_file: /home/stack/container-images-ovn.yaml - name: Get the proper neutron-ovn-ha.yaml path stat: @@ -79,7 +79,7 @@ - name: Set image tag from puddle version set_fact: - docker_image_tag: "{{ core_puddle_version.stdout }}" + container_image_tag: "{{ core_puddle_version.stdout }}" - name: Get registry namespace shell: cat containers-prepare-parameter.yaml | grep -v _namespace | grep namespace | awk '{print $2}' @@ -94,7 +94,7 @@ msg: "{{ core_puddle_version.stdout }}" - debug: - msg: "{{ docker_image_tag }}" + msg: "{{ container_image_tag }}" - debug: msg: "{{ reg_namespace }}" @@ -102,7 +102,7 @@ - name: Set image tag (tripleo deployment) set_fact: - docker_image_tag: "{{ image_tag }}" + container_image_tag: "{{ image_tag }}" when: - not infrared_deployment|bool @@ -116,16 +116,16 @@ - name: Add ovn container images to ovn_container_images.yaml lineinfile: dest: ~/ovn_container_images.yaml - line: "- imagename: {{ reg_namespace }}/{{ image_prefix }}-{{ item }}:{{ docker_image_tag }}" + line: "- imagename: {{ reg_namespace }}/{{ image_prefix }}-{{ item }}:{{ container_image_tag }}" with_items: - "ovn-northd" - "ovn-controller" - "neutron-server-ovn" - "neutron-metadata-agent-ovn" -- name: Generate docker images environment file +- name: Generate container images environment file shell: | - echo "parameter_defaults:" > ~/docker-images-ovn.yaml + echo "parameter_defaults:" > ~/container-images-ovn.yaml changed_when: False - name: Set the local namespace @@ -160,10 +160,10 @@ when: - local_namespace != '' -- name: Add ovn container images to docker images environment file +- name: Add ovn container images to container images environment file lineinfile: - dest: ~/docker-images-ovn.yaml - line: " {{ item.name }}: {{ local_registry }}/{{ image_prefix }}-{{ item.image_name }}:{{ docker_image_tag }}" + dest: ~/container-images-ovn.yaml + line: " {{ item.name }}: {{ local_registry }}/{{ image_prefix }}-{{ item.image_name }}:{{ container_image_tag }}" with_items: - { name: ContainerNeutronApiImage, image_name: neutron-server-ovn} - { name: ContainerNeutronConfigImage, image_name: neutron-server-ovn} diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/ovn_migration.sh neutron-16.4.2/tools/ovn_migration/tripleo_environment/ovn_migration.sh --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/ovn_migration.sh 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/ovn_migration.sh 2021-11-12 13:56:42.000000000 +0000 @@ -29,7 +29,11 @@ # overcloud deploy script for OVN migration. : ${OVERCLOUD_OVN_DEPLOY_SCRIPT:=~/overcloud-deploy-ovn.sh} +# user on the nodes in the undercloud +: ${UNDERCLOUD_NODE_USER:=heat-admin} + : ${OPT_WORKDIR:=$PWD} +: ${STACK_NAME:=overcloud} : ${PUBLIC_NETWORK_NAME:=public} : ${IMAGE_NAME:=cirros} : ${SERVER_USER_NAME:=cirros} @@ -77,35 +81,23 @@ get_host_ip() { inventory_file=$1 host_name=$2 - ip=`jq -r --arg role _meta --arg hostname $host_name 'to_entries[] | select(.key == $role) | .value.hostvars[$hostname].management_ip' $inventory_file` - if [[ "x$ip" == "x" ]] || [[ "x$ip" == "xnull" ]]; then - # This file does not provide translation from the hostname to the IP, or - # we already have an IP (Queens backwards compatibility) - echo $host_name + host_vars=$(ansible-inventory -i "$inventory_file" --host "$host_name" 2>/dev/null) + if [[ $? -eq 0 ]]; then + echo "$host_vars" | jq -r \.ansible_host else - echo $ip + echo $host_name fi } -get_role_hosts() { +get_group_hosts() { inventory_file=$1 - role_name=$2 - roles=`jq -r \.$role_name\.children\[\] $inventory_file` - for role in $roles; do - # During the rocky cycle the format changed to have .value.hosts - hosts=`jq -r --arg role "$role" 'to_entries[] | select(.key == $role) | .value.hosts[]' $inventory_file` - if [[ "x$hosts" == "x" ]]; then - # But we keep backwards compatibility with nested childrens (Queens) - hosts=`jq -r --arg role "$role" 'to_entries[] | select(.key == $role) | .value.children[]' $inventory_file` - - for host in $hosts; do - HOSTS="$HOSTS `jq -r --arg host "$host" 'to_entries[] | select(.key == $host) | .value.hosts[0]' $inventory_file`" - done - else - HOSTS="${hosts} ${HOSTS}" - fi - done - echo $HOSTS + group_name=$2 + group_graph=$(ansible-inventory -i "$inventory_file" --graph "$group_name" 2>/dev/null) + if [[ $? -eq 0 ]]; then + echo "$group_graph" | sed -ne 's/^[ \t|]\+--\([a-z0-9\-]\+\)$/\1/p' + else + echo "" + fi } # Generate the ansible.cfg file @@ -121,6 +113,7 @@ fact_caching = jsonfile fact_caching_connection = ./ansible_facts_cache fact_caching_timeout = 0 +log_path = $HOME/ovn_migration_ansible.log #roles_path = roles:... @@ -134,39 +127,51 @@ # Generate the inventory file for ansible migration playbook. generate_ansible_inventory_file() { + local dhcp_nodes + echo "Generating the inventory file for ansible-playbook" source $STACKRC_FILE echo "[ovn-dbs]" > hosts_for_migration ovn_central=True - /usr/bin/tripleo-ansible-inventory --list > /tmp/ansible-inventory.txt + inventory_file=$(mktemp --tmpdir ansible-inventory-XXXXXXXX.yaml) + /usr/bin/tripleo-ansible-inventory --stack $STACK_NAME --static-yaml-inventory "$inventory_file" # We want to run ovn_dbs where neutron_api is running - OVN_DBS=$(get_role_hosts /tmp/ansible-inventory.txt neutron_api) + OVN_DBS=$(get_group_hosts "$inventory_file" neutron_api) for node_name in $OVN_DBS; do - node_ip=$(get_host_ip /tmp/ansible-inventory.txt $node_name) + node_ip=$(get_host_ip "$inventory_file" $node_name) node="$node_name ansible_host=$node_ip" if [ "$ovn_central" == "True" ]; then ovn_central=False node="$node_name ansible_host=$node_ip ovn_central=true" fi - echo $node ansible_ssh_user=heat-admin ansible_become=true >> hosts_for_migration + echo $node ansible_ssh_user=$UNDERCLOUD_NODE_USER ansible_become=true >> hosts_for_migration done echo "" >> hosts_for_migration echo "[ovn-controllers]" >> hosts_for_migration # We want to run ovn-controller where OVS agent was running before the migration - OVN_CONTROLLERS=$(get_role_hosts /tmp/ansible-inventory.txt neutron_ovs_agent) + OVN_CONTROLLERS=$(get_group_hosts "$inventory_file" neutron_ovs_agent) for node_name in $OVN_CONTROLLERS; do - node_ip=$(get_host_ip /tmp/ansible-inventory.txt $node_name) - echo $node_name ansible_host=$node_ip ansible_ssh_user=heat-admin ansible_become=true ovn_controller=true >> hosts_for_migration + node_ip=$(get_host_ip "$inventory_file" $node_name) + echo $node_name ansible_host=$node_ip ansible_ssh_user=$UNDERCLOUD_NODE_USER ansible_become=true ovn_controller=true >> hosts_for_migration + done + + echo "" >> hosts_for_migration + echo "[dhcp]" >> hosts_for_migration + dhcp_nodes=$(get_group_hosts "$inventory_file" neutron_dhcp) + for node_name in $dhcp_nodes; do + node_ip=$(get_host_ip "$inventory_file" $node_name) + echo $node_name ansible_host=$node_ip ansible_ssh_user=$UNDERCLOUD_NODE_USER ansible_become=true >> hosts_for_migration done - rm -f /tmp/ansible-inventory.txt + + rm -f "$inventory_file" echo "" >> hosts_for_migration cat >> hosts_for_migration << EOF [overcloud-controllers:children] -ovn-dbs +dhcp [overcloud:children] ovn-controllers @@ -178,7 +183,7 @@ cat >> hosts_for_migration << EOF [$1:vars] -remote_user=heat-admin +remote_user=$UNDERCLOUD_NODE_USER public_network_name=$PUBLIC_NETWORK_NAME image_name=$IMAGE_NAME working_dir=$OPT_WORKDIR @@ -201,10 +206,21 @@ echo "Please review the file before running the next command - setup-mtu-t1" } +# Check if the stack exists +function check_stack { + source $STACKRC_FILE + openstack stack show $STACK_NAME 1> /dev/null || { + echo "ERROR: STACK_NAME=${STACK_NAME} does not exist. Please provide the stack name or its ID " + echo " via STACK_NAME environment variable." + exit 1 + } +} + # Check if the public network exists, and if it has floating ips available oc_check_public_network() { + [ "$VALIDATE_MIGRATION" != "True" ] && return 0 source $OVERCLOUDRC_FILE openstack network show $PUBLIC_NETWORK_NAME 1>/dev/null || { echo "ERROR: PUBLIC_NETWORK_NAME=${PUBLIC_NETWORK_NAME} can't be accessed by the" @@ -318,6 +334,7 @@ ret_val=0 case $command in generate-inventory) + check_stack oc_check_public_network generate_ansible_inventory_file generate_ansible_config_file diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/ovn-migration.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/ovn-migration.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/ovn-migration.yml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/ovn-migration.yml 2021-11-12 13:56:42.000000000 +0000 @@ -25,6 +25,14 @@ tags: - setup + +- name: Stop ml2/ovs resources + hosts: ovn-controllers + roles: + - stop-agents + tags: + - migration + # # TripleO / Director is executed to deploy ovn using "br-migration" for the # dataplane, while br-int is left intact to avoid dataplane disruption. diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/reduce-dhcp-renewal-time.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/reduce-dhcp-renewal-time.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/reduce-dhcp-renewal-time.yml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/reduce-dhcp-renewal-time.yml 2021-11-12 13:56:42.000000000 +0000 @@ -13,12 +13,7 @@ ignore_errors: yes - block: - - name: Get the neutron dhcp agent docker id - shell: - docker ps | grep neutron_dhcp | awk '{print $1}' - register: dhcp_agent_docker_id - ignore_errors: yes - - name: Restart neutron dhcp agent - command: docker restart {{ dhcp_agent_docker_id.stdout }} + shell: + podman restart $(podman ps --filter "name=neutron_dhcp" --format {% raw %}"{{.ID}}"{% endraw %}) ignore_errors: yes diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/delete-neutron-resources/templates/delete-neutron-resources.sh.j2 neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/delete-neutron-resources/templates/delete-neutron-resources.sh.j2 --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/delete-neutron-resources/templates/delete-neutron-resources.sh.j2 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/delete-neutron-resources/templates/delete-neutron-resources.sh.j2 2021-11-12 13:56:42.000000000 +0000 @@ -26,4 +26,7 @@ openstack network delete $i done +# Delete DVR gateway ports +openstack port delete $(openstack port list --device-owner "network:floatingip_agent_gateway" -c id -f value) + exit 0 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/cleanup-dataplane.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/cleanup-dataplane.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/cleanup-dataplane.yml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/cleanup-dataplane.yml 2021-11-12 13:56:42.000000000 +0000 @@ -1,24 +1,19 @@ --- -- name: Quickly disable neutron router and dhcp interfaces +- name: Cleanup neutron router and dhcp interfaces shell: | - for p in `ovs-vsctl show | egrep 'qr-|ha-|qg-|rfp-' | grep Interface | awk '{print $2}'` - do - # p will be having quotes. Eg. "hr-xxxx". So strip the quotes - p=`echo $p | sed -e 's/"//g'` - ovs-vsctl clear Interface $p external-ids - ovs-vsctl set Interface $p admin-state=down - done + ovs-vsctl list interface | awk '/name[ ]*: qr-|ha-|qg-|rfp-/ { print $3 }' | xargs -n1 ovs-vsctl del-port # dhcp tap ports cannot be easily distinguished from ovsfw ports, so we # list them from within the qdhcp namespaces - for netns in `ip netns | awk '{ print $1 }' | grep qdhcp-`; do for dhcp_port in `ip netns exec $netns ip -o link show | awk -F': ' '{print $2}' | grep tap`; do - ovs-vsctl clear Interface $dhcp_port external-ids - ovs-vsctl set Interface $dhcp_port admin-state=down + ovs-vsctl del-port $dhcp_port done done +- name: Cleanup neutron trunk subports + shell: | + ovs-vsctl list interface | awk '/name[ ]*: sp[it]-/ { print $3 }' | xargs -n1 ovs-vsctl del-port - name: Clean neutron datapath security groups from iptables shell: | diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/sync-dbs.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/sync-dbs.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/sync-dbs.yml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/tasks/sync-dbs.yml 2021-11-12 13:56:42.000000000 +0000 @@ -1,20 +1,20 @@ --- -- name: Get the neutron docker ID +- name: Get the neutron container ID shell: - docker ps | grep neutron-server-ovn | awk '{print $1}' - register: neutron_docker_id + podman ps --filter "name=neutron_api" --format {% raw %}"{{.ID}}"{% endraw %} + register: neutron_id - name: Sync neutron db with OVN db (container) - Run 1 - command: docker exec "{{ neutron_docker_id.stdout }}" + command: podman exec "{{ neutron_id.stdout }}" neutron-ovn-db-sync-util --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini --ovn-neutron_sync_mode repair - name: Sync neutron db with OVN db (container) - Run 2 - command: docker exec "{{ neutron_docker_id.stdout }}" + command: podman exec "{{ neutron_id.stdout }}" neutron-ovn-db-sync-util --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini --ovn-neutron_sync_mode repair - name: Pause and let ovn-controllers settle before doing the final activation (5 minute) - pause: minutes=5 \ No newline at end of file + pause: minutes=5 diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/activate-ovn.sh.j2 neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/activate-ovn.sh.j2 --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/activate-ovn.sh.j2 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/activate-ovn.sh.j2 2021-11-12 13:56:42.000000000 +0000 @@ -2,7 +2,7 @@ set -x -docker stop ovn_controller +podman stop ovn_controller # restore bridge mappings ovn_orig_bm=$(ovs-vsctl get open . external_ids:ovn-bridge-mappings-back) @@ -28,7 +28,14 @@ # Activate ovn-controller by configuring integration bridge ovs-vsctl set open . external_ids:ovn-bridge={{ ovn_bridge }} -docker start ovn_controller +# WORKAROUND for https://bugzilla.redhat.com/show_bug.cgi?id=1782834 +# By restarting ovs-vswitchd process, new connection is made based +# on the protocols values set in OVS database. OVS 2.13 by default +# implements OpenFlow protocols up to 1.5 and 1.5 is the one that +# is required by ovn-controller. +systemctl restart openvswitch + +podman start ovn_controller # Delete ovs bridges - br-tun and br-migration ovs-vsctl --if-exists del-br {{ tunnel_bridge }} diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/clone-br-int.sh.j2 neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/clone-br-int.sh.j2 --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/clone-br-int.sh.j2 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/migration/templates/clone-br-int.sh.j2 2021-11-12 13:56:42.000000000 +0000 @@ -73,5 +73,5 @@ } recreate_bridge_mappings -docker restart ovn_controller +podman restart ovn_controller copy_interfaces_to_br_migration diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/resources/create/templates/create-resources.sh.j2 neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/resources/create/templates/create-resources.sh.j2 --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/resources/create/templates/create-resources.sh.j2 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/resources/create/templates/create-resources.sh.j2 2021-11-12 13:56:42.000000000 +0000 @@ -8,12 +8,12 @@ openstack image show $image_name if [ "$?" != "0" ] then - if [ ! -f cirros-0.4.0-x86_64-disk.img ] + if [ ! -f cirros-0.5.2-x86_64-disk.img ] then - curl -Lo cirros-0.4.0-x86_64-disk.img https://github.com/cirros-dev/cirros/releases/download/0.4.0/cirros-0.4.0-x86_64-disk.img + curl -Lo cirros-0.5.2-x86_64-disk.img https://github.com/cirros-dev/cirros/releases/download/0.5.2/cirros-0.5.2-x86_64-disk.img fi - openstack image create "cirros-ovn-migration-{{ resource_suffix }}" --file cirros-0.4.0-x86_64-disk.img \ + openstack image create "cirros-ovn-migration-{{ resource_suffix }}" --file cirros-0.5.2-x86_64-disk.img \ --disk-format qcow2 --container-format bare --public image_name="cirros-ovn-migration-{{ resource_suffix }}" fi @@ -35,18 +35,18 @@ openstack subnet create --network ovn-migration-net-{{ resource_suffix }} --subnet-range 172.168.199.0/24 ovn-migration-subnet-{{ resource_suffix }} -openstack port create --network ovn-migration-net-{{ resource_suffix }} --security-group ovn-migration-sg-{{ resource_suffix }} ovn-migration-server-port-{{ resource_suffix }} - -openstack server create --flavor ovn-migration-{{ resource_suffix }} --image $image_name \ ---key-name ovn-migration-{{ resource_suffix }} \ ---nic port-id=ovn-migration-server-port-{{ resource_suffix }} ovn-migration-server-{{ resource_suffix }} - openstack router create ovn-migration-router-{{ resource_suffix }} openstack router set --external-gateway {{ public_network_name }} ovn-migration-router-{{ resource_suffix }} openstack router add subnet ovn-migration-router-{{ resource_suffix }} ovn-migration-subnet-{{ resource_suffix }} +openstack port create --network ovn-migration-net-{{ resource_suffix }} --security-group ovn-migration-sg-{{ resource_suffix }} ovn-migration-server-port-{{ resource_suffix }} + +openstack server create --flavor ovn-migration-{{ resource_suffix }} --image $image_name \ +--key-name ovn-migration-{{ resource_suffix }} \ +--nic port-id=ovn-migration-server-port-{{ resource_suffix }} ovn-migration-server-{{ resource_suffix }} + server_ip=`openstack floating ip create --port ovn-migration-server-port-{{ resource_suffix }} \ {{ public_network_name }} -c floating_ip_address | grep floating_ip_address \ | awk '{print $4'}` diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/defaults/main.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/defaults/main.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/defaults/main.yml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/defaults/main.yml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,3 @@ +--- +# defaults file for stop-agents +systemd_service_file_dir: /etc/systemd/system diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/cleanup.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/cleanup.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/cleanup.yml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/cleanup.yml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,21 @@ +--- +- name: "stop and disable {{ service.name }} services and healthchecks" + systemd: + name: "{{ item }}" + state: stopped + enabled: no + become: yes + loop: + - "{{ service.healthcheck_timer_file }}" + - "{{ service.healthcheck_service_file }}" + - "{{ service.service_file }}" + +- name: delete ml2 ovs systemd service files + file: + path: "{{ systemd_service_file_dir }}/{{ item }}" + state: absent + loop: + - "{{ service.service_file }}" + - "{{ service.healthcheck_service_file }}" + - "{{ service.healthcheck_timer_file }}" + become: yes diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/main.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/main.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/main.yml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/tasks/main.yml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,18 @@ +--- +- name: populate service facts + service_facts: + +- name: disable ml2 ovs services and healthchecks + include_tasks: cleanup.yml + loop: "{{ ml2_ovs_services }}" + loop_control: + loop_var: service + when: ansible_facts.services[service.service_file] is defined + +- name: remove containers + become: yes + shell: | + for agent in $(podman ps -a --format {% raw %}"{{.ID}}"{% endraw %} --filter "name=(neutron_.*_agent|neutron_dhcp)"); do + echo "Cleaning up agent $agent" + podman rm -f $agent + done diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/vars/main.yml neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/vars/main.yml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/vars/main.yml 1970-01-01 00:00:00.000000000 +0000 +++ neutron-16.4.2/tools/ovn_migration/tripleo_environment/playbooks/roles/stop-agents/vars/main.yml 2021-11-12 13:56:42.000000000 +0000 @@ -0,0 +1,19 @@ +--- +# vars file for stop-agents +ml2_ovs_services: + - name: dhcp + service_file: tripleo_neutron_dhcp.service + healthcheck_service_file: tripleo_neutron_dhcp_healthcheck.service + healthcheck_timer_file: tripleo_neutron_dhcp_healthcheck.timer + - name: l3 + service_file: tripleo_neutron_l3_agent.service + healthcheck_service_file: tripleo_neutron_l3_agent_healthcheck.service + healthcheck_timer_file: tripleo_neutron_l3_agent_healthcheck.timer + - name: metadata + service_file: tripleo_neutron_metadata_agent.service + healthcheck_service_file: tripleo_neutron_metadata_agent_healthcheck.service + healthcheck_timer_file: tripleo_neutron_metadata_agent_healthcheck.timer + - name: ovs + service_file: tripleo_neutron_ovs_agent.service + healthcheck_service_file: tripleo_neutron_ovs_agent_healthcheck.service + healthcheck_timer_file: tripleo_neutron_ovs_agent_healthcheck.timer diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/tox.ini neutron-16.4.2/tox.ini --- neutron-16.0.0~b3~git2020041516.5f42488a9a/tox.ini 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/tox.ini 2021-11-12 13:56:42.000000000 +0000 @@ -14,7 +14,7 @@ passenv = TRACE_FAILONLY GENERATE_HASHES http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY TOX_ENV_SRC_MODULES usedevelop = True deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/ussuri} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt whitelist_externals = sh @@ -62,7 +62,7 @@ {[testenv:functional]deps} commands = {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir}/etc {envdir}/bin - stestr run {posargs} + stestr run --group_regex=neutron\.tests\.functional\.db\.test_migrations\.(TestModelsMigrationsPsql|TestModelsMigrationsMysql) {posargs} [testenv:dsvm-fullstack] setenv = {[testenv]setenv} @@ -71,12 +71,15 @@ # workaround for DB teardown lock contention (bug/1541742) OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:600} OS_TEST_PATH=./neutron/tests/fullstack +# Because of issue with stestr and Python3, we need to avoid too much output +# to be produced during tests, so we will ignore python warnings here + PYTHONWARNINGS=ignore deps = {[testenv:functional]deps} commands = {toxinidir}/tools/generate_dhclient_script_for_fullstack.sh {envdir} {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir}/etc {envdir}/bin - stestr run --concurrency 4 {posargs} + stestr run --concurrency 3 {posargs} [testenv:dsvm-fullstack-gate] setenv = {[testenv:dsvm-fullstack]setenv} @@ -84,7 +87,7 @@ commands = {toxinidir}/tools/generate_dhclient_script_for_fullstack.sh {envdir} {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir}/etc {envdir}/bin - stestr run --concurrency 4 --black-regex neutron.tests.fullstack.test_securitygroup.TestSecurityGroupsSameNetwork.test_securitygroup {posargs} + stestr run --concurrency 3 --black-regex neutron.tests.fullstack.test_securitygroup.TestSecurityGroupsSameNetwork.test_securitygroup {posargs} stestr run --combine --concurrency 1 neutron.tests.fullstack.test_securitygroup.TestSecurityGroupsSameNetwork.test_securitygroup {posargs} [testenv:releasenotes] @@ -131,7 +134,7 @@ [testenv:docs] envdir = {toxworkdir}/docs deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/ussuri} -r{toxinidir}/doc/requirements.txt -r{toxinidir}/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html @@ -213,13 +216,6 @@ deps = bindep commands = bindep test -[testenv:lower-constraints] -setenv = OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:60} -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt - [testenv:requirements] deps = -egit+https://opendev.org/openstack/requirements#egg=openstack-requirements diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/base.yaml neutron-16.4.2/zuul.d/base.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/base.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/zuul.d/base.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -18,9 +18,13 @@ - ^neutron/locale/.*$ - ^releasenotes/.*$ vars: + OVN_BRANCH: v20.03.0 + # TODO(jlibosva): v2.13.1 is incompatible with kernel 4.15.0-118, sticking to commit hash until new v2.13 tag is created + OVS_BRANCH: 0047ca3a0290f1ef954f2c76b31477cf4b9755f5 devstack_services: # Ignore any default set by devstack. Emit a "disable_all_services". base: false + etcd3: false devstack_localrc: INSTALL_TESTONLY_PACKAGES: true DATABASE_PASSWORD: stackdb diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/grenade.yaml neutron-16.4.2/zuul.d/grenade.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/grenade.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/zuul.d/grenade.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -1,12 +1,8 @@ - job: name: neutron-grenade-multinode - parent: legacy-dsvm-base-multinode - run: playbooks/legacy/neutron-grenade-multinode/run.yaml - post-run: playbooks/legacy/neutron-grenade-multinode/post.yaml - timeout: 10800 + parent: grenade-multinode required-projects: - openstack/grenade - - openstack/devstack-gate - openstack/neutron irrelevant-files: &irrelevant-files - ^(test-|)requirements.txt$ @@ -20,18 +16,113 @@ - ^tox.ini$ - ^vagrant/.*$ - ^migration/.*$ + vars: + devstack_services: + etcd: false + br-ex-tcpdump: true + br-int-flows: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false + group-vars: + subnode: + devstack_services: + # Cinder services + c-bak: false + c-vol: false - job: name: neutron-grenade-dvr-multinode - parent: legacy-dsvm-base-multinode - run: playbooks/legacy/neutron-grenade-dvr-multinode/run.yaml - post-run: playbooks/legacy/neutron-grenade-dvr-multinode/post.yaml - timeout: 7500 + parent: grenade-multinode + pre-run: playbooks/dvr-multinode-scenario-pre-run.yaml + roles: + - zuul: openstack/neutron-tempest-plugin required-projects: - openstack/grenade - - openstack/devstack-gate - openstack/neutron irrelevant-files: *irrelevant-files + vars: + devstack_services: + etcd: false + br-ex-tcpdump: true + br-int-flows: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false + devstack_local_conf: + post-config: + $NEUTRON_CONF: + DEFAULT: + router_distributed: True + # NOTE(slaweq): We can get rid of this hardcoded absolute path when + # devstack-tempest job will be switched to use lib/neutron instead of + # lib/neutron-legacy + "/$NEUTRON_CORE_PLUGIN_CONF": + ml2: + mechanism_drivers: openvswitch,l2population + agent: + enable_distributed_routing: True + l2_population: True + tunnel_types: vxlan + arp_responder: True + ovs: + tunnel_bridge: br-tun + bridge_mappings: public:br-ex + $NEUTRON_L3_CONF: + DEFAULT: + agent_mode: dvr + agent: + availability_zone: nova + $NEUTRON_DHCP_CONF: + agent: + availability_zone: nova + group-vars: + subnode: + devstack_services: + q-agt: true + q-l3: true + q-meta: true + # Cinder services + c-bak: false + c-vol: false + devstack_local_conf: + post-config: + $NEUTRON_CONF: + DEFAULT: + router_distributed: True + # NOTE(slaweq): We can get rid of this hardcoded absolute path when + # devstack-tempest job will be switched to use lib/neutron instead of + # lib/neutron-legacy + "/$NEUTRON_CORE_PLUGIN_CONF": + agent: + enable_distributed_routing: True + l2_population: True + tunnel_types: vxlan + arp_responder: True + ovs: + tunnel_bridge: br-tun + bridge_mappings: public:br-ex + $NEUTRON_L3_CONF: + DEFAULT: + agent_mode: dvr_snat + agent: + availability_zone: nova - job: name: neutron-ovn-grenade diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/project.yaml neutron-16.4.2/zuul.d/project.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/project.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/zuul.d/project.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -4,9 +4,8 @@ # Please update this document always when any changes to jobs are made. - project: templates: - - neutron-tempest-plugin-jobs + - neutron-tempest-plugin-jobs-ussuri - openstack-cover-jobs - - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - publish-openstack-docs-pti - periodic-stable-jobs @@ -14,15 +13,7 @@ - release-notes-jobs-python3 check: jobs: - - neutron-functional - - neutron-fullstack - - neutron-fullstack-with-uwsgi - - neutron-rally-task - - neutron-grenade-multinode - - neutron-grenade-dvr-multinode - - neutron-tempest-linuxbridge - - neutron-tempest-with-uwsgi - - tempest-integrated-networking: + - neutron-functional: # We don't run the job on things like neutron docs-only changes irrelevant-files: &irrelevant-files - ^(test-|)requirements.txt$ @@ -37,21 +28,41 @@ - ^vagrant/.*$ - ^migration/.*$ - ^devstack/.*\.sample$ + - neutron-fullstack: + irrelevant-files: *irrelevant-files + - neutron-fullstack-with-uwsgi: + irrelevant-files: *irrelevant-files + - neutron-rally-task: + irrelevant-files: *irrelevant-files + - neutron-grenade-multinode: + irrelevant-files: *irrelevant-files + - neutron-grenade-dvr-multinode: + irrelevant-files: *irrelevant-files + - neutron-tempest-linuxbridge: + irrelevant-files: *irrelevant-files + - neutron-tempest-with-uwsgi: + irrelevant-files: *irrelevant-files + - tempest-integrated-networking: + irrelevant-files: *irrelevant-files - tempest-multinode-full-py3: voting: false irrelevant-files: *irrelevant-files - - neutron-tempest-dvr-ha-multinode-full - - neutron-tempest-iptables_hybrid + - neutron-tempest-dvr-ha-multinode-full: + irrelevant-files: *irrelevant-files + - neutron-tempest-iptables_hybrid: + irrelevant-files: *irrelevant-files - ironic-tempest-ipa-wholedisk-bios-agent_ipmitool-tinyipa: voting: false irrelevant-files: *irrelevant-files - - tempest-slow-py3: + - neutron-tempest-slow-py3: irrelevant-files: *irrelevant-files - - tempest-ipv6-only: + - neutron-tempest-ipv6-only: + irrelevant-files: *irrelevant-files + - neutron-ovn-tempest-ovs-release: irrelevant-files: *irrelevant-files - - neutron-ovn-tempest-ovs-release - neutron-ovn-tempest-ovs-release-ipv6-only: voting: false + irrelevant-files: *irrelevant-files # TODO(slaweq): add this job again to the check queue when it will be # working fine on python 3 #- networking-midonet-tempest-aio-ml2-centos-7: @@ -61,9 +72,20 @@ voting: false - neutron-functional-with-uwsgi: voting: false - - neutron-centos-8-tripleo-standalone + - tripleo-ci-centos-8-content-provider: + voting: false + irrelevant-files: *irrelevant-files + - neutron-centos-8-tripleo-standalone: + vars: &consumer_vars + consumer_job: true + build_container_images: false + remove_tags: + - build + dependencies: &consumer_deps + - tripleo-ci-centos-8-content-provider - neutron-ovn-rally-task: voting: false + irrelevant-files: *irrelevant-files # TripleO jobs that deploy OVN. # Note we don't use a project-template here, so it's easier # to disable voting on one specific job if things go wrong. @@ -73,18 +95,16 @@ # failures, please reach us on #tripleo IRC channel. - neutron-ovn-tripleo-ci-centos-8-containers-multinode: voting: false + vars: *consumer_vars + dependencies: *consumer_deps - neutron-ovn-tempest-slow: voting: false - - neutron-ovn-tempest-full-multinode-ovs-master: - voting: false - openstack-tox-py36: # from openstack-python3-ussuri-jobs template timeout: 3600 - openstack-tox-py37: # from openstack-python3-ussuri-jobs template timeout: 3600 - openstack-tox-py38: # from openstack-python3-ussuri-jobs template timeout: 3600 - - openstack-tox-lower-constraints: # from openstack-tox-lower-constraints template - timeout: 3600 - openstack-tox-cover: # from openstack-cover-jobs template timeout: 4800 gate: @@ -96,23 +116,18 @@ - neutron-tempest-iptables_hybrid - neutron-grenade-multinode - neutron-grenade-dvr-multinode - - tempest-slow-py3: - irrelevant-files: *irrelevant-files - - tempest-ipv6-only: - irrelevant-files: *irrelevant-files + - neutron-tempest-slow-py3 + - neutron-tempest-ipv6-only - neutron-ovn-tempest-ovs-release - openstack-tox-py36: # from openstack-python3-ussuri-jobs template timeout: 3600 - openstack-tox-py37: # from openstack-python3-ussuri-jobs template timeout: 3600 - - openstack-tox-lower-constraints: # from openstack-tox-lower-constraints template - timeout: 3600 #- neutron-ovn-rally-task #- neutron-ovn-tripleo-ci-centos-8-containers-multinode experimental: jobs: - - neutron-ovn-tempest-ovs-master - neutron-ovn-grenade periodic: @@ -120,5 +135,3 @@ - neutron-functional - neutron-tempest-postgres-full - neutron-tempest-mariadb-full - - neutron-tempest-with-os-ken-master - - neutron-ovn-tempest-ovs-master-fedora diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/rally.yaml neutron-16.4.2/zuul.d/rally.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/rally.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/zuul.d/rally.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -14,6 +14,17 @@ neutron: https://opendev.org/openstack/neutron devstack_services: neutron-trunk: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false devstack_local_conf: post-config: $NEUTRON_CONF: @@ -67,6 +78,17 @@ q-meta: false q-metering: false q-dns: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false devstack_localrc: Q_AGENT: ovn Q_ML2_PLUGIN_MECHANISM_DRIVERS: ovn,logger @@ -76,7 +98,6 @@ Q_USE_PROVIDERNET_FOR_PUBLIC: true ENABLE_CHASSIS_AS_GW: true OVN_L3_CREATE_PUBLIC_NETWORK: true - OVN_BRANCH: master devstack_local_conf: post-config: "${RALLY_CONF_DIR}/${RALLY_CONF_FILE}": diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/tempest-multinode.yaml neutron-16.4.2/zuul.d/tempest-multinode.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/tempest-multinode.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/zuul.d/tempest-multinode.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -1,7 +1,6 @@ - job: name: neutron-tempest-dvr-ha-multinode-full parent: tempest-multinode-full-py3 - nodeset: openstack-three-node-bionic timeout: 10800 roles: - zuul: openstack/neutron-tempest-plugin @@ -25,6 +24,20 @@ voting: false vars: tox_envlist: integrated-network + devstack_services: + br-ex-tcpdump: true + br-int-flows: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false devstack_local_conf: post-config: $NEUTRON_CONF: @@ -47,7 +60,7 @@ bridge_mappings: public:br-ex $NEUTRON_L3_CONF: DEFAULT: - agent_mode: dvr + agent_mode: dvr_snat agent: availability_zone: nova $NEUTRON_DHCP_CONF: @@ -59,6 +72,17 @@ q-agt: true q-l3: true q-meta: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false devstack_local_conf: post-config: $NEUTRON_CONF: @@ -83,6 +107,39 @@ availability_zone: nova - job: + name: neutron-tempest-slow-py3 + parent: tempest-slow-py3 + timeout: 10800 + irrelevant-files: *irrelevant-files + vars: + devstack_localrc: + USE_PYTHON3: true + devstack_plugins: + neutron: https://opendev.org/openstack/neutron.git + devstack_services: + br-ex-tcpdump: true + br-int-flows: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false + group-vars: + subnode: + devstack_localrc: + USE_PYTHON3: true + devstack_services: + # Cinder services + c-bak: false + c-vol: false + +- job: name: neutron-ovn-multinode-base description: Base multinode job for devstack/tempest to test Neutron with ovn driver. abstract: true @@ -92,7 +149,6 @@ - openstack/devstack-gate - openstack/neutron - openstack/neutron-tempest-plugin - - openstack/octavia - openstack/tempest irrelevant-files: *irrelevant-files roles: @@ -109,6 +165,10 @@ ENABLE_CHASSIS_AS_GW: true OVN_L3_CREATE_PUBLIC_NETWORK: true OVN_DBS_LOG_LEVEL: dbg + # TODO(mjozefcz): Stop compiling OVS modules when meter action in kernel + # will be released in Ubuntu Bionic. + # More info: https://mail.openvswitch.org/pipermail/ovs-discuss/2018-December/048009.html + OVN_BUILD_MODULES: True DOWNLOAD_DEFAULT_IMAGES: false IMAGE_URLS: "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img,https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img" DEFAULT_IMAGE_NAME: cirros-0.4.0-x86_64-disk @@ -121,22 +181,17 @@ devstack_plugins: neutron: https://opendev.org/openstack/neutron neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin - octavia: https://opendev.org/openstack/octavia zuul_copy_output: '{{ devstack_base_dir }}/data/ovs': 'logs' extensions_to_txt: db: true devstack_services: - c-bak: false etcd: false br-ex-tcpdump: true br-int-flows: true q-ovn-metadata-agent: true - o-api: true - o-hk: true ovn-controller: true ovn-northd: true - ovn-octavia: true ovs-vswitchd: true ovsdb-server: true placement-api: true @@ -148,18 +203,24 @@ q-meta: false q-metering: false q-dns: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false # When running python3 Swift should be disabled for now s-account: false s-container: false s-object: false s-proxy: false tls-proxy: true + q-qos: true group-vars: subnode: devstack_services: ovn-controller: true ovn-northd: false - ovn-octavia: false ovs-vswitchd: true ovsdb-server: true # NOTE(slaweq): it's just to check if this will force devstack to @@ -173,6 +234,9 @@ q-metering: false q-ovn-metadata-agent: true tls-proxy: true + # Cinder services + c-bak: false + c-vol: false devstack_localrc: Q_AGENT: ovn Q_ML2_PLUGIN_MECHANISM_DRIVERS: ovn,logger @@ -183,6 +247,10 @@ OVN_DBS_LOG_LEVEL: dbg USE_PYTHON3: True ENABLE_TLS: True + # TODO(mjozefcz): Stop compiling OVS modules when meter action in kernel + # will be released in Ubuntu Bionic. + # More info: https://mail.openvswitch.org/pipermail/ovs-discuss/2018-December/048009.html + OVN_BUILD_MODULES: True - job: @@ -197,47 +265,3 @@ # be fixed tempest_black_regex: "\ (^tempest.scenario.test_network_v6.TestGettingAddress)" - - -- job: - # TODO(slaweq): propose job with ovs-release and move -master one to - # experimental queue - name: neutron-ovn-tempest-full-multinode-ovs-master - parent: neutron-ovn-multinode-base - vars: - tox_envlist: all-plugin - tempest_test_regex: "^(?!.*\ - (?:.*\\[.*slow.*\\])|\ - (?:tempest.api.network.admin.test_quotas.QuotasTest.test_lbaas_quotas.*)|\ - (?:tempest.api.network.test_load_balancer.*)|\ - (?:tempest.scenario.test_load_balancer.*)|\ - (?:tempest.api.network.admin.test_load_balancer.*)|\ - (?:tempest.api.network.admin.test_lbaas.*)|\ - (?:tempest.api.network.test_fwaas_extensions.*)|\ - (?:tempest.api.network.test_metering_extensions.*)|\ - (?:tempest.thirdparty.boto.test_s3.*)|\ - (?:tempest.api.identity*)|\ - (?:tempest.api.image*)|\ - (?:tempest.api.volume*)|\ - (?:tempest.api.compute.images*)|\ - (?:tempest.api.compute.keypairs*)|\ - (?:tempest.api.compute.certificates*)|\ - (?:tempest.api.compute.flavors*)|\ - (?:tempest.api.compute.test_quotas*)|\ - (?:tempest.api.compute.test_versions*)|\ - (?:tempest.api.compute.volumes*)|\ - (?:tempest.api.compute.admin.test_flavor*)|\ - (?:tempest.api.compute.admin.test_volume*)|\ - (?:tempest.api.compute.admin.test_hypervisor*)|\ - (?:tempest.api.compute.admin.test_aggregate*)|\ - (?:tempest.api.compute.admin.test_quota*)|\ - (?:tempest.scenario.test_volume*))\ - ((^neutron_tempest_plugin.api)|\ - (^neutron_tempest_plugin.scenario)|\ - (tempest.(api|scenario|thirdparty))).*$" - devstack_localrc: - OVN_BRANCH: master - group-vars: - subnode: - devstack_localrc: - OVN_BRANCH: master diff -Nru neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/tempest-singlenode.yaml neutron-16.4.2/zuul.d/tempest-singlenode.yaml --- neutron-16.0.0~b3~git2020041516.5f42488a9a/zuul.d/tempest-singlenode.yaml 2020-04-15 20:24:42.000000000 +0000 +++ neutron-16.4.2/zuul.d/tempest-singlenode.yaml 2021-11-12 13:56:42.000000000 +0000 @@ -104,6 +104,17 @@ devstack_services: postgresql: true mysql: false + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false irrelevant-files: *irrelevant-files - job: @@ -117,22 +128,24 @@ vars: devstack_localrc: MYSQL_SERVICE_NAME: mariadb + devstack_services: + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false # NOTE(ralonsoh): once MariaDB default version in Ubuntu is bumped to # >10.1, this workaround can be removed (bug 1855912) pre-run: playbooks/add_mariadb_repo.yaml irrelevant-files: *irrelevant-files - job: - name: neutron-tempest-with-os-ken-master - parent: tempest-integrated-networking - timeout: 7800 - required-projects: - - openstack/devstack-gate - - openstack/neutron - - openstack/tempest - - openstack/os-ken - -- job: name: neutron-tempest-with-uwsgi parent: tempest-integrated-networking description: Run neutron Tempest tests with uwsgi @@ -140,54 +153,31 @@ vars: devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: true + devstack_services: + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false irrelevant-files: *irrelevant-files - job: name: neutron-ovn-base description: Base job for devstack/tempest to test Neutron with ovn driver. - # TODO(slaweq): consider changing parent to be tempest-integrated-networking - # job instead of devstack-tempest - parent: devstack-tempest + parent: tempest-integrated-networking timeout: 10800 required-projects: &ovn-base-required-projects - openstack/devstack-gate - openstack/neutron - - openstack/neutron-tempest-plugin - - openstack/octavia - openstack/tempest irrelevant-files: *irrelevant-files vars: &ovn-base-vars - tox_envlist: all-plugin - tempest_test_regex: "^(?!.*\ - (?:.*\\[.*slow.*\\])|\ - (?:tempest.api.network.admin.test_quotas.QuotasTest.test_lbaas_quotas.*)|\ - (?:tempest.api.network.test_load_balancer.*)|\ - (?:tempest.scenario.test_load_balancer.*)|\ - (?:tempest.api.network.admin.test_load_balancer.*)|\ - (?:tempest.api.network.admin.test_lbaas.*)|\ - (?:tempest.api.network.test_fwaas_extensions.*)|\ - (?:tempest.api.network.test_metering_extensions.*)|\ - (?:tempest.thirdparty.boto.test_s3.*)|\ - (?:tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_port_security_macspoofing_port)|\ - (?:tempest.api.identity*)|\ - (?:tempest.api.image*)|\ - (?:tempest.api.volume*)|\ - (?:tempest.api.compute.images*)|\ - (?:tempest.api.compute.keypairs*)|\ - (?:tempest.api.compute.certificates*)|\ - (?:tempest.api.compute.flavors*)|\ - (?:tempest.api.compute.test_quotas*)|\ - (?:tempest.api.compute.test_versions*)|\ - (?:tempest.api.compute.volumes*)|\ - (?:tempest.api.compute.admin.test_flavor*)|\ - (?:tempest.api.compute.admin.test_volume*)|\ - (?:tempest.api.compute.admin.test_hypervisor*)|\ - (?:tempest.api.compute.admin.test_aggregate*)|\ - (?:tempest.api.compute.admin.test_quota*)|\ - (?:tempest.scenario.test_volume*))\ - ((^neutron_tempest_plugin.api)|\ - (^neutron_tempest_plugin.scenario)|\ - (tempest.(api|scenario|thirdparty))).*$" tempest_concurrency: 4 devstack_localrc: Q_AGENT: ovn @@ -199,6 +189,10 @@ ENABLE_CHASSIS_AS_GW: true OVN_L3_CREATE_PUBLIC_NETWORK: true OVN_DBS_LOG_LEVEL: dbg + # TODO(mjozefcz): Stop compiling OVS modules when meter action in kernel + # will be released in Ubuntu Bionic. + # More info: https://mail.openvswitch.org/pipermail/ovs-discuss/2018-December/048009.html + OVN_BUILD_MODULES: True USE_PYTHON3: True DOWNLOAD_DEFAULT_IMAGES: false IMAGE_URLS: "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img,https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img" @@ -211,8 +205,6 @@ ENABLE_TLS: True devstack_plugins: neutron: https://opendev.org/openstack/neutron - neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin - octavia: https://opendev.org/openstack/octavia zuul_copy_output: '{{ devstack_base_dir }}/data/ovs': 'logs' extensions_to_txt: @@ -220,9 +212,6 @@ devstack_services: br-ex-tcpdump: true br-int-flows: true - c-api: true - c-sch: true - c-vol: true dstat: true g-api: true g-reg: true @@ -236,17 +225,13 @@ n-sch: true n-super-cond: true q-ovn-metadata-agent: true - o-api: true - o-hk: true ovn-controller: true ovn-northd: true - ovn-octavia: true ovs-vswitchd: true ovsdb-server: true placement-api: true q-svc: true q-dns: true - c-bak: false etcd: false peakmem_tracker: false q-agt: false @@ -260,14 +245,37 @@ s-object: false s-proxy: false tls-proxy: true + q-qos: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false - job: - name: neutron-ovn-tempest-ovs-master - description: Job testing for devstack/tempest testing Neutron with ovn driver and OVN master branch - parent: neutron-ovn-base + name: neutron-tempest-ipv6-only + parent: tempest-ipv6-only + timeout: 10800 + irrelevant-files: *irrelevant-files vars: - devstack_localrc: - OVN_BRANCH: master + tox_envlist: integrated-network + devstack_plugins: + neutron: https://opendev.org/openstack/neutron.git + devstack_services: + br-ex-tcpdump: true + br-int-flows: true + # Cinder services + c-api: false + c-bak: false + c-sch: false + c-vol: false + cinder: false + # Swift services + s-account: false + s-container: false + s-object: false + s-proxy: false - job: name: neutron-ovn-tempest-ovs-ipv6-only-base @@ -285,7 +293,8 @@ vars: devstack_localrc: OVN_BRANCH: v20.03.0 - OVS_BRANCH: v2.13.0 + # TODO(jlibosva): v2.13.1 is incompatible with kernel 4.15.0-118, sticking to commit hash until new v2.13 tag is created + OVS_BRANCH: 0047ca3a0290f1ef954f2c76b31477cf4b9755f5 - job: name: neutron-ovn-tempest-ovs-release @@ -294,10 +303,5 @@ vars: devstack_localrc: OVN_BRANCH: v20.03.0 - OVS_BRANCH: v2.13.0 - -- job: - name: neutron-ovn-tempest-ovs-master-fedora - description: Job testing for devstack/tempest testing Neutron with ovn driver and OVN master branch and Fedora - parent: neutron-ovn-tempest-ovs-master - nodeset: devstack-single-node-fedora-latest + # TODO(jlibosva): v2.13.1 is incompatible with kernel 4.15.0-118, sticking to commit hash until new v2.13 tag is created + OVS_BRANCH: 0047ca3a0290f1ef954f2c76b31477cf4b9755f5