35
35
from oslo_serialization import jsonutils
36
36
from oslo_utils import netutils
37
37
from oslo_utils import strutils
38
- from ovsdbapp .backend .ovs_idl import rowview
38
+ from ovsdbapp .backend .ovs_idl import idlutils
39
39
from ovsdbapp import constants as ovsdbapp_const
40
40
import tenacity
41
41
61
61
BPInfo = collections .namedtuple (
62
62
'BPInfo' , ['bp_param' , 'vnic_type' , 'capabilities' ])
63
63
64
+ HAChassisGroupInfo = collections .namedtuple (
65
+ 'HAChassisGroupInfo' , ['group_name' , 'chassis_list' , 'az_hints' ,
66
+ 'ignore_chassis' ])
67
+
64
68
65
69
class OvsdbClientCommand (object ):
66
70
_CONNECTION = 0
@@ -159,6 +163,11 @@ def ovn_provnet_port_name(network_id):
159
163
return constants .OVN_PROVNET_PORT_NAME_PREFIX + '%s' % network_id
160
164
161
165
166
+ def ovn_extport_chassis_group_name (port_id ):
167
+ # The name of the HA Chassis Group entry will be neutron-extport-<UUID>
168
+ return constants .OVN_HA_CH_GROUP_EXTPORT_PREFIX + '%s' % port_id
169
+
170
+
162
171
def ovn_vhu_sockpath (sock_dir , port_id ):
163
172
# Frame the socket path of a virtio socket
164
173
return os .path .join (
@@ -671,6 +680,12 @@ def is_gateway_chassis(chassis):
671
680
return constants .CMS_OPT_CHASSIS_AS_GW in get_ovn_cms_options (chassis )
672
681
673
682
683
+ def is_extport_host_chassis (chassis ):
684
+ """Check if the given Chassis is marked to host external ports"""
685
+ return (constants .CMS_OPT_CHASSIS_AS_EXTPORT_HOST in
686
+ get_ovn_cms_options (chassis ))
687
+
688
+
674
689
def get_port_capabilities (port ):
675
690
"""Return a list of port's capabilities"""
676
691
return port .get (portbindings .PROFILE , {}).get (constants .PORT_CAP_PARAM , [])
@@ -724,7 +739,7 @@ def get_chassis_in_azs(chassis_list, az_list):
724
739
return chassis
725
740
726
741
727
- def get_gateway_chassis_without_azs (chassis_list ):
742
+ def get_chassis_without_azs (chassis_list ):
728
743
"""Return a set of Chassis that does not belong to any AZs.
729
744
730
745
Filter a list of Chassis and return only the Chassis that does not
@@ -733,7 +748,7 @@ def get_gateway_chassis_without_azs(chassis_list):
733
748
:param chassis_list: A list of Chassis objects
734
749
:returns: A set of Chassis names
735
750
"""
736
- return {ch .name for ch in chassis_list if is_gateway_chassis ( ch ) and not
751
+ return {ch .name for ch in chassis_list if not
737
752
get_chassis_availability_zones (ch )}
738
753
739
754
@@ -862,77 +877,150 @@ def get_ovn_chassis_other_config(chassis):
862
877
return chassis .external_ids
863
878
864
879
865
- def sync_ha_chassis_group (context , network_id , nb_idl , sb_idl , txn ):
866
- """Return the UUID of the HA Chassis Group or the HA Chassis Group cmd.
867
-
868
- Given the Neutron Network ID, this method will return (or create
869
- and then return) the appropriate HA Chassis Group the external
870
- port (in that network) needs to be associated with.
880
+ def _get_info_for_ha_chassis_group (context , port_id , network_id , sb_idl ):
881
+ """Get the common required information to create a HA Chassis Group.
871
882
872
- :param context: Neutron API context.
873
- :param network_id: The Neutron network ID.
874
- :param nb_idl: OVN NB IDL
875
- :param sb_idl: OVN SB IDL
876
- :param txn: The ovsdbapp transaction object.
877
- :returns: The HA Chassis Group UUID or the HA Chassis Group command object.
883
+ :param context: Neutron API context
884
+ :param port_id: The port ID
885
+ :param network_id: The network ID
886
+ :param sb_idl: OVN SB IDL
887
+ :returns: An instance of HAChassisGroupInfo
878
888
"""
889
+ ignore_chassis = set ()
890
+ # If there are Chassis marked for hosting external ports create a HA
891
+ # Chassis Group per external port, otherwise do it at the network level
892
+ chassis_list = sb_idl .get_extport_chassis_from_cms_options ()
893
+ if chassis_list :
894
+ group_name = ovn_extport_chassis_group_name (port_id )
895
+ # Check if the port is bound to a chassis and if so, ignore that
896
+ # chassis when building the HA Chassis Group to ensure the
897
+ # external port is bound to a different chassis than the VM
898
+ ignore_chassis = sb_idl .get_chassis_host_for_port (port_id )
899
+ LOG .debug ('HA Chassis Group %s is based on external port %s '
900
+ '(network %s)' , group_name , port_id , network_id )
901
+ else :
902
+ chassis_list = sb_idl .get_gateway_chassis_from_cms_options (
903
+ name_only = False )
904
+ group_name = ovn_name (network_id )
905
+ LOG .debug ('HA Chassis Group %s is based on network %s' ,
906
+ group_name , network_id )
907
+
908
+ # Get the Availability Zones hints
879
909
plugin = directory .get_plugin ()
880
910
az_hints = common_utils .get_az_hints (
881
911
plugin .get_network (context , network_id ))
882
912
883
- ha_ch_grp_name = ovn_name (network_id )
884
- ext_ids = {constants .OVN_AZ_HINTS_EXT_ID_KEY : ',' .join (az_hints )}
885
- hcg_cmd = txn .add (nb_idl .ha_chassis_group_add (
886
- ha_ch_grp_name , may_exist = True , external_ids = ext_ids ))
913
+ return HAChassisGroupInfo (
914
+ group_name = group_name , chassis_list = chassis_list , az_hints = az_hints ,
915
+ ignore_chassis = ignore_chassis )
887
916
888
- if isinstance (hcg_cmd .result , rowview .RowView ):
889
- # The HA chassis group existed before this transaction.
890
- ha_ch_grp = hcg_cmd .result
891
- else :
892
- # The HA chassis group is being created in this transaction.
893
- ha_ch_grp = None
894
917
895
- # Get the chassis belonging to the AZ hints
896
- ch_list = sb_idl .get_gateway_chassis_from_cms_options (name_only = False )
897
- if not az_hints :
898
- az_chassis = get_gateway_chassis_without_azs (ch_list )
918
+ def _filter_candidates_for_ha_chassis_group (hcg_info ):
919
+ """Filter a list of chassis candidates for a given HA Chassis Group.
920
+
921
+ Filter a list of chassis candidates for a given HA Chassis Group taking
922
+ in consideration availability zones if present.
923
+
924
+ :param hcg_info: A instance of HAChassisGroupInfo
925
+ :returns: A list of chassis
926
+ """
927
+ if hcg_info .az_hints :
928
+ candidates = get_chassis_in_azs (hcg_info .chassis_list ,
929
+ hcg_info .az_hints )
930
+ LOG .debug ('Taking in consideration the AZs "%s" for HA '
931
+ 'Chassis Group %s' , ',' .join (hcg_info .az_hints ),
932
+ hcg_info .group_name )
899
933
else :
900
- az_chassis = get_chassis_in_azs ( ch_list , az_hints )
934
+ candidates = get_chassis_without_azs ( hcg_info . chassis_list )
901
935
936
+ # Remove the ignored Chassis, if present
937
+ if hcg_info .ignore_chassis :
938
+ LOG .debug ('Ignoring chassis %s for HA Chassis Group %s' ,
939
+ ', ' .join (hcg_info .ignore_chassis ), hcg_info .group_name )
940
+ candidates = candidates - hcg_info .ignore_chassis
941
+
942
+ return candidates
943
+
944
+
945
+ def sync_ha_chassis_group (context , port_id , network_id , nb_idl , sb_idl , txn ):
946
+ """Return the UUID of the HA Chassis Group or the HA Chassis Group cmd.
947
+
948
+ Given the Neutron Network ID, this method will return (or create
949
+ and then return) the appropriate HA Chassis Group the external
950
+ port (in that network) needs to be associated with.
951
+
952
+ :param context: Neutron API context
953
+ :param port_id: The port ID
954
+ :param network_id: The network ID
955
+ :param nb_idl: OVN NB IDL
956
+ :param sb_idl: OVN SB IDL
957
+ :param txn: The ovsdbapp transaction object
958
+ :returns: The HA Chassis Group UUID or the HA Chassis Group command object
959
+ """
960
+ # If there are Chassis marked for hosting external ports create a HA
961
+ # Chassis Group per external port, otherwise do it at the network level
962
+ hcg_info = _get_info_for_ha_chassis_group (context , port_id , network_id ,
963
+ sb_idl )
964
+ candidates = _filter_candidates_for_ha_chassis_group (hcg_info )
965
+
966
+ # Try to get the HA Chassis Group or create if it doesn't exist
967
+ ha_ch_grp = ha_ch_grp_cmd = None
968
+ try :
969
+ ha_ch_grp = nb_idl .ha_chassis_group_get (
970
+ hcg_info .group_name ).execute (check_error = True )
971
+ except idlutils .RowNotFound :
972
+ ext_ids = {constants .OVN_AZ_HINTS_EXT_ID_KEY : ',' .join (
973
+ hcg_info .az_hints )}
974
+ ha_ch_grp_cmd = txn .add (nb_idl .ha_chassis_group_add (
975
+ hcg_info .group_name , may_exist = True , external_ids = ext_ids ))
976
+
977
+ max_chassis_number = constants .MAX_CHASSIS_IN_HA_GROUP
902
978
priority = constants .HA_CHASSIS_GROUP_HIGHEST_PRIORITY
979
+
980
+ # Check if the HA Chassis Group existed before. If so, re-calculate
981
+ # the canditates in case something changed and keep the highest priority
982
+ # chassis in the group (if it's an eligible candidate) with the highest
983
+ # priority to avoid external ports from moving around
903
984
if ha_ch_grp :
904
985
# Remove any chassis that no longer belongs to the AZ hints
986
+ # or is ignored
905
987
all_ch = {ch .chassis_name for ch in ha_ch_grp .ha_chassis }
906
- ch_to_del = all_ch - az_chassis
988
+ ch_to_del = all_ch - candidates
907
989
for ch in ch_to_del :
908
990
txn .add (nb_idl .ha_chassis_group_del_chassis (
909
- ha_ch_grp_name , ch , if_exists = True ))
991
+ hcg_info . group_name , ch , if_exists = True ))
910
992
911
- # Find the highest priority chassis in the HA Chassis Group. If
912
- # it exists and still belongs to the same AZ, keep it as the
913
- # highest priority in the group to avoid ports already bond to it
914
- # from moving to another chassis.
993
+ # Find the highest priority chassis in the HA Chassis Group
915
994
high_prio_ch = max (ha_ch_grp .ha_chassis , key = lambda x : x .priority ,
916
995
default = None )
917
- priority = constants .HA_CHASSIS_GROUP_HIGHEST_PRIORITY
918
- if high_prio_ch and high_prio_ch .chassis_name in az_chassis :
996
+ if (high_prio_ch and
997
+ high_prio_ch .chassis_name in candidates ):
998
+ # If found, keep it as the highest priority chassis in the group
919
999
txn .add (nb_idl .ha_chassis_group_add_chassis (
920
- ha_ch_grp_name , high_prio_ch .chassis_name ,
1000
+ hcg_info . group_name , high_prio_ch .chassis_name ,
921
1001
priority = priority ))
922
- az_chassis .remove (high_prio_ch .chassis_name )
1002
+ candidates .remove (high_prio_ch .chassis_name )
923
1003
priority -= 1
924
-
925
- # Randomize the order so that networks belonging to the same
926
- # availability zones do not necessarily end up with the same
927
- # Chassis as the highest priority one.
928
- for ch in random .sample (list (az_chassis ), len (az_chassis )):
1004
+ max_chassis_number -= 1
1005
+ LOG .debug ('Keeping chassis %s as the highest priority chassis '
1006
+ 'for HA Chassis Group %s' , high_prio_ch .chassis_name ,
1007
+ hcg_info .group_name )
1008
+
1009
+ # random.sample() second parameter needs to be <= the list size,
1010
+ # that's why we need to check for the max value here
1011
+ max_chassis_number = min (max_chassis_number , len (candidates ))
1012
+ # Limit the number of members and randomize the order so each group,
1013
+ # even if they belonging to the same availability zones do not
1014
+ # necessarily end up with the same Chassis as the highest priority one.
1015
+ for ch in random .sample (list (candidates ), max_chassis_number ):
929
1016
txn .add (nb_idl .ha_chassis_group_add_chassis (
930
- hcg_cmd , ch , priority = priority ))
1017
+ hcg_info . group_name , ch , priority = priority ))
931
1018
priority -= 1
932
1019
1020
+ LOG .info ('HA Chassis Group %s synchronized' , hcg_info .group_name )
933
1021
# Return the existing register UUID or the HA chassis group creation
934
1022
# command (see ovsdbapp ``HAChassisGroupAddChassisCommand`` class).
935
- return ha_ch_grp .uuid if ha_ch_grp else hcg_cmd
1023
+ return ha_ch_grp .uuid if ha_ch_grp else ha_ch_grp_cmd
936
1024
937
1025
938
1026
def get_subnets_address_scopes (context , subnets , fixed_ips , ml2_plugin ):
@@ -1090,3 +1178,9 @@ def get_requested_chassis(requested_chassis):
1090
1178
if isinstance (requested_chassis , str ):
1091
1179
return requested_chassis .split (',' )
1092
1180
return []
1181
+
1182
+
1183
+ # TODO(lucasagomes): Remove this function when the additional_chassis column
1184
+ # becomes the norm and older versions of OVN are no longer supported
1185
+ def is_additional_chassis_supported (idl ):
1186
+ return idl .is_col_present ('Port_Binding' , 'additional_chassis' )
0 commit comments